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

3
const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1";
4

5
6
/**
 * Retrieve the datastream ID that corresponds to a particular building
7
8
9
 * @param {Number | String} buildingNumber Integer representing the building ID
 * @param {String} phenomenon String representing the phenomenon of interest
 * @param {String} samplingRate String representing the sampling rate of the observations
10
11
 * @returns {Number} Datastream corresponding to the input building
 */
12
const getDatastreamIdFromBuildingNumber = function (
13
14
15
16
17
18
19
20
  buildingNumber,
  phenomenon,
  samplingRate
) {
  const buildingToDatastreamMapping = {
    101: {
      vl: { "15min": "69", "60min": "75" },
      rl: { "15min": "81", "60min": "87" },
21
22
23
24
25
26

      // These Datastreams do not yet have Observations
      // flow: { "15min": "93", "60min": "99" },
      // power: { "15min": "105", "60min": "111" },
      // energy: { "15min": "117", "60min": "123" },
      // energy_verb: { "15min": "129", "60min": "135" },
27
    },
28

29
30
31
    102: {
      vl: { "15min": "70", "60min": "76" },
      rl: { "15min": "82", "60min": "88" },
32
33
34
35
36
37

      // These Datastreams do not yet have Observations
      // flow: { "15min": "94", "60min": "100" },
      // power: { "15min": "106", "60min": "112" },
      // energy: { "15min": "118", "60min": "124" },
      // energy_verb: { "15min": "130", "60min": "136" },
38
    },
39

40
41
42
    107: {
      vl: { "15min": "71", "60min": "77" },
      rl: { "15min": "83", "60min": "89" },
43
44
45
46
47
48

      // These Datastreams do not yet have Observations
      // flow: { "15min": "95", "60min": "101" },
      // power: { "15min": "107", "60min": "113" },
      // energy: { "15min": "119", "60min": "125" },
      // energy_verb: { "15min": "131", "60min": "137" },
49
    },
50

51
    "112, 118": {
52
53
      vl: { "15min": "72", "60min": "78" },
      rl: { "15min": "84", "60min": "90" },
54
55
56
57
58
59

      // These Datastreams do not yet have Observations
      // flow: { "15min": "96", "60min": "102" },
      // power: { "15min": "108", "60min": "114" },
      // energy: { "15min": "120", "60min": "126" },
      // energy_verb: { "15min": "132", "60min": "138" },
60
    },
61

62
63
64
    125: {
      vl: { "15min": "73", "60min": "79" },
      rl: { "15min": "85", "60min": "91" },
65
66
67
68
69
70

      // These Datastreams do not yet have Observations
      // flow: { "15min": "97", "60min": "103" },
      // power: { "15min": "109", "60min": "115" },
      // energy: { "15min": "121", "60min": "127" },
      // energy_verb: { "15min": "133", "60min": "139" },
71
    },
72

73
74
75
76
77
78
79
80
    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" },
    },
81
82
83
84

    weather_station_521: {
      outside_temp: { "15min": "141", "60min": "142" },
    },
85
86
  };

87
88
89
90
91
92
  if (
    buildingToDatastreamMapping?.[buildingNumber]?.[phenomenon]?.[
      samplingRate
    ] === undefined
  )
    return;
93

94
95
96
97
98
99
100
  const datastreamIdMatched = Number(
    buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate]
  );

  return datastreamIdMatched;
};

101
102
103
104
105
106
/**
 * Create URL to fetch the details of single Datastream
 * @param {String} baseUrl Base URL of the STA server
 * @param {Number} datastreamID Integer representing the Datastream ID
 * @returns {String} URL string for fetching a single Datastream
 */
107
const getDatastreamUrl = function (baseUrl, datastreamID) {
108
109
110
111
112
  if (!datastreamID) return;
  const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})`;
  return fullDatastreamURL;
};

113
/**
114
 * Create URL to fetch Observations
115
 * @param {String} baseUrl Base URL of the STA server
116
117
 * @param {Number} datastreamID Integer representing the Datastream ID
 * @returns {String} URL string for fetching Observations
118
 */
119
const getObservationsUrl = function (baseUrl, datastreamID) {
120
  if (!datastreamID) return;
121
122
  const fullObservationsURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`;
  return fullObservationsURL;
