dropDownListHelpers.mjs 17.7 KB
Newer Older
1
2
"use strict";

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
/**
 * Get the selected option(s) from a dropdown list
 *
 * @param {String} selectorStr A CSS selector string representing the dropdown list
 * @returns {Array} An array of string(s) representing the value(s) of the selected `<option>` elements
 */
const getSelectedOptionsFromDropDownList = function (selectorStr) {
  // Array to store our final result
  const selectedOptionsArr = [];

  // Select all the matching <option> elements as a NodeList
  const optionElements = document.querySelectorAll(`${selectorStr} option`);

  optionElements.forEach((optionEl) => {
    if (optionEl.selected) {
      selectedOptionsArr.push(optionEl.value);
    }
  });

  return selectedOptionsArr;
};

/**
 * Process the selected option(s) from a buildings & data points dropdown list.
 *
 * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
 * @returns {Array} An array of string(s) representing the processed value(s) of the selected buildings & data points option(s)
 */
const processSelectionsFromBuildingDataPointOptions = function (
  selectedOptionsArr
) {
  // Array to store our final result
  const selectedOptionsBuildingDataPointArr = [];

  selectedOptionsArr.forEach((optionStr) => {
    // Case 1: <option> element's value CONTAINS a "/" character
    // We wish to create a string like this `Bau 101/VL`
    if (optionStr.includes("/")) {
      // Split the <option> element's value into two substrings
      const optionsStrPartOne = optionStr.slice(0, 3);
      const optionsStrPartTwo = optionStr.slice(3);

      // Create a new string for the first substring
      const optionsStrPartOneNew = `Bau ${optionsStrPartOne}`;

      // Create a new combined string
      const optionsStrNew = optionsStrPartOneNew + optionsStrPartTwo;

      selectedOptionsBuildingDataPointArr.push(optionsStrNew);
    }
    // Case 2: <option> element's value DOES NOT CONTAIN a "/" character
    // We wish to create a string like this `Other/Außentemp`
    else {
      selectedOptionsBuildingDataPointArr.push(`Other/${optionStr}`);
    }
  });

  return selectedOptionsBuildingDataPointArr;
};

/**
 * Split an option element's value (a string) using a forward slash character ("/") as the delimiter
 *
 * @param {String} selectedOptionsStr A string representing the value of the selected `<option>` element
 * @returns {String} Resulting strings after splitting
 */
const splitOptionsTextDelimitedBySlash = function (selectedOptionsStr) {
  return selectedOptionsStr.split("/");
};

/**
 * Split an array of option element's values (strings) which have a forward slash character ("/") as the delimiter
 *
 * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
 * @returns {Array} An array made up of resulting strings after splitting
 */
const splitMultipleOptionsTextDelimitedBySlash = function (selectedOptionsArr) {
  return selectedOptionsArr.map((selectedOption) =>
    splitOptionsTextDelimitedBySlash(selectedOption)
  );
};

/**
 * Get the values from the currently selected options in ALL the drop down lists
 * @returns {Array} An array containing four arrays, where each array contains the values of the selected options
 */
const getSelectedOptionsFromAllDropDownLists = function () {
  const selectedBuildingDataPointOptionsArr =
    processSelectionsFromBuildingDataPointOptions(
      getSelectedOptionsFromDropDownList("#drop-down--bldg-data-point")
    );

  // Separate the building ID from the data point
  const selectedBuildingDataPointOptionsSplitArr =
    splitMultipleOptionsTextDelimitedBySlash(
      selectedBuildingDataPointOptionsArr
    );

  const selectedAggregationOptionsArr = getSelectedOptionsFromDropDownList(
    "#drop-down--aggregation-type"
  );

  const selectedSamplingRateArr = getSelectedOptionsFromDropDownList(
    "#drop-down--sampling-rate"
  );

  const selectedChartTypeArr = getSelectedOptionsFromDropDownList(
    "#drop-down--chart-type"
  );

  // Ensure that all the options have at least one selection
114
  if (selectedChartTypeArr.length === 0) {
115
    throw new Error("Please ensure that the chart type is selected");
116
  } else if (selectedBuildingDataPointOptionsSplitArr.length === 0) {
117
    throw new Error("Please ensure that at least one data point is selected");
118
  } else if (selectedAggregationOptionsArr.length === 0) {
119
    throw new Error("Please ensure that the aggregation type is selected");
120
  } else if (selectedSamplingRateArr.length === 0) {
121
    throw new Error("Please ensure that the sampling rate is selected");
122
123
124
125
126
127
128
129
  } else {
    return [
      selectedChartTypeArr,
      selectedBuildingDataPointOptionsSplitArr,
      selectedAggregationOptionsArr,
      selectedSamplingRateArr,
    ];
  }
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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
};

