appChart.js 11.6 KB
Newer Older
Pithon Kabiro's avatar
Pithon Kabiro committed
1
"use strict";
Pithon Kabiro's avatar
Pithon Kabiro committed
2

3
4
5
// DEBUG:
// Observations WITHOUT data gap - Bau 225 / Datastream ID = 80
// Observations WITH data gap - Bau 112 / Datastream ID = 78
6

7
export const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1";
8

9
10
11
12
13
14
15
/**
 * Retrieve the datastream ID that corresponds to a particular building
 * @param {*} buildingNumber Integer representing the building ID
 * @param {*} phenomenon String representing the phenomenon of interest
 * @param {*} samplingRate String representing the sampling rate of the observations
 * @returns {Number} Datastream corresponding to the input building
 */
16
17
18
19
export const getDatastreamIdFromBuildingNumber = function(
    buildingNumber,
    phenomenon,
    samplingRate
20
) {
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    const buildingToDatastreamMapping = {
        101: {
            vl: { "15min": "69", "60min": "75" },
            rl: { "15min": "81", "60min": "87" },
            flow: { "15min": "93", "60min": "99" },
            power: { "15min": "105", "60min": "111" },
            energy: { "15min": "117", "60min": "123" },
            energy_verb: { "15min": "129", "60min": "135" },
        },
        102: {
            vl: { "15min": "70", "60min": "76" },
            rl: { "15min": "82", "60min": "88" },
            flow: { "15min": "94", "60min": "100" },
            power: { "15min": "106", "60min": "112" },
            energy: { "15min": "118", "60min": "124" },
            energy_verb: { "15min": "130", "60min": "136" },
        },
        107: {
            vl: { "15min": "71", "60min": "77" },
            rl: { "15min": "83", "60min": "89" },
            flow: { "15min": "95", "60min": "101" },
            power: { "15min": "107", "60min": "113" },
            energy: { "15min": "119", "60min": "125" },
            energy_verb: { "15min": "131", "60min": "137" },
        },
        "112, 118": {
            vl: { "15min": "72", "60min": "78" },
            rl: { "15min": "84", "60min": "90" },
            flow: { "15min": "96", "60min": "102" },
            power: { "15min": "108", "60min": "114" },
            energy: { "15min": "120", "60min": "126" },
            energy_verb: { "15min": "132", "60min": "138" },
        },
        125: {
            vl: { "15min": "73", "60min": "79" },
            rl: { "15min": "85", "60min": "91" },
            flow: { "15min": "97", "60min": "103" },
            power: { "15min": "109", "60min": "115" },
            energy: { "15min": "121", "60min": "127" },
            energy_verb: { "15min": "133", "60min": "139" },
        },
        225: {
            vl: { "15min": "74", "60min": "80" },
            rl: { "15min": "86", "60min": "92" },
            flow: { "15min": "98", "60min": "104" },
            power: { "15min": "110", "60min": "116" },
            energy: { "15min": "122", "60min": "128" },
            energy_verb: { "15min": "134", "60min": "140" },
        },
    };

    if (!buildingNumber) return;

    // check if building is contained in mapping object
    if (!(buildingNumber in buildingToDatastreamMapping)) return;

    const datastreamIdMatched = Number(
        buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate]
    );

    return datastreamIdMatched;
82
83
};

84
85
/**
 * Create URL to fetch the Observations corresponding to a provided Datastream
86
87
 * @param {Number} datastreamID - Integer representing the Datastream ID
 * @returns {String} URL string for fetching the Observations corresponding to a Datastream
88
 */
89
90
91
92
export const getObservationsUrl = function(baseUrl, datastreamID) {
    if (!datastreamID) return;
    const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`;
    return fullDatastreamURL;
93
94
95
96
};

/**
 * Create a temporal filter string for the fetched Observations
97
98
99
 * @param {String} dateStart Start date in YYYY-MM-DD format
 * @param {String} dateStop Stop date in YYYY-MM-DD format
 * @returns {String} Temporal filter string
100
 */
101
102
103
104
export const createTemporalFilterString = function(dateStart, dateStop) {
    if (!dateStart || !dateStop) return;
    const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`;
    return filterString;
105
106
};

