chartLine.mjs 5.09 KB
Newer Older
1
2
"use strict";

3
4
import {
  chartExportOptions,
5
  createFullTitleForLineOrColumnChart,
6
  createSubtitleForChart,
7
  createTooltipDateString,
8
} from "./chartHelpers.mjs";
9

10
/**
11
 * Format the response from SensorThings API to make it suitable for use in a line chart or column chart
12
13
14
 * @param {Array} obsArray Array of observations (timestamp + value) that is response from SensorThings API
 * @returns {Array} Array of formatted observations suitable for use in a line chart
 */
15
const formatSensorThingsApiResponseForLineOrColumnChart = function (obsArray) {
16
17
  if (!obsArray) return;

Pithon Kabiro's avatar
Pithon Kabiro committed
18
  return obsArray.map((result) => {
19
20
21
22
23
24
25
26
27
    const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
    const valueObs = result[1];
    return [timestampObs, valueObs];
  });
};

/**
 * Creates an options object for each series drawn in the line chart
 * @param {Array} formattedObsArraysForLineChart An array of formatted observation array(s) from one or more datastreams
28
 * @param {Array} buildingIdsPhenomenonNamesArr An array of string(s) made up of building ID(s) + phenomenon name(s)
29
30
31
32
 * @returns {Array} An array made up of series options object(s)
 */
const createSeriesOptionsForLineChart = function (
  formattedObsArraysForLineChart,
33
  buildingIdsPhenomenonNamesArr
34
) {
35
  // An array of colors, in hexadecimal format, provided by the global Highcharts object
36
37
  const seriesColors = Highcharts.getOptions().colors;

38
39
40
  // Create a copy of the colors array
  const seriesColorsArr = [...seriesColors];

41
  // Create an array of seriesOptions objects
42
  // Assumes that the observation array of arrays and building IDs + phenomenon names array are of equal length
43
  // Use one of the arrays for looping
44
45
46
  if (
    formattedObsArraysForLineChart.length !==
    buildingIdsPhenomenonNamesArr.length
47
  ) {
48
49
50
    throw new Error(
      "The observations array and phenomenon names array have different lengths"
    );
51
52
53
54
55
56
57
58
59
60
  } else {
    return formattedObsArraysForLineChart.map((formattedObsArray, i) => {
      return {
        name: `${buildingIdsPhenomenonNamesArr[i]}`,
        data: formattedObsArray,
        color: seriesColorsArr[i],
        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
      };
    });
  }
61
62
63
64
};

/**
 * Draw a line chart using Highcharts library
65
 * @param {Array} formattedObsArraysForLineChart An array made up of formatted observation array(s) suitable for use in a line chart. The observations may either be raw or aggregated
Pithon Kabiro's avatar
Pithon Kabiro committed
66
 * @param {Object} extractedFormattedDatastreamPropertiesArr An object that contains arrays of formatted Datastream properties
67
68
69
70
 * @returns {undefined} undefined
 */
const drawLineChartHighcharts = function (
  formattedObsArraysForLineChart,
Pithon Kabiro's avatar
Pithon Kabiro committed
71
  extractedFormattedDatastreamProperties
72
73
) {
  // Arrays of datastream properties
74
  let datastreamNamesArr,
75
    phenomenonNamesArr,
76
    buildingIdsPhenomenonNamesArr,
77
    unitOfMeasurementSymbolsArr,
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
    aggregationInterval,
    aggregationType;

  // Check whether the datastream properties are for aggregated observations
  if (extractedFormattedDatastreamProperties?.aggregationType === undefined) {
    // Case 1: No aggregation
    ({
      datastreamNamesArr,
      phenomenonNamesArr,
      buildingIdsPhenomenonNamesArr,
      unitOfMeasurementSymbolsArr,
    } = extractedFormattedDatastreamProperties);
  } else {
    // Case 2: Aggregation
    ({
      datastreamNamesArr,
      phenomenonNamesArr,
      buildingIdsPhenomenonNamesArr,
      unitOfMeasurementSymbolsArr,
      aggregationInterval,
      aggregationType,
    } = extractedFormattedDatastreamProperties);
  }
101

102
  // Chart title and subtitle text
103
104
105
106
107
  const textChartTitle = createFullTitleForLineOrColumnChart(
    phenomenonNamesArr,
    aggregationInterval,
    aggregationType
  );
108

109
  const textChartSubtitle = createSubtitleForChart(datastreamNamesArr);
110

111
112
113
  // Create the array of series options object(s)
  const seriesOptionsArr = createSeriesOptionsForLineChart(
    formattedObsArraysForLineChart,
114
    buildingIdsPhenomenonNamesArr,
115
116
117
118
119
120
121
122
123
124
125
126
127
    unitOfMeasurementSymbolsArr
  );

  Highcharts.stockChart("chart-line", {
    chart: {
      zoomType: "x",
    },

    rangeSelector: {
      selected: 5,
    },

    title: {
128
      text: textChartTitle,
129
      "align": "center",
130
131
132
    },

    subtitle: {
133
      text: textChartSubtitle,
134
      align: "center",
135
136
137
    },

    tooltip: {
138
139
140
141
      formatter() {
        // Our tooltip is split
        // this.x -- common for all points
        // this.points -- an array containing properties for each series
142
143
144
        return [
          `${createTooltipDateString(this.x, aggregationInterval)}`,
        ].concat(
145
146
147
148
149
150
151
152
153
154
155
156
          this.points
            ? this.points.map(
                (point, i) =>
                  `<span style="color:${point.color}">${
                    point.series.name
                  }</span>: <b>${point.y.toFixed(2)} ${
                    unitOfMeasurementSymbolsArr[i]
                  }</b>`
              )
            : []
        );
      },
157
158
    },

159
160
    exporting: chartExportOptions,

161
162
163
164
    series: seriesOptionsArr,
  });
};

165
166
167
168
export {
  formatSensorThingsApiResponseForLineOrColumnChart,
  drawLineChartHighcharts,
};