dropDownListHelpers.mjs 18.1 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
115
116
117
118
119
120
121
122
123
124
  if (selectedChartTypeArr.length === 0)
    throw new Error("Please ensure that the chart type is selected");

  if (selectedBuildingDataPointOptionsSplitArr.length === 0)
    throw new Error("Please ensure that at least one data point is selected");

  if (selectedSamplingRateArr.length === 0)
    throw new Error("Please ensure that the aggregation type is selected");

  if (selectedSamplingRateArr.length === 0)
    throw new Error("Please ensure that the sampling rate is selected");
125
126

  return [
127
    selectedChartTypeArr,
128
129
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
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
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
292
293
294
295
296
297
298
299
300
301
302
303
304
    selectedBuildingDataPointOptionsSplitArr,
    selectedAggregationOptionsArr,
    selectedSamplingRateArr,
  ];
};

/**
 * 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
) {
  // Calculate the index(es) that we wish to delete
  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
    buildingDataPointSamplingRateAbbrevArr
  );

  // Delete the index(es) of `dT`, modifies the array in place
  // Note: The resulting array is sparse
  foundIndexesArr.forEach(
    (foundIndex) => delete buildingDataPointSamplingRateAbbrevArr[foundIndex]
  );

  // Array to store our final result
  const buildingDataPointFinalArr = [];

  // Remove the empty sub array(s) that makes entire array sparse
  // Note: `empty` does not mean `undefined` or `null`
  buildingDataPointSamplingRateAbbrevArr.forEach(
    (bldgDataPntSmplingRateAbbrvArr) => {
      if (typeof bldgDataPntSmplingRateAbbrvArr === "object") {
        buildingDataPointFinalArr.push(bldgDataPntSmplingRateAbbrvArr);
      }
    }
  );

  return buildingDataPointFinalArr;
};

/**
 * 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];

    // 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(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;
  }
};

305
306
307
308
309
310
311
312
313
314
315
316
317
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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/**
 * 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
    if (selectedChartType === "Scatter Plot") {
      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"
        );
      }
    }
  }
};

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
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
/**
 * 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",
    },
  };

  if (
    fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm] === undefined
  )
    throw new Error(
      "The provided building ID is not valid or is not supported by this function"
    );

  if (
    fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm] ===
    undefined
  )
    throw new Error(
      "The provided data point is not valid or is not supported by this function"
    );

  if (
    fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm] ===
    undefined
  )
    throw new Error(
      "The provided sampling rate is not valid or is not supported by this function"
    );

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

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

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

  return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev];
};

/**
 * 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
) {
465
466
467
468
469
470
471
472
  // 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;
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506

  // 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
  if (selectedBuildingsArr.length !== selectedDataPointsArr.length)
    throw new Error(
      "The buildings array and data points array have different lengths"
    );

  return selectedBuildingsArr.map((selectedBuilding, i) =>
    getBuildingSensorSamplingRateAbbreviation(
      selectedBuilding,
      selectedDataPointsArr[i],
      ...selectedSamplingRateArr
    )
  );
};

export {
  splitMultipleOptionsTextDelimitedBySlash,
  getSelectedOptionsFromAllDropDownLists,
  checkIfSelectedOptionsContainTemperatureDifference,
  deleteTemperatureDifferenceOptions,
  extractTemperatureDifferenceOptions,
  extractBuildingPlusSamplingRate,
507
508
  checkIfSelectedBuildingDataPointsOptionsAreValid,
  checkIfSelectedAggregationOptionsAreValid,
509
510
  getAbbreviationsForSelectedOptionsFromAllDropDownLists,
};