/**
 * Check whether the abbreviated buildings + data points + sampling rate strings
 * contain the temperature difference [dT] between Vorlauf temperature [VL] and
 * Rücklauf temperature [RL] (i.e., dT = VL - RL). Unlike all the other data points,
 * this data point is computed in a separate step
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of strings representing the abbreviated building + data point + sampling rate values
 * @returns {Boolean} true if the selected options contain the string `dT`, false otherwise
 */
const checkIfSelectedOptionsContainTemperatureDifference = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // Create a flattened copy of our input array,
  // then check if it contains the string `dT`
  return buildingDataPointSamplingRateAbbrevArr.flat().includes("dT");
};

/**
 * Get the index(es) of the the abbreviated buildings + data points + sampling rate string(s)
 * that contains the temperature difference (dT)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains an integer(s) whose value(s) are the index(es) of the abbreviated building + data point + sampling rate string(s) containing the temperature difference (dT)
 */
const getIndexesOfTemperatureDifferenceOptions = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // An array to store the final result
  const foundIndexesArr = [];

  // Use the index, i, provided by `forEach` array method
  buildingDataPointSamplingRateAbbrevArr.forEach(
    (bldgDataPntSamplingRateAbbrvArr, i) => {
      if (bldgDataPntSamplingRateAbbrvArr.includes("dT")) {
        foundIndexesArr.push(i);
      }
    }
  );

  return foundIndexesArr;
};

/**
 * Delete the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 */
const deleteTemperatureDifferenceOptions = function (
  buildingDataPointSamplingRateAbbrevArr
) {
183
184
185
186
187
  // Create a copy of the input array, will be modified in place
  const buildingDataPointSamplingRateAbbrevCopyArr = [
    ...buildingDataPointSamplingRateAbbrevArr,
  ];

188
189
  // Calculate the index(es) that we wish to delete
  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
190
    buildingDataPointSamplingRateAbbrevCopyArr
191
192
193
194
195
  );

  // Delete the index(es) of `dT`, modifies the array in place
  // Note: The resulting array is sparse
  foundIndexesArr.forEach(
196
197
    (foundIndex) =>
      delete buildingDataPointSamplingRateAbbrevCopyArr[foundIndex]
198
199
200
201
  );

  // Remove the empty sub array(s) that makes entire array sparse
  // Note: `empty` does not mean `undefined` or `null`
202
203
  return buildingDataPointSamplingRateAbbrevCopyArr.filter(
    (bldgDataPntSamplingRate) => typeof bldgDataPntSamplingRate === "object"
204
205
206
207
208
209
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
  );
};

/**
 * Extract the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 */
const extractTemperatureDifferenceOptions = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // Array to store final result
  const temperatureDifferenceOptionsAbbrevArr = [];

  // Calculate the index(es) that we wish to extract
  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
    buildingDataPointSamplingRateAbbrevArr
  );

  foundIndexesArr.forEach((foundIndex) => {
    // Extracted array for a single found index
    const bldgDataPntSamplingRateAbbrvArr =
      buildingDataPointSamplingRateAbbrevArr[foundIndex];

    temperatureDifferenceOptionsAbbrevArr.push(bldgDataPntSamplingRateAbbrvArr);
  });

  return temperatureDifferenceOptionsAbbrevArr;
};

/**
 * Extract the abbreviated building + sampling rate string(s) for use in calculating the temperature difference (dT = VL - RL)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains nested array(s) made up of abbreviated building + sampling rate string(s)
 */
