"use strict"; import { chartExportOptions } from "./chartExport.mjs"; /** * Format the response from SensorThings API to make it suitable for use in a heatmap * @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 heatmap */ const formatSensorThingsApiResponseForHeatMap = function (obsArray) { if (!obsArray) return; return obsArray.map((obs) => { // 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]; return [timestamp, hourOfDay, value]; }); }; /** * Calculate the minimum and maximum values for a heatmap's color axis * @param {Array} formattedObsArrHeatmap Response from SensorThings API formatted for use in a heatmap * @returns {Object} An object containing the minimum and maximum values */ const calculateMinMaxValuesForHeatmapColorAxis = function ( formattedObsArrHeatmap ) { // The observation value is the third element in array const obsValueArr = formattedObsArrHeatmap.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 }; }; /** * Draw a heatmap using Highcharts library * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties * @returns {undefined} undefined */ const drawHeatMapHighcharts = function ( formattedObsArrayForHeatmap, extractedFormattedDatastreamProperties ) { // Arrays of datastream properties const { datastreamDescriptionsArr, datastreamNamesArr, phenomenonNamesArr, unitOfMeasurementSymbolsArr, } = extractedFormattedDatastreamProperties; const [DATASTREAM_DESCRIPTION] = datastreamDescriptionsArr; const [DATASTREAM_NAME] = datastreamNamesArr; const [PHENOMENON_NAME] = phenomenonNamesArr; const [PHENOMENON_SYMBOL] = unitOfMeasurementSymbolsArr; const { minObsValue: MINIMUM_VALUE_COLOR_AXIS, maxObsValue: MAXIMUM_VALUE_COLOR_AXIS, } = calculateMinMaxValuesForHeatmapColorAxis(formattedObsArrayForHeatmap); Highcharts.chart("chart-heatmap", { chart: { type: "heatmap", zoomType: "x", }, boost: { useGPUTranslations: true, }, title: { text: DATASTREAM_DESCRIPTION, align: "left", x: 40, }, subtitle: { text: DATASTREAM_NAME, 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"], ], min: MINIMUM_VALUE_COLOR_AXIS, max: MAXIMUM_VALUE_COLOR_AXIS, startOnTick: false, endOnTick: false, labels: { format: `{value}${PHENOMENON_SYMBOL}`, }, }, exporting: chartExportOptions, tooltip: { formatter() { const headerString = `${PHENOMENON_NAME}<br/>`; // Check whether the point value is null or not; this will determine the string that we'll render const pointString = this.point.value === null ? `${Highcharts.dateFormat("%e %b, %Y", this.point.x)} ${ this.point.y }:00:00 <b>null</b>` : `${Highcharts.dateFormat("%e %b, %Y", this.point.x)} ${ this.point.y }:00:00 <b>${this.point.value.toFixed( 2 )} ${PHENOMENON_SYMBOL}</b>`; return headerString + pointString; }, }, series: [ { data: formattedObsArrayForHeatmap, boostThreshold: 100, borderWidth: 0, nullColor: "#525252", colsize: 24 * 36e5, // one day turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release }, ], }); }; export { formatSensorThingsApiResponseForHeatMap, drawHeatMapHighcharts };