appChart.js 7.53 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
const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1";
8

9
10
/**
 * Create URL to fetch the Observations corresponding to a provided Datastream
11
12
 * @param {Number} datastreamID - Integer representing the Datastream ID
 * @returns {String} URL string for fetching the Observations corresponding to a Datastream
13
14
15
16
17
18
19
20
21
 */
const getObservationsUrl = function (datastreamID) {
  if (!datastreamID) return;
  const fullDatastreamURL = `${BASE_URL}/Datastreams(${datastreamID})/Observations`;
  return fullDatastreamURL;
};

/**
 * Create a temporal filter string for the fetched Observations
22
23
24
 * @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
25
26
27
28
29
30
31
32
 */
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;
};

const BASE_URL_OBSERVATIONS = getObservationsUrl(80);
Pithon Kabiro's avatar
Pithon Kabiro committed
33
34
const PARAM_RESULT_FORMAT = "dataArray";
const PARAM_ORDER_BY = "phenomenonTime asc";
35
const PARAM_FILTER = createTemporalFilterString("2020-01-01", "2021-01-01");
Pithon Kabiro's avatar
Pithon Kabiro committed
36
37
const PARAM_SELECT = "result,phenomenonTime";

38
39
40
41
42
43
44
45
const axiosGetRequest = axios.get(BASE_URL_OBSERVATIONS, {
  params: {
    "$resultFormat": PARAM_RESULT_FORMAT,
    "$orderBy": PARAM_ORDER_BY,
    "$filter": PARAM_FILTER,
    "$select": PARAM_SELECT,
  },
});
46
47

/**
48
 * Format the response from SensorThings API to make it suitable for heatmap
49
50
 * @param {Array} obsArray Response from SensorThings API as array
 * @returns {Array} Array of formatted observations suitable for use in a heatmap
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 */
const formatSTAResponseForHeatMap = function (obsArray) {
  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;
};

/**
 * Draw a heatmap using Highcharts library
75
76
 * @param {Array} obsArray Response from SensorThings API
 * @returns {Object} Highcharts library heatmap object
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
 */
const drawHeatMapHC = function (obsArray) {
  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,
    },

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

    series: [
      {
152
        data: formatSTAResponseForHeatMap(obsArray),
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
        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
      },
    ],
  });
};

Pithon Kabiro's avatar
Pithon Kabiro committed
168
169
/**
 * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch
170
171
 * @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
172
173
174
175
176
177
178
179
180
181
182
183
184
 */
const formatSTAResponseForLineChart = function (obsArray) {
  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;
};

/**
 * Draw a line chart using Highcharts library
185
186
 * @param {Array} obsArray - Response from SensorThings API
 * @returns {Object} Highcharts library line chart object
Pithon Kabiro's avatar
Pithon Kabiro committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
 */
const drawLineChartHC = function (obsArray) {
  // Create the chart
  Highcharts.stockChart("chart-line", {
    chart: {
      zoomType: "x",
    },

    rangeSelector: {
      selected: 1,
    },

    title: {
      text: "AAPL Stock Price",
    },

    series: [
      {
        name: "AAPL",
        data: formatSTAResponseForLineChart(obsArray),
        tooltip: {
          valueDecimals: 2,
        },
        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
      },
    ],
  });
};

Pithon Kabiro's avatar
Pithon Kabiro committed
216
/**
Pithon Kabiro's avatar
Pithon Kabiro committed
217
 * Follows "@iot.nextLink" links in SensorThingsAPI's response
Pithon Kabiro's avatar
Pithon Kabiro committed
218
 * Appends new results to existing results
Pithon Kabiro's avatar
Pithon Kabiro committed
219
 * @async
220
 * @param {Object} responsePromise Promise object
Pithon Kabiro's avatar
Pithon Kabiro committed
221
 * @returns {Object} - Object containing results from all the "@iot.nextLink" links
Pithon Kabiro's avatar
Pithon Kabiro committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
 */
const followNextLink = function (responsePromise) {
  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);
    });
};

// Get "ALL" the Observations that satisfy our query
245
followNextLink(axiosGetRequest)
Pithon Kabiro's avatar
Pithon Kabiro committed
246
247
248
249
250
251
252
253
254
255
256
257
258
259
  .then((success) => {
    const successValue = success.data.value;

    // Array that will hold the combined observations
    const combinedObservations = [];

    successValue.forEach((dataObj) => {
      // Each page of results will have a dataArray that holds the observations
      const dataArrays = dataObj.dataArray;

      combinedObservations.push(...dataArrays);
    });
    // DEBUG: Check total number of observations
    console.log(combinedObservations.length);
Pithon Kabiro's avatar
Pithon Kabiro committed
260
261
    // DEBUG: Print the array of observations
    console.log(combinedObservations);
Pithon Kabiro's avatar
Pithon Kabiro committed
262
263
264
265
266
267
268

    return combinedObservations;
  })
  .catch((err) => {
    console.log(err);
  })
  .then((observationArr) => {
269
    drawHeatMapHC(observationArr);
Pithon Kabiro's avatar
Pithon Kabiro committed
270
    drawLineChartHC(observationArr);
Pithon Kabiro's avatar
Pithon Kabiro committed
271
  });