const extractBuildingPlusSamplingRate = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // Array to store final result
  const temperatureDifferenceOptionsAbbrevArr = [];

  // Calculate the index(es) that we wish to extract
  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
    buildingDataPointSamplingRateAbbrevArr
  );

  foundIndexesArr.forEach((foundIndex) => {
    // Extracted array for a single found index
    const bldgDataPntSamplingRateAbbrvArr =
      buildingDataPointSamplingRateAbbrevArr[foundIndex];

    // Extract the building and sampling rate strings
    // Note: we have ignored the second element
    const [bldgAbbrv, , samplingRateAbbrv] = bldgDataPntSamplingRateAbbrvArr;

    // Create a new array that contains two elements,
    // the building and sampling rate abbreviated strings
    const bldgSamplingRateAbbrvArr = [bldgAbbrv, samplingRateAbbrv];

    temperatureDifferenceOptionsAbbrevArr.push(bldgSamplingRateAbbrvArr);
  });

  return temperatureDifferenceOptionsAbbrevArr;
};

/**
 * Determine if a chart requires raw observations instead of aggregated observations
 *
 * @param {String} selectedAggregationType The selected aggregation type
 * @param {String} selectedAggregationDuration The selected aggregation duration
 * @returns {Boolean} true if the chart requires raw observations, false if not
 */
const checkIfChartRequiresRawObservations = function (
  selectedAggregationType,
  selectedAggregationDuration
) {
  if (
    selectedAggregationType === "None (raw data)" &&
    selectedAggregationDuration === undefined
  ) {
    return true;
  } else {
    return false;
  }
};

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/**
 * Check if the selected building(s) + data point(s) options are valid for
 * drawing either a heatmp or scatter plot. If these options are
 * invalid, throw an error
 *
 * @param {Array} selectedChartTypeArr An array of string(s) representing the selected chart type(s)
 * @param {Array} selectedBuildingsDataPointsSamplingRateAbbrevNestedArr An array that is made up of sub array(s) whose elements are strings representing the selected buildings, data points and sampling rates
 * @returns {Boolean} true, if there are no errors thrown
 */
const checkIfSelectedBuildingDataPointsOptionsAreValid = function (
  selectedChartTypeArr,
  selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
) {
  for (const selectedChartType of selectedChartTypeArr) {
    // A heatmap can only visualize one data point
    if (selectedChartType === "Heatmap") {
      if (selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length === 1)
        return true;
      else if (
        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length > 1
      ) {
        throw new Error("A heatmap can only display one data point at a time");
      }
    }
    // A scatter plot requires at least two data points
317
    else if (selectedChartType === "Scatter Plot") {
318
319
320
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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
      if (selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length >= 2)
        return true;
      else if (
        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length < 2
      ) {
        throw new Error("A scatter plot requires at least two data points");
      }
    }
  }
};

/**
 * Check if the selected aggregation type options are valid for
 * drawing either a heatmp or scatter plot. If these options are
 * invalid, throw an error
 *
 * @param {Array} selectedChartTypeArr An array of string(s) representing the selected chart type(s)
 * @param {String} selectedAggregationType The selected aggregation type
 * @param {String} selectedAggregationDuration The selected aggregation duration
 * @returns {Boolean} true, if there are no errors thrown
 */
const checkIfSelectedAggregationOptionsAreValid = function (
  selectedChartTypeArr,
  selectedAggregationType,
  selectedAggregationDuration
) {
  for (const selectedChartType of selectedChartTypeArr) {
    if (
      selectedChartType === "Heatmap" ||
      selectedChartType === "Scatter Plot"
    ) {
      // For both chart types, we are interested in raw observations
      if (
        checkIfChartRequiresRawObservations(
          selectedAggregationType,
          selectedAggregationDuration
        )
      )
        return true;
      // Throw error if we attempt to use aggregated observations
      else {
        throw new Error(
          "The selected chart type does not support aggregated results"
        );
      }
    }
  }
};

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
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/**
 * Get the abbreviated form of building IDs, phenomenon names and sensor sampling rates
 * @param {String} buildingFullForm A string representation of the full form of a building ID
 * @param {String} phenomenonFullForm A string representation of the full form of a phenomenon name
 * @param {String} samplingRateFullForm A string representation of the full form of a sensor's sampling rate
 * @returns {Array} An array of abbreviated strings
 */
