appChart.js 14.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
6
import {
  BASE_URL,
  QUERY_PARAMS_COMBINED,
} from "./src_modules/baseUrlPlusQueryParams.mjs";
7

8
import { calculateVorlaufMinusRuecklaufTemperature } from "./src_modules/calculateTemperatureDiff.mjs";
Pithon Kabiro's avatar
Pithon Kabiro committed
9

10
import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./src_modules/fetchData.mjs";
Pithon Kabiro's avatar
Pithon Kabiro committed
11

12
import {
13
14
15
  formatDatastreamMetadataForChart,
  extractPropertiesFromFormattedDatastreamMetadata,
} from "./src_modules/fetchedDataProcessing.mjs";
Pithon Kabiro's avatar
Pithon Kabiro committed
16

Pithon Kabiro's avatar
Pithon Kabiro committed
17
import {
18
19
20
  formatSensorThingsApiResponseForLineOrColumnChart,
  drawLineChartHighcharts,
} from "./src_modules/chartLine.mjs";
Pithon Kabiro's avatar
Pithon Kabiro committed
21

22
import { drawColumnChartHighcharts } from "./src_modules/chartColumn.mjs";
Pithon Kabiro's avatar
Pithon Kabiro committed
23

24
import {
25
26
  showLoadingSpinner,
  hideLoadingSpinner,
27
28
  disableDrawChartButton,
  enableDrawChartButton,
29
} from "./src_modules/loadingIndicator.mjs";
30

31
import { vanillaSelectBox } from "./thirdparty/vanillaSelectBox.mjs";
Pithon Kabiro's avatar
Pithon Kabiro committed
32

33
import {
34
  extractObservationsWithinDatesInterval,
35
  extractUniqueCalendarDatesFromTimestamp,
36
37
38
} from "./src_modules/aggregateHelpers.mjs";

import {
39
40
41
42
43
44
  splitMultipleOptionsTextDelimitedBySlash,
  getSelectedOptionsFromAllDropDownLists,
  checkIfSelectedOptionsContainTemperatureDifference,
  deleteTemperatureDifferenceOptions,
  extractTemperatureDifferenceOptions,
  extractBuildingPlusSamplingRate,
45
46
  checkIfSelectedBuildingDataPointsOptionsAreValid,
  checkIfSelectedAggregationOptionsAreValid,
47
48
  getAbbreviationsForSelectedOptionsFromAllDropDownLists,
} from "./src_modules/dropDownListHelpers.mjs";
49

50
51
52
53
54
55
import {
  drawHeatmapBasedOnSelectedOptions,
  drawScatterPlotFromChartSelection,
  drawLineChartBasedOnSelectedAggregationOptions,
  drawColumnChartBasedOnSelectedAggregationOptions,
} from "./src_modules/dropDownListProcessing.mjs";
Pithon Kabiro's avatar
Pithon Kabiro committed
56

57
/**
58
59
60
 * Use the `vanillaDropDown` library to style the buildings & data points drop down list
 *
 * @returns {undefined}
61
 */
62
63
64
65
66
67
68
69
const styleBuildingsDataPointsDropDown = function () {
  // Create our dropdown list using `vanillaSelectBox`; supports the selection of multiple options
  new vanillaSelectBox("#drop-down--bldg-data-point", {
    "disableSelectAll": true,
    "maxSelect": 5,
    "placeHolder": "--Select--",
    "search": false,
  });
70
71
72
};

/**
73
74
75
 * Use the `vanillaDropDown` library to style the aggregation type drop down list
 *
 * @returns {undefined}
76
 */
77
78
79
80
81
82
83
84
const styleAggregationDropDown = function () {
  // Create our dropdown list using `vanillaSelectBox`
  new vanillaSelectBox("#drop-down--aggregation-type", {
    "disableSelectAll": true,
    "maxSelect": 1,
    "placeHolder": "--Select--",
    "search": false,
  });
85
86
};

87
/**
88
89
90
 * Use the `vanillaDropDown` library to style the third sampling rate down list
 *
 * @returns {undefined}
91
 */