123
124
125
126
};

/**
 * Create a temporal filter string for the fetched Observations
127
128
129
 * @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
130
 */
131
const createTemporalFilterString = function (dateStart, dateStop) {
132
133
134
135
136
  if (!dateStart || !dateStop) return;
  const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`;
  return filterString;
};

137
// const BASE_URL_OBSERVATIONS = getObservationsUrl(80);
138
139
140
141
142
143
144
const QUERY_PARAM_RESULT_FORMAT = "dataArray";
const QUERY_PARAM_ORDER_BY = "phenomenonTime asc";
const QUERY_PARAM_FILTER = createTemporalFilterString(
  "2020-01-01",
  "2021-01-01"
);
const QUERY_PARAM_SELECT = "result,phenomenonTime";
145
const QUERY_PARAMS_COMBINED = {
146
147
148
149
  "$resultFormat": QUERY_PARAM_RESULT_FORMAT,
  "$orderBy": QUERY_PARAM_ORDER_BY,
  "$filter": QUERY_PARAM_FILTER,
  "$select": QUERY_PARAM_SELECT,
150
};
Pithon Kabiro's avatar
Pithon Kabiro committed
151

152
153
/**
 * Perform a GET request using the Axios library
154
 * @param {String} urlObservations A URL that fetches Observations from an STA instance
155
 * @param {Object} urlParamObj The URL parameters to be sent together with the GET request
156
 * @returns {Promise} A promise that contains the first page of results when fulfilled
157
 */
158
const axiosGetRequest = function (urlObservations, urlParamObj) {
159
160
161
162
  return axios.get(urlObservations, {
    params: urlParamObj,
  });
};
163

164
165
166
167
168
169
/**
 * Retrieve the metadata for a single datastream
 * @async
 * @param {String} urlDatastream A URL that fetches a Datastream from an STA instance
 * @returns {Promise} A promise that contains a metadata object for a Datastream when fulfilled
 */
170
const getDatastreamMetadata = async function (urlDatastream) {
171
172
173
174
175
176
177
178
  try {
    // Extract properties of interest
    const {
      data: { description, name, unitOfMeasurement },
    } = await axiosGetRequest(urlDatastream);

    return { description, name, unitOfMeasurement };
  } catch (err) {
179
    console.error(err);
180
181
182
  }
};

183
184
185
186
187
/**
 * Format the response containing a Datastream's metadata from Sensorthings API
 * @param {Object} datastreamMetadata An object containing a Datastream's metadata
 * @returns {Object} An object containing the formatted metadata that is suitable for use in a line chart or heatmap
 */
188
const formatDatastreamMetadataForChart = function (datastreamMetadata) {
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
  const {
    description: datastreamDescription,
    name: datastreamName,
    unitOfMeasurement,
  } = datastreamMetadata;

  // Extract phenomenon name from Datastream name
  const regex = /\/ (.*) DS/;
  const phenomenonName = datastreamName.match(regex)[1]; // use second element in array

  // Match the unitOfMeasurement's string representation of a symbol
  // to an actual symbol, where necessary
  const unitOfMeasurementSymbol = (() => {
    if (unitOfMeasurement.symbol === "degC") {
      return "";
    } else if (unitOfMeasurement.symbol === "m3/h") {
      return "m<sup>3</sup>/h";
    } else {
      return unitOfMeasurement.symbol;
    }
  })();

  return {
    datastreamDescription,
    datastreamName,
    phenomenonName,
    unitOfMeasurementSymbol,
  };
};

219
/**
220
 * Format the response from SensorThings API to make it suitable for heatmap
221
222
 * @param {Array} obsArray Response from SensorThings API as array
 * @returns {Array} Array of formatted observations suitable for use in a heatmap
223
 */
224
const formatSTAResponseForHeatMap = function (obsArray) {
Pithon Kabiro's avatar
Pithon Kabiro committed
225
  if (!obsArray) return;
226
227

  const dataSTAFormatted = obsArray.map((obs) => {
228
229
230
231
232
233
234
235
236
237
238
239
240
    // 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];
241
    return [timestamp, hourOfDay, value];
242
  });
243

244
245
246
247
248
  return dataSTAFormatted;
};

/**
 * Draw a heatmap using Highcharts library
249
 * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap
250
 * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata
251
 * @returns {undefined} undefined
252
 */
253
const drawHeatMapHC = function (
254
  formattedObsArrayForHeatmap,
255
  formattedDatastreamMetadata
256
) {
257
258
259
260
261
262
263
  const {
    datastreamDescription: DATASTREAM_DESCRIPTION,
    datastreamName: DATASTREAM_NAME,
    phenomenonName: PHENOMENON_NAME,
    unitOfMeasurementSymbol: PHENOMENON_SYMBOL,
  } = formattedDatastreamMetadata;

Pithon Kabiro's avatar
Pithon Kabiro committed
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
  // Function returns the min and max observation values
  const {
    minObsValue: MINIMUM_VALUE_COLOR_AXIS,
    maxObsValue: MAXIMUM_VALUE_COLOR_AXIS,
  } = (() => {
    // The observation value is the third element in array
    const obsValueArr = formattedObsArrayForHeatmap.map((obs) => obs[2]);

    // Extract integer part
    const minValue = Math.trunc(Math.min(...obsValueArr));
    const maxValue = Math.trunc(Math.max(...obsValueArr));

    // Calculate the closest multiple of 5
    const minObsValue = minValue - (minValue % 5);
    const maxObsValue = maxValue + (5 - (maxValue % 5));

    return { minObsValue, maxObsValue };
  })();

283
284
285
286
287
288
289
290
291
292
293
  Highcharts.chart("chart-heatmap", {
    chart: {
      type: "heatmap",
      zoomType: "x",
    },

    boost: {
      useGPUTranslations: true,
    },

    title: {
294
      text: DATASTREAM_DESCRIPTION,
295
296
297
298
299
      align: "left",
      x: 40,
    },

    subtitle: {
300
      text: DATASTREAM_NAME,
301
302
303
304
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
      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, 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"],
      ],
Pithon Kabiro's avatar
Pithon Kabiro committed
344
345
      min: MINIMUM_VALUE_COLOR_AXIS,
      max: MAXIMUM_VALUE_COLOR_AXIS,
346
347
348
      startOnTick: false,
      endOnTick: false,
      labels: {
349
350
        // format: "{value}℃",
        format: `{value}${PHENOMENON_SYMBOL}`,
351
352
353
354
355
      },
    },

    series: [
      {
356
        data: formattedObsArrayForHeatmap,
357
358
359
360
361
        boostThreshold: 100,
        borderWidth: 0,
        nullColor: "#525252",
        colsize: 24 * 36e5, // one day
        tooltip: {
362
363
          headerFormat: `${PHENOMENON_NAME}<br/>`,
          valueDecimals: 2,
364
          pointFormat:
365
366
            // "{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>",
            `{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ${PHENOMENON_SYMBOL}</b>`,
Pithon Kabiro's avatar
Pithon Kabiro committed
367
          nullFormat: `{point.x:%e %b, %Y} {point.y}:00: <b>null</b>`,
368
369
370
371
372
373
374
        },
        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
      },
    ],
  });
};

Pithon Kabiro's avatar
Pithon Kabiro committed
375
376
/**
 * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch
377
378
 * @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
379
 */
380
const formatSTAResponseForLineChart = function (obsArray) {
Pithon Kabiro's avatar
Pithon Kabiro committed
381
  if (!obsArray) return;
382
383

  const dataSTAFormatted = obsArray.map((result) => {
Pithon Kabiro's avatar
Pithon Kabiro committed
384
385
    const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
    const valueObs = result[1];
386
    return [timestampObs, valueObs];
Pithon Kabiro's avatar
Pithon Kabiro committed
387
  });
388

Pithon Kabiro's avatar
Pithon Kabiro committed
389
390
391
392
393
  return dataSTAFormatted;
};

/**
 * Draw a line chart using Highcharts library
394
 * @param {Array} formattedObsArrayForLineChart Response from SensorThings API formatted for use in a line chart
395
 * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata
396
 * @returns {undefined} undefined
Pithon Kabiro's avatar
Pithon Kabiro committed
397
 */
398
const drawLineChartHC = function (
399
  formattedObsArrayForLineChart,
400
  formattedDatastreamMetadata
401
) {
402
403
404
405
406
407
408
  const {
    datastreamDescription: DATASTREAM_DESCRIPTION,
    datastreamName: DATASTREAM_NAME,
    phenomenonName: PHENOMENON_NAME,
    unitOfMeasurementSymbol: PHENOMENON_SYMBOL,
  } = formattedDatastreamMetadata;

Pithon Kabiro's avatar
Pithon Kabiro committed
409
410
411
412
413
414
  Highcharts.stockChart("chart-line", {
    chart: {
      zoomType: "x",
    },

    rangeSelector: {
415
      selected: 5,
Pithon Kabiro's avatar
Pithon Kabiro committed
416
417
418
    },

    title: {
419
      text: DATASTREAM_DESCRIPTION,
420
421
422
423
      "align": "left",
    },

    subtitle: {
424
      text: DATASTREAM_NAME,
425
      align: "left",
Pithon Kabiro's avatar
Pithon Kabiro committed
426
427
428
429
    },

    series: [
      {
430
        name: `${PHENOMENON_NAME} (${PHENOMENON_SYMBOL})`,
431
        data: formattedObsArrayForLineChart,
Pithon Kabiro's avatar
Pithon Kabiro committed
432
433
434
435
436
437
438
439
440
        tooltip: {
          valueDecimals: 2,
        },
        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
      },
    ],
  });
};

Pithon Kabiro's avatar
Pithon Kabiro committed
441
/**
Pithon Kabiro's avatar
Pithon Kabiro committed
442
 * Follows "@iot.nextLink" links in SensorThingsAPI's response
Pithon Kabiro's avatar
Pithon Kabiro committed
443
 * Appends new results to existing results
Pithon Kabiro's avatar
Pithon Kabiro committed
444
 * @async
445
446
 * @param {Promise} responsePromise Promise object resulting from an Axios GET request
 * @returns {Object} Object containing results from all the "@iot.nextLink" links
Pithon Kabiro's avatar
Pithon Kabiro committed
447
 */
448
const followNextLink = function (responsePromise) {
Pithon Kabiro's avatar
Pithon Kabiro committed
449
  if (!responsePromise) return;
Pithon Kabiro's avatar
Pithon Kabiro committed
450
  return responsePromise
451
    .then((lastSuccess) => {
Pithon Kabiro's avatar
Pithon Kabiro committed
452
453
454
      if (lastSuccess.data["@iot.nextLink"]) {
        return followNextLink(
          axios.get(lastSuccess.data["@iot.nextLink"])
455
        ).then((nextLinkSuccess) => {
Pithon Kabiro's avatar
Pithon Kabiro committed
456
457
458
459
460
461
462
463
464
          nextLinkSuccess.data.value = lastSuccess.data.value.concat(
            nextLinkSuccess.data.value
          );
          return nextLinkSuccess;
        });
      } else {
        return lastSuccess;
      }
    })
465
    .catch((err) => {
466
      console.error(err);
Pithon Kabiro's avatar
Pithon Kabiro committed
467
468
469
    });
};

470
471
472
/**
 * Retrieve all the Observations from a Datastream after traversing all the "@iot.nextLink" links
 * @async
473
474
 * @param {Promise} httpGetRequestPromise Promise object resulting from an Axios GET request
 * @returns {Promise} A promise that contains an array of Observations from a single Datastream when fulfilled
475
 */
476
const getCombinedObservationsFromAllNextLinks = function (
477
  httpGetRequestPromise
478
) {
479
  return followNextLink(httpGetRequestPromise)
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
    .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);
      });

      return new Promise((resolve, reject) => {
        resolve(combinedObservations);
      });
    })
    .catch((err) => {
495
      console.error(err);
496
497
    });
};
498

499
500
501
502
503
504
/**
 * Retrieve the metadata for a Datastream as well as the Observations corresponding to it
 * @async
 * @param {Promise} metadataPlusObsPromiseArray An array that contains two promises, one for datastream metadata, the other for observations
 * @returns {Promise} A promise that contains two arrays when fulfilled, one for datastream metadata and the other for observations
 */
505
const getMetadataPlusObservationsForChart = async function (
506
  metadataPlusObsPromiseArray
507
) {
508
  // Array to store our final result
509
510
  const combinedResolvedPromises = [];

511
512
  // Use for/of loop - we need to maintain the order of execution of the async operations
  for (const promise of metadataPlusObsPromiseArray) {
513
    try {
514
      // Resolved value of a single promise
515
516
517
      const resolvedPromise = await promise;
      combinedResolvedPromises.push(resolvedPromise);
    } catch (err) {
518
      console.error(err);
519
520
521
522
523
    }
  }
  return combinedResolvedPromises;
};

524
525
526
/**
 * Retrieve all the Observations from an array of Observations promises
 * @async
527
 * @param {Promise} observationPromiseArray An array that contains N observation promises
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
 * @returns {Promise} A promise that contains an array of Observations from multiple Datastreams when fulfilled
 */
const getObservationsFromMultipleDatastreams = async function (
  observationPromiseArray
) {
  // Array to store our final result
  const observationsAllDatastreamsArr = [];

  // Use for/of loop - we need to maintain the order of execution of the async operations
  for (const observationPromise of observationPromiseArray) {
    try {
      // Observations from a single Datastream
      const observations = await observationPromise;
      observationsAllDatastreamsArr.push(observations);
    } catch (err) {
543
      console.error(err);
544
545
546
547
    }
  }
  return observationsAllDatastreamsArr;
};
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578

// Building + phenomenon + sampling rate
const buildingsSensorSamplingRateRLArr = [
  ["101", "rl", "60min"],
  ["102", "rl", "60min"],
  ["107", "rl", "60min"],
  ["112, 118", "rl", "60min"],
  ["125", "rl", "60min"],
  ["225", "rl", "60min"],
];

// Datastreams IDs
const datastreamsRLArr = buildingsSensorSamplingRateRLArr.map((bldg) =>
  getDatastreamIdFromBuildingNumber(...bldg)
);

// Datastreams URLs
const datastreamsUrlRLArr = datastreamsRLArr.map((datastreamId) =>
  getObservationsUrl(BASE_URL, datastreamId)
);

// Promise objects - Observations / RL
const observationsPromisesRLArr = datastreamsUrlRLArr.map((obsUrl) =>
  getCombinedObservationsFromAllNextLinks(
    axiosGetRequest(obsUrl, QUERY_PARAMS_COMBINED)
  )
);

// getObservationsFromMultipleDatastreams(observationsPromisesRLArr).then((x) =>
//   console.log(x)
// );
579

580
const drawScatterPlotHC = function () {
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
  // Ruecklauf
  const ruecklaufArr = [
    [1578193200000, 69.1999969482422],
    [1578196800000, 69.1999969482422],
    [1578200400000, 69.1999969482422],
    [1578204000000, 69.1999969482422],
    [1578207600000, 69.1999969482422],
    [1578211200000, 69.1999969482422],
    [1578214800000, 69.1999969482422],
    [1578218400000, 69.1999969482422],
    [1578222000000, 69.1999969482422],
    [1578225600000, 69.1999969482422],
    [1578229200000, 69.1999969482422],
    [1578232800000, 69.1999969482422],
    [1578236400000, 69.1999969482422],
    [1578240000000, 69.1999969482422],
    [1578243600000, 69.1999969482422],
    [1578247200000, 69.1999969482422],
    [1578250800000, 69.1999969482422],
    [1578254400000, 69.1999969482422],
    [1578258000000, 69.1999969482422],
    [1578261600000, 69.1999969482422],
    [1578265200000, 69.1999969482422],
    [1578268800000, 69.1999969482422],
    [1578272400000, 69.1999969482422],
    [1578276000000, 69.1999969482422],
    [1578279600000, 69.1999969482422],
    [1578283200000, 69.1999969482422],
    [1578286800000, 69.1999969482422],
    [1578290400000, 69.1999969482422],
    [1578294000000, 69.1999969482422],
    [1578297600000, 69.1999969482422],
    [1578301200000, 69.1999969482422],
    [1578304800000, 69.1999969482422],
    [1578308400000, 69.1999969482422],
    [1578312000000, 69.1999969482422],
    [1578315600000, 69.1999969482422],
    [1578319200000, 69.1999969482422],
    [1578322800000, 69.1999969482422],
    [1578326400000, 69.1999969482422],
    [1578330000000, 69.1999969482422],
    [1578333600000, 69.1999969482422],
    [1578337200000, 69.1999969482422],
    [1578340800000, 69.1999969482422],
    [1578344400000, 69.1999969482422],
    [1578348000000, 69.1999969482422],
    [1578351600000, 70.3556109079997],
    [1578355200000, 76.920997634146],
    [1578358800000, 79.4292947098697],
    [1578362400000, 79.2650916245309],
    [1578366000000, 79.6125674172757],
    [1578369600000, 79.0597236964905],
    [1578373200000, 77.7484098868052],
    [1578376800000, 77.1226613864899],
    [1578380400000, 76.9194480415149],
    [1578384000000, 78.0028471359237],
    [1578387600000, 77.1535494270819],
    [1578391200000, 75.0470741498029],
    [1578394800000, 74.679502580818],
    [1578398400000, 73.6077361986314],
    [1578402000000, 72.5580677314758],
    [1578405600000, 72.3134755830553],
    [1578409200000, 73.2778338997311],
    [1578412800000, 73.7656394293467],
    [1578416400000, 74.4736907299466],
    [1578420000000, 74.046935040758],
    [1578423600000, 73.4957105572807],
    [1578427200000, 73.9627712815163],
    [1578430800000, 74.0438044241729],
    [1578434400000, 73.3727496036106],
    [1578438000000, 73.1666655679279],
    [1578441600000, 73.3418058388816],
    [1578445200000, 73.6250001589457],
    [1578448800000, 73.829112378629],
    [1578452400000, 74.564083528116],
    [1578456000000, 75.5183061171072],
    [1578459600000, 77.3372781058983],
    [1578463200000, 78.0196371225993],
    [1578466800000, 77.6398578971368],
    [1578470400000, 78.5464104081542],
    [1578474000000, 78.7977605936686],
    [1578477600000, 76.0006624035588],
    [1578481200000, 74.818987728345],
    [1578484800000, 72.7776491559135],
    [1578488400000, 71.1266380795161],
    [1578492000000, 71.3866485616896],
    [1578495600000, 72.1584558128357],
    [1578499200000, 72.7288795283423],
    [1578502800000, 73.2401491424669],
    [1578506400000, 72.613320930343],
    [1578510000000, 71.7903886201647],
    [1578513600000, 71.4483344078064],
    [1578517200000, 71.8162703686467],
    [1578520800000, 71.3690680013303],
    [1578524400000, 70.6132688085203],
    [1578528000000, 69.9669739277875],
    [1578531600000, 69.2502318650422],
    [1578535200000, 68.8407318482576],
    [1578538800000, 71.4223982252898],
    [1578542400000, 68.8941290716666],
    [1578546000000, 71.8311421724037],
    [1578549600000, 72.5245706435945],
  ];

  // Power
  const powerArr = [
    [1578193200000, 0],
    [1578196800000, 0],
    [1578200400000, 0],
    [1578204000000, 0],
    [1578207600000, 0],
    [1578211200000, 0],
    [1578214800000, 0],
    [1578218400000, 0],
    [1578222000000, 0],
    [1578225600000, 0],
    [1578229200000, 0],
    [1578232800000, 0],
    [1578236400000, 0],
    [1578240000000, 0],
    [1578243600000, 0],
    [1578247200000, 0],
    [1578250800000, 0],
    [1578254400000, 0],
    [1578258000000, 0],
    [1578261600000, 0],
    [1578265200000, 0],
    [1578268800000, 0],
    [1578272400000, 0],
    [1578276000000, 0],
    [1578279600000, 0],
    [1578283200000, 0],
    [1578286800000, 0],
    [1578290400000, 0],
    [1578294000000, 0],
    [1578297600000, 0],
    [1578301200000, 0],
    [1578304800000, 0],
    [1578308400000, 0],
    [1578312000000, 0],
    [1578315600000, 0],
    [1578319200000, 0],
    [1578322800000, 0],
    [1578326400000, 0],
    [1578330000000, 0],
    [1578333600000, 0],
    [1578337200000, 0],
    [1578340800000, 0],
    [1578344400000, 0],
    [1578348000000, 0],
    [1578351600000, 0.831280025800069],
    [1578355200000, 27.4361266860337],
    [1578358800000, 4.02296011930285],
    [1578362400000, 5.46578637448993],
    [1578366000000, 189.045738115567],
    [1578369600000, 262.879154692536],
    [1578373200000, 182.996291840137],
    [1578376800000, 253.720326864073],
    [1578380400000, 266.71791350888],
    [1578384000000, 258.650130305165],
    [1578387600000, 256.817462126146],
    [1578391200000, 251.198874591439],
    [1578394800000, 245.782954276794],
    [1578398400000, 225.835229413786],
    [1578402000000, 191.164833256192],
    [1578405600000, 189.317473084174],
    [1578409200000, 160.866751228135],
    [1578412800000, 165.104705085896],
    [1578416400000, 185.380724406267],
    [1578420000000, 6.318082232318],
    [1578423600000, 22.6244981930396],
    [1578427200000, 0.125080846609247],
    [1578430800000, 0.858333364129066],
    [1578434400000, 3.15562303745482],
    [1578438000000, 1.73965485449897],
    [1578441600000, 3.73938900530338],
    [1578445200000, 0.641666680574417],
    [1578448800000, 1.64397225697835],
    [1578452400000, 14.7165156847371],
    [1578456000000, 6.7406491904815],
    [1578459600000, 257.018884414906],
    [1578463200000, 282.409075120573],
    [1578466800000, 284.999958205159],
    [1578470400000, 291.20836768991],
    [1578474000000, 285.753944205729],
    [1578477600000, 248.43810322171],
    [1578481200000, 227.135268204399],
    [1578484800000, 182.10778157076],
    [1578488400000, 169.076414526325],
    [1578492000000, 160.098294117384],
    [1578495600000, 149.832191919638],
    [1578499200000, 195.966023142751],
    [1578502800000, 159.01891281008],
    [1578506400000, 3.94323859943668],
    [1578510000000, 9.29238140483663],
    [1578513600000, 4.09348021179692],
    [1578517200000, 0],
    [1578520800000, 0],
    [1578524400000, 0],
    [1578528000000, 0],
    [1578531600000, 0],
    [1578535200000, 0],
    [1578538800000, 154.315364068476],
    [1578542400000, 193.405831769548],
    [1578546000000, 136.484141248209],
    [1578549600000, 209.041194383494],
  ];

  const CHART_TITLE = "Height Versus Weight of 507 Individuals by Gender";
  const CHART_SUBTITLE = "Source: Heinz  2003";

  const X_AXIS_TITLE = "Height (cm)";

  const SERIES_1_NAME = "Rücklauftemp";
  const SERIES_1_SYMBOL_COLOR = "rgba(119, 152, 191, .5)";
Pithon Kabiro's avatar
Pithon Kabiro committed
796
  const SERIES_1_TEXT_COLOR = "rgb(119, 152, 191)"; // remove transparency from symbol color for a more "intense" color
797
798
799
800
  const SERIES_1_SYMBOL = "°C";

  const SERIES_2_NAME = "Power";
  const SERIES_2_SYMBOL_COLOR = "rgba(223, 83, 83, .5)";
Pithon Kabiro's avatar
Pithon Kabiro committed
801
  const SERIES_2_TEXT_COLOR = "rgb(223, 83, 83)"; // remove transparency from symbol color for a more "intense" color
802
803
  const SERIES_2_SYMBOL = "kW";

804
805
806
807
808
  Highcharts.chart("chart-scatter-plot", {
    chart: {
      type: "scatter",
      zoomType: "xy",
    },
809

810
    title: {
811
      text: CHART_TITLE,
812
    },
813

814
    subtitle: {
815
      text: CHART_SUBTITLE,
816
    },
817

818
819
820
    xAxis: {
      title: {
        enabled: true,
821
        text: X_AXIS_TITLE,
822
      },
823
      type: "datetime",
824
825
826
827
      startOnTick: true,
      endOnTick: true,
      showLastLabel: true,
    },
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843

    yAxis: [
      {
        // Primary yAxis
        labels: {
          format: `{value} ${SERIES_1_SYMBOL}`,
          style: {
            color: SERIES_1_TEXT_COLOR,
          },
        },
        title: {
          text: SERIES_1_NAME,
          style: {
            color: SERIES_1_TEXT_COLOR,
          },
        },
844
      },
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
      {
        // Secondary yAxis
        title: {
          text: SERIES_2_NAME,
          style: {
            color: SERIES_2_TEXT_COLOR,
          },
        },
        labels: {
          format: `{value} ${SERIES_2_SYMBOL}`,
          style: {
            color: SERIES_2_TEXT_COLOR,
          },
        },
        opposite: true,
      },
    ],

863
864
865
866
867
868
869
870
871
872
    legend: {
      layout: "vertical",
      align: "left",
      verticalAlign: "top",
      x: 100,
      y: 70,
      floating: true,
      backgroundColor: Highcharts.defaultOptions.chart.backgroundColor,
      borderWidth: 1,
    },
873

874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
    plotOptions: {
      scatter: {
        marker: {
          radius: 5,
          states: {
            hover: {
              enabled: true,
              lineColor: "rgb(100,100,100)",
            },
          },
        },
        states: {
          hover: {
            marker: {
              enabled: false,
            },
          },
        },
        tooltip: {
Pithon Kabiro's avatar
Pithon Kabiro committed
893
          headerFormat: "{point.x:%e %b, %Y %H:%M:%S}: <b>{point.y}</b>",
894
          pointFormat: "{point.x} cm, {point.y} kg",
Pithon Kabiro's avatar
Pithon Kabiro committed
895
          valueDecimals: 2,
896
897
898
        },
      },
    },
899

900
901
    series: [
      {
902
903
904
        name: SERIES_1_NAME,
        color: SERIES_1_SYMBOL_COLOR,
        data: ruecklaufArr,
Pithon Kabiro's avatar
Pithon Kabiro committed
905
906
907
        tooltip: {
          valueSuffix: ` ${SERIES_1_SYMBOL}`,
        },
908
909
      },
      {
910
911
912
        name: SERIES_2_NAME,
        color: SERIES_2_SYMBOL_COLOR,
        data: powerArr,
Pithon Kabiro's avatar
Pithon Kabiro committed
913
914
915
916
917
918
        // need this property for the dual y-axes to work
        // defines the y-axis that this series refers to
        yAxis: 1,
        tooltip: {
          valueSuffix: ` ${SERIES_2_SYMBOL}`,
        },
919
920
921
922
923
924
925
      },
    ],
  });
};

drawScatterPlotHC();

926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
export {
  BASE_URL,
  QUERY_PARAMS_COMBINED,
  getDatastreamIdFromBuildingNumber,
  getDatastreamUrl,
  getObservationsUrl,
  createTemporalFilterString,
  axiosGetRequest,
  getDatastreamMetadata,
  formatDatastreamMetadataForChart,
  formatSTAResponseForHeatMap,
  drawHeatMapHC,
  formatSTAResponseForLineChart,
  drawLineChartHC,
  getCombinedObservationsFromAllNextLinks,
  getMetadataPlusObservationsForChart,
};