"use strict"; const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1"; /** * Retrieve the datastream ID that corresponds to a particular building * @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 * @returns {Number} Datastream corresponding to the input building */ const getDatastreamIdFromBuildingNumber = function ( buildingNumber, phenomenon, samplingRate ) { const buildingToDatastreamMapping = { 101: { vl: { "15min": "69", "60min": "75" }, rl: { "15min": "81", "60min": "87" }, // 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" }, }, 102: { vl: { "15min": "70", "60min": "76" }, rl: { "15min": "82", "60min": "88" }, // 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" }, }, 107: { vl: { "15min": "71", "60min": "77" }, rl: { "15min": "83", "60min": "89" }, // 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" }, }, "112, 118": { vl: { "15min": "72", "60min": "78" }, rl: { "15min": "84", "60min": "90" }, // 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" }, }, 125: { vl: { "15min": "73", "60min": "79" }, rl: { "15min": "85", "60min": "91" }, // 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" }, }, 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" }, }, }; if ( buildingToDatastreamMapping?.[buildingNumber]?.[phenomenon]?.[ samplingRate ] === undefined ) return; const datastreamIdMatched = Number( buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate] ); return datastreamIdMatched; }; /** * 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 */ const getDatastreamUrl = function (baseUrl, datastreamID) { if (!datastreamID) return; const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})`; return fullDatastreamURL; }; /** * Create URL to fetch Observations * @param {String} baseUrl Base URL of the STA server * @param {Number} datastreamID Integer representing the Datastream ID * @returns {String} URL string for fetching Observations */ const getObservationsUrl = function (baseUrl, datastreamID) { if (!datastreamID) return; const fullObservationsURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`; return fullObservationsURL; }; /** * Create a temporal filter string for the fetched Observations * @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 */ const createTemporalFilterString = function (dateStart, dateStop) { if (!dateStart || !dateStop) return; const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`; return filterString; }; // const BASE_URL_OBSERVATIONS = getObservationsUrl(80); 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"; const QUERY_PARAMS_COMBINED = { "$resultFormat": QUERY_PARAM_RESULT_FORMAT, "$orderBy": QUERY_PARAM_ORDER_BY, "$filter": QUERY_PARAM_FILTER, "$select": QUERY_PARAM_SELECT, }; /** * Perform a GET request using the Axios library * @param {String} urlObservations A URL that fetches Observations from an STA instance * @param {Object} urlParamObj The URL parameters to be sent together with the GET request * @returns {Promise} A promise that contains the first page of results when fulfilled */ const axiosGetRequest = function (urlObservations, urlParamObj) { return axios.get(urlObservations, { params: urlParamObj, }); }; /** * 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 */ const getDatastreamMetadata = async function (urlDatastream) { try { // Extract properties of interest const { data: { description, name, unitOfMeasurement }, } = await axiosGetRequest(urlDatastream); return { description, name, unitOfMeasurement }; } catch (err) { console.error(err); } }; /** * 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 */ const formatDatastreamMetadataForChart = function (datastreamMetadata) { 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 "m3/h"; } else { return unitOfMeasurement.symbol; } })(); return { datastreamDescription, datastreamName, phenomenonName, unitOfMeasurementSymbol, }; }; /** * Format the response from SensorThings API to make it suitable for heatmap * @param {Array} obsArray Response from SensorThings API as array * @returns {Array} Array of formatted observations suitable for use in a heatmap */ const formatSTAResponseForHeatMap = function (obsArray) { if (!obsArray) return; const dataSTAFormatted = 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]; }); return dataSTAFormatted; }; /** * Draw a heatmap using Highcharts library * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata * @returns {undefined} undefined */ const drawHeatMapHC = function ( formattedObsArrayForHeatmap, formattedDatastreamMetadata ) { const { datastreamDescription: DATASTREAM_DESCRIPTION, datastreamName: DATASTREAM_NAME, phenomenonName: PHENOMENON_NAME, unitOfMeasurementSymbol: PHENOMENON_SYMBOL, } = formattedDatastreamMetadata; // 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 }; })(); 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}℃", format: `{value}${PHENOMENON_SYMBOL}`, }, }, series: [ { data: formattedObsArrayForHeatmap, boostThreshold: 100, borderWidth: 0, nullColor: "#525252", colsize: 24 * 36e5, // one day tooltip: { headerFormat: `${PHENOMENON_NAME}
`, valueDecimals: 2, pointFormat: // "{point.x:%e %b, %Y} {point.y}:00: {point.value} ℃", `{point.x:%e %b, %Y} {point.y}:00: {point.value} ${PHENOMENON_SYMBOL}`, nullFormat: `{point.x:%e %b, %Y} {point.y}:00: null`, }, turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release }, ], }); }; /** * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch * @param {Array} obsArray Response from SensorThings API as array * @returns {Array} Array of formatted observations suitable for use in a line chart */ const formatSTAResponseForLineChart = function (obsArray) { if (!obsArray) return; const dataSTAFormatted = obsArray.map((result) => { const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp const valueObs = result[1]; return [timestampObs, valueObs]; }); return dataSTAFormatted; }; /** * Draw a line chart using Highcharts library * @param {Array} formattedObsArrayForLineChart Response from SensorThings API formatted for use in a line chart * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata * @returns {undefined} undefined */ const drawLineChartHC = function ( formattedObsArrayForLineChart, formattedDatastreamMetadata ) { const { datastreamDescription: DATASTREAM_DESCRIPTION, datastreamName: DATASTREAM_NAME, phenomenonName: PHENOMENON_NAME, unitOfMeasurementSymbol: PHENOMENON_SYMBOL, } = formattedDatastreamMetadata; Highcharts.stockChart("chart-line", { chart: { zoomType: "x", }, rangeSelector: { selected: 5, }, title: { text: DATASTREAM_DESCRIPTION, "align": "left", }, subtitle: { text: DATASTREAM_NAME, align: "left", }, series: [ { name: `${PHENOMENON_NAME} (${PHENOMENON_SYMBOL})`, data: formattedObsArrayForLineChart, tooltip: { valueDecimals: 2, }, turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release }, ], }); }; /** * Follows "@iot.nextLink" links in SensorThingsAPI's response * Appends new results to existing results * @async * @param {Promise} responsePromise Promise object resulting from an Axios GET request * @returns {Object} Object containing results from all the "@iot.nextLink" links */ const followNextLink = function (responsePromise) { if (!responsePromise) return; return responsePromise .then((lastSuccess) => { if (lastSuccess.data["@iot.nextLink"]) { return followNextLink( axios.get(lastSuccess.data["@iot.nextLink"]) ).then((nextLinkSuccess) => { nextLinkSuccess.data.value = lastSuccess.data.value.concat( nextLinkSuccess.data.value ); return nextLinkSuccess; }); } else { return lastSuccess; } }) .catch((err) => { console.error(err); }); }; /** * Retrieve all the Observations from a Datastream after traversing all the "@iot.nextLink" links * @async * @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 */ const getCombinedObservationsFromAllNextLinks = function ( httpGetRequestPromise ) { return followNextLink(httpGetRequestPromise) .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) => { console.error(err); }); }; /** * 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 */ const getMetadataPlusObservationsForChart = async function ( metadataPlusObsPromiseArray ) { // Array to store our final result const combinedResolvedPromises = []; // Use for/of loop - we need to maintain the order of execution of the async operations for (const promise of metadataPlusObsPromiseArray) { try { // Resolved value of a single promise const resolvedPromise = await promise; combinedResolvedPromises.push(resolvedPromise); } catch (err) { console.error(err); } } return combinedResolvedPromises; }; /** * Retrieve all the Observations from an array of Observations promises * @async * @param {Promise} observationPromiseArray An array that contains N observation promises * @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) { console.error(err); } } return observationsAllDatastreamsArr; }; // 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) // ); const drawScatterPlotHC = function () { // 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)"; const SERIES_1_TEXT_COLOR = "rgb(119, 152, 191)"; // remove transparency from symbol color const SERIES_1_SYMBOL = "°C"; const SERIES_2_NAME = "Power"; const SERIES_2_SYMBOL_COLOR = "rgba(223, 83, 83, .5)"; const SERIES_2_TEXT_COLOR = "rgb(223, 83, 83)"; // remove transparency from symbol color const SERIES_2_SYMBOL = "kW"; Highcharts.chart("chart-scatter-plot", { chart: { type: "scatter", zoomType: "xy", }, title: { text: CHART_TITLE, }, subtitle: { text: CHART_SUBTITLE, }, xAxis: { title: { enabled: true, text: X_AXIS_TITLE, }, type: "datetime", startOnTick: true, endOnTick: true, showLastLabel: true, }, 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, }, }, }, { // 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, }, ], legend: { layout: "vertical", align: "left", verticalAlign: "top", x: 100, y: 70, floating: true, backgroundColor: Highcharts.defaultOptions.chart.backgroundColor, borderWidth: 1, }, plotOptions: { scatter: { marker: { radius: 5, states: { hover: { enabled: true, lineColor: "rgb(100,100,100)", }, }, }, states: { hover: { marker: { enabled: false, }, }, }, tooltip: { headerFormat: "{series.name}
", pointFormat: "{point.x} cm, {point.y} kg", }, }, }, series: [ { name: SERIES_1_NAME, color: SERIES_1_SYMBOL_COLOR, data: ruecklaufArr, yAxis: 1, // need this property for the dual y-axes }, { name: SERIES_2_NAME, color: SERIES_2_SYMBOL_COLOR, data: powerArr, }, ], }); }; drawScatterPlotHC(); export { BASE_URL, QUERY_PARAMS_COMBINED, getDatastreamIdFromBuildingNumber, getDatastreamUrl, getObservationsUrl, createTemporalFilterString, axiosGetRequest, getDatastreamMetadata, formatDatastreamMetadataForChart, formatSTAResponseForHeatMap, drawHeatMapHC, formatSTAResponseForLineChart, drawLineChartHC, getCombinedObservationsFromAllNextLinks, getMetadataPlusObservationsForChart, };