92
93
94
95
96
97
98
99
const styleSamplingRateDropDown = function () {
  // Create our dropdown list using `vanillaSelectBox`
  new vanillaSelectBox("#drop-down--sampling-rate", {
    "disableSelectAll": true,
    "maxSelect": 1,
    "placeHolder": "--Select--",
    "search": false,
  });
100
101
102
};

/**
103
104
105
 * Use the `vanillaDropDown` library to style the chart type drop down list
 *
 * @returns {undefined}
106
 */
107
108
109
110
111
112
113
114
const styleChartTypeDropDown = function () {
  // Create our dropdown list using `vanillaSelectBox`
  new vanillaSelectBox("#drop-down--chart-type", {
    "disableSelectAll": true,
    "maxSelect": 1,
    "placeHolder": "--Select--",
    "search": false,
  });
115
116
};

117
/**
118
119
120
121
 * Callback function that wraps the logic of populating the linked drop down lists.
 * Will run on `DOMContentLoaded` event
 *
 * @returns {undefined}
122
 */
123
124
125
126
127
const afterDocumentLoads = function () {
  styleBuildingsDataPointsDropDown();
  styleAggregationDropDown();
  styleSamplingRateDropDown();
  styleChartTypeDropDown();
128
129
};

130
/**
131
132
133
134
135
136
 * Callback function that draws a chart using options from the drop-down list that
 * have been selected by a user.
 * Will be run when the user clicks a button
 *
 * @async
 * @returns {undefined} undefined
137
 */