107
108
109
110
111
// const BASE_URL_OBSERVATIONS = getObservationsUrl(80);
export const PARAM_RESULT_FORMAT = "dataArray";
export const PARAM_ORDER_BY = "phenomenonTime asc";
// const PARAM_FILTER = createTemporalFilterString("2020-01-01", "2021-01-01");
export const PARAM_SELECT = "result,phenomenonTime";
Pithon Kabiro's avatar
Pithon Kabiro committed
112

113
114
115
116
117
118
119
120
// const axiosGetRequest = axios.get(BASE_URL_OBSERVATIONS, {
//   params: {
//     "$resultFormat": PARAM_RESULT_FORMAT,
//     "$orderBy": PARAM_ORDER_BY,
//     "$filter": PARAM_FILTER,
//     "$select": PARAM_SELECT,
//   },
// });
121

122
123


124
/**
125
 * Format the response from SensorThings API to make it suitable for heatmap
126
127
 * @param {Array} obsArray Response from SensorThings API as array
 * @returns {Array} Array of formatted observations suitable for use in a heatmap
128
 */
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
export const formatSTAResponseForHeatMap = function(obsArray) {
    if (!obsArray) return;
    const dataSTAFormatted = [];
    obsArray.forEach((obs) => {
        // Get the date/time string; first element in input array; remove trailing "Z"
        const obsDateTimeInput = obs[0].slice(0, -1);
        // Get the "date" part of an observation
        const obsDateInput = obs[0].slice(0, 10);
        // Create Date objects
        const obsDateTime = new Date(obsDateTimeInput);
        const obsDate = new Date(obsDateInput);
        // x-axis -> timestamp; will be the same for observations from the same date
        const timestamp = Date.parse(obsDate);
        // y-axis -> hourOfDay
        const hourOfDay = obsDateTime.getHours();
        // value -> the observation's value; second element in input array
        const value = obs[1];
        dataSTAFormatted.push([timestamp, hourOfDay, value]);
    });
    return dataSTAFormatted;
149
150
151
152
};

/**
 * Draw a heatmap using Highcharts library
153
 * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap
154
 * @returns {Object} Highcharts library heatmap object
155
 */
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
export const drawHeatMapHC = function(formattedObsArrayForHeatmap) {
    Highcharts.chart("chart-heatmap", {
        chart: {
            type: "heatmap",
            zoomType: "x",
        },

        boost: {
            useGPUTranslations: true,
        },

        title: {
            text: "Inlet flow (Vorlauf)",
            align: "left",
            x: 40,
        },

        subtitle: {
            text: "Temperature variation by day and hour in 2020",
            align: "left",
            x: 40,
        },

        xAxis: {
            type: "datetime",
            // min: Date.UTC(2017, 0, 1),
            // max: Date.UTC(2017, 11, 31, 23, 59, 59),
            labels: {
                align: "left",
                x: 5,
                y: 14,
                format: "{value:%B}", // long month
            },
            showLastLabel: false,
            tickLength: 16,
        },

        yAxis: {
            title: {
                text: null,
            },
            labels: {
                format: "{value}:00",
            },
            minPadding: 0,
            maxPadding: 0,
            startOnTick: false,
            endOnTick: false,
            // tickPositions: [0, 6, 12, 18, 24],
            tickPositions: [0, 3, 6, 9, 12, 15, 18, 21, 24],
            tickWidth: 1,
            min: 0,
            max: 23,
            reversed: true,
210
        },
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240

        colorAxis: {
            stops: [
                [0, "#3060cf"],
                [0.5, "#fffbbc"],
                [0.9, "#c4463a"],
                [1, "#c4463a"],
            ],
            min: 60,
            max: 85,
            startOnTick: false,
            endOnTick: false,
            labels: {
                format: "{value}℃",
            },
        },

        series: [{
            data: formattedObsArrayForHeatmap,
            boostThreshold: 100,
            borderWidth: 0,
            nullColor: "#525252",
            colsize: 24 * 36e5, // one day
            tooltip: {
                headerFormat: "Temperature<br/>",
                pointFormat: "{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>",
            },
            turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
        }, ],
    });
241
242
};

Pithon Kabiro's avatar
Pithon Kabiro committed
243
244
/**
 * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch
245
246
 * @param {Array} obsArray Response from SensorThings API as array
 * @returns {Array} Array of formatted observations suitable for use in a line chart
Pithon Kabiro's avatar
Pithon Kabiro committed
247
 */