const getBuildingSensorSamplingRateAbbreviation = function (
  buildingFullForm,
  phenomenonFullForm,
  samplingRateFullForm
) {
  const fullFormToAbbreviationMapping = {
    buildings: {
      "Bau 101": "101",
      "Bau 102": "102",
      "Bau 107": "107",
      "Bau 112": "112, 118",
      "Bau 125": "125",
      "Bau 225": "225",
      "Other": "weather_station_521",
    },

    phenomenon: {
      VL: "vl",
      RL: "rl",
      dT: "dT",
      Durchfluss: "flow",
      Leistung: "power",
      Energie: "energy",
      Energie_VERBR: "energy_verb",
      Außentemp: "outside_temp",
    },

    samplingRate: {
      "15 min": "15min",
      "60 min": "60min",
    },
  };

  const buildingAbbrev =
    fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm];

  const phenomenonAbbrev =
    fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm];

  const samplingRateAbbrev =
    fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm];

416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
  if (buildingAbbrev === undefined) {
    throw new Error(
      "The provided building ID is not valid or is not supported by this function"
    );
  } else if (phenomenonAbbrev === undefined) {
    throw new Error(
      "The provided data point is not valid or is not supported by this function"
    );
  } else if (samplingRateAbbrev === undefined) {
    throw new Error(
      "The provided sampling rate is not valid or is not supported by this function"
    );
  } else {
    return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev];
  }
431
432
433
434
435
436
437
438
439
440
441
};

/**
 * Get the abbreviated form for the currently selected options in ALL the drop down lists
 *
 * @param {Array} allSelectedOptionsArr An array containing four arrays, where each array contains the values of the selected options
 * @returns {Array} An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s)
 */
const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function (
  allSelectedOptionsArr
) {
442
443
444
445
446
447
448
449
  // Note: The buildings + data points array is the second element and
  // the sampling rate array is the fourth element, therefore we skip the first and third elementa
  const [
    ,
    selectedBuildingDataPointOptionsSplitArr,
    ,
    selectedSamplingRateArr,
  ] = allSelectedOptionsArr;
450
451
452
453
454
455
456
457
458
459
460
461
462

  // The building is the first element
  const selectedBuildingsArr = selectedBuildingDataPointOptionsSplitArr.map(
    (selectedBuildingDataPoint) => selectedBuildingDataPoint[0]
  );

  // The data point is the second element
  const selectedDataPointsArr = selectedBuildingDataPointOptionsSplitArr.map(
    (selectedBuildingDataPoint) => selectedBuildingDataPoint[1]
  );

  // Assume that the buildings and data points arrays have equal length
  // use one of the arrays for looping
463
  if (selectedBuildingsArr.length !== selectedDataPointsArr.length) {
464
465
466
    throw new Error(
      "The buildings array and data points array have different lengths"
    );
467
468
469
470
471
472
473
474
475
  } else {
    return selectedBuildingsArr.map((selectedBuilding, i) =>
      getBuildingSensorSamplingRateAbbreviation(
        selectedBuilding,
        selectedDataPointsArr[i],
        ...selectedSamplingRateArr
      )
    );
  }
476
477
478
479
480
481
482
483
484
};

export {
  splitMultipleOptionsTextDelimitedBySlash,
  getSelectedOptionsFromAllDropDownLists,
  checkIfSelectedOptionsContainTemperatureDifference,
  deleteTemperatureDifferenceOptions,
  extractTemperatureDifferenceOptions,
  extractBuildingPlusSamplingRate,
485
486
  checkIfSelectedBuildingDataPointsOptionsAreValid,
  checkIfSelectedAggregationOptionsAreValid,
487
488
  getAbbreviationsForSelectedOptionsFromAllDropDownLists,
};