138
const drawChartUsingSelectedOptions = async function () {
139
  try {
140
141
    const selectedOptionsAllDropDownLists =
      getSelectedOptionsFromAllDropDownLists();
142

143
    // Note: The chart type amd aggregation type + duration are the first and
144
145
    // third elements respectively, we have ignored the second and fourth elements
    const [selectedChartTypeArr, , selectedAggregationTypeDurationArr] =
146
      selectedOptionsAllDropDownLists;
147

148
149
150
151
    // Create an array of aggregation type and duration
    const selectedAggregationTypeDurationSplitNestedArr =
      splitMultipleOptionsTextDelimitedBySlash(
        selectedAggregationTypeDurationArr
152
153
      );

154
155
156
    // Separate the aggregation type and the aggregation duration strings
    const [selectedAggregationTypeDurationSplitArr] =
      selectedAggregationTypeDurationSplitNestedArr;
157

158
    const [selectedAggregationType, selectedAggregationDuration] =
159
      selectedAggregationTypeDurationSplitArr;
160

161
    // Array of building(s) + data point(s) + sampling rate
162
    const selectedBuildingsDataPointsSamplingRateAbbrevNestedArr =
163
164
      getAbbreviationsForSelectedOptionsFromAllDropDownLists(
        selectedOptionsAllDropDownLists
165
166
      );

167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
    // The values of these references is either equal to `true` or an error will be thrown
    // We would like the errors to be thrown at this point before we begin performing any asynchronous tasks
    const selectedAggregationOptionsAreValid =
      checkIfSelectedAggregationOptionsAreValid(
        selectedChartTypeArr,
        selectedAggregationType,
        selectedAggregationDuration
      );

    const selectedBuildingDataPointsOptionsAreValid =
      checkIfSelectedBuildingDataPointsOptionsAreValid(
        selectedChartTypeArr,
        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
      );

182
183
    // Check whether we have dT (temperature difference), if so, delete these options,
    // then compute abbreviations for raw observations
184
    const selectedBuildingsDataPointsSamplingRateAbbrevRawObsArr =
185
      checkIfSelectedOptionsContainTemperatureDifference(
186
        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
187
      )
188
        ? deleteTemperatureDifferenceOptions(
189
            selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
190
          )
191
        : selectedBuildingsDataPointsSamplingRateAbbrevNestedArr;
192

193
194
    // Check if we have dT (temperature difference)
    const selectedBuildingsDataPointsSamplingRateAbbrevTempDiffArr =
195
      checkIfSelectedOptionsContainTemperatureDifference(
196
        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
197
198
      )
        ? extractTemperatureDifferenceOptions(
199
            selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
200
201
202
203
204
205
          )
        : [];

    // Display the loading indicator
    showLoadingSpinner();

206
207
208
209
210
211
    // Disable the 'draw chart' button
    disableDrawChartButton();

    // Fetch the observations + metadata / raw observations
    const observationsRawPlusMetadataArr =
      selectedBuildingsDataPointsSamplingRateAbbrevRawObsArr.length === 0
212
213
214
215
        ? [[], []]
        : await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
            BASE_URL,
            QUERY_PARAMS_COMBINED,
216
            selectedBuildingsDataPointsSamplingRateAbbrevRawObsArr
217
218
          );

219
220
221
    // Fetch the observations + metadata / temperature difference (dT)
    const observationsTempDiffPlusMetadataArr =
      selectedBuildingsDataPointsSamplingRateAbbrevTempDiffArr.length === 0
222
223
224
225
226
        ? [[], []]
        : await calculateVorlaufMinusRuecklaufTemperature(
            BASE_URL,
            QUERY_PARAMS_COMBINED,
            extractBuildingPlusSamplingRate(
227
              selectedBuildingsDataPointsSamplingRateAbbrevTempDiffArr
228
229
230
            )
          );

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
    // If there is an error in fetching metadata + observations (Case 1: raw observations)
    // the returned array will have this structure: [[undefined, undefined], undefined]
    // Note that the second element is not an array as we would expect but is a
    // a single `undefined` value
    if (typeof observationsRawPlusMetadataArr[0][0] === "undefined") {
      throw new Error(
        `There was a problem in fetching metadata and observations`
      );
    }

    // If there is an error in fetching metadata + observations (Case 2: temperature difference, dT)
    // a single `undefined` value instead of an array (as we would expect) will be returned
    if (typeof observationsTempDiffPlusMetadataArr === "undefined")
      throw new Error(
        `There was a problem in calculating the temperature difference (dT).\nThis is most likely due to a problem in fetching metadata and observations`
      );

248
    // Extract the combined arrays for observations and metadata / raw observations
249
    const [observationsRawNestedArr, metadataRawNestedArr] =
250
      observationsRawPlusMetadataArr;
251

252
    // Extract the combined arrays for observations and metadata / temperature difference (dT)
253
    const [observationsTempDiffNestedArr, metadataTempDiffNestedArr] =
254
      observationsTempDiffPlusMetadataArr;
255
256
257

    // Create a combined array of observations and metadata
    const observationsPlusMetadataCombined = [
258
259
      [...observationsRawNestedArr, ...observationsTempDiffNestedArr],
      [...metadataRawNestedArr, ...metadataTempDiffNestedArr],
260
    ];
261

262
263
    const [observationsComboNestedArr, metadataComboNestedArr] =
      observationsPlusMetadataCombined;
264

265
266
267
    // Create formatted array(s) for metadata - used by ALL chart types
    const formattedMetadataNestedArr = metadataComboNestedArr.map(
      (metadataObj) => formatDatastreamMetadataForChart(metadataObj)
268
269
    );

270
271
    // Extract the formatted metadata properties for the raw observations - used by ALL chart types
    const rawObservationsExtractedFormattedDatastreamProperties =
272
273
      extractPropertiesFromFormattedDatastreamMetadata(
        formattedMetadataNestedArr,
274
        false
275
276
      );

277
278
    // The formatted abbreviations array is nested
    const [selectedBuildingsDataPointsSamplingRateAbbrevArr] =
279
      selectedBuildingsDataPointsSamplingRateAbbrevNestedArr;
280

281
282
283
    // Extract the formatted sampling rate string - used by ALL chart types
    const [, , selectedSamplingRateAbbrev] =
      selectedBuildingsDataPointsSamplingRateAbbrevArr;
284

285
286
287
    // User-specified start date and end date for aggregation - used by MULTIPLE chart types
    const aggregationStartDate = "2020-01-01";
    const aggregationEndDate = "2020-12-31";
288

289
290
291
292
293
294
295
296
297
298
299
300
301
302
    // Create final array of observations- used by MULTIPLE chart types
    // If we are performing aggregation, it was envisioned that the user would
    // select observations that fall within a start and end date
    const observationsComboNestedFinalArr =
      selectedAggregationType === "None (raw data)"
        ? observationsComboNestedArr
        : observationsComboNestedArr.map((observationsArr) =>
            extractObservationsWithinDatesInterval(
              observationsArr,
              selectedSamplingRateAbbrev,
              aggregationStartDate,
              aggregationEndDate
            )
          );
303

304
    // Unique calendar dates - used by MULTIPLE chart types
305
    const uniqueCalendarDatesNestedArr = observationsComboNestedFinalArr.map(
306
307
308
309
      (observationsArr) =>
        extractUniqueCalendarDatesFromTimestamp(observationsArr)
    );

310
    for (const selectedChartType of selectedChartTypeArr) {
311
312
313
314
315
316
317
318
319
320
      switch (selectedChartType) {
        // We are interested in raw observations ONLY
        case "Heatmap":
          if (
            selectedAggregationOptionsAreValid &&
            selectedBuildingDataPointsOptionsAreValid
          ) {
            drawHeatmapBasedOnSelectedOptions(
              observationsComboNestedFinalArr,
              rawObservationsExtractedFormattedDatastreamProperties
321
            );
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
          }
          break;

        // We are interested in raw observations ONLY
        case "Scatter Plot":
          if (
            selectedAggregationOptionsAreValid &&
            selectedBuildingDataPointsOptionsAreValid
          ) {
            drawScatterPlotFromChartSelection(
              observationsComboNestedFinalArr,
              rawObservationsExtractedFormattedDatastreamProperties
            );
          }
          break;

        // We are interested in BOTH raw observations and aggregated observations
        case "Line":
          // Raw observations
          if (selectedAggregationType === "None (raw data)") {
            // Create formatted array(s) for observations
            const formattedRawObservationsLineChartNestedArr =
              observationsComboNestedFinalArr.map((observationsArr) =>
                formatSensorThingsApiResponseForLineOrColumnChart(
                  observationsArr
                )
              );

            drawLineChartHighcharts(
              formattedRawObservationsLineChartNestedArr,
              rawObservationsExtractedFormattedDatastreamProperties
353
            );
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
          }
          // Aggregated observations
          else {
            drawLineChartBasedOnSelectedAggregationOptions(
              selectedAggregationType,
              selectedAggregationDuration,
              observationsComboNestedFinalArr,
              selectedSamplingRateAbbrev,
              uniqueCalendarDatesNestedArr,
              formattedMetadataNestedArr
            );
          }
          break;

        // We are interested in BOTH raw observations and aggregated observations
        case "Column":
          // Raw observations
          if (selectedAggregationType === "None (raw data)") {
            // Create formatted array(s) for observations
            const formattedRawObservationsColumnChartNestedArr =
              observationsComboNestedFinalArr.map((observationsArr) =>
                formatSensorThingsApiResponseForLineOrColumnChart(
                  observationsArr
                )
              );

            drawColumnChartHighcharts(
              formattedRawObservationsColumnChartNestedArr,
              rawObservationsExtractedFormattedDatastreamProperties
            );
          }
          // Aggregated observations
          else {
            drawColumnChartBasedOnSelectedAggregationOptions(
              selectedAggregationType,
              selectedAggregationDuration,
              observationsComboNestedFinalArr,
              selectedSamplingRateAbbrev,
              uniqueCalendarDatesNestedArr,
              formattedMetadataNestedArr
            );
          }
          break;
397

398
399
        default:
          throw new Error("None of the chart type options were selected");
400
      }
401
    }
402
403
  } catch (err) {
    console.error(err);
404
405
406

    // Display a dialog window with the error message
    alert(err);
407
  } finally {
408
409
410
    // Enable the 'draw chart' button
    enableDrawChartButton();

411
412
    // Hide the loading indicator
    hideLoadingSpinner();
413
414
415
  }
};

416
417
418
419
420
document.addEventListener("DOMContentLoaded", afterDocumentLoads);

document
  .querySelector("#btn-draw-chart")
  .addEventListener("click", drawChartUsingSelectedOptions);