river-data-widget
Advanced tools
+32
-11
@@ -1,8 +0,8 @@ | ||
| /*! RiverDataWidget v1.0.1 2023-05-17 11:39:50 | ||
| /*! RiverDataWidget v1.1.0 2023-05-31 14:34:01 | ||
| *! https://github.com/pb-uk/river-data-widget#readme | ||
| *! Copyright (C) 2023 pbuk (https://github.com/pb-uk). | ||
| *! License AGPL-3.0-or-later. | ||
| *! License MIT. | ||
| */ | ||
| var version = "1.0.1"; | ||
| var version = "1.1.0"; | ||
@@ -138,3 +138,2 @@ class RiverDataWidgetError extends Error { | ||
| const PLOT_COLOR = '#77C'; | ||
| class Chart { | ||
@@ -148,2 +147,5 @@ constructor(el, series, options = {}) { | ||
| this.strokeWidth = 2; | ||
| this.plotColor = '#77C'; | ||
| this.labelBg = 'rgba(255,255,255,0.5)'; | ||
| this.labelBgWidth = '0.5em'; | ||
| this.attribution = 'Uses Environment Agency data from the real-time API (Beta)'; | ||
@@ -268,11 +270,26 @@ // CSS settings. | ||
| const [time, value] = data[data.length - 1]; | ||
| const { minTime, timeScale, minValue, valueScale } = this.getLimits(); | ||
| const { minTime, timeScale, maxValue, minValue } = this.getLimits(); | ||
| const v = formatter == null ? value : formatter(value); | ||
| const xOffset = this.strokeWidth / 2; | ||
| const yOffset = this.plotHeight - this.strokeWidth / 2; | ||
| // const yOffset = this.plotHeight - this.strokeWidth / 2; | ||
| const x = xOffset + (time - minTime) * timeScale; | ||
| const highLabel = (value - minValue) / valueScale >= 0.5; | ||
| const y = yOffset - this.plotHeight * (highLabel ? 1 : 0.5) + this.fontSizePx * 2; | ||
| this.el.append(createSvgElement('text', { x, y, 'text-anchor': 'end' }, { fill: PLOT_COLOR, 'font-size': '1.5em', 'font-weight': 'bold' }, `${v} ${unit}`)); | ||
| this.el.append(createSvgElement('text', { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' }, { fill: PLOT_COLOR }, `${timeFormatter.format(new Date(time * 1000))}`)); | ||
| const isHighLabel = (value - minValue) / (maxValue - minValue) < 0.5; | ||
| const y = this.plotHeight * (isHighLabel ? 0 : 0.5) + this.fontSizePx * 2; | ||
| this.el.append( | ||
| // Background for value label. | ||
| createSvgElement('text', { x, y, 'text-anchor': 'end' }, { | ||
| 'font-size': '1.5em', | ||
| 'font-weight': 'bold', | ||
| stroke: this.labelBg, | ||
| 'stroke-width': this.labelBgWidth, | ||
| }, `${v} ${unit}`), | ||
| // Value label. | ||
| createSvgElement('text', { x, y, 'text-anchor': 'end' }, { fill: this.plotColor, 'font-size': '1.5em', 'font-weight': 'bold' }, `${v} ${unit}`), | ||
| // Background for time label. | ||
| createSvgElement('text', { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' }, { | ||
| stroke: this.labelBg, | ||
| 'stroke-width': this.labelBgWidth, | ||
| }, `${timeFormatter.format(new Date(time * 1000))}`), | ||
| // Time label. | ||
| createSvgElement('text', { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' }, { fill: this.plotColor }, `${timeFormatter.format(new Date(time * 1000))}`)); | ||
| } | ||
@@ -297,3 +314,3 @@ plotData() { | ||
| d: points.join(''), | ||
| stroke: PLOT_COLOR, | ||
| stroke: this.plotColor, | ||
| 'stroke-width': this.strokeWidth, | ||
@@ -532,2 +549,6 @@ fill: 'none', | ||
| const series1 = { data, unit, formatter: round3 }; | ||
| // Set max/min options for plot from widget options. | ||
| if (options.riverDataWidgetMaxValue != null) { | ||
| series1.max = parseFloat(options.riverDataWidgetMaxValue); | ||
| } | ||
| if (options.riverDataWidgetMinValue != null) { | ||
@@ -534,0 +555,0 @@ series1.min = parseFloat(options.riverDataWidgetMinValue); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sources":["../src/error.ts","../src/helpers/format.ts","../src/flood-monitoring-api/error.ts","../src/flood-monitoring-api/measure.ts","../src/helpers/dom.ts","../src/helpers/time.ts","../src/widget/chart.ts","../src/flood-monitoring-api/api.ts","../src/flood-monitoring-api/store.ts","../src/flood-monitoring-api/reading.ts","../src/widget/render.ts","../src/autoload.ts"],"sourcesContent":["export type RiverDataWidgetErrorInfo = Record<string, unknown>;\n\nexport class RiverDataWidgetError extends Error {\n public info: RiverDataWidgetErrorInfo;\n\n constructor(msg: string, info: RiverDataWidgetErrorInfo = {}) {\n super(msg);\n this.name = 'RiverDataWidgetError';\n this.info = info;\n }\n}\n","export const FONT_STACK =\n '-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Roboto\",\"Helvetica Neue\",Arial,sans-serif';\n\nexport const round3 = (value: number) =>\n value < 100 ? value.toPrecision(3) : Math.round(value).toString();\n","export class FloodMonitoringApiError extends Error {\n public info: Record<string, unknown>;\n\n constructor(msg: string, info: Record<string, unknown> = {}) {\n super(msg);\n this.name = 'FloodMonitoringApiError';\n this.info = info;\n }\n}\n","import { FloodMonitoringApiError } from './error';\n\nexport { parseMeasureId };\n\nconst parseMeasureId = (measureId: string) => {\n // ............base/ stat-paramet-qualifi- type -interva-unit\n const regExp = /(.*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)$/;\n const matches = measureId.match(regExp);\n if (matches === null) {\n throw new FloodMonitoringApiError('Cannot parse measure id', { measureId });\n }\n const [unit, interval, type, qualifier, parameter, stationId] =\n matches.reverse();\n const qualifiedParameter = qualifier.length\n ? `${parameter}-${qualifier}`\n : parameter;\n return {\n stationId,\n parameter,\n qualifier,\n type,\n interval,\n unit,\n qualifiedParameter,\n };\n};\n\nconst measureTranslations: Record<string, Record<string, string>> = {\n unit: {\n m3_s: 'm³/s',\n mAOD: 'm',\n mASD: 'm',\n },\n qualifiedParameter: {\n 'level-stage': 'level',\n 'level-downstage': 'downstream level',\n },\n};\n\nexport const translateMeasureProperties = (measure: Record<string, string>) => {\n const translated: Record<string, string> = {};\n for (const prop in measure) {\n const value = measure[prop];\n if (measureTranslations[prop] && measureTranslations[prop][value]) {\n translated[prop] = measureTranslations[prop][value];\n } else {\n translated[prop] = value;\n }\n }\n return translated;\n};\n","/**\n * RiverDataWidget https://github.com/pb-uk/river-data-widget.\n *\n * @copyright Copyright (C) 2022 pbuk https://github.com/pb-uk.\n * @license AGPL-3.0-or-later see LICENSE.md.\n */\n\nexport { createElement, createSvgElement, setAttributes, setStyles };\n\ntype AttributeList = Record<string, string | number>;\n\nconst setAttributes = <T extends HTMLElement | SVGElement>(\n el: T,\n attributes: AttributeList\n): T => {\n Object.entries(attributes).forEach(([key, value]) => {\n el.setAttribute(key, `${value}`);\n });\n return el;\n};\n\nconst setStyles = <T extends HTMLElement | SVGElement>(\n el: T,\n styles: AttributeList\n): T => {\n Object.entries(styles).forEach(([key, value]) => {\n // Workaround (el.style.setProperty uses kebab-case keys).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (<any>el.style)[key] = value;\n });\n return el;\n};\n\nconst createElement = (\n name = 'div',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n): HTMLElement => {\n const el = document.createElement(name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n\nconst createSvgElement = (\n name = 'svg',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n) => {\n const el = document.createElementNS('http://www.w3.org/2000/svg', name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n","export const MINUTE_MS = 60000;\n// const HOUR_MS = 3600000;\nexport const DAY_MS = 86400000;\n\n/**\n * Get the Date at the start of a day in UTC or local time.\n *\n * @param offset\n * @param timeZone The time zone offset in minutes, or set to `true` to use the\n * local time zone (`false`, the default, uses UTC).\n * @returns The reqested date.\n */\nexport const startOfDay = (\n date: Date | null = null,\n offset = 0,\n timeZone: boolean | number = false\n): Date => {\n if (timeZone === false) {\n // Use UTC.\n const base = date === null ? Date.now() : date.valueOf();\n return new Date(Math.floor(base / DAY_MS + offset) * DAY_MS);\n }\n\n const now = new Date();\n const tz = timeZone === true ? now.getTimezoneOffset() : timeZone;\n const local = now.valueOf() + tz * MINUTE_MS;\n return new Date(Math.floor(local / DAY_MS + offset) * DAY_MS);\n};\n\n/**\n * | | long |short|narrow|numeric|2-digit|\n * |:-------:|:-----------:|:---:|:----:|:-----:|:-----:|\n * | weekday | Monday | Mon | M | | |\n * | era | Anno Domini | AD | A | | |\n * | year | | | | 2012 | 12 |\n * | month | March | Mar | M | 3 | 03 |\n * | day | | | | 1 | 01 |\n * | hour | | | | 1 | 01 |\n * | minute | | | | 1 | 01 |\n * | second | | | | 1 | 01 |\n *\n * * fractionalSecondDigits: 1, 2 or 3 for number of digits.\n * * timeZoneName: long (Pacific Standard Time), short (PST),\n * longOffset (GMT-0800), shortOffset (GMT-8), longGeneric (Pacific Time),\n * shortGeneric (PT).\n */\n\nexport const dateFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n // year: 'numeric',\n});\n\nexport const timeFormatter = new Intl.DateTimeFormat('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n timeZoneName: 'short',\n});\n\nexport const dddFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'short',\n});\n\nexport const dMmmFormatter = new Intl.DateTimeFormat('en-GB', {\n day: 'numeric',\n month: 'short',\n});\n","import { createSvgElement } from '../helpers/dom';\nimport { timeFormatter, dddFormatter, dMmmFormatter } from '../helpers/time';\nimport { FloodMonitoringApiError } from '../flood-monitoring-api/error';\nimport { FONT_STACK } from '../helpers/format';\n\nconst PLOT_COLOR = '#77C';\n\nexport interface ChartOptions {\n minTime?: number;\n maxTime?: number;\n attribution?: string;\n}\n\nexport interface ChartScaleLimits {\n minTime: number;\n maxTime: number;\n timeScale: number;\n minValue: number;\n maxValue: number;\n valueScale: number;\n}\n\nexport interface ChartSeries {\n data: TimeSeriesValue[];\n min?: number;\n max?: number;\n unit?: string;\n formatter?: (value: number) => string;\n}\n\nexport type TimeSeriesValue = [\n ts: number, // Unix time stamp (seconds).\n v: number // Value.\n];\n\nexport class Chart {\n protected fontSizePx = 14;\n\n protected el: SVGElement;\n protected series: ChartSeries[];\n protected options: ChartOptions;\n protected width = 480; // 400;\n protected height = 270; // 225;\n protected plotHeight = this.height - this.fontSizePx * 4.5;\n protected strokeWidth = 2;\n protected limits?: ChartScaleLimits;\n\n protected attribution =\n 'Uses Environment Agency data from the real-time API (Beta)';\n\n // CSS settings.\n // Just readable at 320x180.\n // Good from 400x225.\n // Perfect at 480x270 (font is 12px);\n protected styles = {\n 'font-family': FONT_STACK,\n 'font-size': `${this.fontSizePx}px`,\n display: 'block',\n margin: 'auto',\n 'max-width': '150vh',\n };\n\n constructor(\n el: HTMLElement,\n series: ChartSeries[],\n options: ChartOptions = {}\n ) {\n this.series = series;\n this.options = options;\n const viewBox = `0 0 ${this.width} ${this.height}`;\n this.attribution = options.attribution ?? this.attribution;\n this.el = createSvgElement('svg', { viewBox }, this.styles);\n el.append(this.el);\n }\n\n getLimits(): ChartScaleLimits {\n if (this.limits == null) {\n throw new FloodMonitoringApiError('Chart axis limits have not been set');\n }\n return this.limits;\n }\n\n getHorizontalGridlines(): SVGElement[] {\n const { minTime, maxTime, timeScale, minValue, maxValue, valueScale } =\n this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight;\n const x1 = xOffset;\n const x2 = xOffset + (maxTime - minTime) * timeScale;\n // Horizontal grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n const valueRange = maxValue - minValue;\n // Horizontal grid interval.\n const [interval, exponent] = getInterval(valueRange, 9);\n const factor = 10 ** -exponent;\n const base = Math.ceil((minValue * factor) / interval + 1) * interval;\n let i = 0;\n let current = base / factor;\n while (current < maxValue) {\n const y1 = yOffset - (current - minValue) * valueScale;\n lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n labels.append(\n createSvgElement('text', { x: x1 + 4, y: y1 + 4 }, {}, `${current}`)\n );\n ++i;\n current = (base + i * interval) / factor;\n }\n const timeAxisLine = createSvgElement(\n 'line',\n { x1, y1: yOffset, x2, y2: yOffset },\n { stroke: '#777' }\n );\n\n return [lines, labels, timeAxisLine];\n }\n\n getTimeScale(): SVGElement[] {\n const { minTime, maxTime, timeScale } = this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight + this.strokeWidth / 2;\n const y1 = yOffset + this.fontSizePx * 3;\n const y2 = yOffset - this.plotHeight;\n // Vertical grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n // Vertical grid interval.\n const base = minTime;\n const interval = 86400;\n let i = 0;\n let current = base;\n const labelOffset = 43200 * timeScale;\n const fill = '#444';\n while (current <= maxTime) {\n const x1 = xOffset + (current - minTime) * timeScale;\n const d = new Date(current * 1000);\n // lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n lines.append(createSvgElement('line', { x1, y1, x2: x1, y2 }));\n labels.append(\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 1.8,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dddFormatter.format(d)}`\n ),\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 0.5,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dMmmFormatter.format(d)}`\n )\n );\n ++i;\n current = base + i * interval;\n }\n return [lines, labels];\n }\n\n render() {\n // Calculate axis scales.\n const limits = getLimits(this.series[0].data);\n limits.minValue = this.series[0].min ?? limits.minValue;\n limits.maxValue = this.series[0].max ?? limits.maxValue;\n limits.minTime = this.options.minTime ?? limits.minTime;\n limits.maxTime = this.options.maxTime ?? limits.maxTime;\n\n this.limits = {\n ...limits,\n valueScale:\n (this.plotHeight - this.strokeWidth) /\n (limits.maxValue - limits.minValue),\n timeScale:\n (this.width - this.strokeWidth) / (limits.maxTime - limits.minTime),\n };\n\n // Time axis.\n const [timeLines, timeLabels] = this.getTimeScale();\n this.el.append(timeLines);\n\n // Value axis.\n const [valueLines, valueLabels, timeAxisLine] =\n this.getHorizontalGridlines();\n this.el.append(valueLines);\n this.el.append(timeAxisLine);\n\n this.plotData();\n\n // Plot labels on top of the line.\n this.el.append(timeLabels);\n this.el.append(valueLabels);\n\n this.el.append(\n createSvgElement(\n 'text',\n {\n x: this.width / 2,\n 'text-anchor': 'middle',\n y: this.height - this.fontSizePx * 0.5,\n },\n { fill: '#595959' },\n this.attribution\n )\n );\n\n this.plotLastValue();\n }\n\n plotLastValue() {\n const { data, unit, formatter } = this.series[0];\n const [time, value] = data[data.length - 1];\n const { minTime, timeScale, minValue, valueScale } = this.getLimits();\n\n const v = formatter == null ? value : formatter(value);\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight - this.strokeWidth / 2;\n const x = xOffset + (time - minTime) * timeScale;\n const highLabel = (value - minValue) / valueScale >= 0.5;\n const y =\n yOffset - this.plotHeight * (highLabel ? 1 : 0.5) + this.fontSizePx * 2;\n\n this.el.append(\n createSvgElement(\n 'text',\n { x, y, 'text-anchor': 'end' },\n { fill: PLOT_COLOR, 'font-size': '1.5em', 'font-weight': 'bold' },\n `${v} ${unit}`\n )\n );\n this.el.append(\n createSvgElement(\n 'text',\n { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' },\n { fill: PLOT_COLOR },\n `${timeFormatter.format(new Date(time * 1000))}`\n )\n );\n }\n\n plotData() {\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight - this.strokeWidth / 2;\n const { data } = this.series[0];\n const { minTime, timeScale, minValue, valueScale } = this.getLimits();\n // First data point.\n const x = xOffset + (data[0][0] - minTime) * timeScale;\n const y = yOffset - (data[0][1] - minValue) * valueScale;\n const points = [`M${x},${y}`];\n // Remaining data points.\n for (let i = 1; i < data.length; ++i) {\n const x = xOffset + (data[i][0] - minTime) * timeScale;\n const y = yOffset - (data[i][1] - minValue) * valueScale;\n points.push(`L${x},${y}`);\n }\n // Plot the data.\n const path = createSvgElement('path', {\n d: points.join(''),\n stroke: PLOT_COLOR,\n 'stroke-width': this.strokeWidth,\n fill: 'none',\n });\n this.el.append(path);\n }\n}\n\nexport const getLimits = (data: TimeSeriesValue[]) => {\n if (data.length < 1) {\n throw new Error('Readings must not be empty');\n }\n const minTime = data[0][0];\n const maxTime = data[data.length - 1][0];\n let minValue = Infinity;\n let maxValue = -minValue;\n data.forEach(([, value]) => {\n minValue = Math.min(minValue, value);\n maxValue = Math.max(maxValue, value);\n });\n return { minTime, maxTime, minValue, maxValue };\n};\n\nexport const getInterval = (range: number, maxDivisions: number) => {\n const exponent = Math.floor(Math.log10(range)) - 1;\n const k = range / (maxDivisions * 10 ** exponent);\n const mantissa = k <= 2 ? 2 : k <= 5 ? 5 : 10;\n return [mantissa, exponent];\n};\n","// There is no need to be secure about this!\nconst baseUrl = 'http://environment.data.gov.uk/flood-monitoring';\n\nexport interface ApiResponse<T> {\n data: {\n items: T;\n };\n response: Response;\n}\n\nexport interface ApiParameters {\n since?: string; // Time from.\n _sorted?: ''; // Flag for sorting.\n}\n\nexport const apiFetch = async (\n path: string,\n query = {}\n): Promise<ApiResponse<unknown>> => {\n const queryString = new URLSearchParams(query).toString();\n const uri = queryString\n ? `${baseUrl}${path}?${queryString}`\n : `${baseUrl}${path}`;\n const response = await fetch(uri);\n return { data: await response.json(), response };\n};\n\n/**\n * Convert a Date to a format recognized by the EA API for a query parameter.\n *\n * @param date Convert from.\n * @returns A string in the EA API query parameter format.\n */\nexport const toTimeParameter = (date: Date): string => {\n return date.toISOString().substring(0, 19) + 'Z';\n};\n\n/*\nUseful response headers\n Date: 'Sat, 13 May 2023 09:14:07 GMT',\n last-modified: Sat, 13 May 2023 09:03:13 GMT,\nResponse meta:\n publisher: 'Environment Agency',\n license: 'http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/',\n documentation: 'http://environment.data.gov.uk/flood-monitoring/doc/reference',\n version: '0.9',\n comment: 'Status: Beta service',\n hasFormat: [\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.csv?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.rdf?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.ttl?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.html?_sorted=&since=2023-05-12T08%3A00%3A00Z\"\n ],\n*/\n","const prefix = 'riverDataWidget';\n\nconst addPrefix = (key: string): string => `${prefix}|${key}`;\n\nlet instance: Store;\n\nclass Store {\n clear(destroy = false) {\n for (const key of this.keys()) {\n localStorage.removeItem(addPrefix(key));\n }\n if (destroy) {\n localStorage.removeItem(prefix);\n return;\n }\n localStorage.setItem(prefix, JSON.stringify([]));\n }\n\n get(key: string) {\n const value = localStorage.getItem(addPrefix(key));\n return value === null ? null : JSON.parse(value);\n }\n\n has(key: string): boolean {\n return this.keys().includes(key);\n }\n\n /**\n * Detect active localStorage.\n *\n * @returns true iff localStorage for the widget is active.\n */\n isActive() {\n return localStorage.getItem(prefix) !== null;\n }\n\n keys(): string[] {\n const storedKeys = localStorage.getItem(prefix);\n return storedKeys === null ? [] : JSON.parse(storedKeys);\n }\n\n set(key: string, value: unknown) {\n const json = JSON.stringify(value);\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n if (!keys.includes(key)) {\n keys.push(key);\n localStorage.setItem(prefix, JSON.stringify(keys));\n }\n localStorage.setItem(addPrefix(key), json);\n }\n\n unset(key: string): boolean {\n // Remove it before we do anything else.\n localStorage.removeItem(addPrefix(key));\n\n // Then remove it from the list of keys.\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n const index = keys.indexOf(key);\n\n // If it doesn't exist we don't have to remove it.\n if (index === -1) return false;\n\n keys.splice(index, 1);\n localStorage.setItem(prefix, JSON.stringify(keys));\n return true;\n }\n}\n\nexport const useStore = (): Store => {\n if (!instance) {\n instance = new Store();\n }\n return instance;\n};\n","import { apiFetch, toTimeParameter } from './api';\nimport { useStore } from './store';\nimport { MINUTE_MS, startOfDay } from '../helpers/time';\n\nimport type { ApiParameters, ApiResponse } from './api';\n\n// Throttle requests to five minutes.\nconst THROTTLE_MS = 5 * MINUTE_MS;\n\n/**\n * Internal format for readings.\n */\nexport type Reading = [\n timestamp: number, // Unix epoch timestamp (seconds).\n value: number // Value.\n];\n\n/**\n * Internal format for readings.\n */\nexport interface ReadingOptions {\n since?: Date; // Time from.\n}\n\n/**\n * Internal format for readings.\n */\ntype ReadingResponse = [a: Reading[], b: ApiResponse<ReadingDTO[]>];\n\n/**\n * Data transfer object for readings provided by the API.\n */\ninterface ReadingDTO {\n '@id': string; // The URL of this reading.\n dateTime: string; // e.g. '2023-05-13T09:00:00Z'.\n measure: string; // The URL of the measure.\n value: number; // The value in the appropriate units.\n}\n\ninterface StoredReadings {\n storedSince: number;\n lastCheck: number;\n data: Reading[];\n}\n\n/**\n * Fetch the readings for a measure.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nconst fetchMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<ReadingResponse> => {\n // Set the parameters for the request.\n const params: ApiParameters = { _sorted: '' };\n if (options.since) {\n params.since = toTimeParameter(options.since);\n }\n // Get the response, casting the items to ReadingDTOs.\n const response = <ApiResponse<ReadingDTO[]>>(\n await apiFetch(`/id/measures/${id}/readings`, params)\n );\n return [parseReadings(response.data.items)[id] || [], response];\n};\n\nexport const filterSince = (data: Reading[], since: number) => {\n const position = data.findIndex((reading) => reading[0] >= since);\n return position < 0 ? [] : data.slice(position);\n};\n\n/**\n * Get the readings for a measure.\n *\n * @todo Caching and throttling.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nexport const getMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<Reading[]> => {\n // Get the saved readings.\n const key = `readings|${id}`;\n const store = useStore();\n\n const stored: StoredReadings = store.get(key) || {\n data: [],\n lastCheck: 0,\n storedSince: Infinity,\n };\n const { data, lastCheck } = stored;\n let { storedSince } = stored;\n\n const discardBefore = startOfDay(null, -8, true).valueOf() / 1000;\n\n // Discard any older than 30 days.\n while (data.length && data[0][0] < discardBefore) {\n [storedSince] = data[0];\n data.shift();\n }\n\n // If we have data early enough apply throttle.\n const lastStored = data.length ? data[data.length - 1][0] : 0;\n const requestedSince = (options.since && options.since.valueOf() / 1000) || 0;\n if (\n storedSince <= requestedSince &&\n Date.now() < lastCheck * 1000 + THROTTLE_MS\n ) {\n // Throttled.\n return filterSince(data, requestedSince);\n }\n\n const fetchOptions: ReadingOptions = {\n ...options,\n since: new Date(Math.max(requestedSince, lastStored) * 1000),\n };\n\n const [newData] = await fetchMeasureReadings(id, fetchOptions);\n mergeReadings(data, newData);\n storedSince = Math.min(requestedSince, storedSince);\n store.set(key, { lastCheck: Date.now() / 1000, data, storedSince });\n return filterSince(data, requestedSince);\n};\n\nexport const mergeReadings = (first: Reading[], second: Reading[]): void => {\n if (!second.length) return;\n\n let firstPos = first.length - 1;\n while (firstPos >= 0 && first[firstPos][0] >= second[0][0]) {\n --firstPos;\n }\n first.splice(firstPos + 1, Infinity, ...second);\n};\n\nconst parseReadings = (items: ReadingDTO[]): Record<string, Reading[]> => {\n const ranges: Record<string, Reading[]> = {};\n items.forEach(({ measure, dateTime, value }) => {\n if (ranges[measure] == null) {\n ranges[measure] = [];\n }\n ranges[measure].unshift([new Date(dateTime).valueOf() / 1000, value]);\n });\n\n const rangesById: Record<string, Reading[]> = {};\n Object.entries(ranges).forEach(([key, range]) => {\n rangesById[key.substring(key.lastIndexOf('/') + 1)] = range;\n });\n\n return rangesById;\n};\n","import { RiverDataWidgetError } from '../error';\nimport { round3 } from '../helpers/format';\nimport {\n parseMeasureId,\n translateMeasureProperties,\n} from '../flood-monitoring-api/measure';\nimport { Chart } from './chart';\nimport { getMeasureReadings } from '../flood-monitoring-api';\nimport { startOfDay } from '../helpers/time';\n\nimport type { ChartSeries } from './chart';\n\nconst drawMeasureWidget = async (\n parentEl: HTMLElement,\n measureId: string,\n options: Record<string, unknown> = {}\n) => {\n // Get readings for the last 7 days in local time.\n const since = startOfDay(null, -7, true);\n\n const data = await getMeasureReadings(measureId, { since });\n\n parentEl.replaceChildren();\n\n const measure = parseMeasureId(measureId);\n const { unit } = translateMeasureProperties(measure);\n\n // const [time, value] = data[data.length - 1];\n // const v = round3(value);\n // const param = m.qualifiedParameter;\n // const station = measure.stationId;\n // const unit = m.unit;\n\n // let textEl = createElement('div');\n // const d = dateFormatter.format(new Date(time * 1000));\n // const t = timeFormatter.format(new Date(time * 1000));\n // textEl.innerHTML = `The most recent ${param} reading for station ${station} was ${v} ${unit} at ${t} on ${d}.`;\n // widgetEl.append(textEl);\n\n const series1: ChartSeries = { data, unit, formatter: round3 };\n if (options.riverDataWidgetMinValue != null) {\n series1.min = parseFloat(<string>options.riverDataWidgetMinValue);\n }\n const minTime = startOfDay(new Date(data[0][0] * 1000)).valueOf() / 1000;\n const maxTime =\n startOfDay(new Date(data[data.length - 1][0] * 1000), 1).valueOf() / 1000;\n const chartOptions = {\n minTime,\n maxTime,\n // attribution: `www.riverdata.co.uk/station/${measure.stationId}`,\n };\n\n const chart = new Chart(parentEl, [series1], chartOptions);\n chart.render();\n};\n\n/**\n * Load a widget specified by a DOM element.\n */\nexport const loadWidget = (el: HTMLElement | string) => {\n // Get the target element from a query selector if necessary and check it\n // exists.\n const targetEl =\n typeof el === 'string' ? <HTMLElement>document.querySelector(el) : el;\n if (targetEl === null) {\n throw new Error('Target element not found');\n }\n\n // Parse element for widget type and options.\n const widgetIdParts = targetEl.dataset.riverDataWidget?.split(':') ?? [];\n const type = widgetIdParts.shift();\n const id = widgetIdParts.join(':');\n const options = targetEl.dataset;\n\n switch (type) {\n case 'measure':\n drawMeasureWidget(targetEl, id, options);\n break;\n\n // The 'station' widget is experimental in v1.0 and should not be used.\n // case 'station':\n // break;\n\n default:\n throw new RiverDataWidgetError('Unknown widget definition', { type, id });\n }\n};\n","import { loadWidget } from './widget/render';\n\nconst autoload = async () => {\n document.querySelectorAll('[data-river-data-widget]').forEach((el) => {\n try {\n loadWidget(<HTMLElement>el);\n } catch (error) {\n console.error(error, { error });\n }\n });\n};\n\nif (document.readyState === 'loading') {\n // Loading hasn't finished yet.\n document.addEventListener('DOMContentLoaded', autoload);\n} else {\n // `DOMContentLoaded` has already fired.\n autoload();\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"index.js","sources":["../src/error.ts","../src/helpers/format.ts","../src/flood-monitoring-api/error.ts","../src/flood-monitoring-api/measure.ts","../src/helpers/dom.ts","../src/helpers/time.ts","../src/widget/chart.ts","../src/flood-monitoring-api/api.ts","../src/flood-monitoring-api/store.ts","../src/flood-monitoring-api/reading.ts","../src/widget/render.ts","../src/autoload.ts"],"sourcesContent":["export type RiverDataWidgetErrorInfo = Record<string, unknown>;\n\nexport class RiverDataWidgetError extends Error {\n public info: RiverDataWidgetErrorInfo;\n\n constructor(msg: string, info: RiverDataWidgetErrorInfo = {}) {\n super(msg);\n this.name = 'RiverDataWidgetError';\n this.info = info;\n }\n}\n","export const FONT_STACK =\n '-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Roboto\",\"Helvetica Neue\",Arial,sans-serif';\n\nexport const round3 = (value: number) =>\n value < 100 ? value.toPrecision(3) : Math.round(value).toString();\n","export class FloodMonitoringApiError extends Error {\n public info: Record<string, unknown>;\n\n constructor(msg: string, info: Record<string, unknown> = {}) {\n super(msg);\n this.name = 'FloodMonitoringApiError';\n this.info = info;\n }\n}\n","import { FloodMonitoringApiError } from './error';\n\nexport { parseMeasureId };\n\nconst parseMeasureId = (measureId: string) => {\n // ............base/ stat-paramet-qualifi- type -interva-unit\n const regExp = /(.*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)$/;\n const matches = measureId.match(regExp);\n if (matches === null) {\n throw new FloodMonitoringApiError('Cannot parse measure id', { measureId });\n }\n const [unit, interval, type, qualifier, parameter, stationId] =\n matches.reverse();\n const qualifiedParameter = qualifier.length\n ? `${parameter}-${qualifier}`\n : parameter;\n return {\n stationId,\n parameter,\n qualifier,\n type,\n interval,\n unit,\n qualifiedParameter,\n };\n};\n\nconst measureTranslations: Record<string, Record<string, string>> = {\n unit: {\n m3_s: 'm³/s',\n mAOD: 'm',\n mASD: 'm',\n },\n qualifiedParameter: {\n 'level-stage': 'level',\n 'level-downstage': 'downstream level',\n },\n};\n\nexport const translateMeasureProperties = (measure: Record<string, string>) => {\n const translated: Record<string, string> = {};\n for (const prop in measure) {\n const value = measure[prop];\n if (measureTranslations[prop] && measureTranslations[prop][value]) {\n translated[prop] = measureTranslations[prop][value];\n } else {\n translated[prop] = value;\n }\n }\n return translated;\n};\n","/**\n * RiverDataWidget https://github.com/pb-uk/river-data-widget.\n *\n * @copyright Copyright (C) 2022 pbuk https://github.com/pb-uk.\n * @license AGPL-3.0-or-later see LICENSE.md.\n */\n\nexport { createElement, createSvgElement, setAttributes, setStyles };\n\ntype AttributeList = Record<string, string | number>;\n\nconst setAttributes = <T extends HTMLElement | SVGElement>(\n el: T,\n attributes: AttributeList\n): T => {\n Object.entries(attributes).forEach(([key, value]) => {\n el.setAttribute(key, `${value}`);\n });\n return el;\n};\n\nconst setStyles = <T extends HTMLElement | SVGElement>(\n el: T,\n styles: AttributeList\n): T => {\n Object.entries(styles).forEach(([key, value]) => {\n // Workaround (el.style.setProperty uses kebab-case keys).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (<any>el.style)[key] = value;\n });\n return el;\n};\n\nconst createElement = (\n name = 'div',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n): HTMLElement => {\n const el = document.createElement(name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n\nconst createSvgElement = (\n name = 'svg',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n) => {\n const el = document.createElementNS('http://www.w3.org/2000/svg', name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n","export const MINUTE_MS = 60000;\n// const HOUR_MS = 3600000;\nexport const DAY_MS = 86400000;\n\n/**\n * Get the Date at the start of a day in UTC or local time.\n *\n * @param offset\n * @param timeZone The time zone offset in minutes, or set to `true` to use the\n * local time zone (`false`, the default, uses UTC).\n * @returns The reqested date.\n */\nexport const startOfDay = (\n date: Date | null = null,\n offset = 0,\n timeZone: boolean | number = false\n): Date => {\n if (timeZone === false) {\n // Use UTC.\n const base = date === null ? Date.now() : date.valueOf();\n return new Date(Math.floor(base / DAY_MS + offset) * DAY_MS);\n }\n\n const now = new Date();\n const tz = timeZone === true ? now.getTimezoneOffset() : timeZone;\n const local = now.valueOf() + tz * MINUTE_MS;\n return new Date(Math.floor(local / DAY_MS + offset) * DAY_MS);\n};\n\n/**\n * | | long |short|narrow|numeric|2-digit|\n * |:-------:|:-----------:|:---:|:----:|:-----:|:-----:|\n * | weekday | Monday | Mon | M | | |\n * | era | Anno Domini | AD | A | | |\n * | year | | | | 2012 | 12 |\n * | month | March | Mar | M | 3 | 03 |\n * | day | | | | 1 | 01 |\n * | hour | | | | 1 | 01 |\n * | minute | | | | 1 | 01 |\n * | second | | | | 1 | 01 |\n *\n * * fractionalSecondDigits: 1, 2 or 3 for number of digits.\n * * timeZoneName: long (Pacific Standard Time), short (PST),\n * longOffset (GMT-0800), shortOffset (GMT-8), longGeneric (Pacific Time),\n * shortGeneric (PT).\n */\n\nexport const dateFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n // year: 'numeric',\n});\n\nexport const timeFormatter = new Intl.DateTimeFormat('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n timeZoneName: 'short',\n});\n\nexport const dddFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'short',\n});\n\nexport const dMmmFormatter = new Intl.DateTimeFormat('en-GB', {\n day: 'numeric',\n month: 'short',\n});\n","import { createSvgElement } from '../helpers/dom';\nimport { timeFormatter, dddFormatter, dMmmFormatter } from '../helpers/time';\nimport { FloodMonitoringApiError } from '../flood-monitoring-api/error';\nimport { FONT_STACK } from '../helpers/format';\n\nexport interface ChartOptions {\n minTime?: number;\n maxTime?: number;\n attribution?: string;\n}\n\nexport interface ChartScaleLimits {\n minTime: number;\n maxTime: number;\n timeScale: number;\n minValue: number;\n maxValue: number;\n valueScale: number;\n}\n\nexport interface ChartSeries {\n data: TimeSeriesValue[];\n min?: number;\n max?: number;\n unit?: string;\n formatter?: (value: number) => string;\n}\n\nexport type TimeSeriesValue = [\n ts: number, // Unix time stamp (seconds).\n v: number // Value.\n];\n\nexport class Chart {\n protected fontSizePx = 14;\n\n protected el: SVGElement;\n protected series: ChartSeries[];\n protected options: ChartOptions;\n protected width = 480; // 400;\n protected height = 270; // 225;\n protected plotHeight = this.height - this.fontSizePx * 4.5;\n protected strokeWidth = 2;\n protected limits?: ChartScaleLimits;\n\n protected plotColor = '#77C';\n protected labelBg = 'rgba(255,255,255,0.5)';\n protected labelBgWidth = '0.5em';\n\n protected attribution =\n 'Uses Environment Agency data from the real-time API (Beta)';\n\n // CSS settings.\n // Just readable at 320x180.\n // Good from 400x225.\n // Perfect at 480x270 (font is 12px);\n protected styles = {\n 'font-family': FONT_STACK,\n 'font-size': `${this.fontSizePx}px`,\n display: 'block',\n margin: 'auto',\n 'max-width': '150vh',\n };\n\n constructor(\n el: HTMLElement,\n series: ChartSeries[],\n options: ChartOptions = {}\n ) {\n this.series = series;\n this.options = options;\n const viewBox = `0 0 ${this.width} ${this.height}`;\n this.attribution = options.attribution ?? this.attribution;\n this.el = createSvgElement('svg', { viewBox }, this.styles);\n el.append(this.el);\n }\n\n getLimits(): ChartScaleLimits {\n if (this.limits == null) {\n throw new FloodMonitoringApiError('Chart axis limits have not been set');\n }\n return this.limits;\n }\n\n getHorizontalGridlines(): SVGElement[] {\n const { minTime, maxTime, timeScale, minValue, maxValue, valueScale } =\n this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight;\n const x1 = xOffset;\n const x2 = xOffset + (maxTime - minTime) * timeScale;\n // Horizontal grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n const valueRange = maxValue - minValue;\n // Horizontal grid interval.\n const [interval, exponent] = getInterval(valueRange, 9);\n const factor = 10 ** -exponent;\n const base = Math.ceil((minValue * factor) / interval + 1) * interval;\n let i = 0;\n let current = base / factor;\n while (current < maxValue) {\n const y1 = yOffset - (current - minValue) * valueScale;\n lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n labels.append(\n createSvgElement('text', { x: x1 + 4, y: y1 + 4 }, {}, `${current}`)\n );\n ++i;\n current = (base + i * interval) / factor;\n }\n const timeAxisLine = createSvgElement(\n 'line',\n { x1, y1: yOffset, x2, y2: yOffset },\n { stroke: '#777' }\n );\n\n return [lines, labels, timeAxisLine];\n }\n\n getTimeScale(): SVGElement[] {\n const { minTime, maxTime, timeScale } = this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight + this.strokeWidth / 2;\n const y1 = yOffset + this.fontSizePx * 3;\n const y2 = yOffset - this.plotHeight;\n // Vertical grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n // Vertical grid interval.\n const base = minTime;\n const interval = 86400;\n let i = 0;\n let current = base;\n const labelOffset = 43200 * timeScale;\n const fill = '#444';\n while (current <= maxTime) {\n const x1 = xOffset + (current - minTime) * timeScale;\n const d = new Date(current * 1000);\n // lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n lines.append(createSvgElement('line', { x1, y1, x2: x1, y2 }));\n labels.append(\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 1.8,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dddFormatter.format(d)}`\n ),\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 0.5,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dMmmFormatter.format(d)}`\n )\n );\n ++i;\n current = base + i * interval;\n }\n return [lines, labels];\n }\n\n render() {\n // Calculate axis scales.\n const limits = getLimits(this.series[0].data);\n limits.minValue = this.series[0].min ?? limits.minValue;\n limits.maxValue = this.series[0].max ?? limits.maxValue;\n limits.minTime = this.options.minTime ?? limits.minTime;\n limits.maxTime = this.options.maxTime ?? limits.maxTime;\n\n this.limits = {\n ...limits,\n valueScale:\n (this.plotHeight - this.strokeWidth) /\n (limits.maxValue - limits.minValue),\n timeScale:\n (this.width - this.strokeWidth) / (limits.maxTime - limits.minTime),\n };\n\n // Time axis.\n const [timeLines, timeLabels] = this.getTimeScale();\n this.el.append(timeLines);\n\n // Value axis.\n const [valueLines, valueLabels, timeAxisLine] =\n this.getHorizontalGridlines();\n this.el.append(valueLines);\n this.el.append(timeAxisLine);\n\n this.plotData();\n\n // Plot labels on top of the line.\n this.el.append(timeLabels);\n this.el.append(valueLabels);\n\n this.el.append(\n createSvgElement(\n 'text',\n {\n x: this.width / 2,\n 'text-anchor': 'middle',\n y: this.height - this.fontSizePx * 0.5,\n },\n { fill: '#595959' },\n this.attribution\n )\n );\n\n this.plotLastValue();\n }\n\n plotLastValue() {\n const { data, unit, formatter } = this.series[0];\n const [time, value] = data[data.length - 1];\n const { minTime, timeScale, maxValue, minValue } = this.getLimits();\n\n const v = formatter == null ? value : formatter(value);\n const xOffset = this.strokeWidth / 2;\n // const yOffset = this.plotHeight - this.strokeWidth / 2;\n const x = xOffset + (time - minTime) * timeScale;\n const isHighLabel = (value - minValue) / (maxValue - minValue) < 0.5;\n const y = this.plotHeight * (isHighLabel ? 0 : 0.5) + this.fontSizePx * 2;\n\n this.el.append(\n // Background for value label.\n createSvgElement(\n 'text',\n { x, y, 'text-anchor': 'end' },\n {\n 'font-size': '1.5em',\n 'font-weight': 'bold',\n stroke: this.labelBg,\n 'stroke-width': this.labelBgWidth,\n },\n `${v} ${unit}`\n ),\n // Value label.\n createSvgElement(\n 'text',\n { x, y, 'text-anchor': 'end' },\n { fill: this.plotColor, 'font-size': '1.5em', 'font-weight': 'bold' },\n `${v} ${unit}`\n ),\n // Background for time label.\n createSvgElement(\n 'text',\n { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' },\n {\n stroke: this.labelBg,\n 'stroke-width': this.labelBgWidth,\n },\n `${timeFormatter.format(new Date(time * 1000))}`\n ),\n // Time label.\n createSvgElement(\n 'text',\n { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' },\n { fill: this.plotColor },\n `${timeFormatter.format(new Date(time * 1000))}`\n )\n );\n }\n\n plotData() {\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight - this.strokeWidth / 2;\n const { data } = this.series[0];\n const { minTime, timeScale, minValue, valueScale } = this.getLimits();\n // First data point.\n const x = xOffset + (data[0][0] - minTime) * timeScale;\n const y = yOffset - (data[0][1] - minValue) * valueScale;\n const points = [`M${x},${y}`];\n // Remaining data points.\n for (let i = 1; i < data.length; ++i) {\n const x = xOffset + (data[i][0] - minTime) * timeScale;\n const y = yOffset - (data[i][1] - minValue) * valueScale;\n points.push(`L${x},${y}`);\n }\n // Plot the data.\n const path = createSvgElement('path', {\n d: points.join(''),\n stroke: this.plotColor,\n 'stroke-width': this.strokeWidth,\n fill: 'none',\n });\n this.el.append(path);\n }\n}\n\nexport const getLimits = (data: TimeSeriesValue[]) => {\n if (data.length < 1) {\n throw new Error('Readings must not be empty');\n }\n const minTime = data[0][0];\n const maxTime = data[data.length - 1][0];\n let minValue = Infinity;\n let maxValue = -minValue;\n data.forEach(([, value]) => {\n minValue = Math.min(minValue, value);\n maxValue = Math.max(maxValue, value);\n });\n return { minTime, maxTime, minValue, maxValue };\n};\n\nexport const getInterval = (range: number, maxDivisions: number) => {\n const exponent = Math.floor(Math.log10(range)) - 1;\n const k = range / (maxDivisions * 10 ** exponent);\n const mantissa = k <= 2 ? 2 : k <= 5 ? 5 : 10;\n return [mantissa, exponent];\n};\n","// There is no need to be secure about this!\nconst baseUrl = 'http://environment.data.gov.uk/flood-monitoring';\n\nexport interface ApiResponse<T> {\n data: {\n items: T;\n };\n response: Response;\n}\n\nexport interface ApiParameters {\n since?: string; // Time from.\n _sorted?: ''; // Flag for sorting.\n}\n\nexport const apiFetch = async (\n path: string,\n query = {}\n): Promise<ApiResponse<unknown>> => {\n const queryString = new URLSearchParams(query).toString();\n const uri = queryString\n ? `${baseUrl}${path}?${queryString}`\n : `${baseUrl}${path}`;\n const response = await fetch(uri);\n return { data: await response.json(), response };\n};\n\n/**\n * Convert a Date to a format recognized by the EA API for a query parameter.\n *\n * @param date Convert from.\n * @returns A string in the EA API query parameter format.\n */\nexport const toTimeParameter = (date: Date): string => {\n return date.toISOString().substring(0, 19) + 'Z';\n};\n\n/*\nUseful response headers\n Date: 'Sat, 13 May 2023 09:14:07 GMT',\n last-modified: Sat, 13 May 2023 09:03:13 GMT,\nResponse meta:\n publisher: 'Environment Agency',\n license: 'http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/',\n documentation: 'http://environment.data.gov.uk/flood-monitoring/doc/reference',\n version: '0.9',\n comment: 'Status: Beta service',\n hasFormat: [\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.csv?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.rdf?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.ttl?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.html?_sorted=&since=2023-05-12T08%3A00%3A00Z\"\n ],\n*/\n","const prefix = 'riverDataWidget';\n\nconst addPrefix = (key: string): string => `${prefix}|${key}`;\n\nlet instance: Store;\n\nclass Store {\n clear(destroy = false) {\n for (const key of this.keys()) {\n localStorage.removeItem(addPrefix(key));\n }\n if (destroy) {\n localStorage.removeItem(prefix);\n return;\n }\n localStorage.setItem(prefix, JSON.stringify([]));\n }\n\n get(key: string) {\n const value = localStorage.getItem(addPrefix(key));\n return value === null ? null : JSON.parse(value);\n }\n\n has(key: string): boolean {\n return this.keys().includes(key);\n }\n\n /**\n * Detect active localStorage.\n *\n * @returns true iff localStorage for the widget is active.\n */\n isActive() {\n return localStorage.getItem(prefix) !== null;\n }\n\n keys(): string[] {\n const storedKeys = localStorage.getItem(prefix);\n return storedKeys === null ? [] : JSON.parse(storedKeys);\n }\n\n set(key: string, value: unknown) {\n const json = JSON.stringify(value);\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n if (!keys.includes(key)) {\n keys.push(key);\n localStorage.setItem(prefix, JSON.stringify(keys));\n }\n localStorage.setItem(addPrefix(key), json);\n }\n\n unset(key: string): boolean {\n // Remove it before we do anything else.\n localStorage.removeItem(addPrefix(key));\n\n // Then remove it from the list of keys.\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n const index = keys.indexOf(key);\n\n // If it doesn't exist we don't have to remove it.\n if (index === -1) return false;\n\n keys.splice(index, 1);\n localStorage.setItem(prefix, JSON.stringify(keys));\n return true;\n }\n}\n\nexport const useStore = (): Store => {\n if (!instance) {\n instance = new Store();\n }\n return instance;\n};\n","import { apiFetch, toTimeParameter } from './api';\nimport { useStore } from './store';\nimport { MINUTE_MS, startOfDay } from '../helpers/time';\n\nimport type { ApiParameters, ApiResponse } from './api';\n\n// Throttle requests to five minutes.\nconst THROTTLE_MS = 5 * MINUTE_MS;\n\n/**\n * Internal format for readings.\n */\nexport type Reading = [\n timestamp: number, // Unix epoch timestamp (seconds).\n value: number // Value.\n];\n\n/**\n * Internal format for readings.\n */\nexport interface ReadingOptions {\n since?: Date; // Time from.\n}\n\n/**\n * Internal format for readings.\n */\ntype ReadingResponse = [a: Reading[], b: ApiResponse<ReadingDTO[]>];\n\n/**\n * Data transfer object for readings provided by the API.\n */\ninterface ReadingDTO {\n '@id': string; // The URL of this reading.\n dateTime: string; // e.g. '2023-05-13T09:00:00Z'.\n measure: string; // The URL of the measure.\n value: number; // The value in the appropriate units.\n}\n\ninterface StoredReadings {\n storedSince: number;\n lastCheck: number;\n data: Reading[];\n}\n\n/**\n * Fetch the readings for a measure.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nconst fetchMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<ReadingResponse> => {\n // Set the parameters for the request.\n const params: ApiParameters = { _sorted: '' };\n if (options.since) {\n params.since = toTimeParameter(options.since);\n }\n // Get the response, casting the items to ReadingDTOs.\n const response = <ApiResponse<ReadingDTO[]>>(\n await apiFetch(`/id/measures/${id}/readings`, params)\n );\n return [parseReadings(response.data.items)[id] || [], response];\n};\n\nexport const filterSince = (data: Reading[], since: number) => {\n const position = data.findIndex((reading) => reading[0] >= since);\n return position < 0 ? [] : data.slice(position);\n};\n\n/**\n * Get the readings for a measure.\n *\n * @todo Caching and throttling.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nexport const getMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<Reading[]> => {\n // Get the saved readings.\n const key = `readings|${id}`;\n const store = useStore();\n\n const stored: StoredReadings = store.get(key) || {\n data: [],\n lastCheck: 0,\n storedSince: Infinity,\n };\n const { data, lastCheck } = stored;\n let { storedSince } = stored;\n\n const discardBefore = startOfDay(null, -8, true).valueOf() / 1000;\n\n // Discard any older than 30 days.\n while (data.length && data[0][0] < discardBefore) {\n [storedSince] = data[0];\n data.shift();\n }\n\n // If we have data early enough apply throttle.\n const lastStored = data.length ? data[data.length - 1][0] : 0;\n const requestedSince = (options.since && options.since.valueOf() / 1000) || 0;\n if (\n storedSince <= requestedSince &&\n Date.now() < lastCheck * 1000 + THROTTLE_MS\n ) {\n // Throttled.\n return filterSince(data, requestedSince);\n }\n\n const fetchOptions: ReadingOptions = {\n ...options,\n since: new Date(Math.max(requestedSince, lastStored) * 1000),\n };\n\n const [newData] = await fetchMeasureReadings(id, fetchOptions);\n mergeReadings(data, newData);\n storedSince = Math.min(requestedSince, storedSince);\n store.set(key, { lastCheck: Date.now() / 1000, data, storedSince });\n return filterSince(data, requestedSince);\n};\n\nexport const mergeReadings = (first: Reading[], second: Reading[]): void => {\n if (!second.length) return;\n\n let firstPos = first.length - 1;\n while (firstPos >= 0 && first[firstPos][0] >= second[0][0]) {\n --firstPos;\n }\n first.splice(firstPos + 1, Infinity, ...second);\n};\n\nconst parseReadings = (items: ReadingDTO[]): Record<string, Reading[]> => {\n const ranges: Record<string, Reading[]> = {};\n items.forEach(({ measure, dateTime, value }) => {\n if (ranges[measure] == null) {\n ranges[measure] = [];\n }\n ranges[measure].unshift([new Date(dateTime).valueOf() / 1000, value]);\n });\n\n const rangesById: Record<string, Reading[]> = {};\n Object.entries(ranges).forEach(([key, range]) => {\n rangesById[key.substring(key.lastIndexOf('/') + 1)] = range;\n });\n\n return rangesById;\n};\n","import { RiverDataWidgetError } from '../error';\nimport { round3 } from '../helpers/format';\nimport {\n parseMeasureId,\n translateMeasureProperties,\n} from '../flood-monitoring-api/measure';\nimport { Chart } from './chart';\nimport { getMeasureReadings } from '../flood-monitoring-api';\nimport { startOfDay } from '../helpers/time';\n\nimport type { ChartSeries } from './chart';\n\nconst drawMeasureWidget = async (\n parentEl: HTMLElement,\n measureId: string,\n options: Record<string, unknown> = {}\n) => {\n // Get readings for the last 7 days in local time.\n const since = startOfDay(null, -7, true);\n\n const data = await getMeasureReadings(measureId, { since });\n\n parentEl.replaceChildren();\n\n const measure = parseMeasureId(measureId);\n const { unit } = translateMeasureProperties(measure);\n\n // const [time, value] = data[data.length - 1];\n // const v = round3(value);\n // const param = m.qualifiedParameter;\n // const station = measure.stationId;\n // const unit = m.unit;\n\n // let textEl = createElement('div');\n // const d = dateFormatter.format(new Date(time * 1000));\n // const t = timeFormatter.format(new Date(time * 1000));\n // textEl.innerHTML = `The most recent ${param} reading for station ${station} was ${v} ${unit} at ${t} on ${d}.`;\n // widgetEl.append(textEl);\n\n const series1: ChartSeries = { data, unit, formatter: round3 };\n // Set max/min options for plot from widget options.\n if (options.riverDataWidgetMaxValue != null) {\n series1.max = parseFloat(<string>options.riverDataWidgetMaxValue);\n }\n if (options.riverDataWidgetMinValue != null) {\n series1.min = parseFloat(<string>options.riverDataWidgetMinValue);\n }\n const minTime = startOfDay(new Date(data[0][0] * 1000)).valueOf() / 1000;\n const maxTime =\n startOfDay(new Date(data[data.length - 1][0] * 1000), 1).valueOf() / 1000;\n const chartOptions = {\n minTime,\n maxTime,\n // attribution: `www.riverdata.co.uk/station/${measure.stationId}`,\n };\n\n const chart = new Chart(parentEl, [series1], chartOptions);\n chart.render();\n};\n\n/**\n * Load a widget specified by a DOM element.\n */\nexport const loadWidget = (el: HTMLElement | string) => {\n // Get the target element from a query selector if necessary and check it\n // exists.\n const targetEl =\n typeof el === 'string' ? <HTMLElement>document.querySelector(el) : el;\n if (targetEl === null) {\n throw new Error('Target element not found');\n }\n\n // Parse element for widget type and options.\n const widgetIdParts = targetEl.dataset.riverDataWidget?.split(':') ?? [];\n const type = widgetIdParts.shift();\n const id = widgetIdParts.join(':');\n const options = targetEl.dataset;\n\n switch (type) {\n case 'measure':\n drawMeasureWidget(targetEl, id, options);\n break;\n\n // The 'station' widget is experimental in v1.0 and should not be used.\n // case 'station':\n // break;\n\n default:\n throw new RiverDataWidgetError('Unknown widget definition', { type, id });\n }\n};\n","import { loadWidget } from './widget/render';\n\nconst autoload = async () => {\n document.querySelectorAll('[data-river-data-widget]').forEach((el) => {\n try {\n loadWidget(<HTMLElement>el);\n } catch (error) {\n console.error(error, { error });\n }\n });\n};\n\nif (document.readyState === 'loading') {\n // Loading hasn't finished yet.\n document.addEventListener('DOMContentLoaded', autoload);\n} else {\n // `DOMContentLoaded` has already fired.\n autoload();\n}\n"],"names":[],"mappings":"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"} |
@@ -1,7 +0,7 @@ | ||
| /*! RiverDataWidget v1.0.1 2023-05-17 11:39:50 | ||
| /*! RiverDataWidget v1.1.0 2023-05-31 14:34:01 | ||
| *! https://github.com/pb-uk/river-data-widget#readme | ||
| *! Copyright (C) 2023 pbuk (https://github.com/pb-uk). | ||
| *! License AGPL-3.0-or-later. | ||
| *! License MIT. | ||
| */ | ||
| var RiverDataWidget=function(t){"use strict";class e extends Error{constructor(t,e={}){super(t),this.name="RiverDataWidgetError",this.info=e}}const i=t=>t<100?t.toPrecision(3):Math.round(t).toString();class n extends Error{constructor(t,e={}){super(t),this.name="FloodMonitoringApiError",this.info=e}}const s={unit:{m3_s:"m³/s",mAOD:"m",mASD:"m"},qualifiedParameter:{"level-stage":"level","level-downstage":"downstream level"}},a=(t="svg",e={},i={},n=!1)=>{const s=document.createElementNS("http://www.w3.org/2000/svg",t);return!1!==n&&(s.innerHTML=n),((t,e)=>(Object.entries(e).forEach((([e,i])=>{t.style[e]=i})),t))(((t,e)=>(Object.entries(e).forEach((([e,i])=>{t.setAttribute(e,`${i}`)})),t))(s,e),i)},o=864e5,r=(t=null,e=0,i=!1)=>{if(!1===i){const i=null===t?Date.now():t.valueOf();return new Date(Math.floor(i/o+e)*o)}const n=new Date,s=!0===i?n.getTimezoneOffset():i,a=n.valueOf()+6e4*s;return new Date(Math.floor(a/o+e)*o)},l=new Intl.DateTimeFormat("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1,timeZoneName:"short"}),h=new Intl.DateTimeFormat("en-GB",{weekday:"short"}),c=new Intl.DateTimeFormat("en-GB",{day:"numeric",month:"short"}),m="#77C";class d{constructor(t,e,i={}){var n;this.fontSizePx=14,this.width=480,this.height=270,this.plotHeight=this.height-4.5*this.fontSizePx,this.strokeWidth=2,this.attribution="Uses Environment Agency data from the real-time API (Beta)",this.styles={"font-family":'-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif',"font-size":`${this.fontSizePx}px`,display:"block",margin:"auto","max-width":"150vh"},this.series=e,this.options=i;const s=`0 0 ${this.width} ${this.height}`;this.attribution=null!==(n=i.attribution)&&void 0!==n?n:this.attribution,this.el=a("svg",{viewBox:s},this.styles),t.append(this.el)}getLimits(){if(null==this.limits)throw new n("Chart axis limits have not been set");return this.limits}getHorizontalGridlines(){const{minTime:t,maxTime:e,timeScale:i,minValue:n,maxValue:s,valueScale:o}=this.getLimits(),r=this.strokeWidth/2,l=this.plotHeight,h=r,c=r+(e-t)*i,m=a("g",{stroke:"#ddd"}),d=a("g"),u=s-n,[f,p]=g(u,9),x=10**-p,S=Math.ceil(n*x/f+1)*f;let v=0,w=S/x;for(;w<s;){const t=l-(w-n)*o;m.append(a("line",{x1:h,y1:t,x2:c,y2:t})),d.append(a("text",{x:h+4,y:t+4},{},`${w}`)),++v,w=(S+v*f)/x}return[m,d,a("line",{x1:h,y1:l,x2:c,y2:l},{stroke:"#777"})]}getTimeScale(){const{minTime:t,maxTime:e,timeScale:i}=this.getLimits(),n=this.strokeWidth/2,s=this.plotHeight+this.strokeWidth/2,o=s+3*this.fontSizePx,r=s-this.plotHeight,l=a("g",{stroke:"#ddd"}),m=a("g"),d=t;let u=0,g=d;const f=43200*i,p="#444";for(;g<=e;){const e=n+(g-t)*i,s=new Date(1e3*g);l.append(a("line",{x1:e,y1:o,x2:e,y2:r})),m.append(a("text",{x:e+f,y:o-1.8*this.fontSizePx,"text-anchor":"middle"},{fill:p},`${h.format(s)}`),a("text",{x:e+f,y:o-.5*this.fontSizePx,"text-anchor":"middle"},{fill:p},`${c.format(s)}`)),++u,g=d+86400*u}return[l,m]}render(){var t,e,i,n;const s=u(this.series[0].data);s.minValue=null!==(t=this.series[0].min)&&void 0!==t?t:s.minValue,s.maxValue=null!==(e=this.series[0].max)&&void 0!==e?e:s.maxValue,s.minTime=null!==(i=this.options.minTime)&&void 0!==i?i:s.minTime,s.maxTime=null!==(n=this.options.maxTime)&&void 0!==n?n:s.maxTime,this.limits=Object.assign(Object.assign({},s),{valueScale:(this.plotHeight-this.strokeWidth)/(s.maxValue-s.minValue),timeScale:(this.width-this.strokeWidth)/(s.maxTime-s.minTime)});const[o,r]=this.getTimeScale();this.el.append(o);const[l,h,c]=this.getHorizontalGridlines();this.el.append(l),this.el.append(c),this.plotData(),this.el.append(r),this.el.append(h),this.el.append(a("text",{x:this.width/2,"text-anchor":"middle",y:this.height-.5*this.fontSizePx},{fill:"#595959"},this.attribution)),this.plotLastValue()}plotLastValue(){const{data:t,unit:e,formatter:i}=this.series[0],[n,s]=t[t.length-1],{minTime:o,timeScale:r,minValue:h,valueScale:c}=this.getLimits(),d=null==i?s:i(s),u=this.strokeWidth/2+(n-o)*r,g=(s-h)/c>=.5,f=this.plotHeight-this.strokeWidth/2-this.plotHeight*(g?1:.5)+2*this.fontSizePx;this.el.append(a("text",{x:u,y:f,"text-anchor":"end"},{fill:m,"font-size":"1.5em","font-weight":"bold"},`${d} ${e}`)),this.el.append(a("text",{x:u,y:f+1.5*this.fontSizePx,"text-anchor":"end"},{fill:m},`${l.format(new Date(1e3*n))}`))}plotData(){const t=this.strokeWidth/2,e=this.plotHeight-this.strokeWidth/2,{data:i}=this.series[0],{minTime:n,timeScale:s,minValue:o,valueScale:r}=this.getLimits(),l=[`M${t+(i[0][0]-n)*s},${e-(i[0][1]-o)*r}`];for(let a=1;a<i.length;++a){const h=t+(i[a][0]-n)*s,c=e-(i[a][1]-o)*r;l.push(`L${h},${c}`)}const h=a("path",{d:l.join(""),stroke:m,"stroke-width":this.strokeWidth,fill:"none"});this.el.append(h)}}const u=t=>{if(t.length<1)throw new Error("Readings must not be empty");const e=t[0][0],i=t[t.length-1][0];let n=1/0,s=-n;return t.forEach((([,t])=>{n=Math.min(n,t),s=Math.max(s,t)})),{minTime:e,maxTime:i,minValue:n,maxValue:s}},g=(t,e)=>{const i=Math.floor(Math.log10(t))-1,n=t/(e*10**i);return[n<=2?2:n<=5?5:10,i]},f="http://environment.data.gov.uk/flood-monitoring",p="riverDataWidget",x=t=>`${p}|${t}`;let S;class v{clear(t=!1){for(const t of this.keys())localStorage.removeItem(x(t));t?localStorage.removeItem(p):localStorage.setItem(p,JSON.stringify([]))}get(t){const e=localStorage.getItem(x(t));return null===e?null:JSON.parse(e)}has(t){return this.keys().includes(t)}isActive(){return null!==localStorage.getItem(p)}keys(){const t=localStorage.getItem(p);return null===t?[]:JSON.parse(t)}set(t,e){const i=JSON.stringify(e),n=localStorage.getItem(p),s=null===n?[]:JSON.parse(n);s.includes(t)||(s.push(t),localStorage.setItem(p,JSON.stringify(s))),localStorage.setItem(x(t),i)}unset(t){localStorage.removeItem(x(t));const e=localStorage.getItem(p),i=null===e?[]:JSON.parse(e),n=i.indexOf(t);return-1!==n&&(i.splice(n,1),localStorage.setItem(p,JSON.stringify(i)),!0)}}const w=async(t,e={})=>{const i={_sorted:""};e.since&&(i.since=e.since.toISOString().substring(0,19)+"Z");const n=await(async(t,e={})=>{const i=new URLSearchParams(e).toString(),n=i?`${f}${t}?${i}`:`${f}${t}`,s=await fetch(n);return{data:await s.json(),response:s}})(`/id/measures/${t}/readings`,i);return[T(n.data.items)[t]||[],n]},y=(t,e)=>{const i=t.findIndex((t=>t[0]>=e));return i<0?[]:t.slice(i)},O=async(t,e={})=>{const i=`readings|${t}`,n=(S||(S=new v),S),s=n.get(i)||{data:[],lastCheck:0,storedSince:1/0},{data:a,lastCheck:o}=s;let{storedSince:l}=s;const h=r(null,-8,!0).valueOf()/1e3;for(;a.length&&a[0][0]<h;)[l]=a[0],a.shift();const c=a.length?a[a.length-1][0]:0,m=e.since&&e.since.valueOf()/1e3||0;if(l<=m&&Date.now()<1e3*o+3e5)return y(a,m);const d=Object.assign(Object.assign({},e),{since:new Date(1e3*Math.max(m,c))}),[u]=await w(t,d);return k(a,u),l=Math.min(m,l),n.set(i,{lastCheck:Date.now()/1e3,data:a,storedSince:l}),y(a,m)},k=(t,e)=>{if(!e.length)return;let i=t.length-1;for(;i>=0&&t[i][0]>=e[0][0];)--i;t.splice(i+1,1/0,...e)},T=t=>{const e={};t.forEach((({measure:t,dateTime:i,value:n})=>{null==e[t]&&(e[t]=[]),e[t].unshift([new Date(i).valueOf()/1e3,n])}));const i={};return Object.entries(e).forEach((([t,e])=>{i[t.substring(t.lastIndexOf("/")+1)]=e})),i},D=async(t,e,a={})=>{const o=r(null,-7,!0),l=await O(e,{since:o});t.replaceChildren();const h=(t=>{const e=t.match(/(.*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)$/);if(null===e)throw new n("Cannot parse measure id",{measureId:t});const[i,s,a,o,r,l]=e.reverse();return{stationId:l,parameter:r,qualifier:o,type:a,interval:s,unit:i,qualifiedParameter:o.length?`${r}-${o}`:r}})(e),{unit:c}=(t=>{const e={};for(const i in t){const n=t[i];s[i]&&s[i][n]?e[i]=s[i][n]:e[i]=n}return e})(h),m={data:l,unit:c,formatter:i};null!=a.riverDataWidgetMinValue&&(m.min=parseFloat(a.riverDataWidgetMinValue));const u=r(new Date(1e3*l[0][0])).valueOf()/1e3,g=r(new Date(1e3*l[l.length-1][0]),1).valueOf()/1e3;new d(t,[m],{minTime:u,maxTime:g}).render()},$=async()=>{document.querySelectorAll("[data-river-data-widget]").forEach((t=>{try{(t=>{var i,n;const s="string"==typeof t?document.querySelector(t):t;if(null===s)throw new Error("Target element not found");const a=null!==(n=null===(i=s.dataset.riverDataWidget)||void 0===i?void 0:i.split(":"))&&void 0!==n?n:[],o=a.shift(),r=a.join(":"),l=s.dataset;if("measure"!==o)throw new e("Unknown widget definition",{type:o,id:r});D(s,r,l)})(t)}catch(t){console.error(t,{error:t})}}))};return"loading"===document.readyState?document.addEventListener("DOMContentLoaded",$):$(),t.version="1.0.1",t}({}); | ||
| var RiverDataWidget=function(t){"use strict";class e extends Error{constructor(t,e={}){super(t),this.name="RiverDataWidgetError",this.info=e}}const i=t=>t<100?t.toPrecision(3):Math.round(t).toString();class n extends Error{constructor(t,e={}){super(t),this.name="FloodMonitoringApiError",this.info=e}}const s={unit:{m3_s:"m³/s",mAOD:"m",mASD:"m"},qualifiedParameter:{"level-stage":"level","level-downstage":"downstream level"}},a=(t="svg",e={},i={},n=!1)=>{const s=document.createElementNS("http://www.w3.org/2000/svg",t);return!1!==n&&(s.innerHTML=n),((t,e)=>(Object.entries(e).forEach((([e,i])=>{t.style[e]=i})),t))(((t,e)=>(Object.entries(e).forEach((([e,i])=>{t.setAttribute(e,`${i}`)})),t))(s,e),i)},o=864e5,r=(t=null,e=0,i=!1)=>{if(!1===i){const i=null===t?Date.now():t.valueOf();return new Date(Math.floor(i/o+e)*o)}const n=new Date,s=!0===i?n.getTimezoneOffset():i,a=n.valueOf()+6e4*s;return new Date(Math.floor(a/o+e)*o)},l=new Intl.DateTimeFormat("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1,timeZoneName:"short"}),h=new Intl.DateTimeFormat("en-GB",{weekday:"short"}),m=new Intl.DateTimeFormat("en-GB",{day:"numeric",month:"short"});class c{constructor(t,e,i={}){var n;this.fontSizePx=14,this.width=480,this.height=270,this.plotHeight=this.height-4.5*this.fontSizePx,this.strokeWidth=2,this.plotColor="#77C",this.labelBg="rgba(255,255,255,0.5)",this.labelBgWidth="0.5em",this.attribution="Uses Environment Agency data from the real-time API (Beta)",this.styles={"font-family":'-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif',"font-size":`${this.fontSizePx}px`,display:"block",margin:"auto","max-width":"150vh"},this.series=e,this.options=i;const s=`0 0 ${this.width} ${this.height}`;this.attribution=null!==(n=i.attribution)&&void 0!==n?n:this.attribution,this.el=a("svg",{viewBox:s},this.styles),t.append(this.el)}getLimits(){if(null==this.limits)throw new n("Chart axis limits have not been set");return this.limits}getHorizontalGridlines(){const{minTime:t,maxTime:e,timeScale:i,minValue:n,maxValue:s,valueScale:o}=this.getLimits(),r=this.strokeWidth/2,l=this.plotHeight,h=r,m=r+(e-t)*i,c=a("g",{stroke:"#ddd"}),d=a("g"),g=s-n,[f,p]=u(g,9),x=10**-p,w=Math.ceil(n*x/f+1)*f;let S=0,v=w/x;for(;v<s;){const t=l-(v-n)*o;c.append(a("line",{x1:h,y1:t,x2:m,y2:t})),d.append(a("text",{x:h+4,y:t+4},{},`${v}`)),++S,v=(w+S*f)/x}return[c,d,a("line",{x1:h,y1:l,x2:m,y2:l},{stroke:"#777"})]}getTimeScale(){const{minTime:t,maxTime:e,timeScale:i}=this.getLimits(),n=this.strokeWidth/2,s=this.plotHeight+this.strokeWidth/2,o=s+3*this.fontSizePx,r=s-this.plotHeight,l=a("g",{stroke:"#ddd"}),c=a("g"),d=t;let u=0,g=d;const f=43200*i,p="#444";for(;g<=e;){const e=n+(g-t)*i,s=new Date(1e3*g);l.append(a("line",{x1:e,y1:o,x2:e,y2:r})),c.append(a("text",{x:e+f,y:o-1.8*this.fontSizePx,"text-anchor":"middle"},{fill:p},`${h.format(s)}`),a("text",{x:e+f,y:o-.5*this.fontSizePx,"text-anchor":"middle"},{fill:p},`${m.format(s)}`)),++u,g=d+86400*u}return[l,c]}render(){var t,e,i,n;const s=d(this.series[0].data);s.minValue=null!==(t=this.series[0].min)&&void 0!==t?t:s.minValue,s.maxValue=null!==(e=this.series[0].max)&&void 0!==e?e:s.maxValue,s.minTime=null!==(i=this.options.minTime)&&void 0!==i?i:s.minTime,s.maxTime=null!==(n=this.options.maxTime)&&void 0!==n?n:s.maxTime,this.limits=Object.assign(Object.assign({},s),{valueScale:(this.plotHeight-this.strokeWidth)/(s.maxValue-s.minValue),timeScale:(this.width-this.strokeWidth)/(s.maxTime-s.minTime)});const[o,r]=this.getTimeScale();this.el.append(o);const[l,h,m]=this.getHorizontalGridlines();this.el.append(l),this.el.append(m),this.plotData(),this.el.append(r),this.el.append(h),this.el.append(a("text",{x:this.width/2,"text-anchor":"middle",y:this.height-.5*this.fontSizePx},{fill:"#595959"},this.attribution)),this.plotLastValue()}plotLastValue(){const{data:t,unit:e,formatter:i}=this.series[0],[n,s]=t[t.length-1],{minTime:o,timeScale:r,maxValue:h,minValue:m}=this.getLimits(),c=null==i?s:i(s),d=this.strokeWidth/2+(n-o)*r,u=(s-m)/(h-m)<.5,g=this.plotHeight*(u?0:.5)+2*this.fontSizePx;this.el.append(a("text",{x:d,y:g,"text-anchor":"end"},{"font-size":"1.5em","font-weight":"bold",stroke:this.labelBg,"stroke-width":this.labelBgWidth},`${c} ${e}`),a("text",{x:d,y:g,"text-anchor":"end"},{fill:this.plotColor,"font-size":"1.5em","font-weight":"bold"},`${c} ${e}`),a("text",{x:d,y:g+1.5*this.fontSizePx,"text-anchor":"end"},{stroke:this.labelBg,"stroke-width":this.labelBgWidth},`${l.format(new Date(1e3*n))}`),a("text",{x:d,y:g+1.5*this.fontSizePx,"text-anchor":"end"},{fill:this.plotColor},`${l.format(new Date(1e3*n))}`))}plotData(){const t=this.strokeWidth/2,e=this.plotHeight-this.strokeWidth/2,{data:i}=this.series[0],{minTime:n,timeScale:s,minValue:o,valueScale:r}=this.getLimits(),l=[`M${t+(i[0][0]-n)*s},${e-(i[0][1]-o)*r}`];for(let a=1;a<i.length;++a){const h=t+(i[a][0]-n)*s,m=e-(i[a][1]-o)*r;l.push(`L${h},${m}`)}const h=a("path",{d:l.join(""),stroke:this.plotColor,"stroke-width":this.strokeWidth,fill:"none"});this.el.append(h)}}const d=t=>{if(t.length<1)throw new Error("Readings must not be empty");const e=t[0][0],i=t[t.length-1][0];let n=1/0,s=-n;return t.forEach((([,t])=>{n=Math.min(n,t),s=Math.max(s,t)})),{minTime:e,maxTime:i,minValue:n,maxValue:s}},u=(t,e)=>{const i=Math.floor(Math.log10(t))-1,n=t/(e*10**i);return[n<=2?2:n<=5?5:10,i]},g="http://environment.data.gov.uk/flood-monitoring",f="riverDataWidget",p=t=>`${f}|${t}`;let x;class w{clear(t=!1){for(const t of this.keys())localStorage.removeItem(p(t));t?localStorage.removeItem(f):localStorage.setItem(f,JSON.stringify([]))}get(t){const e=localStorage.getItem(p(t));return null===e?null:JSON.parse(e)}has(t){return this.keys().includes(t)}isActive(){return null!==localStorage.getItem(f)}keys(){const t=localStorage.getItem(f);return null===t?[]:JSON.parse(t)}set(t,e){const i=JSON.stringify(e),n=localStorage.getItem(f),s=null===n?[]:JSON.parse(n);s.includes(t)||(s.push(t),localStorage.setItem(f,JSON.stringify(s))),localStorage.setItem(p(t),i)}unset(t){localStorage.removeItem(p(t));const e=localStorage.getItem(f),i=null===e?[]:JSON.parse(e),n=i.indexOf(t);return-1!==n&&(i.splice(n,1),localStorage.setItem(f,JSON.stringify(i)),!0)}}const S=async(t,e={})=>{const i={_sorted:""};e.since&&(i.since=e.since.toISOString().substring(0,19)+"Z");const n=await(async(t,e={})=>{const i=new URLSearchParams(e).toString(),n=i?`${g}${t}?${i}`:`${g}${t}`,s=await fetch(n);return{data:await s.json(),response:s}})(`/id/measures/${t}/readings`,i);return[D(n.data.items)[t]||[],n]},v=(t,e)=>{const i=t.findIndex((t=>t[0]>=e));return i<0?[]:t.slice(i)},y=async(t,e={})=>{const i=`readings|${t}`,n=(x||(x=new w),x),s=n.get(i)||{data:[],lastCheck:0,storedSince:1/0},{data:a,lastCheck:o}=s;let{storedSince:l}=s;const h=r(null,-8,!0).valueOf()/1e3;for(;a.length&&a[0][0]<h;)[l]=a[0],a.shift();const m=a.length?a[a.length-1][0]:0,c=e.since&&e.since.valueOf()/1e3||0;if(l<=c&&Date.now()<1e3*o+3e5)return v(a,c);const d=Object.assign(Object.assign({},e),{since:new Date(1e3*Math.max(c,m))}),[u]=await S(t,d);return k(a,u),l=Math.min(c,l),n.set(i,{lastCheck:Date.now()/1e3,data:a,storedSince:l}),v(a,c)},k=(t,e)=>{if(!e.length)return;let i=t.length-1;for(;i>=0&&t[i][0]>=e[0][0];)--i;t.splice(i+1,1/0,...e)},D=t=>{const e={};t.forEach((({measure:t,dateTime:i,value:n})=>{null==e[t]&&(e[t]=[]),e[t].unshift([new Date(i).valueOf()/1e3,n])}));const i={};return Object.entries(e).forEach((([t,e])=>{i[t.substring(t.lastIndexOf("/")+1)]=e})),i},$=async(t,e,a={})=>{const o=r(null,-7,!0),l=await y(e,{since:o});t.replaceChildren();const h=(t=>{const e=t.match(/(.*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)$/);if(null===e)throw new n("Cannot parse measure id",{measureId:t});const[i,s,a,o,r,l]=e.reverse();return{stationId:l,parameter:r,qualifier:o,type:a,interval:s,unit:i,qualifiedParameter:o.length?`${r}-${o}`:r}})(e),{unit:m}=(t=>{const e={};for(const i in t){const n=t[i];s[i]&&s[i][n]?e[i]=s[i][n]:e[i]=n}return e})(h),d={data:l,unit:m,formatter:i};null!=a.riverDataWidgetMaxValue&&(d.max=parseFloat(a.riverDataWidgetMaxValue)),null!=a.riverDataWidgetMinValue&&(d.min=parseFloat(a.riverDataWidgetMinValue));const u=r(new Date(1e3*l[0][0])).valueOf()/1e3,g=r(new Date(1e3*l[l.length-1][0]),1).valueOf()/1e3;new c(t,[d],{minTime:u,maxTime:g}).render()},b=async()=>{document.querySelectorAll("[data-river-data-widget]").forEach((t=>{try{(t=>{var i,n;const s="string"==typeof t?document.querySelector(t):t;if(null===s)throw new Error("Target element not found");const a=null!==(n=null===(i=s.dataset.riverDataWidget)||void 0===i?void 0:i.split(":"))&&void 0!==n?n:[],o=a.shift(),r=a.join(":"),l=s.dataset;if("measure"!==o)throw new e("Unknown widget definition",{type:o,id:r});$(s,r,l)})(t)}catch(t){console.error(t,{error:t})}}))};return"loading"===document.readyState?document.addEventListener("DOMContentLoaded",b):b(),t.version="1.1.0",t}({}); | ||
| //# sourceMappingURL=river-data-widget.min.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"river-data-widget.min.js","sources":["../src/error.ts","../src/helpers/format.ts","../src/flood-monitoring-api/error.ts","../src/flood-monitoring-api/measure.ts","../src/helpers/dom.ts","../src/helpers/time.ts","../src/widget/chart.ts","../src/flood-monitoring-api/api.ts","../src/flood-monitoring-api/store.ts","../src/flood-monitoring-api/reading.ts","../src/widget/render.ts","../src/autoload.ts"],"sourcesContent":["export type RiverDataWidgetErrorInfo = Record<string, unknown>;\n\nexport class RiverDataWidgetError extends Error {\n public info: RiverDataWidgetErrorInfo;\n\n constructor(msg: string, info: RiverDataWidgetErrorInfo = {}) {\n super(msg);\n this.name = 'RiverDataWidgetError';\n this.info = info;\n }\n}\n","export const FONT_STACK =\n '-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Roboto\",\"Helvetica Neue\",Arial,sans-serif';\n\nexport const round3 = (value: number) =>\n value < 100 ? value.toPrecision(3) : Math.round(value).toString();\n","export class FloodMonitoringApiError extends Error {\n public info: Record<string, unknown>;\n\n constructor(msg: string, info: Record<string, unknown> = {}) {\n super(msg);\n this.name = 'FloodMonitoringApiError';\n this.info = info;\n }\n}\n","import { FloodMonitoringApiError } from './error';\n\nexport { parseMeasureId };\n\nconst parseMeasureId = (measureId: string) => {\n // ............base/ stat-paramet-qualifi- type -interva-unit\n const regExp = /(.*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)$/;\n const matches = measureId.match(regExp);\n if (matches === null) {\n throw new FloodMonitoringApiError('Cannot parse measure id', { measureId });\n }\n const [unit, interval, type, qualifier, parameter, stationId] =\n matches.reverse();\n const qualifiedParameter = qualifier.length\n ? `${parameter}-${qualifier}`\n : parameter;\n return {\n stationId,\n parameter,\n qualifier,\n type,\n interval,\n unit,\n qualifiedParameter,\n };\n};\n\nconst measureTranslations: Record<string, Record<string, string>> = {\n unit: {\n m3_s: 'm³/s',\n mAOD: 'm',\n mASD: 'm',\n },\n qualifiedParameter: {\n 'level-stage': 'level',\n 'level-downstage': 'downstream level',\n },\n};\n\nexport const translateMeasureProperties = (measure: Record<string, string>) => {\n const translated: Record<string, string> = {};\n for (const prop in measure) {\n const value = measure[prop];\n if (measureTranslations[prop] && measureTranslations[prop][value]) {\n translated[prop] = measureTranslations[prop][value];\n } else {\n translated[prop] = value;\n }\n }\n return translated;\n};\n","/**\n * RiverDataWidget https://github.com/pb-uk/river-data-widget.\n *\n * @copyright Copyright (C) 2022 pbuk https://github.com/pb-uk.\n * @license AGPL-3.0-or-later see LICENSE.md.\n */\n\nexport { createElement, createSvgElement, setAttributes, setStyles };\n\ntype AttributeList = Record<string, string | number>;\n\nconst setAttributes = <T extends HTMLElement | SVGElement>(\n el: T,\n attributes: AttributeList\n): T => {\n Object.entries(attributes).forEach(([key, value]) => {\n el.setAttribute(key, `${value}`);\n });\n return el;\n};\n\nconst setStyles = <T extends HTMLElement | SVGElement>(\n el: T,\n styles: AttributeList\n): T => {\n Object.entries(styles).forEach(([key, value]) => {\n // Workaround (el.style.setProperty uses kebab-case keys).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (<any>el.style)[key] = value;\n });\n return el;\n};\n\nconst createElement = (\n name = 'div',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n): HTMLElement => {\n const el = document.createElement(name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n\nconst createSvgElement = (\n name = 'svg',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n) => {\n const el = document.createElementNS('http://www.w3.org/2000/svg', name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n","export const MINUTE_MS = 60000;\n// const HOUR_MS = 3600000;\nexport const DAY_MS = 86400000;\n\n/**\n * Get the Date at the start of a day in UTC or local time.\n *\n * @param offset\n * @param timeZone The time zone offset in minutes, or set to `true` to use the\n * local time zone (`false`, the default, uses UTC).\n * @returns The reqested date.\n */\nexport const startOfDay = (\n date: Date | null = null,\n offset = 0,\n timeZone: boolean | number = false\n): Date => {\n if (timeZone === false) {\n // Use UTC.\n const base = date === null ? Date.now() : date.valueOf();\n return new Date(Math.floor(base / DAY_MS + offset) * DAY_MS);\n }\n\n const now = new Date();\n const tz = timeZone === true ? now.getTimezoneOffset() : timeZone;\n const local = now.valueOf() + tz * MINUTE_MS;\n return new Date(Math.floor(local / DAY_MS + offset) * DAY_MS);\n};\n\n/**\n * | | long |short|narrow|numeric|2-digit|\n * |:-------:|:-----------:|:---:|:----:|:-----:|:-----:|\n * | weekday | Monday | Mon | M | | |\n * | era | Anno Domini | AD | A | | |\n * | year | | | | 2012 | 12 |\n * | month | March | Mar | M | 3 | 03 |\n * | day | | | | 1 | 01 |\n * | hour | | | | 1 | 01 |\n * | minute | | | | 1 | 01 |\n * | second | | | | 1 | 01 |\n *\n * * fractionalSecondDigits: 1, 2 or 3 for number of digits.\n * * timeZoneName: long (Pacific Standard Time), short (PST),\n * longOffset (GMT-0800), shortOffset (GMT-8), longGeneric (Pacific Time),\n * shortGeneric (PT).\n */\n\nexport const dateFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n // year: 'numeric',\n});\n\nexport const timeFormatter = new Intl.DateTimeFormat('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n timeZoneName: 'short',\n});\n\nexport const dddFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'short',\n});\n\nexport const dMmmFormatter = new Intl.DateTimeFormat('en-GB', {\n day: 'numeric',\n month: 'short',\n});\n","import { createSvgElement } from '../helpers/dom';\nimport { timeFormatter, dddFormatter, dMmmFormatter } from '../helpers/time';\nimport { FloodMonitoringApiError } from '../flood-monitoring-api/error';\nimport { FONT_STACK } from '../helpers/format';\n\nconst PLOT_COLOR = '#77C';\n\nexport interface ChartOptions {\n minTime?: number;\n maxTime?: number;\n attribution?: string;\n}\n\nexport interface ChartScaleLimits {\n minTime: number;\n maxTime: number;\n timeScale: number;\n minValue: number;\n maxValue: number;\n valueScale: number;\n}\n\nexport interface ChartSeries {\n data: TimeSeriesValue[];\n min?: number;\n max?: number;\n unit?: string;\n formatter?: (value: number) => string;\n}\n\nexport type TimeSeriesValue = [\n ts: number, // Unix time stamp (seconds).\n v: number // Value.\n];\n\nexport class Chart {\n protected fontSizePx = 14;\n\n protected el: SVGElement;\n protected series: ChartSeries[];\n protected options: ChartOptions;\n protected width = 480; // 400;\n protected height = 270; // 225;\n protected plotHeight = this.height - this.fontSizePx * 4.5;\n protected strokeWidth = 2;\n protected limits?: ChartScaleLimits;\n\n protected attribution =\n 'Uses Environment Agency data from the real-time API (Beta)';\n\n // CSS settings.\n // Just readable at 320x180.\n // Good from 400x225.\n // Perfect at 480x270 (font is 12px);\n protected styles = {\n 'font-family': FONT_STACK,\n 'font-size': `${this.fontSizePx}px`,\n display: 'block',\n margin: 'auto',\n 'max-width': '150vh',\n };\n\n constructor(\n el: HTMLElement,\n series: ChartSeries[],\n options: ChartOptions = {}\n ) {\n this.series = series;\n this.options = options;\n const viewBox = `0 0 ${this.width} ${this.height}`;\n this.attribution = options.attribution ?? this.attribution;\n this.el = createSvgElement('svg', { viewBox }, this.styles);\n el.append(this.el);\n }\n\n getLimits(): ChartScaleLimits {\n if (this.limits == null) {\n throw new FloodMonitoringApiError('Chart axis limits have not been set');\n }\n return this.limits;\n }\n\n getHorizontalGridlines(): SVGElement[] {\n const { minTime, maxTime, timeScale, minValue, maxValue, valueScale } =\n this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight;\n const x1 = xOffset;\n const x2 = xOffset + (maxTime - minTime) * timeScale;\n // Horizontal grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n const valueRange = maxValue - minValue;\n // Horizontal grid interval.\n const [interval, exponent] = getInterval(valueRange, 9);\n const factor = 10 ** -exponent;\n const base = Math.ceil((minValue * factor) / interval + 1) * interval;\n let i = 0;\n let current = base / factor;\n while (current < maxValue) {\n const y1 = yOffset - (current - minValue) * valueScale;\n lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n labels.append(\n createSvgElement('text', { x: x1 + 4, y: y1 + 4 }, {}, `${current}`)\n );\n ++i;\n current = (base + i * interval) / factor;\n }\n const timeAxisLine = createSvgElement(\n 'line',\n { x1, y1: yOffset, x2, y2: yOffset },\n { stroke: '#777' }\n );\n\n return [lines, labels, timeAxisLine];\n }\n\n getTimeScale(): SVGElement[] {\n const { minTime, maxTime, timeScale } = this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight + this.strokeWidth / 2;\n const y1 = yOffset + this.fontSizePx * 3;\n const y2 = yOffset - this.plotHeight;\n // Vertical grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n // Vertical grid interval.\n const base = minTime;\n const interval = 86400;\n let i = 0;\n let current = base;\n const labelOffset = 43200 * timeScale;\n const fill = '#444';\n while (current <= maxTime) {\n const x1 = xOffset + (current - minTime) * timeScale;\n const d = new Date(current * 1000);\n // lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n lines.append(createSvgElement('line', { x1, y1, x2: x1, y2 }));\n labels.append(\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 1.8,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dddFormatter.format(d)}`\n ),\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 0.5,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dMmmFormatter.format(d)}`\n )\n );\n ++i;\n current = base + i * interval;\n }\n return [lines, labels];\n }\n\n render() {\n // Calculate axis scales.\n const limits = getLimits(this.series[0].data);\n limits.minValue = this.series[0].min ?? limits.minValue;\n limits.maxValue = this.series[0].max ?? limits.maxValue;\n limits.minTime = this.options.minTime ?? limits.minTime;\n limits.maxTime = this.options.maxTime ?? limits.maxTime;\n\n this.limits = {\n ...limits,\n valueScale:\n (this.plotHeight - this.strokeWidth) /\n (limits.maxValue - limits.minValue),\n timeScale:\n (this.width - this.strokeWidth) / (limits.maxTime - limits.minTime),\n };\n\n // Time axis.\n const [timeLines, timeLabels] = this.getTimeScale();\n this.el.append(timeLines);\n\n // Value axis.\n const [valueLines, valueLabels, timeAxisLine] =\n this.getHorizontalGridlines();\n this.el.append(valueLines);\n this.el.append(timeAxisLine);\n\n this.plotData();\n\n // Plot labels on top of the line.\n this.el.append(timeLabels);\n this.el.append(valueLabels);\n\n this.el.append(\n createSvgElement(\n 'text',\n {\n x: this.width / 2,\n 'text-anchor': 'middle',\n y: this.height - this.fontSizePx * 0.5,\n },\n { fill: '#595959' },\n this.attribution\n )\n );\n\n this.plotLastValue();\n }\n\n plotLastValue() {\n const { data, unit, formatter } = this.series[0];\n const [time, value] = data[data.length - 1];\n const { minTime, timeScale, minValue, valueScale } = this.getLimits();\n\n const v = formatter == null ? value : formatter(value);\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight - this.strokeWidth / 2;\n const x = xOffset + (time - minTime) * timeScale;\n const highLabel = (value - minValue) / valueScale >= 0.5;\n const y =\n yOffset - this.plotHeight * (highLabel ? 1 : 0.5) + this.fontSizePx * 2;\n\n this.el.append(\n createSvgElement(\n 'text',\n { x, y, 'text-anchor': 'end' },\n { fill: PLOT_COLOR, 'font-size': '1.5em', 'font-weight': 'bold' },\n `${v} ${unit}`\n )\n );\n this.el.append(\n createSvgElement(\n 'text',\n { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' },\n { fill: PLOT_COLOR },\n `${timeFormatter.format(new Date(time * 1000))}`\n )\n );\n }\n\n plotData() {\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight - this.strokeWidth / 2;\n const { data } = this.series[0];\n const { minTime, timeScale, minValue, valueScale } = this.getLimits();\n // First data point.\n const x = xOffset + (data[0][0] - minTime) * timeScale;\n const y = yOffset - (data[0][1] - minValue) * valueScale;\n const points = [`M${x},${y}`];\n // Remaining data points.\n for (let i = 1; i < data.length; ++i) {\n const x = xOffset + (data[i][0] - minTime) * timeScale;\n const y = yOffset - (data[i][1] - minValue) * valueScale;\n points.push(`L${x},${y}`);\n }\n // Plot the data.\n const path = createSvgElement('path', {\n d: points.join(''),\n stroke: PLOT_COLOR,\n 'stroke-width': this.strokeWidth,\n fill: 'none',\n });\n this.el.append(path);\n }\n}\n\nexport const getLimits = (data: TimeSeriesValue[]) => {\n if (data.length < 1) {\n throw new Error('Readings must not be empty');\n }\n const minTime = data[0][0];\n const maxTime = data[data.length - 1][0];\n let minValue = Infinity;\n let maxValue = -minValue;\n data.forEach(([, value]) => {\n minValue = Math.min(minValue, value);\n maxValue = Math.max(maxValue, value);\n });\n return { minTime, maxTime, minValue, maxValue };\n};\n\nexport const getInterval = (range: number, maxDivisions: number) => {\n const exponent = Math.floor(Math.log10(range)) - 1;\n const k = range / (maxDivisions * 10 ** exponent);\n const mantissa = k <= 2 ? 2 : k <= 5 ? 5 : 10;\n return [mantissa, exponent];\n};\n","// There is no need to be secure about this!\nconst baseUrl = 'http://environment.data.gov.uk/flood-monitoring';\n\nexport interface ApiResponse<T> {\n data: {\n items: T;\n };\n response: Response;\n}\n\nexport interface ApiParameters {\n since?: string; // Time from.\n _sorted?: ''; // Flag for sorting.\n}\n\nexport const apiFetch = async (\n path: string,\n query = {}\n): Promise<ApiResponse<unknown>> => {\n const queryString = new URLSearchParams(query).toString();\n const uri = queryString\n ? `${baseUrl}${path}?${queryString}`\n : `${baseUrl}${path}`;\n const response = await fetch(uri);\n return { data: await response.json(), response };\n};\n\n/**\n * Convert a Date to a format recognized by the EA API for a query parameter.\n *\n * @param date Convert from.\n * @returns A string in the EA API query parameter format.\n */\nexport const toTimeParameter = (date: Date): string => {\n return date.toISOString().substring(0, 19) + 'Z';\n};\n\n/*\nUseful response headers\n Date: 'Sat, 13 May 2023 09:14:07 GMT',\n last-modified: Sat, 13 May 2023 09:03:13 GMT,\nResponse meta:\n publisher: 'Environment Agency',\n license: 'http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/',\n documentation: 'http://environment.data.gov.uk/flood-monitoring/doc/reference',\n version: '0.9',\n comment: 'Status: Beta service',\n hasFormat: [\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.csv?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.rdf?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.ttl?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.html?_sorted=&since=2023-05-12T08%3A00%3A00Z\"\n ],\n*/\n","const prefix = 'riverDataWidget';\n\nconst addPrefix = (key: string): string => `${prefix}|${key}`;\n\nlet instance: Store;\n\nclass Store {\n clear(destroy = false) {\n for (const key of this.keys()) {\n localStorage.removeItem(addPrefix(key));\n }\n if (destroy) {\n localStorage.removeItem(prefix);\n return;\n }\n localStorage.setItem(prefix, JSON.stringify([]));\n }\n\n get(key: string) {\n const value = localStorage.getItem(addPrefix(key));\n return value === null ? null : JSON.parse(value);\n }\n\n has(key: string): boolean {\n return this.keys().includes(key);\n }\n\n /**\n * Detect active localStorage.\n *\n * @returns true iff localStorage for the widget is active.\n */\n isActive() {\n return localStorage.getItem(prefix) !== null;\n }\n\n keys(): string[] {\n const storedKeys = localStorage.getItem(prefix);\n return storedKeys === null ? [] : JSON.parse(storedKeys);\n }\n\n set(key: string, value: unknown) {\n const json = JSON.stringify(value);\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n if (!keys.includes(key)) {\n keys.push(key);\n localStorage.setItem(prefix, JSON.stringify(keys));\n }\n localStorage.setItem(addPrefix(key), json);\n }\n\n unset(key: string): boolean {\n // Remove it before we do anything else.\n localStorage.removeItem(addPrefix(key));\n\n // Then remove it from the list of keys.\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n const index = keys.indexOf(key);\n\n // If it doesn't exist we don't have to remove it.\n if (index === -1) return false;\n\n keys.splice(index, 1);\n localStorage.setItem(prefix, JSON.stringify(keys));\n return true;\n }\n}\n\nexport const useStore = (): Store => {\n if (!instance) {\n instance = new Store();\n }\n return instance;\n};\n","import { apiFetch, toTimeParameter } from './api';\nimport { useStore } from './store';\nimport { MINUTE_MS, startOfDay } from '../helpers/time';\n\nimport type { ApiParameters, ApiResponse } from './api';\n\n// Throttle requests to five minutes.\nconst THROTTLE_MS = 5 * MINUTE_MS;\n\n/**\n * Internal format for readings.\n */\nexport type Reading = [\n timestamp: number, // Unix epoch timestamp (seconds).\n value: number // Value.\n];\n\n/**\n * Internal format for readings.\n */\nexport interface ReadingOptions {\n since?: Date; // Time from.\n}\n\n/**\n * Internal format for readings.\n */\ntype ReadingResponse = [a: Reading[], b: ApiResponse<ReadingDTO[]>];\n\n/**\n * Data transfer object for readings provided by the API.\n */\ninterface ReadingDTO {\n '@id': string; // The URL of this reading.\n dateTime: string; // e.g. '2023-05-13T09:00:00Z'.\n measure: string; // The URL of the measure.\n value: number; // The value in the appropriate units.\n}\n\ninterface StoredReadings {\n storedSince: number;\n lastCheck: number;\n data: Reading[];\n}\n\n/**\n * Fetch the readings for a measure.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nconst fetchMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<ReadingResponse> => {\n // Set the parameters for the request.\n const params: ApiParameters = { _sorted: '' };\n if (options.since) {\n params.since = toTimeParameter(options.since);\n }\n // Get the response, casting the items to ReadingDTOs.\n const response = <ApiResponse<ReadingDTO[]>>(\n await apiFetch(`/id/measures/${id}/readings`, params)\n );\n return [parseReadings(response.data.items)[id] || [], response];\n};\n\nexport const filterSince = (data: Reading[], since: number) => {\n const position = data.findIndex((reading) => reading[0] >= since);\n return position < 0 ? [] : data.slice(position);\n};\n\n/**\n * Get the readings for a measure.\n *\n * @todo Caching and throttling.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nexport const getMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<Reading[]> => {\n // Get the saved readings.\n const key = `readings|${id}`;\n const store = useStore();\n\n const stored: StoredReadings = store.get(key) || {\n data: [],\n lastCheck: 0,\n storedSince: Infinity,\n };\n const { data, lastCheck } = stored;\n let { storedSince } = stored;\n\n const discardBefore = startOfDay(null, -8, true).valueOf() / 1000;\n\n // Discard any older than 30 days.\n while (data.length && data[0][0] < discardBefore) {\n [storedSince] = data[0];\n data.shift();\n }\n\n // If we have data early enough apply throttle.\n const lastStored = data.length ? data[data.length - 1][0] : 0;\n const requestedSince = (options.since && options.since.valueOf() / 1000) || 0;\n if (\n storedSince <= requestedSince &&\n Date.now() < lastCheck * 1000 + THROTTLE_MS\n ) {\n // Throttled.\n return filterSince(data, requestedSince);\n }\n\n const fetchOptions: ReadingOptions = {\n ...options,\n since: new Date(Math.max(requestedSince, lastStored) * 1000),\n };\n\n const [newData] = await fetchMeasureReadings(id, fetchOptions);\n mergeReadings(data, newData);\n storedSince = Math.min(requestedSince, storedSince);\n store.set(key, { lastCheck: Date.now() / 1000, data, storedSince });\n return filterSince(data, requestedSince);\n};\n\nexport const mergeReadings = (first: Reading[], second: Reading[]): void => {\n if (!second.length) return;\n\n let firstPos = first.length - 1;\n while (firstPos >= 0 && first[firstPos][0] >= second[0][0]) {\n --firstPos;\n }\n first.splice(firstPos + 1, Infinity, ...second);\n};\n\nconst parseReadings = (items: ReadingDTO[]): Record<string, Reading[]> => {\n const ranges: Record<string, Reading[]> = {};\n items.forEach(({ measure, dateTime, value }) => {\n if (ranges[measure] == null) {\n ranges[measure] = [];\n }\n ranges[measure].unshift([new Date(dateTime).valueOf() / 1000, value]);\n });\n\n const rangesById: Record<string, Reading[]> = {};\n Object.entries(ranges).forEach(([key, range]) => {\n rangesById[key.substring(key.lastIndexOf('/') + 1)] = range;\n });\n\n return rangesById;\n};\n","import { RiverDataWidgetError } from '../error';\nimport { round3 } from '../helpers/format';\nimport {\n parseMeasureId,\n translateMeasureProperties,\n} from '../flood-monitoring-api/measure';\nimport { Chart } from './chart';\nimport { getMeasureReadings } from '../flood-monitoring-api';\nimport { startOfDay } from '../helpers/time';\n\nimport type { ChartSeries } from './chart';\n\nconst drawMeasureWidget = async (\n parentEl: HTMLElement,\n measureId: string,\n options: Record<string, unknown> = {}\n) => {\n // Get readings for the last 7 days in local time.\n const since = startOfDay(null, -7, true);\n\n const data = await getMeasureReadings(measureId, { since });\n\n parentEl.replaceChildren();\n\n const measure = parseMeasureId(measureId);\n const { unit } = translateMeasureProperties(measure);\n\n // const [time, value] = data[data.length - 1];\n // const v = round3(value);\n // const param = m.qualifiedParameter;\n // const station = measure.stationId;\n // const unit = m.unit;\n\n // let textEl = createElement('div');\n // const d = dateFormatter.format(new Date(time * 1000));\n // const t = timeFormatter.format(new Date(time * 1000));\n // textEl.innerHTML = `The most recent ${param} reading for station ${station} was ${v} ${unit} at ${t} on ${d}.`;\n // widgetEl.append(textEl);\n\n const series1: ChartSeries = { data, unit, formatter: round3 };\n if (options.riverDataWidgetMinValue != null) {\n series1.min = parseFloat(<string>options.riverDataWidgetMinValue);\n }\n const minTime = startOfDay(new Date(data[0][0] * 1000)).valueOf() / 1000;\n const maxTime =\n startOfDay(new Date(data[data.length - 1][0] * 1000), 1).valueOf() / 1000;\n const chartOptions = {\n minTime,\n maxTime,\n // attribution: `www.riverdata.co.uk/station/${measure.stationId}`,\n };\n\n const chart = new Chart(parentEl, [series1], chartOptions);\n chart.render();\n};\n\n/**\n * Load a widget specified by a DOM element.\n */\nexport const loadWidget = (el: HTMLElement | string) => {\n // Get the target element from a query selector if necessary and check it\n // exists.\n const targetEl =\n typeof el === 'string' ? <HTMLElement>document.querySelector(el) : el;\n if (targetEl === null) {\n throw new Error('Target element not found');\n }\n\n // Parse element for widget type and options.\n const widgetIdParts = targetEl.dataset.riverDataWidget?.split(':') ?? [];\n const type = widgetIdParts.shift();\n const id = widgetIdParts.join(':');\n const options = targetEl.dataset;\n\n switch (type) {\n case 'measure':\n drawMeasureWidget(targetEl, id, options);\n break;\n\n // The 'station' widget is experimental in v1.0 and should not be used.\n // case 'station':\n // break;\n\n default:\n throw new RiverDataWidgetError('Unknown widget definition', { type, id });\n }\n};\n","import { loadWidget } from './widget/render';\n\nconst autoload = async () => {\n document.querySelectorAll('[data-river-data-widget]').forEach((el) => {\n try {\n loadWidget(<HTMLElement>el);\n } catch (error) {\n console.error(error, { error });\n }\n });\n};\n\nif (document.readyState === 'loading') {\n // Loading hasn't finished yet.\n document.addEventListener('DOMContentLoaded', autoload);\n} else {\n // `DOMContentLoaded` has already fired.\n autoload();\n}\n"],"names":["RiverDataWidgetError","Error","constructor","msg","info","super","this","name","round3","value","toPrecision","Math","round","toString","FloodMonitoringApiError","measureTranslations","unit","m3_s","mAOD","mASD","qualifiedParameter","createSvgElement","attributes","styles","innerHTML","el","document","createElementNS","Object","entries","forEach","key","style","setStyles","setAttribute","setAttributes","DAY_MS","startOfDay","date","offset","timeZone","base","Date","now","valueOf","floor","tz","getTimezoneOffset","local","timeFormatter","Intl","DateTimeFormat","hour","minute","hour12","timeZoneName","dddFormatter","weekday","dMmmFormatter","day","month","PLOT_COLOR","Chart","series","options","fontSizePx","width","height","plotHeight","strokeWidth","attribution","display","margin","viewBox","_a","append","getLimits","limits","getHorizontalGridlines","minTime","maxTime","timeScale","minValue","maxValue","valueScale","xOffset","yOffset","x1","x2","lines","stroke","labels","valueRange","interval","exponent","getInterval","factor","ceil","i","current","y1","y2","x","y","getTimeScale","labelOffset","fill","d","format","render","data","min","_b","max","_c","_d","assign","timeLines","timeLabels","valueLines","valueLabels","timeAxisLine","plotData","plotLastValue","formatter","time","length","v","highLabel","points","push","path","join","Infinity","range","maxDivisions","log10","k","baseUrl","prefix","addPrefix","instance","Store","clear","destroy","keys","localStorage","removeItem","setItem","JSON","stringify","get","getItem","parse","has","includes","isActive","storedKeys","set","json","unset","index","indexOf","splice","fetchMeasureReadings","async","id","params","_sorted","since","toISOString","substring","response","query","queryString","URLSearchParams","uri","fetch","apiFetch","parseReadings","items","filterSince","position","findIndex","reading","slice","getMeasureReadings","store","stored","lastCheck","storedSince","discardBefore","shift","lastStored","requestedSince","fetchOptions","newData","mergeReadings","first","second","firstPos","ranges","measure","dateTime","unshift","rangesById","lastIndexOf","drawMeasureWidget","parentEl","measureId","replaceChildren","matches","match","type","qualifier","parameter","stationId","reverse","parseMeasureId","translated","prop","translateMeasureProperties","series1","riverDataWidgetMinValue","parseFloat","autoload","querySelectorAll","targetEl","querySelector","widgetIdParts","dataset","riverDataWidget","split","loadWidget","error","console","readyState","addEventListener"],"mappings":"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"} | ||
| {"version":3,"file":"river-data-widget.min.js","sources":["../src/error.ts","../src/helpers/format.ts","../src/flood-monitoring-api/error.ts","../src/flood-monitoring-api/measure.ts","../src/helpers/dom.ts","../src/helpers/time.ts","../src/widget/chart.ts","../src/flood-monitoring-api/api.ts","../src/flood-monitoring-api/store.ts","../src/flood-monitoring-api/reading.ts","../src/widget/render.ts","../src/autoload.ts"],"sourcesContent":["export type RiverDataWidgetErrorInfo = Record<string, unknown>;\n\nexport class RiverDataWidgetError extends Error {\n public info: RiverDataWidgetErrorInfo;\n\n constructor(msg: string, info: RiverDataWidgetErrorInfo = {}) {\n super(msg);\n this.name = 'RiverDataWidgetError';\n this.info = info;\n }\n}\n","export const FONT_STACK =\n '-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Roboto\",\"Helvetica Neue\",Arial,sans-serif';\n\nexport const round3 = (value: number) =>\n value < 100 ? value.toPrecision(3) : Math.round(value).toString();\n","export class FloodMonitoringApiError extends Error {\n public info: Record<string, unknown>;\n\n constructor(msg: string, info: Record<string, unknown> = {}) {\n super(msg);\n this.name = 'FloodMonitoringApiError';\n this.info = info;\n }\n}\n","import { FloodMonitoringApiError } from './error';\n\nexport { parseMeasureId };\n\nconst parseMeasureId = (measureId: string) => {\n // ............base/ stat-paramet-qualifi- type -interva-unit\n const regExp = /(.*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)-([^-]*)$/;\n const matches = measureId.match(regExp);\n if (matches === null) {\n throw new FloodMonitoringApiError('Cannot parse measure id', { measureId });\n }\n const [unit, interval, type, qualifier, parameter, stationId] =\n matches.reverse();\n const qualifiedParameter = qualifier.length\n ? `${parameter}-${qualifier}`\n : parameter;\n return {\n stationId,\n parameter,\n qualifier,\n type,\n interval,\n unit,\n qualifiedParameter,\n };\n};\n\nconst measureTranslations: Record<string, Record<string, string>> = {\n unit: {\n m3_s: 'm³/s',\n mAOD: 'm',\n mASD: 'm',\n },\n qualifiedParameter: {\n 'level-stage': 'level',\n 'level-downstage': 'downstream level',\n },\n};\n\nexport const translateMeasureProperties = (measure: Record<string, string>) => {\n const translated: Record<string, string> = {};\n for (const prop in measure) {\n const value = measure[prop];\n if (measureTranslations[prop] && measureTranslations[prop][value]) {\n translated[prop] = measureTranslations[prop][value];\n } else {\n translated[prop] = value;\n }\n }\n return translated;\n};\n","/**\n * RiverDataWidget https://github.com/pb-uk/river-data-widget.\n *\n * @copyright Copyright (C) 2022 pbuk https://github.com/pb-uk.\n * @license AGPL-3.0-or-later see LICENSE.md.\n */\n\nexport { createElement, createSvgElement, setAttributes, setStyles };\n\ntype AttributeList = Record<string, string | number>;\n\nconst setAttributes = <T extends HTMLElement | SVGElement>(\n el: T,\n attributes: AttributeList\n): T => {\n Object.entries(attributes).forEach(([key, value]) => {\n el.setAttribute(key, `${value}`);\n });\n return el;\n};\n\nconst setStyles = <T extends HTMLElement | SVGElement>(\n el: T,\n styles: AttributeList\n): T => {\n Object.entries(styles).forEach(([key, value]) => {\n // Workaround (el.style.setProperty uses kebab-case keys).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (<any>el.style)[key] = value;\n });\n return el;\n};\n\nconst createElement = (\n name = 'div',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n): HTMLElement => {\n const el = document.createElement(name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n\nconst createSvgElement = (\n name = 'svg',\n attributes: AttributeList = {},\n styles: AttributeList = {},\n innerHTML: string | false = false\n) => {\n const el = document.createElementNS('http://www.w3.org/2000/svg', name);\n if (innerHTML !== false) {\n el.innerHTML = innerHTML;\n }\n return setStyles(setAttributes(el, attributes), styles);\n};\n","export const MINUTE_MS = 60000;\n// const HOUR_MS = 3600000;\nexport const DAY_MS = 86400000;\n\n/**\n * Get the Date at the start of a day in UTC or local time.\n *\n * @param offset\n * @param timeZone The time zone offset in minutes, or set to `true` to use the\n * local time zone (`false`, the default, uses UTC).\n * @returns The reqested date.\n */\nexport const startOfDay = (\n date: Date | null = null,\n offset = 0,\n timeZone: boolean | number = false\n): Date => {\n if (timeZone === false) {\n // Use UTC.\n const base = date === null ? Date.now() : date.valueOf();\n return new Date(Math.floor(base / DAY_MS + offset) * DAY_MS);\n }\n\n const now = new Date();\n const tz = timeZone === true ? now.getTimezoneOffset() : timeZone;\n const local = now.valueOf() + tz * MINUTE_MS;\n return new Date(Math.floor(local / DAY_MS + offset) * DAY_MS);\n};\n\n/**\n * | | long |short|narrow|numeric|2-digit|\n * |:-------:|:-----------:|:---:|:----:|:-----:|:-----:|\n * | weekday | Monday | Mon | M | | |\n * | era | Anno Domini | AD | A | | |\n * | year | | | | 2012 | 12 |\n * | month | March | Mar | M | 3 | 03 |\n * | day | | | | 1 | 01 |\n * | hour | | | | 1 | 01 |\n * | minute | | | | 1 | 01 |\n * | second | | | | 1 | 01 |\n *\n * * fractionalSecondDigits: 1, 2 or 3 for number of digits.\n * * timeZoneName: long (Pacific Standard Time), short (PST),\n * longOffset (GMT-0800), shortOffset (GMT-8), longGeneric (Pacific Time),\n * shortGeneric (PT).\n */\n\nexport const dateFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n // year: 'numeric',\n});\n\nexport const timeFormatter = new Intl.DateTimeFormat('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n timeZoneName: 'short',\n});\n\nexport const dddFormatter = new Intl.DateTimeFormat('en-GB', {\n weekday: 'short',\n});\n\nexport const dMmmFormatter = new Intl.DateTimeFormat('en-GB', {\n day: 'numeric',\n month: 'short',\n});\n","import { createSvgElement } from '../helpers/dom';\nimport { timeFormatter, dddFormatter, dMmmFormatter } from '../helpers/time';\nimport { FloodMonitoringApiError } from '../flood-monitoring-api/error';\nimport { FONT_STACK } from '../helpers/format';\n\nexport interface ChartOptions {\n minTime?: number;\n maxTime?: number;\n attribution?: string;\n}\n\nexport interface ChartScaleLimits {\n minTime: number;\n maxTime: number;\n timeScale: number;\n minValue: number;\n maxValue: number;\n valueScale: number;\n}\n\nexport interface ChartSeries {\n data: TimeSeriesValue[];\n min?: number;\n max?: number;\n unit?: string;\n formatter?: (value: number) => string;\n}\n\nexport type TimeSeriesValue = [\n ts: number, // Unix time stamp (seconds).\n v: number // Value.\n];\n\nexport class Chart {\n protected fontSizePx = 14;\n\n protected el: SVGElement;\n protected series: ChartSeries[];\n protected options: ChartOptions;\n protected width = 480; // 400;\n protected height = 270; // 225;\n protected plotHeight = this.height - this.fontSizePx * 4.5;\n protected strokeWidth = 2;\n protected limits?: ChartScaleLimits;\n\n protected plotColor = '#77C';\n protected labelBg = 'rgba(255,255,255,0.5)';\n protected labelBgWidth = '0.5em';\n\n protected attribution =\n 'Uses Environment Agency data from the real-time API (Beta)';\n\n // CSS settings.\n // Just readable at 320x180.\n // Good from 400x225.\n // Perfect at 480x270 (font is 12px);\n protected styles = {\n 'font-family': FONT_STACK,\n 'font-size': `${this.fontSizePx}px`,\n display: 'block',\n margin: 'auto',\n 'max-width': '150vh',\n };\n\n constructor(\n el: HTMLElement,\n series: ChartSeries[],\n options: ChartOptions = {}\n ) {\n this.series = series;\n this.options = options;\n const viewBox = `0 0 ${this.width} ${this.height}`;\n this.attribution = options.attribution ?? this.attribution;\n this.el = createSvgElement('svg', { viewBox }, this.styles);\n el.append(this.el);\n }\n\n getLimits(): ChartScaleLimits {\n if (this.limits == null) {\n throw new FloodMonitoringApiError('Chart axis limits have not been set');\n }\n return this.limits;\n }\n\n getHorizontalGridlines(): SVGElement[] {\n const { minTime, maxTime, timeScale, minValue, maxValue, valueScale } =\n this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight;\n const x1 = xOffset;\n const x2 = xOffset + (maxTime - minTime) * timeScale;\n // Horizontal grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n const valueRange = maxValue - minValue;\n // Horizontal grid interval.\n const [interval, exponent] = getInterval(valueRange, 9);\n const factor = 10 ** -exponent;\n const base = Math.ceil((minValue * factor) / interval + 1) * interval;\n let i = 0;\n let current = base / factor;\n while (current < maxValue) {\n const y1 = yOffset - (current - minValue) * valueScale;\n lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n labels.append(\n createSvgElement('text', { x: x1 + 4, y: y1 + 4 }, {}, `${current}`)\n );\n ++i;\n current = (base + i * interval) / factor;\n }\n const timeAxisLine = createSvgElement(\n 'line',\n { x1, y1: yOffset, x2, y2: yOffset },\n { stroke: '#777' }\n );\n\n return [lines, labels, timeAxisLine];\n }\n\n getTimeScale(): SVGElement[] {\n const { minTime, maxTime, timeScale } = this.getLimits();\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight + this.strokeWidth / 2;\n const y1 = yOffset + this.fontSizePx * 3;\n const y2 = yOffset - this.plotHeight;\n // Vertical grid lines.\n const stroke = '#ddd';\n const lines = createSvgElement('g', { stroke });\n const labels = createSvgElement('g');\n // Vertical grid interval.\n const base = minTime;\n const interval = 86400;\n let i = 0;\n let current = base;\n const labelOffset = 43200 * timeScale;\n const fill = '#444';\n while (current <= maxTime) {\n const x1 = xOffset + (current - minTime) * timeScale;\n const d = new Date(current * 1000);\n // lines.append(createSvgElement('line', { x1, y1, x2, y2: y1 }));\n lines.append(createSvgElement('line', { x1, y1, x2: x1, y2 }));\n labels.append(\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 1.8,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dddFormatter.format(d)}`\n ),\n createSvgElement(\n 'text',\n {\n x: x1 + labelOffset,\n y: y1 - this.fontSizePx * 0.5,\n 'text-anchor': 'middle',\n },\n { fill },\n `${dMmmFormatter.format(d)}`\n )\n );\n ++i;\n current = base + i * interval;\n }\n return [lines, labels];\n }\n\n render() {\n // Calculate axis scales.\n const limits = getLimits(this.series[0].data);\n limits.minValue = this.series[0].min ?? limits.minValue;\n limits.maxValue = this.series[0].max ?? limits.maxValue;\n limits.minTime = this.options.minTime ?? limits.minTime;\n limits.maxTime = this.options.maxTime ?? limits.maxTime;\n\n this.limits = {\n ...limits,\n valueScale:\n (this.plotHeight - this.strokeWidth) /\n (limits.maxValue - limits.minValue),\n timeScale:\n (this.width - this.strokeWidth) / (limits.maxTime - limits.minTime),\n };\n\n // Time axis.\n const [timeLines, timeLabels] = this.getTimeScale();\n this.el.append(timeLines);\n\n // Value axis.\n const [valueLines, valueLabels, timeAxisLine] =\n this.getHorizontalGridlines();\n this.el.append(valueLines);\n this.el.append(timeAxisLine);\n\n this.plotData();\n\n // Plot labels on top of the line.\n this.el.append(timeLabels);\n this.el.append(valueLabels);\n\n this.el.append(\n createSvgElement(\n 'text',\n {\n x: this.width / 2,\n 'text-anchor': 'middle',\n y: this.height - this.fontSizePx * 0.5,\n },\n { fill: '#595959' },\n this.attribution\n )\n );\n\n this.plotLastValue();\n }\n\n plotLastValue() {\n const { data, unit, formatter } = this.series[0];\n const [time, value] = data[data.length - 1];\n const { minTime, timeScale, maxValue, minValue } = this.getLimits();\n\n const v = formatter == null ? value : formatter(value);\n const xOffset = this.strokeWidth / 2;\n // const yOffset = this.plotHeight - this.strokeWidth / 2;\n const x = xOffset + (time - minTime) * timeScale;\n const isHighLabel = (value - minValue) / (maxValue - minValue) < 0.5;\n const y = this.plotHeight * (isHighLabel ? 0 : 0.5) + this.fontSizePx * 2;\n\n this.el.append(\n // Background for value label.\n createSvgElement(\n 'text',\n { x, y, 'text-anchor': 'end' },\n {\n 'font-size': '1.5em',\n 'font-weight': 'bold',\n stroke: this.labelBg,\n 'stroke-width': this.labelBgWidth,\n },\n `${v} ${unit}`\n ),\n // Value label.\n createSvgElement(\n 'text',\n { x, y, 'text-anchor': 'end' },\n { fill: this.plotColor, 'font-size': '1.5em', 'font-weight': 'bold' },\n `${v} ${unit}`\n ),\n // Background for time label.\n createSvgElement(\n 'text',\n { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' },\n {\n stroke: this.labelBg,\n 'stroke-width': this.labelBgWidth,\n },\n `${timeFormatter.format(new Date(time * 1000))}`\n ),\n // Time label.\n createSvgElement(\n 'text',\n { x, y: y + this.fontSizePx * 1.5, 'text-anchor': 'end' },\n { fill: this.plotColor },\n `${timeFormatter.format(new Date(time * 1000))}`\n )\n );\n }\n\n plotData() {\n const xOffset = this.strokeWidth / 2;\n const yOffset = this.plotHeight - this.strokeWidth / 2;\n const { data } = this.series[0];\n const { minTime, timeScale, minValue, valueScale } = this.getLimits();\n // First data point.\n const x = xOffset + (data[0][0] - minTime) * timeScale;\n const y = yOffset - (data[0][1] - minValue) * valueScale;\n const points = [`M${x},${y}`];\n // Remaining data points.\n for (let i = 1; i < data.length; ++i) {\n const x = xOffset + (data[i][0] - minTime) * timeScale;\n const y = yOffset - (data[i][1] - minValue) * valueScale;\n points.push(`L${x},${y}`);\n }\n // Plot the data.\n const path = createSvgElement('path', {\n d: points.join(''),\n stroke: this.plotColor,\n 'stroke-width': this.strokeWidth,\n fill: 'none',\n });\n this.el.append(path);\n }\n}\n\nexport const getLimits = (data: TimeSeriesValue[]) => {\n if (data.length < 1) {\n throw new Error('Readings must not be empty');\n }\n const minTime = data[0][0];\n const maxTime = data[data.length - 1][0];\n let minValue = Infinity;\n let maxValue = -minValue;\n data.forEach(([, value]) => {\n minValue = Math.min(minValue, value);\n maxValue = Math.max(maxValue, value);\n });\n return { minTime, maxTime, minValue, maxValue };\n};\n\nexport const getInterval = (range: number, maxDivisions: number) => {\n const exponent = Math.floor(Math.log10(range)) - 1;\n const k = range / (maxDivisions * 10 ** exponent);\n const mantissa = k <= 2 ? 2 : k <= 5 ? 5 : 10;\n return [mantissa, exponent];\n};\n","// There is no need to be secure about this!\nconst baseUrl = 'http://environment.data.gov.uk/flood-monitoring';\n\nexport interface ApiResponse<T> {\n data: {\n items: T;\n };\n response: Response;\n}\n\nexport interface ApiParameters {\n since?: string; // Time from.\n _sorted?: ''; // Flag for sorting.\n}\n\nexport const apiFetch = async (\n path: string,\n query = {}\n): Promise<ApiResponse<unknown>> => {\n const queryString = new URLSearchParams(query).toString();\n const uri = queryString\n ? `${baseUrl}${path}?${queryString}`\n : `${baseUrl}${path}`;\n const response = await fetch(uri);\n return { data: await response.json(), response };\n};\n\n/**\n * Convert a Date to a format recognized by the EA API for a query parameter.\n *\n * @param date Convert from.\n * @returns A string in the EA API query parameter format.\n */\nexport const toTimeParameter = (date: Date): string => {\n return date.toISOString().substring(0, 19) + 'Z';\n};\n\n/*\nUseful response headers\n Date: 'Sat, 13 May 2023 09:14:07 GMT',\n last-modified: Sat, 13 May 2023 09:03:13 GMT,\nResponse meta:\n publisher: 'Environment Agency',\n license: 'http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/',\n documentation: 'http://environment.data.gov.uk/flood-monitoring/doc/reference',\n version: '0.9',\n comment: 'Status: Beta service',\n hasFormat: [\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.csv?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.rdf?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.ttl?_sorted=&since=2023-05-12T08%3A00%3A00Z\",\n \"http://environment.data.gov.uk/flood-monitoring/id/measures/3400TH-level-stage-i-15_min-mAOD/readings.html?_sorted=&since=2023-05-12T08%3A00%3A00Z\"\n ],\n*/\n","const prefix = 'riverDataWidget';\n\nconst addPrefix = (key: string): string => `${prefix}|${key}`;\n\nlet instance: Store;\n\nclass Store {\n clear(destroy = false) {\n for (const key of this.keys()) {\n localStorage.removeItem(addPrefix(key));\n }\n if (destroy) {\n localStorage.removeItem(prefix);\n return;\n }\n localStorage.setItem(prefix, JSON.stringify([]));\n }\n\n get(key: string) {\n const value = localStorage.getItem(addPrefix(key));\n return value === null ? null : JSON.parse(value);\n }\n\n has(key: string): boolean {\n return this.keys().includes(key);\n }\n\n /**\n * Detect active localStorage.\n *\n * @returns true iff localStorage for the widget is active.\n */\n isActive() {\n return localStorage.getItem(prefix) !== null;\n }\n\n keys(): string[] {\n const storedKeys = localStorage.getItem(prefix);\n return storedKeys === null ? [] : JSON.parse(storedKeys);\n }\n\n set(key: string, value: unknown) {\n const json = JSON.stringify(value);\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n if (!keys.includes(key)) {\n keys.push(key);\n localStorage.setItem(prefix, JSON.stringify(keys));\n }\n localStorage.setItem(addPrefix(key), json);\n }\n\n unset(key: string): boolean {\n // Remove it before we do anything else.\n localStorage.removeItem(addPrefix(key));\n\n // Then remove it from the list of keys.\n const storedKeys = localStorage.getItem(prefix);\n const keys: string[] = storedKeys === null ? [] : JSON.parse(storedKeys);\n const index = keys.indexOf(key);\n\n // If it doesn't exist we don't have to remove it.\n if (index === -1) return false;\n\n keys.splice(index, 1);\n localStorage.setItem(prefix, JSON.stringify(keys));\n return true;\n }\n}\n\nexport const useStore = (): Store => {\n if (!instance) {\n instance = new Store();\n }\n return instance;\n};\n","import { apiFetch, toTimeParameter } from './api';\nimport { useStore } from './store';\nimport { MINUTE_MS, startOfDay } from '../helpers/time';\n\nimport type { ApiParameters, ApiResponse } from './api';\n\n// Throttle requests to five minutes.\nconst THROTTLE_MS = 5 * MINUTE_MS;\n\n/**\n * Internal format for readings.\n */\nexport type Reading = [\n timestamp: number, // Unix epoch timestamp (seconds).\n value: number // Value.\n];\n\n/**\n * Internal format for readings.\n */\nexport interface ReadingOptions {\n since?: Date; // Time from.\n}\n\n/**\n * Internal format for readings.\n */\ntype ReadingResponse = [a: Reading[], b: ApiResponse<ReadingDTO[]>];\n\n/**\n * Data transfer object for readings provided by the API.\n */\ninterface ReadingDTO {\n '@id': string; // The URL of this reading.\n dateTime: string; // e.g. '2023-05-13T09:00:00Z'.\n measure: string; // The URL of the measure.\n value: number; // The value in the appropriate units.\n}\n\ninterface StoredReadings {\n storedSince: number;\n lastCheck: number;\n data: Reading[];\n}\n\n/**\n * Fetch the readings for a measure.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nconst fetchMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<ReadingResponse> => {\n // Set the parameters for the request.\n const params: ApiParameters = { _sorted: '' };\n if (options.since) {\n params.since = toTimeParameter(options.since);\n }\n // Get the response, casting the items to ReadingDTOs.\n const response = <ApiResponse<ReadingDTO[]>>(\n await apiFetch(`/id/measures/${id}/readings`, params)\n );\n return [parseReadings(response.data.items)[id] || [], response];\n};\n\nexport const filterSince = (data: Reading[], since: number) => {\n const position = data.findIndex((reading) => reading[0] >= since);\n return position < 0 ? [] : data.slice(position);\n};\n\n/**\n * Get the readings for a measure.\n *\n * @todo Caching and throttling.\n *\n * @param id The EA measure id.\n * @returns A promise for an array of readings for the measure.\n */\nexport const getMeasureReadings = async (\n id: string,\n options: ReadingOptions = {}\n): Promise<Reading[]> => {\n // Get the saved readings.\n const key = `readings|${id}`;\n const store = useStore();\n\n const stored: StoredReadings = store.get(key) || {\n data: [],\n lastCheck: 0,\n storedSince: Infinity,\n };\n const { data, lastCheck } = stored;\n let { storedSince } = stored;\n\n const discardBefore = startOfDay(null, -8, true).valueOf() / 1000;\n\n // Discard any older than 30 days.\n while (data.length && data[0][0] < discardBefore) {\n [storedSince] = data[0];\n data.shift();\n }\n\n // If we have data early enough apply throttle.\n const lastStored = data.length ? data[data.length - 1][0] : 0;\n const requestedSince = (options.since && options.since.valueOf() / 1000) || 0;\n if (\n storedSince <= requestedSince &&\n Date.now() < lastCheck * 1000 + THROTTLE_MS\n ) {\n // Throttled.\n return filterSince(data, requestedSince);\n }\n\n const fetchOptions: ReadingOptions = {\n ...options,\n since: new Date(Math.max(requestedSince, lastStored) * 1000),\n };\n\n const [newData] = await fetchMeasureReadings(id, fetchOptions);\n mergeReadings(data, newData);\n storedSince = Math.min(requestedSince, storedSince);\n store.set(key, { lastCheck: Date.now() / 1000, data, storedSince });\n return filterSince(data, requestedSince);\n};\n\nexport const mergeReadings = (first: Reading[], second: Reading[]): void => {\n if (!second.length) return;\n\n let firstPos = first.length - 1;\n while (firstPos >= 0 && first[firstPos][0] >= second[0][0]) {\n --firstPos;\n }\n first.splice(firstPos + 1, Infinity, ...second);\n};\n\nconst parseReadings = (items: ReadingDTO[]): Record<string, Reading[]> => {\n const ranges: Record<string, Reading[]> = {};\n items.forEach(({ measure, dateTime, value }) => {\n if (ranges[measure] == null) {\n ranges[measure] = [];\n }\n ranges[measure].unshift([new Date(dateTime).valueOf() / 1000, value]);\n });\n\n const rangesById: Record<string, Reading[]> = {};\n Object.entries(ranges).forEach(([key, range]) => {\n rangesById[key.substring(key.lastIndexOf('/') + 1)] = range;\n });\n\n return rangesById;\n};\n","import { RiverDataWidgetError } from '../error';\nimport { round3 } from '../helpers/format';\nimport {\n parseMeasureId,\n translateMeasureProperties,\n} from '../flood-monitoring-api/measure';\nimport { Chart } from './chart';\nimport { getMeasureReadings } from '../flood-monitoring-api';\nimport { startOfDay } from '../helpers/time';\n\nimport type { ChartSeries } from './chart';\n\nconst drawMeasureWidget = async (\n parentEl: HTMLElement,\n measureId: string,\n options: Record<string, unknown> = {}\n) => {\n // Get readings for the last 7 days in local time.\n const since = startOfDay(null, -7, true);\n\n const data = await getMeasureReadings(measureId, { since });\n\n parentEl.replaceChildren();\n\n const measure = parseMeasureId(measureId);\n const { unit } = translateMeasureProperties(measure);\n\n // const [time, value] = data[data.length - 1];\n // const v = round3(value);\n // const param = m.qualifiedParameter;\n // const station = measure.stationId;\n // const unit = m.unit;\n\n // let textEl = createElement('div');\n // const d = dateFormatter.format(new Date(time * 1000));\n // const t = timeFormatter.format(new Date(time * 1000));\n // textEl.innerHTML = `The most recent ${param} reading for station ${station} was ${v} ${unit} at ${t} on ${d}.`;\n // widgetEl.append(textEl);\n\n const series1: ChartSeries = { data, unit, formatter: round3 };\n // Set max/min options for plot from widget options.\n if (options.riverDataWidgetMaxValue != null) {\n series1.max = parseFloat(<string>options.riverDataWidgetMaxValue);\n }\n if (options.riverDataWidgetMinValue != null) {\n series1.min = parseFloat(<string>options.riverDataWidgetMinValue);\n }\n const minTime = startOfDay(new Date(data[0][0] * 1000)).valueOf() / 1000;\n const maxTime =\n startOfDay(new Date(data[data.length - 1][0] * 1000), 1).valueOf() / 1000;\n const chartOptions = {\n minTime,\n maxTime,\n // attribution: `www.riverdata.co.uk/station/${measure.stationId}`,\n };\n\n const chart = new Chart(parentEl, [series1], chartOptions);\n chart.render();\n};\n\n/**\n * Load a widget specified by a DOM element.\n */\nexport const loadWidget = (el: HTMLElement | string) => {\n // Get the target element from a query selector if necessary and check it\n // exists.\n const targetEl =\n typeof el === 'string' ? <HTMLElement>document.querySelector(el) : el;\n if (targetEl === null) {\n throw new Error('Target element not found');\n }\n\n // Parse element for widget type and options.\n const widgetIdParts = targetEl.dataset.riverDataWidget?.split(':') ?? [];\n const type = widgetIdParts.shift();\n const id = widgetIdParts.join(':');\n const options = targetEl.dataset;\n\n switch (type) {\n case 'measure':\n drawMeasureWidget(targetEl, id, options);\n break;\n\n // The 'station' widget is experimental in v1.0 and should not be used.\n // case 'station':\n // break;\n\n default:\n throw new RiverDataWidgetError('Unknown widget definition', { type, id });\n }\n};\n","import { loadWidget } from './widget/render';\n\nconst autoload = async () => {\n document.querySelectorAll('[data-river-data-widget]').forEach((el) => {\n try {\n loadWidget(<HTMLElement>el);\n } catch (error) {\n console.error(error, { error });\n }\n });\n};\n\nif (document.readyState === 'loading') {\n // Loading hasn't finished yet.\n document.addEventListener('DOMContentLoaded', autoload);\n} else {\n // `DOMContentLoaded` has already fired.\n autoload();\n}\n"],"names":["RiverDataWidgetError","Error","constructor","msg","info","super","this","name","round3","value","toPrecision","Math","round","toString","FloodMonitoringApiError","measureTranslations","unit","m3_s","mAOD","mASD","qualifiedParameter","createSvgElement","attributes","styles","innerHTML","el","document","createElementNS","Object","entries","forEach","key","style","setStyles","setAttribute","setAttributes","DAY_MS","startOfDay","date","offset","timeZone","base","Date","now","valueOf","floor","tz","getTimezoneOffset","local","timeFormatter","Intl","DateTimeFormat","hour","minute","hour12","timeZoneName","dddFormatter","weekday","dMmmFormatter","day","month","Chart","series","options","fontSizePx","width","height","plotHeight","strokeWidth","plotColor","labelBg","labelBgWidth","attribution","display","margin","viewBox","_a","append","getLimits","limits","getHorizontalGridlines","minTime","maxTime","timeScale","minValue","maxValue","valueScale","xOffset","yOffset","x1","x2","lines","stroke","labels","valueRange","interval","exponent","getInterval","factor","ceil","i","current","y1","y2","x","y","getTimeScale","labelOffset","fill","d","format","render","data","min","_b","max","_c","_d","assign","timeLines","timeLabels","valueLines","valueLabels","timeAxisLine","plotData","plotLastValue","formatter","time","length","v","isHighLabel","points","push","path","join","Infinity","range","maxDivisions","log10","k","baseUrl","prefix","addPrefix","instance","Store","clear","destroy","keys","localStorage","removeItem","setItem","JSON","stringify","get","getItem","parse","has","includes","isActive","storedKeys","set","json","unset","index","indexOf","splice","fetchMeasureReadings","async","id","params","_sorted","since","toISOString","substring","response","query","queryString","URLSearchParams","uri","fetch","apiFetch","parseReadings","items","filterSince","position","findIndex","reading","slice","getMeasureReadings","store","stored","lastCheck","storedSince","discardBefore","shift","lastStored","requestedSince","fetchOptions","newData","mergeReadings","first","second","firstPos","ranges","measure","dateTime","unshift","rangesById","lastIndexOf","drawMeasureWidget","parentEl","measureId","replaceChildren","matches","match","type","qualifier","parameter","stationId","reverse","parseMeasureId","translated","prop","translateMeasureProperties","series1","riverDataWidgetMaxValue","parseFloat","riverDataWidgetMinValue","autoload","querySelectorAll","targetEl","querySelector","widgetIdParts","dataset","riverDataWidget","split","loadWidget","error","console","readyState","addEventListener"],"mappings":"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"} |
+16
-658
@@ -1,661 +0,19 @@ | ||
| GNU AFFERO GENERAL PUBLIC LICENSE | ||
| Version 3, 19 November 2007 | ||
| Copyright (c) 2023 pbuk https://github.com/pb-uk | ||
| Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> | ||
| Everyone is permitted to copy and distribute verbatim copies | ||
| of this license document, but changing it is not allowed. | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| Preamble | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| The GNU Affero General Public License is a free, copyleft license for | ||
| software and other kinds of works, specifically designed to ensure | ||
| cooperation with the community in the case of network server software. | ||
| The licenses for most software and other practical works are designed | ||
| to take away your freedom to share and change the works. By contrast, | ||
| our General Public Licenses are intended to guarantee your freedom to | ||
| share and change all versions of a program--to make sure it remains free | ||
| software for all its users. | ||
| When we speak of free software, we are referring to freedom, not | ||
| price. Our General Public Licenses are designed to make sure that you | ||
| have the freedom to distribute copies of free software (and charge for | ||
| them if you wish), that you receive source code or can get it if you | ||
| want it, that you can change the software or use pieces of it in new | ||
| free programs, and that you know you can do these things. | ||
| Developers that use our General Public Licenses protect your rights | ||
| with two steps: (1) assert copyright on the software, and (2) offer | ||
| you this License which gives you legal permission to copy, distribute | ||
| and/or modify the software. | ||
| A secondary benefit of defending all users' freedom is that | ||
| improvements made in alternate versions of the program, if they | ||
| receive widespread use, become available for other developers to | ||
| incorporate. Many developers of free software are heartened and | ||
| encouraged by the resulting cooperation. However, in the case of | ||
| software used on network servers, this result may fail to come about. | ||
| The GNU General Public License permits making a modified version and | ||
| letting the public access it on a server without ever releasing its | ||
| source code to the public. | ||
| The GNU Affero General Public License is designed specifically to | ||
| ensure that, in such cases, the modified source code becomes available | ||
| to the community. It requires the operator of a network server to | ||
| provide the source code of the modified version running there to the | ||
| users of that server. Therefore, public use of a modified version, on | ||
| a publicly accessible server, gives the public access to the source | ||
| code of the modified version. | ||
| An older license, called the Affero General Public License and | ||
| published by Affero, was designed to accomplish similar goals. This is | ||
| a different license, not a version of the Affero GPL, but Affero has | ||
| released a new version of the Affero GPL which permits relicensing under | ||
| this license. | ||
| The precise terms and conditions for copying, distribution and | ||
| modification follow. | ||
| TERMS AND CONDITIONS | ||
| 0. Definitions. | ||
| "This License" refers to version 3 of the GNU Affero General Public License. | ||
| "Copyright" also means copyright-like laws that apply to other kinds of | ||
| works, such as semiconductor masks. | ||
| "The Program" refers to any copyrightable work licensed under this | ||
| License. Each licensee is addressed as "you". "Licensees" and | ||
| "recipients" may be individuals or organizations. | ||
| To "modify" a work means to copy from or adapt all or part of the work | ||
| in a fashion requiring copyright permission, other than the making of an | ||
| exact copy. The resulting work is called a "modified version" of the | ||
| earlier work or a work "based on" the earlier work. | ||
| A "covered work" means either the unmodified Program or a work based | ||
| on the Program. | ||
| To "propagate" a work means to do anything with it that, without | ||
| permission, would make you directly or secondarily liable for | ||
| infringement under applicable copyright law, except executing it on a | ||
| computer or modifying a private copy. Propagation includes copying, | ||
| distribution (with or without modification), making available to the | ||
| public, and in some countries other activities as well. | ||
| To "convey" a work means any kind of propagation that enables other | ||
| parties to make or receive copies. Mere interaction with a user through | ||
| a computer network, with no transfer of a copy, is not conveying. | ||
| An interactive user interface displays "Appropriate Legal Notices" | ||
| to the extent that it includes a convenient and prominently visible | ||
| feature that (1) displays an appropriate copyright notice, and (2) | ||
| tells the user that there is no warranty for the work (except to the | ||
| extent that warranties are provided), that licensees may convey the | ||
| work under this License, and how to view a copy of this License. If | ||
| the interface presents a list of user commands or options, such as a | ||
| menu, a prominent item in the list meets this criterion. | ||
| 1. Source Code. | ||
| The "source code" for a work means the preferred form of the work | ||
| for making modifications to it. "Object code" means any non-source | ||
| form of a work. | ||
| A "Standard Interface" means an interface that either is an official | ||
| standard defined by a recognized standards body, or, in the case of | ||
| interfaces specified for a particular programming language, one that | ||
| is widely used among developers working in that language. | ||
| The "System Libraries" of an executable work include anything, other | ||
| than the work as a whole, that (a) is included in the normal form of | ||
| packaging a Major Component, but which is not part of that Major | ||
| Component, and (b) serves only to enable use of the work with that | ||
| Major Component, or to implement a Standard Interface for which an | ||
| implementation is available to the public in source code form. A | ||
| "Major Component", in this context, means a major essential component | ||
| (kernel, window system, and so on) of the specific operating system | ||
| (if any) on which the executable work runs, or a compiler used to | ||
| produce the work, or an object code interpreter used to run it. | ||
| The "Corresponding Source" for a work in object code form means all | ||
| the source code needed to generate, install, and (for an executable | ||
| work) run the object code and to modify the work, including scripts to | ||
| control those activities. However, it does not include the work's | ||
| System Libraries, or general-purpose tools or generally available free | ||
| programs which are used unmodified in performing those activities but | ||
| which are not part of the work. For example, Corresponding Source | ||
| includes interface definition files associated with source files for | ||
| the work, and the source code for shared libraries and dynamically | ||
| linked subprograms that the work is specifically designed to require, | ||
| such as by intimate data communication or control flow between those | ||
| subprograms and other parts of the work. | ||
| The Corresponding Source need not include anything that users | ||
| can regenerate automatically from other parts of the Corresponding | ||
| Source. | ||
| The Corresponding Source for a work in source code form is that | ||
| same work. | ||
| 2. Basic Permissions. | ||
| All rights granted under this License are granted for the term of | ||
| copyright on the Program, and are irrevocable provided the stated | ||
| conditions are met. This License explicitly affirms your unlimited | ||
| permission to run the unmodified Program. The output from running a | ||
| covered work is covered by this License only if the output, given its | ||
| content, constitutes a covered work. This License acknowledges your | ||
| rights of fair use or other equivalent, as provided by copyright law. | ||
| You may make, run and propagate covered works that you do not | ||
| convey, without conditions so long as your license otherwise remains | ||
| in force. You may convey covered works to others for the sole purpose | ||
| of having them make modifications exclusively for you, or provide you | ||
| with facilities for running those works, provided that you comply with | ||
| the terms of this License in conveying all material for which you do | ||
| not control copyright. Those thus making or running the covered works | ||
| for you must do so exclusively on your behalf, under your direction | ||
| and control, on terms that prohibit them from making any copies of | ||
| your copyrighted material outside their relationship with you. | ||
| Conveying under any other circumstances is permitted solely under | ||
| the conditions stated below. Sublicensing is not allowed; section 10 | ||
| makes it unnecessary. | ||
| 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | ||
| No covered work shall be deemed part of an effective technological | ||
| measure under any applicable law fulfilling obligations under article | ||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or | ||
| similar laws prohibiting or restricting circumvention of such | ||
| measures. | ||
| When you convey a covered work, you waive any legal power to forbid | ||
| circumvention of technological measures to the extent such circumvention | ||
| is effected by exercising rights under this License with respect to | ||
| the covered work, and you disclaim any intention to limit operation or | ||
| modification of the work as a means of enforcing, against the work's | ||
| users, your or third parties' legal rights to forbid circumvention of | ||
| technological measures. | ||
| 4. Conveying Verbatim Copies. | ||
| You may convey verbatim copies of the Program's source code as you | ||
| receive it, in any medium, provided that you conspicuously and | ||
| appropriately publish on each copy an appropriate copyright notice; | ||
| keep intact all notices stating that this License and any | ||
| non-permissive terms added in accord with section 7 apply to the code; | ||
| keep intact all notices of the absence of any warranty; and give all | ||
| recipients a copy of this License along with the Program. | ||
| You may charge any price or no price for each copy that you convey, | ||
| and you may offer support or warranty protection for a fee. | ||
| 5. Conveying Modified Source Versions. | ||
| You may convey a work based on the Program, or the modifications to | ||
| produce it from the Program, in the form of source code under the | ||
| terms of section 4, provided that you also meet all of these conditions: | ||
| a) The work must carry prominent notices stating that you modified | ||
| it, and giving a relevant date. | ||
| b) The work must carry prominent notices stating that it is | ||
| released under this License and any conditions added under section | ||
| 7. This requirement modifies the requirement in section 4 to | ||
| "keep intact all notices". | ||
| c) You must license the entire work, as a whole, under this | ||
| License to anyone who comes into possession of a copy. This | ||
| License will therefore apply, along with any applicable section 7 | ||
| additional terms, to the whole of the work, and all its parts, | ||
| regardless of how they are packaged. This License gives no | ||
| permission to license the work in any other way, but it does not | ||
| invalidate such permission if you have separately received it. | ||
| d) If the work has interactive user interfaces, each must display | ||
| Appropriate Legal Notices; however, if the Program has interactive | ||
| interfaces that do not display Appropriate Legal Notices, your | ||
| work need not make them do so. | ||
| A compilation of a covered work with other separate and independent | ||
| works, which are not by their nature extensions of the covered work, | ||
| and which are not combined with it such as to form a larger program, | ||
| in or on a volume of a storage or distribution medium, is called an | ||
| "aggregate" if the compilation and its resulting copyright are not | ||
| used to limit the access or legal rights of the compilation's users | ||
| beyond what the individual works permit. Inclusion of a covered work | ||
| in an aggregate does not cause this License to apply to the other | ||
| parts of the aggregate. | ||
| 6. Conveying Non-Source Forms. | ||
| You may convey a covered work in object code form under the terms | ||
| of sections 4 and 5, provided that you also convey the | ||
| machine-readable Corresponding Source under the terms of this License, | ||
| in one of these ways: | ||
| a) Convey the object code in, or embodied in, a physical product | ||
| (including a physical distribution medium), accompanied by the | ||
| Corresponding Source fixed on a durable physical medium | ||
| customarily used for software interchange. | ||
| b) Convey the object code in, or embodied in, a physical product | ||
| (including a physical distribution medium), accompanied by a | ||
| written offer, valid for at least three years and valid for as | ||
| long as you offer spare parts or customer support for that product | ||
| model, to give anyone who possesses the object code either (1) a | ||
| copy of the Corresponding Source for all the software in the | ||
| product that is covered by this License, on a durable physical | ||
| medium customarily used for software interchange, for a price no | ||
| more than your reasonable cost of physically performing this | ||
| conveying of source, or (2) access to copy the | ||
| Corresponding Source from a network server at no charge. | ||
| c) Convey individual copies of the object code with a copy of the | ||
| written offer to provide the Corresponding Source. This | ||
| alternative is allowed only occasionally and noncommercially, and | ||
| only if you received the object code with such an offer, in accord | ||
| with subsection 6b. | ||
| d) Convey the object code by offering access from a designated | ||
| place (gratis or for a charge), and offer equivalent access to the | ||
| Corresponding Source in the same way through the same place at no | ||
| further charge. You need not require recipients to copy the | ||
| Corresponding Source along with the object code. If the place to | ||
| copy the object code is a network server, the Corresponding Source | ||
| may be on a different server (operated by you or a third party) | ||
| that supports equivalent copying facilities, provided you maintain | ||
| clear directions next to the object code saying where to find the | ||
| Corresponding Source. Regardless of what server hosts the | ||
| Corresponding Source, you remain obligated to ensure that it is | ||
| available for as long as needed to satisfy these requirements. | ||
| e) Convey the object code using peer-to-peer transmission, provided | ||
| you inform other peers where the object code and Corresponding | ||
| Source of the work are being offered to the general public at no | ||
| charge under subsection 6d. | ||
| A separable portion of the object code, whose source code is excluded | ||
| from the Corresponding Source as a System Library, need not be | ||
| included in conveying the object code work. | ||
| A "User Product" is either (1) a "consumer product", which means any | ||
| tangible personal property which is normally used for personal, family, | ||
| or household purposes, or (2) anything designed or sold for incorporation | ||
| into a dwelling. In determining whether a product is a consumer product, | ||
| doubtful cases shall be resolved in favor of coverage. For a particular | ||
| product received by a particular user, "normally used" refers to a | ||
| typical or common use of that class of product, regardless of the status | ||
| of the particular user or of the way in which the particular user | ||
| actually uses, or expects or is expected to use, the product. A product | ||
| is a consumer product regardless of whether the product has substantial | ||
| commercial, industrial or non-consumer uses, unless such uses represent | ||
| the only significant mode of use of the product. | ||
| "Installation Information" for a User Product means any methods, | ||
| procedures, authorization keys, or other information required to install | ||
| and execute modified versions of a covered work in that User Product from | ||
| a modified version of its Corresponding Source. The information must | ||
| suffice to ensure that the continued functioning of the modified object | ||
| code is in no case prevented or interfered with solely because | ||
| modification has been made. | ||
| If you convey an object code work under this section in, or with, or | ||
| specifically for use in, a User Product, and the conveying occurs as | ||
| part of a transaction in which the right of possession and use of the | ||
| User Product is transferred to the recipient in perpetuity or for a | ||
| fixed term (regardless of how the transaction is characterized), the | ||
| Corresponding Source conveyed under this section must be accompanied | ||
| by the Installation Information. But this requirement does not apply | ||
| if neither you nor any third party retains the ability to install | ||
| modified object code on the User Product (for example, the work has | ||
| been installed in ROM). | ||
| The requirement to provide Installation Information does not include a | ||
| requirement to continue to provide support service, warranty, or updates | ||
| for a work that has been modified or installed by the recipient, or for | ||
| the User Product in which it has been modified or installed. Access to a | ||
| network may be denied when the modification itself materially and | ||
| adversely affects the operation of the network or violates the rules and | ||
| protocols for communication across the network. | ||
| Corresponding Source conveyed, and Installation Information provided, | ||
| in accord with this section must be in a format that is publicly | ||
| documented (and with an implementation available to the public in | ||
| source code form), and must require no special password or key for | ||
| unpacking, reading or copying. | ||
| 7. Additional Terms. | ||
| "Additional permissions" are terms that supplement the terms of this | ||
| License by making exceptions from one or more of its conditions. | ||
| Additional permissions that are applicable to the entire Program shall | ||
| be treated as though they were included in this License, to the extent | ||
| that they are valid under applicable law. If additional permissions | ||
| apply only to part of the Program, that part may be used separately | ||
| under those permissions, but the entire Program remains governed by | ||
| this License without regard to the additional permissions. | ||
| When you convey a copy of a covered work, you may at your option | ||
| remove any additional permissions from that copy, or from any part of | ||
| it. (Additional permissions may be written to require their own | ||
| removal in certain cases when you modify the work.) You may place | ||
| additional permissions on material, added by you to a covered work, | ||
| for which you have or can give appropriate copyright permission. | ||
| Notwithstanding any other provision of this License, for material you | ||
| add to a covered work, you may (if authorized by the copyright holders of | ||
| that material) supplement the terms of this License with terms: | ||
| a) Disclaiming warranty or limiting liability differently from the | ||
| terms of sections 15 and 16 of this License; or | ||
| b) Requiring preservation of specified reasonable legal notices or | ||
| author attributions in that material or in the Appropriate Legal | ||
| Notices displayed by works containing it; or | ||
| c) Prohibiting misrepresentation of the origin of that material, or | ||
| requiring that modified versions of such material be marked in | ||
| reasonable ways as different from the original version; or | ||
| d) Limiting the use for publicity purposes of names of licensors or | ||
| authors of the material; or | ||
| e) Declining to grant rights under trademark law for use of some | ||
| trade names, trademarks, or service marks; or | ||
| f) Requiring indemnification of licensors and authors of that | ||
| material by anyone who conveys the material (or modified versions of | ||
| it) with contractual assumptions of liability to the recipient, for | ||
| any liability that these contractual assumptions directly impose on | ||
| those licensors and authors. | ||
| All other non-permissive additional terms are considered "further | ||
| restrictions" within the meaning of section 10. If the Program as you | ||
| received it, or any part of it, contains a notice stating that it is | ||
| governed by this License along with a term that is a further | ||
| restriction, you may remove that term. If a license document contains | ||
| a further restriction but permits relicensing or conveying under this | ||
| License, you may add to a covered work material governed by the terms | ||
| of that license document, provided that the further restriction does | ||
| not survive such relicensing or conveying. | ||
| If you add terms to a covered work in accord with this section, you | ||
| must place, in the relevant source files, a statement of the | ||
| additional terms that apply to those files, or a notice indicating | ||
| where to find the applicable terms. | ||
| Additional terms, permissive or non-permissive, may be stated in the | ||
| form of a separately written license, or stated as exceptions; | ||
| the above requirements apply either way. | ||
| 8. Termination. | ||
| You may not propagate or modify a covered work except as expressly | ||
| provided under this License. Any attempt otherwise to propagate or | ||
| modify it is void, and will automatically terminate your rights under | ||
| this License (including any patent licenses granted under the third | ||
| paragraph of section 11). | ||
| However, if you cease all violation of this License, then your | ||
| license from a particular copyright holder is reinstated (a) | ||
| provisionally, unless and until the copyright holder explicitly and | ||
| finally terminates your license, and (b) permanently, if the copyright | ||
| holder fails to notify you of the violation by some reasonable means | ||
| prior to 60 days after the cessation. | ||
| Moreover, your license from a particular copyright holder is | ||
| reinstated permanently if the copyright holder notifies you of the | ||
| violation by some reasonable means, this is the first time you have | ||
| received notice of violation of this License (for any work) from that | ||
| copyright holder, and you cure the violation prior to 30 days after | ||
| your receipt of the notice. | ||
| Termination of your rights under this section does not terminate the | ||
| licenses of parties who have received copies or rights from you under | ||
| this License. If your rights have been terminated and not permanently | ||
| reinstated, you do not qualify to receive new licenses for the same | ||
| material under section 10. | ||
| 9. Acceptance Not Required for Having Copies. | ||
| You are not required to accept this License in order to receive or | ||
| run a copy of the Program. Ancillary propagation of a covered work | ||
| occurring solely as a consequence of using peer-to-peer transmission | ||
| to receive a copy likewise does not require acceptance. However, | ||
| nothing other than this License grants you permission to propagate or | ||
| modify any covered work. These actions infringe copyright if you do | ||
| not accept this License. Therefore, by modifying or propagating a | ||
| covered work, you indicate your acceptance of this License to do so. | ||
| 10. Automatic Licensing of Downstream Recipients. | ||
| Each time you convey a covered work, the recipient automatically | ||
| receives a license from the original licensors, to run, modify and | ||
| propagate that work, subject to this License. You are not responsible | ||
| for enforcing compliance by third parties with this License. | ||
| An "entity transaction" is a transaction transferring control of an | ||
| organization, or substantially all assets of one, or subdividing an | ||
| organization, or merging organizations. If propagation of a covered | ||
| work results from an entity transaction, each party to that | ||
| transaction who receives a copy of the work also receives whatever | ||
| licenses to the work the party's predecessor in interest had or could | ||
| give under the previous paragraph, plus a right to possession of the | ||
| Corresponding Source of the work from the predecessor in interest, if | ||
| the predecessor has it or can get it with reasonable efforts. | ||
| You may not impose any further restrictions on the exercise of the | ||
| rights granted or affirmed under this License. For example, you may | ||
| not impose a license fee, royalty, or other charge for exercise of | ||
| rights granted under this License, and you may not initiate litigation | ||
| (including a cross-claim or counterclaim in a lawsuit) alleging that | ||
| any patent claim is infringed by making, using, selling, offering for | ||
| sale, or importing the Program or any portion of it. | ||
| 11. Patents. | ||
| A "contributor" is a copyright holder who authorizes use under this | ||
| License of the Program or a work on which the Program is based. The | ||
| work thus licensed is called the contributor's "contributor version". | ||
| A contributor's "essential patent claims" are all patent claims | ||
| owned or controlled by the contributor, whether already acquired or | ||
| hereafter acquired, that would be infringed by some manner, permitted | ||
| by this License, of making, using, or selling its contributor version, | ||
| but do not include claims that would be infringed only as a | ||
| consequence of further modification of the contributor version. For | ||
| purposes of this definition, "control" includes the right to grant | ||
| patent sublicenses in a manner consistent with the requirements of | ||
| this License. | ||
| Each contributor grants you a non-exclusive, worldwide, royalty-free | ||
| patent license under the contributor's essential patent claims, to | ||
| make, use, sell, offer for sale, import and otherwise run, modify and | ||
| propagate the contents of its contributor version. | ||
| In the following three paragraphs, a "patent license" is any express | ||
| agreement or commitment, however denominated, not to enforce a patent | ||
| (such as an express permission to practice a patent or covenant not to | ||
| sue for patent infringement). To "grant" such a patent license to a | ||
| party means to make such an agreement or commitment not to enforce a | ||
| patent against the party. | ||
| If you convey a covered work, knowingly relying on a patent license, | ||
| and the Corresponding Source of the work is not available for anyone | ||
| to copy, free of charge and under the terms of this License, through a | ||
| publicly available network server or other readily accessible means, | ||
| then you must either (1) cause the Corresponding Source to be so | ||
| available, or (2) arrange to deprive yourself of the benefit of the | ||
| patent license for this particular work, or (3) arrange, in a manner | ||
| consistent with the requirements of this License, to extend the patent | ||
| license to downstream recipients. "Knowingly relying" means you have | ||
| actual knowledge that, but for the patent license, your conveying the | ||
| covered work in a country, or your recipient's use of the covered work | ||
| in a country, would infringe one or more identifiable patents in that | ||
| country that you have reason to believe are valid. | ||
| If, pursuant to or in connection with a single transaction or | ||
| arrangement, you convey, or propagate by procuring conveyance of, a | ||
| covered work, and grant a patent license to some of the parties | ||
| receiving the covered work authorizing them to use, propagate, modify | ||
| or convey a specific copy of the covered work, then the patent license | ||
| you grant is automatically extended to all recipients of the covered | ||
| work and works based on it. | ||
| A patent license is "discriminatory" if it does not include within | ||
| the scope of its coverage, prohibits the exercise of, or is | ||
| conditioned on the non-exercise of one or more of the rights that are | ||
| specifically granted under this License. You may not convey a covered | ||
| work if you are a party to an arrangement with a third party that is | ||
| in the business of distributing software, under which you make payment | ||
| to the third party based on the extent of your activity of conveying | ||
| the work, and under which the third party grants, to any of the | ||
| parties who would receive the covered work from you, a discriminatory | ||
| patent license (a) in connection with copies of the covered work | ||
| conveyed by you (or copies made from those copies), or (b) primarily | ||
| for and in connection with specific products or compilations that | ||
| contain the covered work, unless you entered into that arrangement, | ||
| or that patent license was granted, prior to 28 March 2007. | ||
| Nothing in this License shall be construed as excluding or limiting | ||
| any implied license or other defenses to infringement that may | ||
| otherwise be available to you under applicable patent law. | ||
| 12. No Surrender of Others' Freedom. | ||
| If conditions are imposed on you (whether by court order, agreement or | ||
| otherwise) that contradict the conditions of this License, they do not | ||
| excuse you from the conditions of this License. If you cannot convey a | ||
| covered work so as to satisfy simultaneously your obligations under this | ||
| License and any other pertinent obligations, then as a consequence you may | ||
| not convey it at all. For example, if you agree to terms that obligate you | ||
| to collect a royalty for further conveying from those to whom you convey | ||
| the Program, the only way you could satisfy both those terms and this | ||
| License would be to refrain entirely from conveying the Program. | ||
| 13. Remote Network Interaction; Use with the GNU General Public License. | ||
| Notwithstanding any other provision of this License, if you modify the | ||
| Program, your modified version must prominently offer all users | ||
| interacting with it remotely through a computer network (if your version | ||
| supports such interaction) an opportunity to receive the Corresponding | ||
| Source of your version by providing access to the Corresponding Source | ||
| from a network server at no charge, through some standard or customary | ||
| means of facilitating copying of software. This Corresponding Source | ||
| shall include the Corresponding Source for any work covered by version 3 | ||
| of the GNU General Public License that is incorporated pursuant to the | ||
| following paragraph. | ||
| Notwithstanding any other provision of this License, you have | ||
| permission to link or combine any covered work with a work licensed | ||
| under version 3 of the GNU General Public License into a single | ||
| combined work, and to convey the resulting work. The terms of this | ||
| License will continue to apply to the part which is the covered work, | ||
| but the work with which it is combined will remain governed by version | ||
| 3 of the GNU General Public License. | ||
| 14. Revised Versions of this License. | ||
| The Free Software Foundation may publish revised and/or new versions of | ||
| the GNU Affero General Public License from time to time. Such new versions | ||
| will be similar in spirit to the present version, but may differ in detail to | ||
| address new problems or concerns. | ||
| Each version is given a distinguishing version number. If the | ||
| Program specifies that a certain numbered version of the GNU Affero General | ||
| Public License "or any later version" applies to it, you have the | ||
| option of following the terms and conditions either of that numbered | ||
| version or of any later version published by the Free Software | ||
| Foundation. If the Program does not specify a version number of the | ||
| GNU Affero General Public License, you may choose any version ever published | ||
| by the Free Software Foundation. | ||
| If the Program specifies that a proxy can decide which future | ||
| versions of the GNU Affero General Public License can be used, that proxy's | ||
| public statement of acceptance of a version permanently authorizes you | ||
| to choose that version for the Program. | ||
| Later license versions may give you additional or different | ||
| permissions. However, no additional obligations are imposed on any | ||
| author or copyright holder as a result of your choosing to follow a | ||
| later version. | ||
| 15. Disclaimer of Warranty. | ||
| THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | ||
| APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | ||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | ||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | ||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||
| PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | ||
| IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | ||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||
| 16. Limitation of Liability. | ||
| IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | ||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | ||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | ||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | ||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | ||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | ||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | ||
| SUCH DAMAGES. | ||
| 17. Interpretation of Sections 15 and 16. | ||
| If the disclaimer of warranty and limitation of liability provided | ||
| above cannot be given local legal effect according to their terms, | ||
| reviewing courts shall apply local law that most closely approximates | ||
| an absolute waiver of all civil liability in connection with the | ||
| Program, unless a warranty or assumption of liability accompanies a | ||
| copy of the Program in return for a fee. | ||
| END OF TERMS AND CONDITIONS | ||
| How to Apply These Terms to Your New Programs | ||
| If you develop a new program, and you want it to be of the greatest | ||
| possible use to the public, the best way to achieve this is to make it | ||
| free software which everyone can redistribute and change under these terms. | ||
| To do so, attach the following notices to the program. It is safest | ||
| to attach them to the start of each source file to most effectively | ||
| state the exclusion of warranty; and each file should have at least | ||
| the "copyright" line and a pointer to where the full notice is found. | ||
| <one line to give the program's name and a brief idea of what it does.> | ||
| Copyright (C) <year> <name of author> | ||
| This program is free software: you can redistribute it and/or modify | ||
| it under the terms of the GNU Affero General Public License as published | ||
| by the Free Software Foundation, either version 3 of the License, or | ||
| (at your option) any later version. | ||
| This program is distributed in the hope that it will be useful, | ||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| GNU Affero General Public License for more details. | ||
| You should have received a copy of the GNU Affero General Public License | ||
| along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
| Also add information on how to contact you by electronic and paper mail. | ||
| If your software can interact with users remotely through a computer | ||
| network, you should also make sure that it provides a way for users to | ||
| get its source. For example, if your program is a web application, its | ||
| interface could display a "Source" link that leads users to an archive | ||
| of the code. There are many ways you could offer source, and different | ||
| solutions will be better for different programs; see section 13 for the | ||
| specific requirements. | ||
| You should also get your employer (if you work as a programmer) or school, | ||
| if any, to sign a "copyright disclaimer" for the program, if necessary. | ||
| For more information on this, and how to apply and follow the GNU AGPL, see | ||
| <https://www.gnu.org/licenses/>. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+2
-2
| { | ||
| "name": "river-data-widget", | ||
| "version": "1.0.1", | ||
| "version": "1.1.0", | ||
| "description": "A web widget to display river flow and other data.", | ||
@@ -27,3 +27,3 @@ "type": "module", | ||
| "author": "pbuk (https://github.com/pb-uk)", | ||
| "license": "AGPL-3.0-or-later", | ||
| "license": "MIT", | ||
| "bugs": { | ||
@@ -30,0 +30,0 @@ "url": "https://github.com/pb-uk/river-data-widget/issues" |
-16
| # RiverDataWidget | ||
| Copyright (C) 2022 PBUK https://github.com/pb-uk. | ||
| This program is free software: you can redistribute it and/or modify | ||
| it under the terms of the GNU Affero General Public License as published | ||
| by the Free Software Foundation, either version 3 of the License, or | ||
| (at your option) any later version. | ||
| This program is distributed in the hope that it will be useful, | ||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| GNU Affero General Public License for more details. | ||
| You should have received a copy of the GNU Affero General Public License | ||
| along with this program. If not, see <https://www.gnu.org/licenses/>. |
Copyleft License
LicenseCopyleft license information was found.
Non-permissive License
LicenseA license not known to be considered permissive was found.
0
-100%100
42.86%637
3.75%128350
-18.9%7
-12.5%