248
249
250
251
252
253
254
255
256
export const formatSTAResponseForLineChart = function(obsArray) {
    if (!obsArray) return;
    const dataSTAFormatted = [];
    obsArray.forEach((result) => {
        const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
        const valueObs = result[1];
        dataSTAFormatted.push([timestampObs, valueObs]);
    });
    return dataSTAFormatted;
Pithon Kabiro's avatar
Pithon Kabiro committed
257
258
259
260
};

/**
 * Draw a line chart using Highcharts library
261
 * @param {Array} formattedObsArrayForLineChart - Response from SensorThings API formatted for use in a line chart
262
 * @returns {Object} Highcharts library line chart object
Pithon Kabiro's avatar
Pithon Kabiro committed
263
 */
264
265
266
267
268
269
270
271
272
export const drawLineChartHC = function(formattedObsArrayForLineChart) {
    // Create the chart
    Highcharts.stockChart("chart-line", {
        chart: {
            zoomType: "x",
        },

        rangeSelector: {
            selected: 1,
Pithon Kabiro's avatar
Pithon Kabiro committed
273
        },
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293

        title: {
            text: "Inlet flow (Vorlauf)",
            "align": "left",
        },

        subtitle: {
            text: "Temperature variation by hour in 2020",
            align: "left",
        },

        series: [{
            name: "AAPL",
            data: formattedObsArrayForLineChart,
            tooltip: {
                valueDecimals: 2,
            },
            turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
        }, ],
    });
Pithon Kabiro's avatar
Pithon Kabiro committed
294
295
};

Pithon Kabiro's avatar
Pithon Kabiro committed
296
/**
Pithon Kabiro's avatar
Pithon Kabiro committed
297
 * Follows "@iot.nextLink" links in SensorThingsAPI's response
Pithon Kabiro's avatar
Pithon Kabiro committed
298
 * Appends new results to existing results
Pithon Kabiro's avatar
Pithon Kabiro committed
299
 * @async
300
 * @param {Object} responsePromise Promise object
Pithon Kabiro's avatar
Pithon Kabiro committed
301
 * @returns {Object} - Object containing results from all the "@iot.nextLink" links
Pithon Kabiro's avatar
Pithon Kabiro committed
302
 */
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
export const followNextLink = function(responsePromise) {
    if (!responsePromise) return;
    return responsePromise
        .then(function(lastSuccess) {
            if (lastSuccess.data["@iot.nextLink"]) {
                return followNextLink(
                    axios.get(lastSuccess.data["@iot.nextLink"])
                ).then(function(nextLinkSuccess) {
                    nextLinkSuccess.data.value = lastSuccess.data.value.concat(
                        nextLinkSuccess.data.value
                    );
                    return nextLinkSuccess;
                });
            } else {
                return lastSuccess;
            }
        })
        .catch(function(err) {
            console.log(err);
Pithon Kabiro's avatar
Pithon Kabiro committed
322
323
324
325
        });
};

// Get "ALL" the Observations that satisfy our query
326
327
328
// followNextLink(axiosGetRequest)
//   .then((success) => {
//     const successValue = success.data.value;
Pithon Kabiro's avatar
Pithon Kabiro committed
329

330
331
//     // Array that will hold the combined observations
//     const combinedObservations = [];
Pithon Kabiro's avatar
Pithon Kabiro committed
332

333
334
335
//     successValue.forEach((dataObj) => {
//       // Each page of results will have a dataArray that holds the observations
//       const dataArrays = dataObj.dataArray;
Pithon Kabiro's avatar
Pithon Kabiro committed
336

337
338
339
340
341
342
//       combinedObservations.push(...dataArrays);
//     });
//     // DEBUG: Check total number of observations
//     console.log(combinedObservations.length);
//     // DEBUG: Print the array of observations
//     console.log(combinedObservations);
Pithon Kabiro's avatar
Pithon Kabiro committed
343

344
345
346
347
348
349
350
351
//     return combinedObservations;
//   })
//   .catch((err) => {
//     console.log(err);
//   })
//   .then((observationArr) => {
//     drawHeatMapHC(observationArr);
//     drawLineChartHC(observationArr);
352
//   });