From 68ddd14913214f5def24c1307e365e503f4f0901 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Tue, 25 Feb 2025 15:16:31 +0100 Subject: [PATCH 01/20] very very wip --- ui/component/or-chart/package.json | 1 + ui/component/or-chart/src/index.ts | 276 ++++++++++++++++++++--------- 2 files changed, 191 insertions(+), 86 deletions(-) diff --git a/ui/component/or-chart/package.json b/ui/component/or-chart/package.json index 97c2cb34a6..e8a65f17b4 100644 --- a/ui/component/or-chart/package.json +++ b/ui/component/or-chart/package.json @@ -28,6 +28,7 @@ "chart.js": "^3.6.0", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-annotation": "^1.1.0", + "echarts": "^5.6.0", "jsonpath-plus": "^6.0.1", "lit": "^2.0.2", "moment": "^2.29.4" diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index a7c19bd5e6..4f6f9712c0 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -20,6 +20,8 @@ import "@openremote/or-asset-tree"; import "@openremote/or-mwc-components/or-mwc-input"; import "@openremote/or-components/or-panel"; import "@openremote/or-translate"; +import {ECharts, EChartsOption, init} from "echarts"; +import _ from "lodash"; import { Chart, ChartConfiguration, @@ -55,7 +57,7 @@ import {ListItem} from "@openremote/or-mwc-components/or-mwc-list"; import { when } from "lit/directives/when.js"; import {createRef, Ref, ref } from "lit/directives/ref.js"; -Chart.register(LineController, ScatterController, LineElement, PointElement, LinearScale, TimeScale, Title, Filler, Legend, Tooltip, ChartAnnotation); +//Chart.register(LineController, ScatterController, LineElement, PointElement, LinearScale, TimeScale, Title, Filler, Legend, Tooltip, ChartAnnotation); export class OrChartEvent extends CustomEvent { @@ -421,15 +423,15 @@ export class OrChart extends translate(i18next)(LitElement) { protected _loading: boolean = false; @property() - protected _data?: ChartDataset<"line", ScatterDataPoint[]>[] = undefined; + protected _data?: ValueDatapoint[]; @property() protected _tableTemplate?: TemplateResult; @query("#chart") - protected _chartElem!: HTMLCanvasElement; - - protected _chart?: Chart; + protected _chartElem!: HTMLDivElement; + protected _chartOptions: EChartsOption = {}; + protected _chart?: ECharts; protected _style!: CSSStyleDeclaration; protected _startOfPeriod?: number; protected _endOfPeriod?: number; @@ -437,6 +439,16 @@ export class OrChart extends translate(i18next)(LitElement) { protected _stepSize?: number; protected _latestError?: string; protected _dataAbortController?: AbortController; + protected _seriesTemplate = { + name: 'label', + type: 'line', + showSymbol: false, + data: {}, + sampling: 'lttb', + itemStyle: { + color: this._style.getPropertyValue("--internal-or-attribute-history-graph-line-color") + } + } constructor() { super(); @@ -473,7 +485,7 @@ export class OrChart extends translate(i18next)(LitElement) { if (reloadData) { this._data = undefined; if (this._chart) { - this._chart.destroy(); + this._chart.dispose(); this._chart = undefined; } this._loadData(); @@ -486,98 +498,190 @@ export class OrChart extends translate(i18next)(LitElement) { const now = moment().toDate().getTime(); if (!this._chart) { - const options = { - type: "line", - data: { - datasets: this._data + + const data = + + + this._chartOptions = { + animation: false, + grid: { + show: true, + backgroundColor: this._style.getPropertyValue("--internal-or-asset-viewer-panel-color"), + borderColor: this._style.getPropertyValue("--internal-or-attribute-history-text-color") }, - options: { - responsive: true, - maintainAspectRatio: false, - onResize: throttle(() => { this.dispatchEvent(new OrChartEvent("resize")); this.applyChartResponsiveness(); }, 200), - showLines: true, - plugins: { - legend: { - display: false - }, - tooltip: { - mode: "x", - intersect: false, - xPadding: 10, - yPadding: 10, - titleMarginBottom: 10, - callbacks: { - label: (tooltipItem: any) => tooltipItem.dataset.label + ': ' + tooltipItem.formattedValue + tooltipItem.dataset.unit, - } - }, - annotation: { - annotations: [ - { - type: "line", - xMin: now, - xMax: now, - borderColor: "#275582", - borderWidth: 2 - } - ] + backgroundColor: this._style.getPropertyValue("--internal-or-asset-viewer-panel-color"), + tooltip: { + trigger: 'axis', + axisPointer: { type: 'cross'}, + //formatter: (params: any) => { + // if (Array.isArray(params) && params.length > 0) { + // const yValue = params[0].value[1]; + // return yValue !== undefined ? yValue.toString() : ''; + // } + // return '' + //} + }, + toolbox: { + right: '9%', + top: '5%', + feature: { + dataView: {readOnly: true}, + magicType: { + type: ['line', 'bar'] }, + saveAsImage: {} + } + }, + xAxis: { + type: 'time', + axisLine: { onZero: false, lineStyle: {color: this._style.getPropertyValue("--internal-or-attribute-history-text-color")}}, + splitLine: {show:true}, + min: this._startOfPeriod, + max: this._endOfPeriod, + axisLabel: { + showMinLabel: true, + showMaxLabel: true, + hideOverlap: true, + } + }, + yAxis: + { + type: 'value', + axisLine: { lineStyle: {color: this._style.getPropertyValue("--internal-or-attribute-history-text-color")}}, + boundaryGap: ['10%', '10%'], + scale: true }, - hover: { - mode: 'x', - intersect: false + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100 }, - scales: { - y: { - ticks: { - beginAtZero: true - }, - grid: { - color: "#cccccc" + { + start: 0, + end: 100, + backgroundColor: bgColor, + fillerColor: bgColor, + dataBackground: { + areaStyle: { + color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color") } }, - y1: { - display: this.rightAxisAttributes.length > 0, - position: 'right', - ticks: { - beginAtZero: true - }, - grid: { - drawOnChartArea: false + selectedDataBackground: { + areaStyle: { + color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color"), } }, - x: { - type: "time", - min: this._startOfPeriod, - max: this._endOfPeriod, - time: { - tooltipFormat: 'MMM D, YYYY, HH:mm:ss', - displayFormats: { - millisecond: 'HH:mm:ss.SSS', - second: 'HH:mm:ss', - minute: "HH:mm", - hour: (this._endOfPeriod && this._startOfPeriod && this._endOfPeriod - this._startOfPeriod > 86400000) ? "MMM DD, HH:mm" : "HH:mm", - day: "MMM DD", - week: "w" - }, - unit: this._timeUnits, - stepSize: this._stepSize - }, - ticks: { - autoSkip: true, - color: "#000", - font: { - family: "'Open Sans', Helvetica, Arial, Lucida, sans-serif", - size: 9, - style: "normal" - } + moveHandleStyle: { + color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color") + }, + emphasis: { + moveHandleStyle: { + color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color") }, - gridLines: { - color: "#cccccc" + handleLabel: { + show: true } + }, + handleLabel: { + show: false } } - } - } as ChartConfiguration<"line", ScatterDataPoint[]>; + ], + series: [data] + } + //const options = { + // type: "line", + // data: { + // datasets: this._data + // }, + // options: { + // responsive: true, + // maintainAspectRatio: false, + // onResize: throttle(() => { this.dispatchEvent(new OrChartEvent("resize")); this.applyChartResponsiveness(); }, 200), + // showLines: true, + // plugins: { + // legend: { + // display: false + // }, + // tooltip: { + // mode: "x", + // intersect: false, + // xPadding: 10, + // yPadding: 10, + // titleMarginBottom: 10, + // callbacks: { + // label: (tooltipItem: any) => tooltipItem.dataset.label + ': ' + tooltipItem.formattedValue + tooltipItem.dataset.unit, + // } + // }, + // annotation: { + // annotations: [ + // { + // type: "line", + // xMin: now, + // xMax: now, + // borderColor: "#275582", + // borderWidth: 2 + // } + // ] + // }, + // }, + // hover: { + // mode: 'x', + // intersect: false + // }, + // scales: { + // y: { + // ticks: { + // beginAtZero: true + // }, + // grid: { + // color: "#cccccc" + // } + // }, + // y1: { + // display: this.rightAxisAttributes.length > 0, + // position: 'right', + // ticks: { + // beginAtZero: true + // }, + // grid: { + // drawOnChartArea: false + // } + // }, + // x: { + // type: "time", + // min: this._startOfPeriod, + // max: this._endOfPeriod, + // time: { + // tooltipFormat: 'MMM D, YYYY, HH:mm:ss', + // displayFormats: { + // millisecond: 'HH:mm:ss.SSS', + // second: 'HH:mm:ss', + // minute: "HH:mm", + // hour: (this._endOfPeriod && this._startOfPeriod && this._endOfPeriod - this._startOfPeriod > 86400000) ? "MMM DD, HH:mm" : "HH:mm", + // day: "MMM DD", + // week: "w" + // }, + // unit: this._timeUnits, + // stepSize: this._stepSize + // }, + // ticks: { + // autoSkip: true, + // color: "#000", + // font: { + // family: "'Open Sans', Helvetica, Arial, Lucida, sans-serif", + // size: 9, + // style: "normal" + // } + // }, + // gridLines: { + // color: "#cccccc" + // } + // } + // } + // } + //} as ChartConfiguration<"line", ScatterDataPoint[]>; const mergedOptions = Util.mergeObjects(options, this.chartOptions, false); From 26ce6fee766f32525d5e9509ee01a5f4be0ab438 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Tue, 25 Feb 2025 17:49:52 +0100 Subject: [PATCH 02/20] very wip but compiling --- ui/component/or-chart/src/index.ts | 169 +++++++++++++++++------------ yarn.lock | 27 +++++ 2 files changed, 128 insertions(+), 68 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 4f6f9712c0..f2d4cbe8a9 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -378,7 +378,7 @@ export class OrChart extends translate(i18next)(LitElement) { public rightAxisAttributes: AttributeRef[] = []; @property() - public dataProvider?: (startOfPeriod: number, endOfPeriod: number, timeUnits: TimeUnit, stepSize: number) => Promise[]> + public dataProvider?: (startOfPeriod: number, endOfPeriod: number, timeUnits: TimeUnit, stepSize: number) => Promise<[]> @property({type: Array}) public colors: string[] = ["#3869B1", "#DA7E30", "#3F9852", "#CC2428", "#6B4C9A", "#922427", "#958C3D", "#535055"]; @@ -439,16 +439,6 @@ export class OrChart extends translate(i18next)(LitElement) { protected _stepSize?: number; protected _latestError?: string; protected _dataAbortController?: AbortController; - protected _seriesTemplate = { - name: 'label', - type: 'line', - showSymbol: false, - data: {}, - sampling: 'lttb', - itemStyle: { - color: this._style.getPropertyValue("--internal-or-attribute-history-graph-line-color") - } - } constructor() { super(); @@ -499,9 +489,6 @@ export class OrChart extends translate(i18next)(LitElement) { if (!this._chart) { - const data = - - this._chartOptions = { animation: false, grid: { @@ -560,8 +547,8 @@ export class OrChart extends translate(i18next)(LitElement) { { start: 0, end: 100, - backgroundColor: bgColor, - fillerColor: bgColor, + //backgroundColor: bgColor, + //fillerColor: bgColor, dataBackground: { areaStyle: { color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color") @@ -588,8 +575,9 @@ export class OrChart extends translate(i18next)(LitElement) { } } ], - series: [data] + series: [], } + console.log(this._chartOptions); //const options = { // type: "line", // data: { @@ -683,19 +671,24 @@ export class OrChart extends translate(i18next)(LitElement) { // } //} as ChartConfiguration<"line", ScatterDataPoint[]>; - const mergedOptions = Util.mergeObjects(options, this.chartOptions, false); + //WAT DOET DIT ? + //const mergedOptions = Util.mergeObjects(options, this.chartOptions, false); + + // Initialize echarts instance + this._chart = init(this._chartElem); + // Set chart options to default + this._chart.setOption(this._chartOptions); + // Make chart size responsive + window.addEventListener("resize", () => this._chart!.resize()); + const resizeObserver = new ResizeObserver(() => this._chart!.resize()); + resizeObserver.observe(this._chartElem); + // Add event listener for zooming + //this._chart!.on('datazoom', _.debounce((params: any) => { this._onZoomChange(params); }, 1500)); - this._chart = new Chart<"line", ScatterDataPoint[]>(this._chartElem.getContext("2d")!, mergedOptions as ChartConfiguration<"line", ScatterDataPoint[]>); } else { if (changedProperties.has("_data")) { - this._chart.options.scales!.x!.min = this._startOfPeriod; - this._chart.options!.scales!.x!.max = this._endOfPeriod; - (this._chart.options!.scales!.x! as TimeScaleOptions).time!.unit = this._timeUnits!; - (this._chart.options!.scales!.x! as TimeScaleOptions).time!.stepSize = this._stepSize!; - (this._chart.options!.plugins!.annotation!.annotations! as AnnotationOptions<"line">[])[0].xMin = now; - (this._chart.options!.plugins!.annotation!.annotations! as AnnotationOptions<"line">[])[0].xMax = now; - this._chart.data.datasets = this._data; - this._chart.update(); + //Update chart to data from set period + this._updateChartData(); } } this.onCompleted().then(() => { @@ -749,7 +742,7 @@ export class OrChart extends translate(i18next)(LitElement) { `)} - +
${(this.timestampControls || this.attributeControls || this.showLegend) ? html` @@ -815,7 +808,7 @@ export class OrChart extends translate(i18next)(LitElement) { const axisNote = (this.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; const bgColor = this.colors[colourIndex] || ""; return html` -
+
${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(this.assets[assetIndex]!.type!), undefined, undefined, bgColor.split('#')[1])}
@@ -855,32 +848,34 @@ export class OrChart extends translate(i18next)(LitElement) { } } - removeDatasetHighlight(bgColor:string) { - if(this._chart && this._chart.data && this._chart.data.datasets){ - this._chart.data.datasets.map((dataset, idx) => { - if (dataset.borderColor && typeof dataset.borderColor === "string" && dataset.borderColor.length === 9) { - dataset.borderColor = dataset.borderColor.slice(0, -2); - dataset.backgroundColor = dataset.borderColor; - } - }); - this._chart.update(); - } - } - - addDatasetHighlight(assetId?:string, attrName?:string) { - if (!assetId || !attrName) return; - - if(this._chart && this._chart.data && this._chart.data.datasets){ - this._chart.data.datasets.map((dataset, idx) => { - if ((dataset as any).assetId === assetId && (dataset as any).attrName === attrName) { - return - } - dataset.borderColor = dataset.borderColor + "36"; - dataset.backgroundColor = dataset.borderColor; - }); - this._chart.update(); - } - } + //DEFAULT ECHARTS + //removeDatasetHighlight(bgColor:string) { + // if(this._chart && this._chart.data && this._chart.data.datasets){ + // this._chart.data.datasets.map((dataset, idx) => { + // if (dataset.borderColor && typeof dataset.borderColor === "string" && dataset.borderColor.length === 9) { + // dataset.borderColor = dataset.borderColor.slice(0, -2); + // dataset.backgroundColor = dataset.borderColor; + // } + // }); + // this._chart.update(); + // } + //} + + //DEFAULT ECHARTS + //addDatasetHighlight(assetId?:string, attrName?:string) { + // if (!assetId || !attrName) return; + // + // if(this._chart && this._chart.data && this._chart.data.datasets){ + // this._chart.data.datasets.map((dataset, idx) => { + // if ((dataset as any).assetId === assetId && (dataset as any).attrName === attrName) { + // return + // } + // dataset.borderColor = dataset.borderColor + "36"; + // dataset.backgroundColor = dataset.borderColor; + // }); + // this._chart.update(); + // } + //} async loadSettings(reset: boolean) { @@ -1070,7 +1065,7 @@ export class OrChart extends translate(i18next)(LitElement) { protected _cleanup() { if (this._chart) { - this._chart.destroy(); + this._chart.dispose(); this._chart = undefined; this.requestUpdate(); } @@ -1203,7 +1198,7 @@ export class OrChart extends translate(i18next)(LitElement) { const now = moment().toDate().getTime(); let predictedFromTimestamp = now < this._startOfPeriod ? this._startOfPeriod : now; - const data: ChartDataset<"line", ScatterDataPoint[]>[] = []; + const data: any = []; let promises; try { @@ -1262,17 +1257,24 @@ export class OrChart extends translate(i18next)(LitElement) { } - protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string | undefined, from: number, to: number, predicted: boolean, label?: string, options?: any): Promise> { + protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string | undefined, from: number, to: number, predicted: boolean, label?: string, options?: any) { - const dataset: ChartDataset<"line", ScatterDataPoint[]> = { - borderColor: color, - backgroundColor: color, - label: label, - pointRadius: 2, - fill: false, - data: [], - borderDash: predicted ? [2, 4] : undefined - }; + const dataset = { + name: label, + type: 'line', + showSymbol: false, + data: {}, + sampling: 'lttb', + lineStyle: { + color: color, + type: predicted ? [2, 4] : undefined, + }, + emphasis: { + focus: 'self', //hide other lines when hovering over one + }, + //smooth: true, + //areaStyle: fill settings + } if (asset.id && attribute.name && this.datapointQuery) { let response: GenericAxiosResponse[]>; @@ -1284,6 +1286,7 @@ export class OrChart extends translate(i18next)(LitElement) { // If amount of data points is set, only allow a maximum of 1 points per pixel in width // Otherwise, dynamically set amount of data points based on chart width (1000px = 200 data points) + // >-------------IS DIT NOG WEL NODIG? ECHARTS HEEFT ZELF LTTB if(query.amountOfPoints) { if(this._chartElem?.clientWidth > 0) { query.amountOfPoints = Math.min(query.amountOfPoints, this._chartElem?.clientWidth) @@ -1309,12 +1312,42 @@ export class OrChart extends translate(i18next)(LitElement) { response = await manager.rest.api.AssetPredictedDatapointResource.getPredictedDatapoints(asset.id, attribute.name, query, options) } + let cheese: ValueDatapoint[] = []; + if (response.status === 200) { - dataset.data = response.data.filter(value => value.y !== null && value.y !== undefined) as ScatterDataPoint[]; + cheese = response.data + .filter(value => value.y !== null && value.y !== undefined) + .map(point => ({ x: point.x, y: point.y } as ValueDatapoint)) + + dataset.data = cheese.map(point => [point.x, point.y]); } } return dataset; } + //protected _onZoomChange(params: any) { + // this._zoomChanged = true; + // const { start: zoomStartPercentage, end: zoomEndPercentage } = params.batch[0]; + // //Define the start and end of the period based on the zoomed area + // this._queryStartOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomStartPercentage / 100); + // this._queryEndOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomEndPercentage / 100); +// + // this._loadData().then(() => { + // this._updateChartData(); + // }); +// + //} + + protected _updateChartData(){ + + this._chart!.setOption({ + xAxis: { + min: this._startOfPeriod, + max: this._endOfPeriod + }, + series: this._data + }); + } + } diff --git a/yarn.lock b/yarn.lock index 81952301f4..c411eaba7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2581,6 +2581,7 @@ __metadata: chart.js: "npm:^3.6.0" chartjs-adapter-moment: "npm:^1.0.0" chartjs-plugin-annotation: "npm:^1.1.0" + echarts: "npm:^5.6.0" jsonpath-plus: "npm:^6.0.1" lit: "npm:^2.0.2" moment: "npm:^2.29.4" @@ -4853,6 +4854,16 @@ __metadata: languageName: node linkType: hard +"echarts@npm:^5.6.0": + version: 5.6.0 + resolution: "echarts@npm:5.6.0" + dependencies: + tslib: "npm:2.3.0" + zrender: "npm:5.6.1" + checksum: 10c0/6d6a2ee88534d1ff0433e935c542237b9896de1c94959f47ebc7e0e9da26f59bf11c91ed6fc135b62ad2786c779ee12bc536fa481e60532dad5b6a2f5167e9ea + languageName: node + linkType: hard + "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -9356,6 +9367,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.3.0": + version: 2.3.0 + resolution: "tslib@npm:2.3.0" + checksum: 10c0/a845aed84e7e7dbb4c774582da60d7030ea39d67307250442d35c4c5dd77e4b44007098c37dd079e100029c76055f2a362734b8442ba828f8cc934f15ed9be61 + languageName: node + linkType: hard + "tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -10026,3 +10044,12 @@ __metadata: checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f languageName: node linkType: hard + +"zrender@npm:5.6.1": + version: 5.6.1 + resolution: "zrender@npm:5.6.1" + dependencies: + tslib: "npm:2.3.0" + checksum: 10c0/dc1cc570054640cbd8fbb7b92e6252f225319522bfe3e8dc8bf02cc02d414e00a4c8d0a6f89bfc9d96e5e9511fdca94dd3d06bf53690df2b2f12b0fc560ac307 + languageName: node + linkType: hard From 2a5b959c344b38aaa8a82c63e5abdb97af29cc9d Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Wed, 26 Feb 2025 11:43:08 +0100 Subject: [PATCH 03/20] store broken build --- ui/component/or-chart/src/index.ts | 182 +++++++++++++++-------------- 1 file changed, 94 insertions(+), 88 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index f2d4cbe8a9..5767dceca4 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -315,7 +315,7 @@ const style = css` flex-direction: column; align-items: center; } - canvas { + #chart { width: 100% !important; height: 100%; !important; } @@ -446,20 +446,26 @@ export class OrChart extends translate(i18next)(LitElement) { } connectedCallback() { + console.log('connectedCallback triggered'); super.connectedCallback(); this._style = window.getComputedStyle(this); } disconnectedCallback(): void { + console.log('disconnectedCallback triggered'); super.disconnectedCallback(); this._cleanup(); + } firstUpdated() { + console.log('firstUpdated triggered'); this.loadSettings(false); } updated(changedProperties: PropertyValues) { + console.log('updated triggered'); + super.updated(changedProperties); if (changedProperties.has("realm")) { @@ -473,110 +479,102 @@ export class OrChart extends translate(i18next)(LitElement) { changedProperties.has("rightAxisAttributes") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); if (reloadData) { + console.log('reloadData triggered'); this._data = undefined; if (this._chart) { + console.log('releadData found _chart exists so disposing'); this._chart.dispose(); this._chart = undefined; } this._loadData(); } - if (!this._data) { - return; - } + //if (!this._data) { + // console.log("Data is not loaded yet"); + // return; + //} const now = moment().toDate().getTime(); if (!this._chart) { this._chartOptions = { - animation: false, - grid: { - show: true, - backgroundColor: this._style.getPropertyValue("--internal-or-asset-viewer-panel-color"), - borderColor: this._style.getPropertyValue("--internal-or-attribute-history-text-color") + title: { + text: 'Temperature Change in the Coming Week' }, - backgroundColor: this._style.getPropertyValue("--internal-or-asset-viewer-panel-color"), tooltip: { - trigger: 'axis', - axisPointer: { type: 'cross'}, - //formatter: (params: any) => { - // if (Array.isArray(params) && params.length > 0) { - // const yValue = params[0].value[1]; - // return yValue !== undefined ? yValue.toString() : ''; - // } - // return '' - //} + trigger: 'axis' }, + legend: {}, toolbox: { - right: '9%', - top: '5%', + show: true, feature: { - dataView: {readOnly: true}, - magicType: { - type: ['line', 'bar'] + dataZoom: { + yAxisIndex: 'none' }, + dataView: { readOnly: false }, + magicType: { type: ['line', 'bar'] }, + restore: {}, saveAsImage: {} } }, xAxis: { - type: 'time', - axisLine: { onZero: false, lineStyle: {color: this._style.getPropertyValue("--internal-or-attribute-history-text-color")}}, - splitLine: {show:true}, - min: this._startOfPeriod, - max: this._endOfPeriod, + type: 'category', + boundaryGap: false, + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + }, + yAxis: { + type: 'value', axisLabel: { - showMinLabel: true, - showMaxLabel: true, - hideOverlap: true, + formatter: '{value} °C' } }, - yAxis: + series: [ { - type: 'value', - axisLine: { lineStyle: {color: this._style.getPropertyValue("--internal-or-attribute-history-text-color")}}, - boundaryGap: ['10%', '10%'], - scale: true - }, - dataZoom: [ - { - type: 'inside', - start: 0, - end: 100 + name: 'Highest', + type: 'line', + data: [10, 11, 13, 11, 12, 12, 9], + markPoint: { + data: [ + { type: 'max', name: 'Max' }, + { type: 'min', name: 'Min' } + ] + }, + markLine: { + data: [{ type: 'average', name: 'Avg' }] + } }, { - start: 0, - end: 100, - //backgroundColor: bgColor, - //fillerColor: bgColor, - dataBackground: { - areaStyle: { - color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color") - } - }, - selectedDataBackground: { - areaStyle: { - color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color"), - } - }, - moveHandleStyle: { - color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color") + name: 'Lowest', + type: 'line', + data: [1, -2, 2, 5, 3, 2, 0], + markPoint: { + data: [{ name: '周最低', value: -2, xAxis: 1, yAxis: -1.5 }] }, - emphasis: { - moveHandleStyle: { - color: this._style.getPropertyValue("--internal-or-attribute-history-graph-fill-color") - }, - handleLabel: { - show: true - } - }, - handleLabel: { - show: false + markLine: { + data: [ + { type: 'average', name: 'Avg' }, + [ + { + symbol: 'none', + x: '90%', + yAxis: 'max' + }, + { + symbol: 'circle', + label: { + position: 'start', + formatter: 'Max' + }, + type: 'max', + name: '最高点' + } + ] + ] } } - ], - series: [], - } + ] + }; console.log(this._chartOptions); //const options = { // type: "line", @@ -679,16 +677,17 @@ export class OrChart extends translate(i18next)(LitElement) { // Set chart options to default this._chart.setOption(this._chartOptions); // Make chart size responsive - window.addEventListener("resize", () => this._chart!.resize()); - const resizeObserver = new ResizeObserver(() => this._chart!.resize()); - resizeObserver.observe(this._chartElem); + //window.addEventListener("resize", () => this._chart!.resize()); + //const resizeObserver = new ResizeObserver(() => this._chart!.resize()); + //resizeObserver.observe(this._chartElem); // Add event listener for zooming //this._chart!.on('datazoom', _.debounce((params: any) => { this._onZoomChange(params); }, 1500)); } else { if (changedProperties.has("_data")) { //Update chart to data from set period - this._updateChartData(); + //this._updateChartData(); + console.log('line 682'); } } this.onCompleted().then(() => { @@ -728,7 +727,7 @@ export class OrChart extends translate(i18next)(LitElement) { } render() { - const disabled = this._loading || this._latestError; + const disabled = false; // TEMP EDIT this._loading || this._latestError; return html`
@@ -878,6 +877,7 @@ export class OrChart extends translate(i18next)(LitElement) { //} async loadSettings(reset: boolean) { + console.log('loadSettings triggered'); if(this.assetAttributes == undefined || reset) { this.assetAttributes = []; @@ -962,6 +962,7 @@ export class OrChart extends translate(i18next)(LitElement) { } async saveSettings() { + console.log('saveSettings triggered'); if (!this.panelName) { return; @@ -1064,7 +1065,9 @@ export class OrChart extends translate(i18next)(LitElement) { } protected _cleanup() { + console.log('cleanup triggered'); if (this._chart) { + console.log('cleanup found _chart exists so disposing'); this._chart.dispose(); this._chart = undefined; this.requestUpdate(); @@ -1088,6 +1091,7 @@ export class OrChart extends translate(i18next)(LitElement) { } protected _getAttributeOptionsOld(): [string, string][] | undefined { + console.log('getAttributeOptionsOld triggered'); if(!this.activeAsset || !this.activeAsset.attributes) { return; } @@ -1167,6 +1171,7 @@ export class OrChart extends translate(i18next)(LitElement) { } protected async _loadData() { + console.log('loadData triggered'); if (this._data || !this.assetAttributes || !this.assets || (this.assets.length === 0 && !this.dataProvider) || (this.assetAttributes.length === 0 && !this.dataProvider) || !this.datapointQuery) { return; } @@ -1234,6 +1239,7 @@ export class OrChart extends translate(i18next)(LitElement) { } this._data = data; + console.log(data); // DEBUGGING this._loading = false; } catch (ex) { @@ -1287,18 +1293,18 @@ export class OrChart extends translate(i18next)(LitElement) { // If amount of data points is set, only allow a maximum of 1 points per pixel in width // Otherwise, dynamically set amount of data points based on chart width (1000px = 200 data points) // >-------------IS DIT NOG WEL NODIG? ECHARTS HEEFT ZELF LTTB - if(query.amountOfPoints) { - if(this._chartElem?.clientWidth > 0) { - query.amountOfPoints = Math.min(query.amountOfPoints, this._chartElem?.clientWidth) - } - } else { - if(this._chartElem?.clientWidth > 0) { - query.amountOfPoints = Math.round(this._chartElem.clientWidth / 5) - } else { + //if(query.amountOfPoints) { + // if(this._chartElem?.clientWidth > 0) { + // query.amountOfPoints = Math.min(query.amountOfPoints, this._chartElem?.clientWidth) + // } + //} else { + // if(this._chartElem?.clientWidth > 0) { + // query.amountOfPoints = Math.round(this._chartElem.clientWidth / 5) + // } else { console.warn("Could not grab width of the Chart for estimating amount of data points. Using 100 points instead.") query.amountOfPoints = 100; - } - } + // } + //} } else if(query.type === 'interval' && !query.interval) { const diffInHours = (this.datapointQuery.toTimestamp! - this.datapointQuery.fromTimestamp!) / 1000 / 60 / 60; @@ -1340,7 +1346,7 @@ export class OrChart extends translate(i18next)(LitElement) { //} protected _updateChartData(){ - + console.log('updateChartData triggered'); this._chart!.setOption({ xAxis: { min: this._startOfPeriod, From 135f29942533291b502d7ab7a6123746dbe0f8e3 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Thu, 27 Feb 2025 16:48:31 +0100 Subject: [PATCH 04/20] basics operational --- ui/component/or-chart/src/index.ts | 156 +++++++++++++++++------------ 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 5767dceca4..7e2e183e41 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -127,7 +127,7 @@ const style = css` --internal-or-chart-controls-margin: var(--or-chart-controls-margin, 0 0 20px 0); --internal-or-chart-controls-margin-children: var(--or-chart-controls-margin-children, 0 auto 20px auto); --internal-or-chart-graph-fill-color: var(--or-chart-graph-fill-color, var(--or-app-color4, ${unsafeCSS(DefaultColor4)})); - --internal-or-chart-graph-fill-opacity: var(--or-chart-graph-fill-opacity, 1); + --internal-or-chart-graph-fill-opacity: var(--or-chart-graph-fill-opacity, 0.25); --internal-or-chart-graph-line-color: var(--or-chart-graph-line-color, var(--or-app-color4, ${unsafeCSS(DefaultColor4)})); --internal-or-chart-graph-point-color: var(--or-chart-graph-point-color, var(--or-app-color3, ${unsafeCSS(DefaultColor3)})); --internal-or-chart-graph-point-border-color: var(--or-chart-graph-point-border-color, var(--or-app-color5, ${unsafeCSS(DefaultColor5)})); @@ -498,82 +498,104 @@ export class OrChart extends translate(i18next)(LitElement) { if (!this._chart) { + let bgColor = this._style.getPropertyValue("--internal-or-chart-graph-fill-color").trim(); + const opacity = Number(this._style.getPropertyValue("--internal-or-chart-graph-fill-opacity").trim()); + if (!isNaN(opacity)) { + if (bgColor.startsWith("#") && (bgColor.length === 4 || bgColor.length === 7)) { + bgColor += (bgColor.length === 4 ? Math.round(opacity * 255).toString(16).substr(0, 1) : Math.round(opacity * 255).toString(16)); + } else if (bgColor.startsWith("rgb(")) { + bgColor = bgColor.substring(0, bgColor.length - 1) + opacity; + } + } + + this._chartOptions = { - title: { - text: 'Temperature Change in the Coming Week' + animation: false, + grid: { + show: true, + backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), + borderColor: this._style.getPropertyValue("--internal-or-chart-text-color") }, + backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), tooltip: { - trigger: 'axis' + trigger: 'axis', + axisPointer: { type: 'cross'}, + //formatter: (params: any) => { + // if (Array.isArray(params) && params.length > 0) { + // const yValue = params[0].value[1]; + // return yValue !== undefined ? yValue.toString() : ''; + // } + // return '' + //} }, - legend: {}, toolbox: { - show: true, + right: '9%', + top: '5%', feature: { - dataZoom: { - yAxisIndex: 'none' + dataView: {readOnly: true}, + magicType: { + type: ['line', 'bar'] }, - dataView: { readOnly: false }, - magicType: { type: ['line', 'bar'] }, - restore: {}, saveAsImage: {} } }, xAxis: { - type: 'category', - boundaryGap: false, - data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - }, - yAxis: { - type: 'value', + type: 'time', + axisLine: { onZero: false, lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")}}, + splitLine: {show:true}, + min: this._startOfPeriod, + max: this._endOfPeriod, axisLabel: { - formatter: '{value} °C' + showMinLabel: true, + showMaxLabel: true, + hideOverlap: true, } }, - series: [ + yAxis: { - name: 'Highest', - type: 'line', - data: [10, 11, 13, 11, 12, 12, 9], - markPoint: { - data: [ - { type: 'max', name: 'Max' }, - { type: 'min', name: 'Min' } - ] - }, - markLine: { - data: [{ type: 'average', name: 'Avg' }] - } + type: 'value', + axisLine: { lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")}}, + boundaryGap: ['10%', '10%'], + scale: true }, + dataZoom: [ { - name: 'Lowest', - type: 'line', - data: [1, -2, 2, 5, 3, 2, 0], - markPoint: { - data: [{ name: '周最低', value: -2, xAxis: 1, yAxis: -1.5 }] + type: 'inside', + start: 0, + end: 100 + }, + { + start: 0, + end: 100, + backgroundColor: bgColor, + fillerColor: bgColor, + dataBackground: { + areaStyle: { + color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") + } + }, + selectedDataBackground: { + areaStyle: { + color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color"), + } + }, + moveHandleStyle: { + color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") + }, + emphasis: { + moveHandleStyle: { + color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") + }, + handleLabel: { + show: true + } }, - markLine: { - data: [ - { type: 'average', name: 'Avg' }, - [ - { - symbol: 'none', - x: '90%', - yAxis: 'max' - }, - { - symbol: 'circle', - label: { - position: 'start', - formatter: 'Max' - }, - type: 'max', - name: '最高点' - } - ] - ] + handleLabel: { + show: false } } - ] + ], + series: [], }; console.log(this._chartOptions); //const options = { @@ -677,16 +699,16 @@ export class OrChart extends translate(i18next)(LitElement) { // Set chart options to default this._chart.setOption(this._chartOptions); // Make chart size responsive - //window.addEventListener("resize", () => this._chart!.resize()); - //const resizeObserver = new ResizeObserver(() => this._chart!.resize()); - //resizeObserver.observe(this._chartElem); + window.addEventListener("resize", () => this._chart!.resize()); + const resizeObserver = new ResizeObserver(() => this._chart!.resize()); + resizeObserver.observe(this._chartElem); // Add event listener for zooming //this._chart!.on('datazoom', _.debounce((params: any) => { this._onZoomChange(params); }, 1500)); } else { if (changedProperties.has("_data")) { //Update chart to data from set period - //this._updateChartData(); + this._updateChartData(); console.log('line 682'); } } @@ -1231,6 +1253,7 @@ export class OrChart extends translate(i18next)(LitElement) { dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, this.colors[colourIndex], predictedFromTimestamp, this._endOfPeriod!, true, asset.name + " " + label + " " + i18next.t("predicted"), options); data.push(dataset); + }); } @@ -1275,9 +1298,12 @@ export class OrChart extends translate(i18next)(LitElement) { color: color, type: predicted ? [2, 4] : undefined, }, - emphasis: { - focus: 'self', //hide other lines when hovering over one + itemStyle: { + color: color }, + //emphasis: { + // focus: 'self', //hide other lines when hovering over one + //}, //smooth: true, //areaStyle: fill settings } @@ -1326,9 +1352,12 @@ export class OrChart extends translate(i18next)(LitElement) { .map(point => ({ x: point.x, y: point.y } as ValueDatapoint)) dataset.data = cheese.map(point => [point.x, point.y]); + dataset.showSymbol = cheese.length <=10; //Only show symbols when there are 10 or fewer data points to be shown } } + + return dataset; } @@ -1355,5 +1384,4 @@ export class OrChart extends translate(i18next)(LitElement) { series: this._data }); } - } From ba3269b9bf6d0a3aa1250f5f9dec88b6d0796f03 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Thu, 27 Feb 2025 18:59:44 +0100 Subject: [PATCH 05/20] Added: units in tooltip, now markline, legend mouseover highlighting --- ui/component/or-chart/src/index.ts | 93 +++++++++++++++++++----------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 7e2e183e41..03c21ac230 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -541,8 +541,11 @@ export class OrChart extends translate(i18next)(LitElement) { }, xAxis: { type: 'time', - axisLine: { onZero: false, lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")}}, - splitLine: {show:true}, + axisLine: { + onZero: false, + lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")} + }, + splitLine: {show: true}, min: this._startOfPeriod, max: this._endOfPeriod, axisLabel: { @@ -829,7 +832,7 @@ export class OrChart extends translate(i18next)(LitElement) { const axisNote = (this.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; const bgColor = this.colors[colourIndex] || ""; return html` -
+
${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(this.assets[assetIndex]!.type!), undefined, undefined, bgColor.split('#')[1])}
@@ -869,34 +872,39 @@ export class OrChart extends translate(i18next)(LitElement) { } } - //DEFAULT ECHARTS - //removeDatasetHighlight(bgColor:string) { - // if(this._chart && this._chart.data && this._chart.data.datasets){ - // this._chart.data.datasets.map((dataset, idx) => { - // if (dataset.borderColor && typeof dataset.borderColor === "string" && dataset.borderColor.length === 9) { - // dataset.borderColor = dataset.borderColor.slice(0, -2); - // dataset.backgroundColor = dataset.borderColor; - // } - // }); - // this._chart.update(); - // } - //} + removeDatasetHighlight() { + if(this._chart){ + console.log('removeDatasetHighlight triggered'); + let options = this._chart.getOption(); + if (options.series && Array.isArray(options.series)) { + options.series.forEach(function (series) { + series.lineStyle.opacity = 1; + }); + } + this._chart.setOption(options); + } + } + + addDatasetHighlight(assetId?:string, attrName?:string) { + if (this._chart) { + console.log('addDatasetHighlight triggered'); + let options = this._chart.getOption(); + console.log(options); + if (options.series && Array.isArray(options.series)) { + options.series.forEach(function (series) { + if (series.assetId != assetId || series.attrName != attrName) { + console.log('setting opacity to 0.2 was here'); + series.lineStyle.opacity = 0.2; + } + }); + } + console.log(options); + this._chart.setOption(options) + } + }; + + - //DEFAULT ECHARTS - //addDatasetHighlight(assetId?:string, attrName?:string) { - // if (!assetId || !attrName) return; - // - // if(this._chart && this._chart.data && this._chart.data.datasets){ - // this._chart.data.datasets.map((dataset, idx) => { - // if ((dataset as any).assetId === assetId && (dataset as any).attrName === attrName) { - // return - // } - // dataset.borderColor = dataset.borderColor + "36"; - // dataset.backgroundColor = dataset.borderColor; - // }); - // this._chart.update(); - // } - //} async loadSettings(reset: boolean) { console.log('loadSettings triggered'); @@ -1244,7 +1252,7 @@ export class OrChart extends translate(i18next)(LitElement) { const unit = Util.resolveUnits(Util.getAttributeUnits(attribute, descriptors[0], asset.type)); const colourIndex = index % this.colors.length; const options = { signal: this._dataAbortController?.signal }; - let dataset = await this._loadAttributeData(asset, attribute, this.colors[colourIndex], this._startOfPeriod!, this._endOfPeriod!, false, asset.name + " " + label, options); + let dataset = await this._loadAttributeData(asset, attribute, this.colors[colourIndex], this._startOfPeriod!, this._endOfPeriod!, false, asset.name + " " + label, options, unit); (dataset as any).assetId = asset.id; (dataset as any).attrName = attribute.name; (dataset as any).unit = unit; @@ -1286,7 +1294,7 @@ export class OrChart extends translate(i18next)(LitElement) { } - protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string | undefined, from: number, to: number, predicted: boolean, label?: string, options?: any) { + protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string | undefined, from: number, to: number, predicted: boolean, label?: string, options?: any, unit?: any) { const dataset = { name: label, @@ -1297,10 +1305,15 @@ export class OrChart extends translate(i18next)(LitElement) { lineStyle: { color: color, type: predicted ? [2, 4] : undefined, + opacity: 1 }, itemStyle: { color: color }, + tooltip: { + // @ts-ignore + valueFormatter: value => value + unit + }, //emphasis: { // focus: 'self', //hide other lines when hovering over one //}, @@ -1381,7 +1394,21 @@ export class OrChart extends translate(i18next)(LitElement) { min: this._startOfPeriod, max: this._endOfPeriod }, - series: this._data + series: this._data!.map(series => ({ + ...series, + markLine: { + symbol: 'circle', + silent: true, + data: [{ name: '', xAxis: new Date().toISOString(), label: { formatter: '{b}' } }], + lineStyle: { + color: this._style.getPropertyValue("--internal-or-chart-text-color"), + type: 'solid', + width: 2, + opacity: 1 + } + } + })) }); + console.log(this._data); } } From 7673676da6c4d9c356e57a6bedd48b7c13c7cb27 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Fri, 28 Feb 2025 14:28:40 +0100 Subject: [PATCH 06/20] added dynamic zoom query , added debounce on highlight events --- ui/component/or-chart/src/index.ts | 159 ++++++++--------------------- 1 file changed, 40 insertions(+), 119 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 03c21ac230..9c595bb07b 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -422,6 +422,9 @@ export class OrChart extends translate(i18next)(LitElement) { @property() protected _loading: boolean = false; + @property() + protected _zoomChanged: boolean = false; + @property() protected _data?: ValueDatapoint[]; @@ -435,6 +438,8 @@ export class OrChart extends translate(i18next)(LitElement) { protected _style!: CSSStyleDeclaration; protected _startOfPeriod?: number; protected _endOfPeriod?: number; + protected _queryStartOfPeriod?: number; + protected _queryEndOfPeriod?: number; protected _timeUnits?: TimeUnit; protected _stepSize?: number; protected _latestError?: string; @@ -601,101 +606,6 @@ export class OrChart extends translate(i18next)(LitElement) { series: [], }; console.log(this._chartOptions); - //const options = { - // type: "line", - // data: { - // datasets: this._data - // }, - // options: { - // responsive: true, - // maintainAspectRatio: false, - // onResize: throttle(() => { this.dispatchEvent(new OrChartEvent("resize")); this.applyChartResponsiveness(); }, 200), - // showLines: true, - // plugins: { - // legend: { - // display: false - // }, - // tooltip: { - // mode: "x", - // intersect: false, - // xPadding: 10, - // yPadding: 10, - // titleMarginBottom: 10, - // callbacks: { - // label: (tooltipItem: any) => tooltipItem.dataset.label + ': ' + tooltipItem.formattedValue + tooltipItem.dataset.unit, - // } - // }, - // annotation: { - // annotations: [ - // { - // type: "line", - // xMin: now, - // xMax: now, - // borderColor: "#275582", - // borderWidth: 2 - // } - // ] - // }, - // }, - // hover: { - // mode: 'x', - // intersect: false - // }, - // scales: { - // y: { - // ticks: { - // beginAtZero: true - // }, - // grid: { - // color: "#cccccc" - // } - // }, - // y1: { - // display: this.rightAxisAttributes.length > 0, - // position: 'right', - // ticks: { - // beginAtZero: true - // }, - // grid: { - // drawOnChartArea: false - // } - // }, - // x: { - // type: "time", - // min: this._startOfPeriod, - // max: this._endOfPeriod, - // time: { - // tooltipFormat: 'MMM D, YYYY, HH:mm:ss', - // displayFormats: { - // millisecond: 'HH:mm:ss.SSS', - // second: 'HH:mm:ss', - // minute: "HH:mm", - // hour: (this._endOfPeriod && this._startOfPeriod && this._endOfPeriod - this._startOfPeriod > 86400000) ? "MMM DD, HH:mm" : "HH:mm", - // day: "MMM DD", - // week: "w" - // }, - // unit: this._timeUnits, - // stepSize: this._stepSize - // }, - // ticks: { - // autoSkip: true, - // color: "#000", - // font: { - // family: "'Open Sans', Helvetica, Arial, Lucida, sans-serif", - // size: 9, - // style: "normal" - // } - // }, - // gridLines: { - // color: "#cccccc" - // } - // } - // } - // } - //} as ChartConfiguration<"line", ScatterDataPoint[]>; - - //WAT DOET DIT ? - //const mergedOptions = Util.mergeObjects(options, this.chartOptions, false); // Initialize echarts instance this._chart = init(this._chartElem); @@ -706,7 +616,7 @@ export class OrChart extends translate(i18next)(LitElement) { const resizeObserver = new ResizeObserver(() => this._chart!.resize()); resizeObserver.observe(this._chartElem); // Add event listener for zooming - //this._chart!.on('datazoom', _.debounce((params: any) => { this._onZoomChange(params); }, 1500)); + this._chart!.on('datazoom', _.debounce((params: any) => { this._onZoomChange(params); }, 1500)); } else { if (changedProperties.has("_data")) { @@ -832,7 +742,7 @@ export class OrChart extends translate(i18next)(LitElement) { const axisNote = (this.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; const bgColor = this.colors[colourIndex] || ""; return html` -
+
${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(this.assets[assetIndex]!.type!), undefined, undefined, bgColor.split('#')[1])}
@@ -874,7 +784,6 @@ export class OrChart extends translate(i18next)(LitElement) { removeDatasetHighlight() { if(this._chart){ - console.log('removeDatasetHighlight triggered'); let options = this._chart.getOption(); if (options.series && Array.isArray(options.series)) { options.series.forEach(function (series) { @@ -887,18 +796,14 @@ export class OrChart extends translate(i18next)(LitElement) { addDatasetHighlight(assetId?:string, attrName?:string) { if (this._chart) { - console.log('addDatasetHighlight triggered'); let options = this._chart.getOption(); - console.log(options); if (options.series && Array.isArray(options.series)) { options.series.forEach(function (series) { if (series.assetId != assetId || series.attrName != attrName) { - console.log('setting opacity to 0.2 was here'); series.lineStyle.opacity = 0.2; } }); } - console.log(options); this._chart.setOption(options) } }; @@ -1202,7 +1107,7 @@ export class OrChart extends translate(i18next)(LitElement) { protected async _loadData() { console.log('loadData triggered'); - if (this._data || !this.assetAttributes || !this.assets || (this.assets.length === 0 && !this.dataProvider) || (this.assetAttributes.length === 0 && !this.dataProvider) || !this.datapointQuery) { + if ((this._data && !this._zoomChanged) || !this.assetAttributes || !this.assets || (this.assets.length === 0 && !this.dataProvider) || (this.assetAttributes.length === 0 && !this.dataProvider) || !this.datapointQuery) { return; } @@ -1237,11 +1142,13 @@ export class OrChart extends translate(i18next)(LitElement) { let promises; try { - if(this.dataProvider) { + if(this.dataProvider && !this._zoomChanged) { await this.dataProvider(this._startOfPeriod, this._endOfPeriod, (interval.toString() as TimeUnit), stepSize).then((dataset) => { dataset.forEach((set) => { data.push(set); }); + console.log('yo mom wasnt here'); }); } else { + console.log('yo mom was here'); this._dataAbortController = new AbortController(); promises = this.assetAttributes.map(async ([assetIndex, attribute], index) => { @@ -1272,6 +1179,7 @@ export class OrChart extends translate(i18next)(LitElement) { this._data = data; console.log(data); // DEBUGGING this._loading = false; + this._zoomChanged = false; } catch (ex) { console.error(ex); @@ -1279,6 +1187,7 @@ export class OrChart extends translate(i18next)(LitElement) { return; // If request has been canceled (using AbortController); return, and prevent _loading is set to false. } this._loading = false; + this._zoomChanged = false; if(isAxiosError(ex)) { if(ex.message.includes("timeout")) { @@ -1321,11 +1230,23 @@ export class OrChart extends translate(i18next)(LitElement) { //areaStyle: fill settings } + console.log('load attribute data triggered'); + if (asset.id && attribute.name && this.datapointQuery) { let response: GenericAxiosResponse[]>; const query = JSON.parse(JSON.stringify(this.datapointQuery)); // recreating object, since the changes shouldn't apply to parent components; only or-chart itself. - query.fromTimestamp = this._startOfPeriod; - query.toTimestamp = this._endOfPeriod; + + if (!this._zoomChanged) { + query.fromTimestamp = this._startOfPeriod; + query.toTimestamp = this._endOfPeriod; + } else { + query.fromTimestamp = this._queryStartOfPeriod; + query.toTimestamp = this._queryEndOfPeriod; + } + + console.log('query.fromTimestamp: ' + query.fromTimestamp + asset.id + attribute.name + 'Predicted: ' + predicted); + console.log('query.toTimestamp: ' + query.toTimestamp + asset.id + attribute.name + 'Predicted: ' + predicted); + if(query.type == 'lttb') { @@ -1365,7 +1286,7 @@ export class OrChart extends translate(i18next)(LitElement) { .map(point => ({ x: point.x, y: point.y } as ValueDatapoint)) dataset.data = cheese.map(point => [point.x, point.y]); - dataset.showSymbol = cheese.length <=10; //Only show symbols when there are 10 or fewer data points to be shown + dataset.showSymbol = cheese.length <=30; //Only show symbols when there are 30 or fewer data points to be shown } } @@ -1374,18 +1295,18 @@ export class OrChart extends translate(i18next)(LitElement) { return dataset; } - //protected _onZoomChange(params: any) { - // this._zoomChanged = true; - // const { start: zoomStartPercentage, end: zoomEndPercentage } = params.batch[0]; - // //Define the start and end of the period based on the zoomed area - // this._queryStartOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomStartPercentage / 100); - // this._queryEndOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomEndPercentage / 100); -// - // this._loadData().then(() => { - // this._updateChartData(); - // }); -// - //} + protected _onZoomChange(params: any) { + this._zoomChanged = true; + console.log('onZoomChange triggered'); + const { start: zoomStartPercentage, end: zoomEndPercentage } = params.batch[0]; + //Define the start and end of the period based on the zoomed area + this._queryStartOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomStartPercentage / 100); + this._queryEndOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomEndPercentage / 100); + this._loadData().then(() => { + this._updateChartData(); + }); + + } protected _updateChartData(){ console.log('updateChartData triggered'); From 09d77d1c911e221eab13dacc4d5423b813be9a0f Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Fri, 28 Feb 2025 17:20:08 +0100 Subject: [PATCH 07/20] added y axis min max settings, scaling still needs a little tweaking --- ui/component/or-chart/src/index.ts | 83 ++++++++++++++++++------------ 1 file changed, 50 insertions(+), 33 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 9c595bb07b..b89f4b89a2 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -21,7 +21,6 @@ import "@openremote/or-mwc-components/or-mwc-input"; import "@openremote/or-components/or-panel"; import "@openremote/or-translate"; import {ECharts, EChartsOption, init} from "echarts"; -import _ from "lodash"; import { Chart, ChartConfiguration, @@ -51,7 +50,7 @@ import {GenericAxiosResponse, isAxiosError} from "@openremote/rest"; import {OrAttributePicker, OrAttributePickerPickedEvent} from "@openremote/or-attribute-picker"; import {OrMwcDialog, showDialog} from "@openremote/or-mwc-components/or-mwc-dialog"; import {cache} from "lit/directives/cache.js"; -import {throttle} from "lodash"; +import {debounce, throttle} from "lodash"; import {getContentWithMenuTemplate} from "@openremote/or-mwc-components/or-mwc-menu"; import {ListItem} from "@openremote/or-mwc-components/or-mwc-list"; import { when } from "lit/directives/when.js"; @@ -389,7 +388,7 @@ export class OrChart extends translate(i18next)(LitElement) { @property({type: Object}) public config?: OrChartConfig; - @property({type: Object}) // options that will get merged with our default chartjs configuration. + @property({type: Object}) // WIDGET NEED TO CONVERT TO ECHARTS OPTION FORMAT, NOW WORKAROUND IN PLACE options that will get merged with our default chartjs configuration. public chartOptions?: any @property({type: String}) @@ -438,8 +437,8 @@ export class OrChart extends translate(i18next)(LitElement) { protected _style!: CSSStyleDeclaration; protected _startOfPeriod?: number; protected _endOfPeriod?: number; - protected _queryStartOfPeriod?: number; - protected _queryEndOfPeriod?: number; + protected _zoomStartOfPeriod?: number; + protected _zoomEndOfPeriod?: number; protected _timeUnits?: TimeUnit; protected _stepSize?: number; protected _latestError?: string; @@ -494,10 +493,10 @@ export class OrChart extends translate(i18next)(LitElement) { this._loadData(); } - //if (!this._data) { - // console.log("Data is not loaded yet"); - // return; - //} + if (!this._data) { + console.log("Data is not loaded yet"); + return; + } const now = moment().toDate().getTime(); @@ -519,7 +518,11 @@ export class OrChart extends translate(i18next)(LitElement) { grid: { show: true, backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), - borderColor: this._style.getPropertyValue("--internal-or-chart-text-color") + borderColor: this._style.getPropertyValue("--internal-or-chart-text-color"), + left: '5%', // 5% padding + top: '5.5%', + right: '5%', + bottom: '15%' }, backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), tooltip: { @@ -534,8 +537,8 @@ export class OrChart extends translate(i18next)(LitElement) { //} }, toolbox: { - right: '9%', - top: '5%', + right: '4.5%', + top: '0%', feature: { dataView: {readOnly: true}, magicType: { @@ -559,13 +562,25 @@ export class OrChart extends translate(i18next)(LitElement) { hideOverlap: true, } }, - yAxis: + yAxis: [ { type: 'value', axisLine: { lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")}}, boundaryGap: ['10%', '10%'], - scale: true + scale: true, + min: this.chartOptions.options.scales.y.min ? this.chartOptions.options.scales.y.min : undefined, + max: this.chartOptions.options.scales.y.max ? this.chartOptions.options.scales.y.max : undefined }, + { + type: 'value', + show: this.rightAxisAttributes.length > 0, + axisLine: { lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")}}, + boundaryGap: ['10%', '10%'], + scale: true, + min: this.chartOptions.options.scales.y1.min ? this.chartOptions.options.scales.y1.min : undefined, + max: this.chartOptions.options.scales.y1.max ? this.chartOptions.options.scales.y1.max : undefined + } + ], dataZoom: [ { type: 'inside', @@ -595,7 +610,7 @@ export class OrChart extends translate(i18next)(LitElement) { color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") }, handleLabel: { - show: true + show: false } }, handleLabel: { @@ -605,6 +620,7 @@ export class OrChart extends translate(i18next)(LitElement) { ], series: [], }; + console.log(this._chartOptions); // Initialize echarts instance @@ -616,15 +632,15 @@ export class OrChart extends translate(i18next)(LitElement) { const resizeObserver = new ResizeObserver(() => this._chart!.resize()); resizeObserver.observe(this._chartElem); // Add event listener for zooming - this._chart!.on('datazoom', _.debounce((params: any) => { this._onZoomChange(params); }, 1500)); + this._chart!.on('datazoom', debounce((params: any) => { this._onZoomChange(params); }, 1500)); - } else { - if (changedProperties.has("_data")) { - //Update chart to data from set period - this._updateChartData(); - console.log('line 682'); - } } + + if (changedProperties.has("_data")) { + //Update chart to data from set period + this._updateChartData(); + } + this.onCompleted().then(() => { this.dispatchEvent(new OrChartEvent('rendered')); }); @@ -742,7 +758,7 @@ export class OrChart extends translate(i18next)(LitElement) { const axisNote = (this.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; const bgColor = this.colors[colourIndex] || ""; return html` -
+
${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(this.assets[assetIndex]!.type!), undefined, undefined, bgColor.split('#')[1])}
@@ -1123,8 +1139,12 @@ export class OrChart extends translate(i18next)(LitElement) { this._loading = true; const dates: [Date, Date] = this.timePresetOptions!.get(this.timePresetKey!)!(new Date()); - this._startOfPeriod = this.timeframe ? this.timeframe[0].getTime() : dates[0].getTime(); - this._endOfPeriod = this.timeframe ? this.timeframe[1].getTime() : dates[1].getTime(); + + if(!this._zoomChanged || !this._startOfPeriod || !this._endOfPeriod) { + // If zoom has changed, we want to keep the previous start and end of period + this._startOfPeriod = this.timeframe ? this.timeframe[0].getTime() : dates[0].getTime(); + this._endOfPeriod = this.timeframe ? this.timeframe[1].getTime() : dates[1].getTime(); + } const diffInHours = (this._endOfPeriod - this._startOfPeriod) / 1000 / 60 / 60; const intervalArr = this._getInterval(diffInHours); @@ -1145,10 +1165,8 @@ export class OrChart extends translate(i18next)(LitElement) { if(this.dataProvider && !this._zoomChanged) { await this.dataProvider(this._startOfPeriod, this._endOfPeriod, (interval.toString() as TimeUnit), stepSize).then((dataset) => { dataset.forEach((set) => { data.push(set); }); - console.log('yo mom wasnt here'); }); } else { - console.log('yo mom was here'); this._dataAbortController = new AbortController(); promises = this.assetAttributes.map(async ([assetIndex, attribute], index) => { @@ -1163,7 +1181,7 @@ export class OrChart extends translate(i18next)(LitElement) { (dataset as any).assetId = asset.id; (dataset as any).attrName = attribute.name; (dataset as any).unit = unit; - (dataset as any).yAxisID = shownOnRightAxis ? 'y1' : 'y'; + (dataset as any).yAxisIndex = shownOnRightAxis ? '1' : '0'; data.push(dataset); dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, this.colors[colourIndex], predictedFromTimestamp, this._endOfPeriod!, true, asset.name + " " + label + " " + i18next.t("predicted"), options); @@ -1240,8 +1258,8 @@ export class OrChart extends translate(i18next)(LitElement) { query.fromTimestamp = this._startOfPeriod; query.toTimestamp = this._endOfPeriod; } else { - query.fromTimestamp = this._queryStartOfPeriod; - query.toTimestamp = this._queryEndOfPeriod; + query.fromTimestamp = this._zoomStartOfPeriod; + query.toTimestamp = this._zoomEndOfPeriod; } console.log('query.fromTimestamp: ' + query.fromTimestamp + asset.id + attribute.name + 'Predicted: ' + predicted); @@ -1300,8 +1318,8 @@ export class OrChart extends translate(i18next)(LitElement) { console.log('onZoomChange triggered'); const { start: zoomStartPercentage, end: zoomEndPercentage } = params.batch[0]; //Define the start and end of the period based on the zoomed area - this._queryStartOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomStartPercentage / 100); - this._queryEndOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomEndPercentage / 100); + this._zoomStartOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomStartPercentage / 100); + this._zoomEndOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomEndPercentage / 100); this._loadData().then(() => { this._updateChartData(); }); @@ -1330,6 +1348,5 @@ export class OrChart extends translate(i18next)(LitElement) { } })) }); - console.log(this._data); } } From 8415c1a95ada225330cd8d75c7366dd5653fb32e Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Mon, 3 Mar 2025 18:39:45 +0100 Subject: [PATCH 08/20] add more toolbox and zoombar show settings(needs tweaking), editted formatting/padding --- ui/app/shared/locales/en/or.json | 2 + ui/component/or-chart/src/index.ts | 119 +++++++++++------- .../src/settings/chart-settings.ts | 25 ++++ .../src/widgets/chart-widget.ts | 8 +- 4 files changed, 107 insertions(+), 47 deletions(-) diff --git a/ui/app/shared/locales/en/or.json b/ui/app/shared/locales/en/or.json index d83d803dd9..38cbff05c8 100644 --- a/ui/app/shared/locales/en/or.json +++ b/ui/app/shared/locales/en/or.json @@ -738,6 +738,8 @@ "showLegend": "Show legend", "showGeoJson": "Show GeoJSON overlay", "showValueAs": "Show value as", + "showZoomBar": "Show zoom bar", + "showToolBox": "Show toolbox", "customLabel": "Custom label", "customTimeSpan": "Custom", "userCanEdit": "Allow user input", diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index b89f4b89a2..72014acc0c 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -418,6 +418,12 @@ export class OrChart extends translate(i18next)(LitElement) { @property() public denseLegend: boolean = false; + @property() + public showZoomBar: boolean = true; + + @property() + public showToolBox: boolean = true; + @property() protected _loading: boolean = false; @@ -498,8 +504,6 @@ export class OrChart extends translate(i18next)(LitElement) { return; } - const now = moment().toDate().getTime(); - if (!this._chart) { let bgColor = this._style.getPropertyValue("--internal-or-chart-graph-fill-color").trim(); @@ -513,6 +517,7 @@ export class OrChart extends translate(i18next)(LitElement) { } + this._chartOptions = { animation: false, grid: { @@ -520,9 +525,9 @@ export class OrChart extends translate(i18next)(LitElement) { backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), borderColor: this._style.getPropertyValue("--internal-or-chart-text-color"), left: '5%', // 5% padding - top: '5.5%', + //top: '5.5%', right: '5%', - bottom: '15%' + //bottom: '15%' }, backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), tooltip: { @@ -536,17 +541,7 @@ export class OrChart extends translate(i18next)(LitElement) { // return '' //} }, - toolbox: { - right: '4.5%', - top: '0%', - feature: { - dataView: {readOnly: true}, - magicType: { - type: ['line', 'bar'] - }, - saveAsImage: {} - } - }, + toolbox: {}, xAxis: { type: 'time', axisLine: { @@ -560,6 +555,17 @@ export class OrChart extends translate(i18next)(LitElement) { showMinLabel: true, showMaxLabel: true, hideOverlap: true, + formatter: { + year: '{yyyy}-{MMM}', + month: '{yy}-{MMM}', + day: '{d}-{MMM}', + hour: '{HH}:{mm}', + minute: '{HH}:{mm}', + second: '{HH}:{mm}:{ss}', + millisecond: '{d}-{MMM} {hh}:{mm}', + // @ts-ignore + none: '{MMM}-{dd} {hh}:{mm}' + } } }, yAxis: [ @@ -568,7 +574,7 @@ export class OrChart extends translate(i18next)(LitElement) { axisLine: { lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")}}, boundaryGap: ['10%', '10%'], scale: true, - min: this.chartOptions.options.scales.y.min ? this.chartOptions.options.scales.y.min : undefined, + min: this.chartOptions.options.scales.y.min ? this.chartOptions.options.scales.y.min : undefined, //NOG FIXEN MET MERGEN VAN CHARTOPTIONS max: this.chartOptions.options.scales.y.max ? this.chartOptions.options.scales.y.max : undefined }, { @@ -586,40 +592,59 @@ export class OrChart extends translate(i18next)(LitElement) { type: 'inside', start: 0, end: 100 + } + ], + series: [], + }; + + // Add dataZoom bar if enabled + if(this.showZoomBar) { + (this._chartOptions!.dataZoom! as any[]).push({ + start: 0, + end: 100, + backgroundColor: bgColor, + fillerColor: bgColor, + dataBackground: { + areaStyle: { + color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") + } }, - { - start: 0, - end: 100, - backgroundColor: bgColor, - fillerColor: bgColor, - dataBackground: { - areaStyle: { - color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") - } - }, - selectedDataBackground: { - areaStyle: { - color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color"), - } - }, + selectedDataBackground: { + areaStyle: { + color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color"), + } + }, + moveHandleStyle: { + color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") + }, + emphasis: { moveHandleStyle: { color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") }, - emphasis: { - moveHandleStyle: { - color: this._style.getPropertyValue("--internal-or-chart-graph-fill-color") - }, - handleLabel: { - show: false - } - }, handleLabel: { show: false } + }, + handleLabel: { + show: false } - ], - series: [], - }; + }) + } + + // Add toolbox if enabled + if(this.showToolBox) { + this._chartOptions!.toolbox! = { + right: '4.5%', + top: '0%', + feature: { + dataView: {readOnly: true}, + magicType: { + type: ['line', 'bar'] + }, + saveAsImage: {} + } + } + } console.log(this._chartOptions); @@ -633,6 +658,8 @@ export class OrChart extends translate(i18next)(LitElement) { resizeObserver.observe(this._chartElem); // Add event listener for zooming this._chart!.on('datazoom', debounce((params: any) => { this._onZoomChange(params); }, 1500)); + // Add event listener for chart resize + this._chart!.on('resize', throttle(() => { this.applyChartResponsiveness(); }, 200)); } @@ -1296,15 +1323,15 @@ export class OrChart extends translate(i18next)(LitElement) { response = await manager.rest.api.AssetPredictedDatapointResource.getPredictedDatapoints(asset.id, attribute.name, query, options) } - let cheese: ValueDatapoint[] = []; + let data: ValueDatapoint[] = []; if (response.status === 200) { - cheese = response.data + data = response.data .filter(value => value.y !== null && value.y !== undefined) .map(point => ({ x: point.x, y: point.y } as ValueDatapoint)) - dataset.data = cheese.map(point => [point.x, point.y]); - dataset.showSymbol = cheese.length <=30; //Only show symbols when there are 30 or fewer data points to be shown + dataset.data = data.map(point => [point.x, point.y]); + dataset.showSymbol = data.length <=30; //Only show symbols when there are 30 or fewer data points to be shown } } diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index 8da97fe582..26f665ad33 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -100,6 +100,21 @@ export class ChartSettings extends WidgetSettings { @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onShowLegendToggle(ev)}" >
+ + +
+ + +
+ +
+ + +
@@ -274,6 +289,16 @@ export class ChartSettings extends WidgetSettings { this.notifyConfigUpdate(); } + protected onShowZoomBarToggle(ev: OrInputChangedEvent) { + this.widgetConfig.showZoomBar = ev.detail.value; + this.notifyConfigUpdate(); + } + + protected onShowToolBoxToggle(ev: OrInputChangedEvent) { + this.widgetConfig.showToolBox = ev.detail.value; + this.notifyConfigUpdate(); + } + protected setAxisMinMaxValue(axis: 'left' | 'right', type: 'min' | 'max', value?: number) { if(axis === 'left') { if(type === 'min') { diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index e7c617fd9c..b6283c7765 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -19,6 +19,8 @@ export interface ChartWidgetConfig extends WidgetConfig { showTimestampControls: boolean; defaultTimePresetKey: string; showLegend: boolean; + showZoomBar: boolean; + showToolBox: boolean; } function getDefaultTimePresetOptions(): Map { @@ -75,7 +77,9 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { }, showTimestampControls: false, defaultTimePresetKey: preset, - showLegend: true + showLegend: true, + showZoomBar: true, + showToolBox: true }; } @@ -194,6 +198,8 @@ export class ChartWidget extends OrAssetWidget { return html` Date: Tue, 4 Mar 2025 17:06:04 +0100 Subject: [PATCH 09/20] changed margins, highlight event, added chartwidget, changed chartwidget defaults --- ui/component/or-chart/src/index.ts | 19 ++++++++++--------- .../src/settings/chart-settings.ts | 6 +++--- .../src/widgets/chart-widget.ts | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 72014acc0c..040f6f0953 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -524,10 +524,10 @@ export class OrChart extends translate(i18next)(LitElement) { show: true, backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), borderColor: this._style.getPropertyValue("--internal-or-chart-text-color"), - left: '5%', // 5% padding - //top: '5.5%', - right: '5%', - //bottom: '15%' + left: 50,//'5%', // 5% padding + right: 50,//'5%', + top: this.showToolBox ? 28 : 10, + bottom: this.showZoomBar ? 68 : 20 }, backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), tooltip: { @@ -555,6 +555,7 @@ export class OrChart extends translate(i18next)(LitElement) { showMinLabel: true, showMaxLabel: true, hideOverlap: true, + textStyle: {fontWeight: 'bold'}, formatter: { year: '{yyyy}-{MMM}', month: '{yy}-{MMM}', @@ -562,9 +563,9 @@ export class OrChart extends translate(i18next)(LitElement) { hour: '{HH}:{mm}', minute: '{HH}:{mm}', second: '{HH}:{mm}:{ss}', - millisecond: '{d}-{MMM} {hh}:{mm}', + millisecond: '{d}-{MMM} {HH}:{mm}', // @ts-ignore - none: '{MMM}-{dd} {hh}:{mm}' + none: '{MMM}-{dd} {HH}:{mm}' } } }, @@ -634,8 +635,8 @@ export class OrChart extends translate(i18next)(LitElement) { // Add toolbox if enabled if(this.showToolBox) { this._chartOptions!.toolbox! = { - right: '4.5%', - top: '0%', + right: 45, + top: 0, feature: { dataView: {readOnly: true}, magicType: { @@ -785,7 +786,7 @@ export class OrChart extends translate(i18next)(LitElement) { const axisNote = (this.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; const bgColor = this.colors[colourIndex] || ""; return html` -
+
${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(this.assets[assetIndex]!.type!), undefined, undefined, bgColor.split('#')[1])}
diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index 26f665ad33..cacd1b8fb6 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -86,7 +86,7 @@ export class ChartSettings extends WidgetSettings { @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onTimePresetSelect(ev)}" >
- +
@@ -104,14 +104,14 @@ export class ChartSettings extends WidgetSettings {
-
-
diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index b6283c7765..6098119c56 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -78,8 +78,8 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { showTimestampControls: false, defaultTimePresetKey: preset, showLegend: true, - showZoomBar: true, - showToolBox: true + showZoomBar: false, + showToolBox: false }; } From 1504bbb28fa3675794cef8a4f62ad80b10ed6fca Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Wed, 5 Mar 2025 16:57:29 +0100 Subject: [PATCH 10/20] added attributesettings, compiling but color doesnt work yet --- ui/app/shared/locales/en/or.json | 9 +- ui/component/or-chart/src/index.ts | 42 ++++- .../src/panels/attributes-panel.ts | 25 +-- .../src/settings/chart-settings.ts | 161 ++++++++++++++++-- .../src/widgets/chart-widget.ts | 19 +++ 5 files changed, 225 insertions(+), 31 deletions(-) diff --git a/ui/app/shared/locales/en/or.json b/ui/app/shared/locales/en/or.json index 38cbff05c8..cbda0317cb 100644 --- a/ui/app/shared/locales/en/or.json +++ b/ui/app/shared/locales/en/or.json @@ -753,7 +753,14 @@ "zoom": "Zoom", "center": "Center", "imageUrl": "Image URL", - "noImageSelected": "No image selected" + "noImageSelected": "No image selected", + "faint": "Faint", + "smooth": "Smooth", + "stepped": "Stepped", + "fill": "Fill", + "extendData": "Extend last datapoint", + "lineColor": "Line color" + }, "south": "South", "east": "East", diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 040f6f0953..854cbe5cf0 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -376,6 +376,26 @@ export class OrChart extends translate(i18next)(LitElement) { @property({type: Array}) // List of AttributeRef that are shown on the right axis instead. public rightAxisAttributes: AttributeRef[] = []; + @property({type: Object}) + public attributeSettings: { + rightAxisAttributes: AttributeRef[], + smoothAttributes: AttributeRef[], + steppedAttributes: AttributeRef[], + areaAttributes: AttributeRef[], + faintAttributes: AttributeRef[], + extendedAttributes: AttributeRef[], + colorPickedAttributes: Map + } = { + rightAxisAttributes: [], + smoothAttributes: [], + steppedAttributes: [], + areaAttributes: [], + faintAttributes: [], + extendedAttributes: [], + colorPickedAttributes: new Map() + }; + + @property() public dataProvider?: (startOfPeriod: number, endOfPeriod: number, timeUnits: TimeUnit, stepSize: number) => Promise<[]> @@ -486,7 +506,7 @@ export class OrChart extends translate(i18next)(LitElement) { } const reloadData = changedProperties.has("datapointQuery") || changedProperties.has("timePresetKey") || changedProperties.has("timeframe") || - changedProperties.has("rightAxisAttributes") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); + changedProperties.has("attributeSettings") || changedProperties.has("rightAxisAttributes") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); if (reloadData) { console.log('reloadData triggered'); @@ -1200,19 +1220,25 @@ export class OrChart extends translate(i18next)(LitElement) { const asset = this.assets[assetIndex]; const shownOnRightAxis = !!this.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); + const smooth = !!this.attributeSettings.smoothAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); + const stepped = !!this.attributeSettings.steppedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); + const area = !!this.attributeSettings.areaAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); + const faint = !!this.attributeSettings.faintAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); + const extended = !!this.attributeSettings.extendedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); + const color = Array.from(this.attributeSettings.colorPickedAttributes).find(([{ name, id }]) => name === attribute.name && id === asset.id)?.[1]; const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset.type, attribute.name, attribute); const label = Util.getAttributeLabel(attribute, descriptors[0], asset.type, false); const unit = Util.resolveUnits(Util.getAttributeUnits(attribute, descriptors[0], asset.type)); const colourIndex = index % this.colors.length; const options = { signal: this._dataAbortController?.signal }; - let dataset = await this._loadAttributeData(asset, attribute, this.colors[colourIndex], this._startOfPeriod!, this._endOfPeriod!, false, asset.name + " " + label, options, unit); + let dataset = await this._loadAttributeData(asset, attribute, color ?? this.colors[colourIndex], this._startOfPeriod!, this._endOfPeriod!, false, smooth, stepped, area, faint, extended, asset.name + " " + label, options, unit); (dataset as any).assetId = asset.id; (dataset as any).attrName = attribute.name; (dataset as any).unit = unit; (dataset as any).yAxisIndex = shownOnRightAxis ? '1' : '0'; data.push(dataset); - dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, this.colors[colourIndex], predictedFromTimestamp, this._endOfPeriod!, true, asset.name + " " + label + " " + i18next.t("predicted"), options); + dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, color ?? this.colors[colourIndex], predictedFromTimestamp, this._endOfPeriod!, true, smooth, stepped, area, faint, extended, asset.name + " " + label + " " + i18next.t("predicted"), options); data.push(dataset); }); @@ -1249,7 +1275,7 @@ export class OrChart extends translate(i18next)(LitElement) { } - protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string | undefined, from: number, to: number, predicted: boolean, label?: string, options?: any, unit?: any) { + protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string | undefined, from: number, to: number, predicted: boolean, smooth: boolean, stepped: boolean, area: boolean, faint: boolean, extended: boolean, label?: string, options?: any, unit?: any) { const dataset = { name: label, @@ -1260,7 +1286,7 @@ export class OrChart extends translate(i18next)(LitElement) { lineStyle: { color: color, type: predicted ? [2, 4] : undefined, - opacity: 1 + opacity: faint ? 0.3 : 1, }, itemStyle: { color: color @@ -1269,6 +1295,10 @@ export class OrChart extends translate(i18next)(LitElement) { // @ts-ignore valueFormatter: value => value + unit }, + smooth: smooth, + step: stepped ? 'end' : undefined, + areaStyle: area ? { color: color, opacity: faint ? 0.1 : 0.3 } : undefined, + //emphasis: { // focus: 'self', //hide other lines when hovering over one //}, @@ -1318,6 +1348,8 @@ export class OrChart extends translate(i18next)(LitElement) { query.interval = (intervalArr[0].toString() + " " + intervalArr[1].toString()); // for example: "5 minute" } + //HIER EXTENDER TOEVOEGEN + if(!predicted) { response = await manager.rest.api.AssetDatapointResource.getDatapoints(asset.id, attribute.name, query, options) } else { diff --git a/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts b/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts index c635c3a0ab..7612d84b96 100644 --- a/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts +++ b/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts @@ -66,7 +66,7 @@ const styling = css` position: relative; cursor: pointer; display: flex; - flex-direction: row; + flex-direction: column; align-items: stretch; gap: 10px; padding: 0; @@ -91,7 +91,7 @@ const styling = css` flex: 1; justify-content: end; align-items: center; - display: flex; + display: none; gap: 8px; } @@ -124,6 +124,7 @@ const styling = css` .attribute-list-item:hover .attribute-list-item-actions { background: white; z-index: 1; + display: flex; } .attribute-list-item:hover .button-action { @@ -252,17 +253,19 @@ export class AttributesPanel extends LitElement { const label = Util.getAttributeLabel(attribute, descriptors[0], asset.type, true); return html`
-
- ${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(asset.type))} -
-
- ${when(!!this.attributeLabelCallback, +
+
+ ${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(asset.type))} +
+
+ ${when(!!this.attributeLabelCallback, () => this.attributeLabelCallback!(asset, attribute, label), () => html` - ${asset.name} - ${label} - ` - )} + ${asset.name} + ${label} + ` + ) } +
diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index cacd1b8fb6..4182c7e538 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -23,7 +23,7 @@ const styling = css` @customElement("chart-settings") export class ChartSettings extends WidgetSettings { - protected readonly widgetConfig!: ChartWidgetConfig + protected readonly widgetConfig!: ChartWidgetConfig; protected timePresetOptions: Map = new Map(); protected samplingOptions: Map = new Map(); @@ -50,20 +50,80 @@ export class ChartSettings extends WidgetSettings { const samplingValue = Array.from(this.samplingOptions.entries()).find((entry => entry[1] === this.widgetConfig.datapointQuery.type))![0] const attributeLabelCallback = (asset: Asset, attribute: Attribute, attributeLabel: string) => { const isOnRightAxis = isMultiAxis && this.widgetConfig.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isFaint = this.widgetConfig.attributeSettings.faintAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isSmooth = this.widgetConfig.attributeSettings.smoothAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isStepped = this.widgetConfig.attributeSettings.steppedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isArea = this.widgetConfig.attributeSettings.areaAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isExtended = this.widgetConfig.attributeSettings.extendedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; return html` ${asset.name} ${attributeLabel} ${when(isOnRightAxis, () => html` - +
+ `)} + ${when(isFaint, () => html` +
+ `)} + ${when(isSmooth, () => html` +
+ `)} + ${when(isStepped, () => html` +
+ `)} + ${when(isArea, () => html` +
+ `)} + ${when(isExtended, () => html` +
`)} ` + + //return html` + // ${asset.name} + // ${attributeLabel} + // ${when(isOnRightAxis, () => html` + // + // `)} + //` } const attributeActionCallback = (attributeRef: AttributeRef): AttributeAction[] => { - return [{ - icon: this.widgetConfig.rightAxisAttributes.includes(attributeRef) ? "arrow-right-bold" : "arrow-left-bold", - tooltip: i18next.t('dashboard.toggleAxis'), - disabled: false - }] + return [ + { + icon: 'palette', + tooltip: i18next.t('dashboard.lineColor'), + disabled: false + }, + { + icon: 'chart-bell-curve-cumulative', + tooltip: i18next.t("dashboard.smooth"), + disabled: false + }, + { + icon: 'square-wave', + tooltip: i18next.t('dashboard.stepped'), + disabled: false + }, + { + icon: 'chart-areaspline-variant', + tooltip: i18next.t('dashboard.fill'), + disabled: false + }, + { + icon: 'arrange-send-backward', + tooltip: i18next.t('dashboard.faint'), + disabled: false + }, + { + icon: 'arrow-expand-right', + tooltip: i18next.t('dashboard.extendData'), + disabled: false + }, + { + icon: this.widgetConfig.rightAxisAttributes.includes(attributeRef) ? "arrow-right-bold" : "arrow-left-bold", + tooltip: i18next.t('dashboard.toggleAxis'), + disabled: false + } + ] } return html`
@@ -236,13 +296,86 @@ export class ChartSettings extends WidgetSettings { // When a user clicks on ANY action in the attribute list, we want to switch between LEFT and RIGHT axis. // Since that is the only action, there is no need to check the ev.action variable. protected onAttributeAction(ev: AttributeActionEvent) { - if(this.widgetConfig.attributeRefs.indexOf(ev.detail.attributeRef) >= 0) { - if(this.widgetConfig.rightAxisAttributes.includes(ev.detail.attributeRef)) { - this.removeFromRightAxis(ev.detail.attributeRef); - } else { - this.addToRightAxis(ev.detail.attributeRef); - } - this.notifyConfigUpdate(); + const { asset ,attributeRef, action } = ev.detail; + + switch (action.icon) { + case "arrow-right-bold": + case "arrow-left-bold": + if (this.widgetConfig.attributeRefs.indexOf(attributeRef) >= 0) { + if (this.widgetConfig.rightAxisAttributes.includes(attributeRef)) { + this.removeFromRightAxis(attributeRef); + } else { + this.addToRightAxis(attributeRef); + } + this.notifyConfigUpdate(); + } + break; + case "palette": // Change color + const colorInput = document.createElement('input'); + colorInput.type = 'color'; + colorInput.style.border = 'none'; + colorInput.style.height = '31px'; + colorInput.style.width = '31px'; + colorInput.style.padding = '1px 3px'; + colorInput.style.minHeight = '22px'; + colorInput.style.minWidth = '30px'; + colorInput.style.cursor = 'pointer'; + colorInput.addEventListener('change', (e: any) => { + this.widgetConfig.attributeSettings.colorPickedAttributes.set(attributeRef, e.target.value); + this.notifyConfigUpdate(); + }); + colorInput.click(); + break; + //return html` + // + //`; + //break; + case "chart-bell-curve-cumulative": + console.log('klikkerieklik'); + if (!this.widgetConfig.attributeSettings.smoothAttributes.includes(attributeRef)) { + this.widgetConfig.attributeSettings.smoothAttributes.push(attributeRef); + } else { + this.widgetConfig.attributeSettings.smoothAttributes.splice(this.widgetConfig.attributeSettings.areaAttributes.indexOf(attributeRef), 1); + + } + this.notifyConfigUpdate(); + break; + case "square-wave": + if (!this.widgetConfig.attributeSettings.steppedAttributes.includes(attributeRef)) { + this.widgetConfig.attributeSettings.steppedAttributes.push(attributeRef); + } else { + this.widgetConfig.attributeSettings.steppedAttributes.splice(this.widgetConfig.attributeSettings.areaAttributes.indexOf(attributeRef), 1); + } + this.notifyConfigUpdate(); + break; + case "chart-areaspline-variant": + if (!this.widgetConfig.attributeSettings.areaAttributes.includes(attributeRef)) { + this.widgetConfig.attributeSettings.areaAttributes.push(attributeRef); + } else { + this.widgetConfig.attributeSettings.areaAttributes.splice(this.widgetConfig.attributeSettings.areaAttributes.indexOf(attributeRef), 1); + } + this.notifyConfigUpdate(); + break; + case "arrange-send-backward": + if (!this.widgetConfig.attributeSettings.faintAttributes.includes(attributeRef)) { + this.widgetConfig.attributeSettings.faintAttributes.push(attributeRef); + } else { + this.widgetConfig.attributeSettings.faintAttributes.splice(this.widgetConfig.attributeSettings.faintAttributes.indexOf(attributeRef), 1); + } + this.notifyConfigUpdate(); + break; + case "arrow-expand-right": + if (!this.widgetConfig.attributeSettings.extendedAttributes.includes(attributeRef)) { + this.widgetConfig.attributeSettings.extendedAttributes.push(attributeRef); + } else { + this.widgetConfig.attributeSettings.extendedAttributes.splice(this.widgetConfig.attributeSettings.extendedAttributes.indexOf(attributeRef), 1); + } + this.notifyConfigUpdate(); + break; + default: + console.warn('Unknown action', action); } } diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index 6098119c56..2157c4246d 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -14,6 +14,15 @@ import "@openremote/or-chart"; export interface ChartWidgetConfig extends WidgetConfig { attributeRefs: AttributeRef[]; rightAxisAttributes: AttributeRef[]; + attributeSettings: { + rightAxisAttributes: AttributeRef[], + smoothAttributes: AttributeRef[], + steppedAttributes: AttributeRef[], + areaAttributes: AttributeRef[], + faintAttributes: AttributeRef[], + extendedAttributes: AttributeRef[], + colorPickedAttributes: Map + }, datapointQuery: AssetDatapointQueryUnion; chartOptions?: any; // ChartConfiguration<"line", ScatterDataPoint[]> showTimestampControls: boolean; @@ -56,6 +65,15 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { return { attributeRefs: [], rightAxisAttributes: [], + attributeSettings: { + rightAxisAttributes: [], + smoothAttributes: [], + steppedAttributes: [], + areaAttributes: [], + faintAttributes: [], + extendedAttributes: [], + colorPickedAttributes: new Map() + }, datapointQuery: { type: "lttb", fromTimestamp: dates[0].getTime(), @@ -197,6 +215,7 @@ export class ChartWidget extends OrAssetWidget { `, () => { return html` Date: Thu, 6 Mar 2025 16:43:13 +0100 Subject: [PATCH 11/20] all attribute settings working, needs code optimisation/cleanup --- docker-compose.yml | 4 +- profile/dev-ui.yml | 2 +- ui/component/or-chart/src/index.ts | 44 +++++++++---------- .../src/settings/chart-settings.ts | 20 ++++++--- .../src/widgets/chart-widget.ts | 5 ++- 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 779b2e8707..10c2682c16 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,8 @@ services: volumes: - postgresql-data:/var/lib/postgresql/data - manager-data:/storage + ports: + - 5432:5432 keycloak: restart: always @@ -64,7 +66,7 @@ services: ports: - "127.0.0.1:8405:8405" # Localhost metrics access environment: - OR_SETUP_TYPE: + OR_SETUP_TYPE: ${OR_SETUP_TYPE:-demo} OR_ADMIN_PASSWORD: OR_SETUP_RUN_ON_RESTART: OR_EMAIL_HOST: diff --git a/profile/dev-ui.yml b/profile/dev-ui.yml index 8e99947654..e1adf3e178 100644 --- a/profile/dev-ui.yml +++ b/profile/dev-ui.yml @@ -52,7 +52,7 @@ services: volumes: - manager-data:/storage environment: - OR_SETUP_RUN_ON_RESTART: ${OR_SETUP_RUN_ON_RESTART:-true} + OR_SETUP_RUN_ON_RESTART: ${OR_SETUP_RUN_ON_RESTART:-false} OR_DEV_MODE: ${OR_DEV_MODE:-true} ports: - "8080:8080" diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 854cbe5cf0..6807776bb3 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -56,8 +56,6 @@ import {ListItem} from "@openremote/or-mwc-components/or-mwc-list"; import { when } from "lit/directives/when.js"; import {createRef, Ref, ref } from "lit/directives/ref.js"; -//Chart.register(LineController, ScatterController, LineElement, PointElement, LinearScale, TimeScale, Title, Filler, Legend, Tooltip, ChartAnnotation); - export class OrChartEvent extends CustomEvent { public static readonly NAME = "or-chart-event"; @@ -376,6 +374,9 @@ export class OrChart extends translate(i18next)(LitElement) { @property({type: Array}) // List of AttributeRef that are shown on the right axis instead. public rightAxisAttributes: AttributeRef[] = []; + @property({type: Array}) + public colorPickedAttributes: Array<{ attributeRef: AttributeRef; color: string }> = []; + @property({type: Object}) public attributeSettings: { rightAxisAttributes: AttributeRef[], @@ -384,7 +385,7 @@ export class OrChart extends translate(i18next)(LitElement) { areaAttributes: AttributeRef[], faintAttributes: AttributeRef[], extendedAttributes: AttributeRef[], - colorPickedAttributes: Map + //colorPickedAttributes: Array<{ attributeRef: AttributeRef; color: string }>; } = { rightAxisAttributes: [], smoothAttributes: [], @@ -392,7 +393,7 @@ export class OrChart extends translate(i18next)(LitElement) { areaAttributes: [], faintAttributes: [], extendedAttributes: [], - colorPickedAttributes: new Map() + //colorPickedAttributes: [], }; @@ -494,7 +495,7 @@ export class OrChart extends translate(i18next)(LitElement) { } updated(changedProperties: PropertyValues) { - console.log('updated triggered'); + // console.log('updated triggered'); super.updated(changedProperties); @@ -505,14 +506,14 @@ export class OrChart extends translate(i18next)(LitElement) { } } - const reloadData = changedProperties.has("datapointQuery") || changedProperties.has("timePresetKey") || changedProperties.has("timeframe") || + const reloadData = changedProperties.has('colorPickedAttributes') || changedProperties.has("datapointQuery") || changedProperties.has("timePresetKey") || changedProperties.has("timeframe") || changedProperties.has("attributeSettings") || changedProperties.has("rightAxisAttributes") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); if (reloadData) { - console.log('reloadData triggered'); + // console.log('reloadData triggered'); this._data = undefined; if (this._chart) { - console.log('releadData found _chart exists so disposing'); + // console.log('releadData found _chart exists so disposing'); this._chart.dispose(); this._chart = undefined; } @@ -520,7 +521,7 @@ export class OrChart extends translate(i18next)(LitElement) { } if (!this._data) { - console.log("Data is not loaded yet"); + // console.log("Data is not loaded yet"); return; } @@ -667,7 +668,7 @@ export class OrChart extends translate(i18next)(LitElement) { } } - console.log(this._chartOptions); + // console.log(this._chartOptions); // Initialize echarts instance this._chart = init(this._chartElem); @@ -876,7 +877,7 @@ export class OrChart extends translate(i18next)(LitElement) { async loadSettings(reset: boolean) { - console.log('loadSettings triggered'); + // console.log('loadSettings triggered'); if(this.assetAttributes == undefined || reset) { this.assetAttributes = []; @@ -961,7 +962,7 @@ export class OrChart extends translate(i18next)(LitElement) { } async saveSettings() { - console.log('saveSettings triggered'); + // console.log('saveSettings triggered'); if (!this.panelName) { return; @@ -1066,7 +1067,7 @@ export class OrChart extends translate(i18next)(LitElement) { protected _cleanup() { console.log('cleanup triggered'); if (this._chart) { - console.log('cleanup found _chart exists so disposing'); + //('cleanup found _chart exists so disposing'); this._chart.dispose(); this._chart = undefined; this.requestUpdate(); @@ -1090,7 +1091,7 @@ export class OrChart extends translate(i18next)(LitElement) { } protected _getAttributeOptionsOld(): [string, string][] | undefined { - console.log('getAttributeOptionsOld triggered'); + //('getAttributeOptionsOld triggered'); if(!this.activeAsset || !this.activeAsset.attributes) { return; } @@ -1170,7 +1171,7 @@ export class OrChart extends translate(i18next)(LitElement) { } protected async _loadData() { - console.log('loadData triggered'); + // console.log('loadData triggered'); if ((this._data && !this._zoomChanged) || !this.assetAttributes || !this.assets || (this.assets.length === 0 && !this.dataProvider) || (this.assetAttributes.length === 0 && !this.dataProvider) || !this.datapointQuery) { return; } @@ -1217,6 +1218,9 @@ export class OrChart extends translate(i18next)(LitElement) { } else { this._dataAbortController = new AbortController(); promises = this.assetAttributes.map(async ([assetIndex, attribute], index) => { + console.log('Stepped attrs:' + JSON.stringify(this.attributeSettings.steppedAttributes)); + console.log('Collored attrs in chart:' + JSON.stringify(this.colorPickedAttributes)); + console.log('cheese:' + this.colorPickedAttributes); const asset = this.assets[assetIndex]; const shownOnRightAxis = !!this.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); @@ -1225,7 +1229,8 @@ export class OrChart extends translate(i18next)(LitElement) { const area = !!this.attributeSettings.areaAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); const faint = !!this.attributeSettings.faintAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); const extended = !!this.attributeSettings.extendedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); - const color = Array.from(this.attributeSettings.colorPickedAttributes).find(([{ name, id }]) => name === attribute.name && id === asset.id)?.[1]; + const color = this.colorPickedAttributes.find(({ attributeRef }) => attributeRef.name === attribute.name && attributeRef.id === asset.id)?.color; + console.log('Picked color found: ' + color); const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset.type, attribute.name, attribute); const label = Util.getAttributeLabel(attribute, descriptors[0], asset.type, false); const unit = Util.resolveUnits(Util.getAttributeUnits(attribute, descriptors[0], asset.type)); @@ -1249,7 +1254,6 @@ export class OrChart extends translate(i18next)(LitElement) { } this._data = data; - console.log(data); // DEBUGGING this._loading = false; this._zoomChanged = false; @@ -1306,7 +1310,7 @@ export class OrChart extends translate(i18next)(LitElement) { //areaStyle: fill settings } - console.log('load attribute data triggered'); + //console.log('load attribute data triggered'); if (asset.id && attribute.name && this.datapointQuery) { let response: GenericAxiosResponse[]>; @@ -1320,10 +1324,6 @@ export class OrChart extends translate(i18next)(LitElement) { query.toTimestamp = this._zoomEndOfPeriod; } - console.log('query.fromTimestamp: ' + query.fromTimestamp + asset.id + attribute.name + 'Predicted: ' + predicted); - console.log('query.toTimestamp: ' + query.toTimestamp + asset.id + attribute.name + 'Predicted: ' + predicted); - - if(query.type == 'lttb') { // If amount of data points is set, only allow a maximum of 1 points per pixel in width diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index 4182c7e538..d64e6279c2 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -25,6 +25,8 @@ export class ChartSettings extends WidgetSettings { protected readonly widgetConfig!: ChartWidgetConfig; + + protected timePresetOptions: Map = new Map(); protected samplingOptions: Map = new Map(); @@ -321,17 +323,21 @@ export class ChartSettings extends WidgetSettings { colorInput.style.minWidth = '30px'; colorInput.style.cursor = 'pointer'; colorInput.addEventListener('change', (e: any) => { - this.widgetConfig.attributeSettings.colorPickedAttributes.set(attributeRef, e.target.value); + const color = e.target.value; + const existingIndex = this.widgetConfig.colorPickedAttributes.findIndex(item => + item.attributeRef.id === attributeRef.id && item.attributeRef.name === attributeRef.name + ); + if (existingIndex >= 0) { + this.widgetConfig.colorPickedAttributes[existingIndex].color = color; + } else { + this.widgetConfig.colorPickedAttributes.push({ attributeRef, color }); + } this.notifyConfigUpdate(); + console.log("Color picked in settings: ", e.target.value); + console.log('Updated widgetconfig colors: ' + JSON.stringify(this.widgetConfig.colorPickedAttributes)); }); colorInput.click(); break; - //return html` - // - //`; - //break; case "chart-bell-curve-cumulative": console.log('klikkerieklik'); if (!this.widgetConfig.attributeSettings.smoothAttributes.includes(attributeRef)) { diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index 2157c4246d..55f4240afc 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -14,6 +14,7 @@ import "@openremote/or-chart"; export interface ChartWidgetConfig extends WidgetConfig { attributeRefs: AttributeRef[]; rightAxisAttributes: AttributeRef[]; + colorPickedAttributes: Array<{ attributeRef: AttributeRef; color: string }>; attributeSettings: { rightAxisAttributes: AttributeRef[], smoothAttributes: AttributeRef[], @@ -21,7 +22,6 @@ export interface ChartWidgetConfig extends WidgetConfig { areaAttributes: AttributeRef[], faintAttributes: AttributeRef[], extendedAttributes: AttributeRef[], - colorPickedAttributes: Map }, datapointQuery: AssetDatapointQueryUnion; chartOptions?: any; // ChartConfiguration<"line", ScatterDataPoint[]> @@ -65,6 +65,7 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { return { attributeRefs: [], rightAxisAttributes: [], + colorPickedAttributes: [], attributeSettings: { rightAxisAttributes: [], smoothAttributes: [], @@ -72,7 +73,6 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { areaAttributes: [], faintAttributes: [], extendedAttributes: [], - colorPickedAttributes: new Map() }, datapointQuery: { type: "lttb", @@ -215,6 +215,7 @@ export class ChartWidget extends OrAssetWidget { `, () => { return html` Date: Thu, 6 Mar 2025 17:54:17 +0100 Subject: [PATCH 12/20] tweaked attributes panel css --- .../src/panels/attributes-panel.ts | 2 +- .../src/settings/chart-settings.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts b/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts index 7612d84b96..adfcce7b95 100644 --- a/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts +++ b/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts @@ -55,7 +55,7 @@ export class AttributesSelectEvent extends CustomEvent<{ assets: Asset[], attrib const styling = css` #attribute-list { - overflow: auto; + overflow: visible; flex: 1 1 0; width: 100%; display: flex; diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index d64e6279c2..de92d30aa8 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -61,22 +61,22 @@ export class ChartSettings extends WidgetSettings { ${asset.name} ${attributeLabel} ${when(isOnRightAxis, () => html` -
+ `)} ${when(isFaint, () => html` -
+ `)} ${when(isSmooth, () => html` -
+ `)} ${when(isStepped, () => html` -
+ `)} ${when(isArea, () => html` -
+ `)} ${when(isExtended, () => html` -
+ `)} ` From 30687dd4860f70d925f3bc2ec088ec36959e6b10 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Mon, 10 Mar 2025 18:31:31 +0100 Subject: [PATCH 13/20] lots of additions --- ui/app/shared/locales/en/or.json | 5 +- ui/component/or-chart/src/index.ts | 128 ++++++++++++------ .../src/panels/attributes-panel.ts | 1 + .../src/settings/chart-settings.ts | 75 ++++++---- 4 files changed, 136 insertions(+), 73 deletions(-) diff --git a/ui/app/shared/locales/en/or.json b/ui/app/shared/locales/en/or.json index cbda0317cb..e20c694b81 100644 --- a/ui/app/shared/locales/en/or.json +++ b/ui/app/shared/locales/en/or.json @@ -35,8 +35,8 @@ "FR": "FR", "SA": "SA", "SU": "SU", - "left": "left", - "right": "right", + "left": "Left", + "right": "Right", "sceneStartTime": "Scene start time", "sceneConfiguration": "Scene Configuration", "temperature": "Temperature", @@ -759,6 +759,7 @@ "stepped": "Stepped", "fill": "Fill", "extendData": "Extend last datapoint", + "extended": "(last known)", "lineColor": "Line color" }, diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 6807776bb3..cde53a3696 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -20,31 +20,17 @@ import "@openremote/or-asset-tree"; import "@openremote/or-mwc-components/or-mwc-input"; import "@openremote/or-components/or-panel"; import "@openremote/or-translate"; -import {ECharts, EChartsOption, init} from "echarts"; +import {ECharts, EChartsOption, init, graphic} from "echarts"; import { - Chart, - ChartConfiguration, - ChartDataset, - Filler, - Legend, - LinearScale, - LineController, - LineElement, - PointElement, - ScatterController, - ScatterDataPoint, - TimeScale, - TimeScaleOptions, + TimeUnit, - Title, - Tooltip + } from "chart.js"; import {InputType, OrMwcInput} from "@openremote/or-mwc-components/or-mwc-input"; import "@openremote/or-components/or-loading-indicator"; import moment from "moment"; import {OrAssetTreeSelectionEvent} from "@openremote/or-asset-tree"; import {getAssetDescriptorIconTemplate} from "@openremote/or-icon"; -import ChartAnnotation, {AnnotationOptions} from "chartjs-plugin-annotation"; import "chartjs-adapter-moment"; import {GenericAxiosResponse, isAxiosError} from "@openremote/rest"; import {OrAttributePicker, OrAttributePickerPickedEvent} from "@openremote/or-attribute-picker"; @@ -802,10 +788,11 @@ export class OrChart extends translate(i18next)(LitElement) { ${this.assetAttributes && this.assetAttributes.map(([assetIndex, attr], index) => { const asset: Asset | undefined = this.assets[assetIndex]; const colourIndex = index % this.colors.length; + const color = this.colorPickedAttributes.find(({ attributeRef }) => attributeRef.name === attr.name && attributeRef.id === asset.id)?.color; const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset!.type, attr.name, attr); const label = Util.getAttributeLabel(attr, descriptors[0], asset!.type, true); const axisNote = (this.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; - const bgColor = this.colors[colourIndex] || ""; + const bgColor = ( color ?? this.colors[colourIndex] ) || ""; return html`
${getAssetDescriptorIconTemplate(AssetModelUtil.getAssetDescriptor(this.assets[assetIndex]!.type!), undefined, undefined, bgColor.split('#')[1])} @@ -852,7 +839,11 @@ export class OrChart extends translate(i18next)(LitElement) { let options = this._chart.getOption(); if (options.series && Array.isArray(options.series)) { options.series.forEach(function (series) { - series.lineStyle.opacity = 1; + if (series.lineStyle.opacity == 0.2) { + series.lineStyle.opacity = 0.31; + } else if (series.lineStyle.opacity != 0.31) { + series.lineStyle.opacity = 1; + } }); } this._chart.setOption(options); @@ -865,7 +856,12 @@ export class OrChart extends translate(i18next)(LitElement) { if (options.series && Array.isArray(options.series)) { options.series.forEach(function (series) { if (series.assetId != assetId || series.attrName != attrName) { - series.lineStyle.opacity = 0.2; + if (series.lineStyle.opacity == 0.31) { // 0.31 is faint setting, 1 is normal + series.lineStyle.opacity = 0.2; + } else { + series.lineStyle.opacity = 0.3; + } + } }); } @@ -1236,15 +1232,27 @@ export class OrChart extends translate(i18next)(LitElement) { const unit = Util.resolveUnits(Util.getAttributeUnits(attribute, descriptors[0], asset.type)); const colourIndex = index % this.colors.length; const options = { signal: this._dataAbortController?.signal }; - let dataset = await this._loadAttributeData(asset, attribute, color ?? this.colors[colourIndex], this._startOfPeriod!, this._endOfPeriod!, false, smooth, stepped, area, faint, extended, asset.name + " " + label, options, unit); + //Load Historic Data + let dataset = await this._loadAttributeData(asset, attribute, color ?? this.colors[colourIndex], this._startOfPeriod!, this._endOfPeriod!, false, smooth, stepped, area, faint, false, asset.name + " " + label, options, unit); (dataset as any).assetId = asset.id; (dataset as any).attrName = attribute.name; (dataset as any).unit = unit; (dataset as any).yAxisIndex = shownOnRightAxis ? '1' : '0'; + (dataset as any).color = color ?? this.colors[colourIndex]; data.push(dataset); - - dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, color ?? this.colors[colourIndex], predictedFromTimestamp, this._endOfPeriod!, true, smooth, stepped, area, faint, extended, asset.name + " " + label + " " + i18next.t("predicted"), options); + //Load Predicted Data + dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, color ?? this.colors[colourIndex], predictedFromTimestamp, this._endOfPeriod!, true, smooth, stepped, area, faint, false , asset.name + " " + label + " " + i18next.t("predicted"), options, unit); data.push(dataset); + //Load Extended Data + let bsNumber = 1; //inserted in from and to, however these are not used in _loadAttributeData anyway, the function references variables outside of it (bad practice) + if (extended) { + console.log('Extended data requested'); + dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, color ?? this.colors[colourIndex], bsNumber, bsNumber, false, false, false, area, faint, extended, asset.name + " " + label + " " + i18next.t("dashboard.extended"), options, unit); + data.push(dataset); + } + + //Is it actually efficient to query three times ? think this can be way more efficient. + }); } @@ -1279,18 +1287,22 @@ export class OrChart extends translate(i18next)(LitElement) { } - protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string | undefined, from: number, to: number, predicted: boolean, smooth: boolean, stepped: boolean, area: boolean, faint: boolean, extended: boolean, label?: string, options?: any, unit?: any) { + protected async _loadAttributeData(asset: Asset, attribute: Attribute, color: string, from: number, to: number, predicted: boolean, smooth: boolean, stepped: boolean, area: boolean, faint: boolean, extended: boolean, label?: string, options?: any, unit?: any) { + + function rgba (color: string, alpha: number) { + return `rgba(${parseInt(color.slice(-6,-4), 16)}, ${parseInt(color.slice(-4,-2), 16)}, ${parseInt(color.slice(-2), 16)}, ${alpha})`; + } const dataset = { name: label, type: 'line', showSymbol: false, - data: {}, + data: [] as [any, any][], sampling: 'lttb', lineStyle: { color: color, - type: predicted ? [2, 4] : undefined, - opacity: faint ? 0.3 : 1, + type: predicted ? [2, 4] : extended ? [0.8, 10] : undefined, + opacity: faint ? 0.31 : 1, }, itemStyle: { color: color @@ -1301,16 +1313,18 @@ export class OrChart extends translate(i18next)(LitElement) { }, smooth: smooth, step: stepped ? 'end' : undefined, - areaStyle: area ? { color: color, opacity: faint ? 0.1 : 0.3 } : undefined, - - //emphasis: { - // focus: 'self', //hide other lines when hovering over one - //}, - //smooth: true, - //areaStyle: fill settings + areaStyle: area ? {color: new graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: rgba(color, faint ? 0.1 : 0.5) + }, + { + offset: 1, + color: rgba(color, 0) + } + ])} as any : undefined, } - //console.log('load attribute data triggered'); if (asset.id && attribute.name && this.datapointQuery) { let response: GenericAxiosResponse[]>; @@ -1325,10 +1339,10 @@ export class OrChart extends translate(i18next)(LitElement) { } if(query.type == 'lttb') { + // NEEDS TO BE REWORKED, ECHARTS HAS LTTB BUILT IN, BUT THIS CAN BE A SETTING IN CHART-SETTINGS // If amount of data points is set, only allow a maximum of 1 points per pixel in width // Otherwise, dynamically set amount of data points based on chart width (1000px = 200 data points) - // >-------------IS DIT NOG WEL NODIG? ECHARTS HEEFT ZELF LTTB //if(query.amountOfPoints) { // if(this._chartElem?.clientWidth > 0) { // query.amountOfPoints = Math.min(query.amountOfPoints, this._chartElem?.clientWidth) @@ -1348,13 +1362,17 @@ export class OrChart extends translate(i18next)(LitElement) { query.interval = (intervalArr[0].toString() + " " + intervalArr[1].toString()); // for example: "5 minute" } - //HIER EXTENDER TOEVOEGEN - if(!predicted) { - response = await manager.rest.api.AssetDatapointResource.getDatapoints(asset.id, attribute.name, query, options) - } else { - response = await manager.rest.api.AssetPredictedDatapointResource.getPredictedDatapoints(asset.id, attribute.name, query, options) - } + if (predicted) { + response = await manager.rest.api.AssetPredictedDatapointResource.getPredictedDatapoints(asset.id, attribute.name, query, options); + } else { + if (extended) { + query.type = 'nearest'; + query.timestamp = new Date().toISOString() + } + + response = await manager.rest.api.AssetDatapointResource.getDatapoints(asset.id, attribute.name, query, options); + } let data: ValueDatapoint[] = []; @@ -1366,10 +1384,32 @@ export class OrChart extends translate(i18next)(LitElement) { dataset.data = data.map(point => [point.x, point.y]); dataset.showSymbol = data.length <=30; //Only show symbols when there are 30 or fewer data points to be shown } - } - + if (extended) { + if (dataset.data.length > 0) { + // Get the first datapoint's timestamp + const firstPointTime = new Date(dataset.data[0][0]).getTime(); + + // If the first point is earlier than startOfPeriod, use startOfPeriod as the starting timestamp + const startTimestamp = firstPointTime < query.fromTimestamp! ? + new Date(query.fromTimestamp!).toISOString() : + dataset.data[0][0]; + + // Use endOfPeriod if it's earlier than now, otherwise use the current time + const now = new Date().getTime(); + const endTimestamp = query.toTimestamp! < now ? + new Date(query.toTimestamp!).toISOString() : + new Date().toISOString(); + // Create a clean extended line by removing any existing points and adding just two points: + // One at the appropriate start time and one at the current time + dataset.data = [ + [startTimestamp, dataset.data[0][1]], + [endTimestamp, dataset.data[0][1]] + ]; + } + } + } return dataset; } diff --git a/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts b/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts index adfcce7b95..86d5eb6381 100644 --- a/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts +++ b/ui/component/or-dashboard-builder/src/panels/attributes-panel.ts @@ -93,6 +93,7 @@ const styling = css` align-items: center; display: none; gap: 8px; + margin-bottom: 20px; } .attribute-list-item-bullet { diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index de92d30aa8..c62dec7e10 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -79,15 +79,9 @@ export class ChartSettings extends WidgetSettings { `)} ` - - //return html` - // ${asset.name} - // ${attributeLabel} - // ${when(isOnRightAxis, () => html` - // - // `)} - //` } + + const attributeActionCallback = (attributeRef: AttributeRef): AttributeAction[] => { return [ { @@ -120,10 +114,16 @@ export class ChartSettings extends WidgetSettings { tooltip: i18next.t('dashboard.extendData'), disabled: false }, + { icon: this.widgetConfig.rightAxisAttributes.includes(attributeRef) ? "arrow-right-bold" : "arrow-left-bold", tooltip: i18next.t('dashboard.toggleAxis'), disabled: false + }, + { + icon: 'mdi-blank', + tooltip: '', + disabled: true } ] } @@ -295,11 +295,14 @@ export class ChartSettings extends WidgetSettings { } } - // When a user clicks on ANY action in the attribute list, we want to switch between LEFT and RIGHT axis. - // Since that is the only action, there is no need to check the ev.action variable. + // Check which icon was pressed and act accordingly. protected onAttributeAction(ev: AttributeActionEvent) { const { asset ,attributeRef, action } = ev.detail; + const findAttributeIndex = (array: AttributeRef[], ref: AttributeRef) => { + return array.findIndex(item => item.id === ref.id && item.name === ref.name); + }; + switch (action.icon) { case "arrow-right-bold": case "arrow-left-bold": @@ -333,56 +336,53 @@ export class ChartSettings extends WidgetSettings { this.widgetConfig.colorPickedAttributes.push({ attributeRef, color }); } this.notifyConfigUpdate(); - console.log("Color picked in settings: ", e.target.value); - console.log('Updated widgetconfig colors: ' + JSON.stringify(this.widgetConfig.colorPickedAttributes)); }); colorInput.click(); break; case "chart-bell-curve-cumulative": - console.log('klikkerieklik'); - if (!this.widgetConfig.attributeSettings.smoothAttributes.includes(attributeRef)) { + if (findAttributeIndex(this.widgetConfig.attributeSettings.smoothAttributes, attributeRef) < 0) { this.widgetConfig.attributeSettings.smoothAttributes.push(attributeRef); } else { - this.widgetConfig.attributeSettings.smoothAttributes.splice(this.widgetConfig.attributeSettings.areaAttributes.indexOf(attributeRef), 1); - + this.widgetConfig.attributeSettings.smoothAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.smoothAttributes, attributeRef), 1); } this.notifyConfigUpdate(); break; case "square-wave": - if (!this.widgetConfig.attributeSettings.steppedAttributes.includes(attributeRef)) { + if (findAttributeIndex(this.widgetConfig.attributeSettings.steppedAttributes, attributeRef) < 0) { this.widgetConfig.attributeSettings.steppedAttributes.push(attributeRef); } else { - this.widgetConfig.attributeSettings.steppedAttributes.splice(this.widgetConfig.attributeSettings.areaAttributes.indexOf(attributeRef), 1); + this.widgetConfig.attributeSettings.steppedAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.steppedAttributes, attributeRef), 1); } this.notifyConfigUpdate(); break; case "chart-areaspline-variant": - if (!this.widgetConfig.attributeSettings.areaAttributes.includes(attributeRef)) { + if (findAttributeIndex(this.widgetConfig.attributeSettings.areaAttributes, attributeRef) < 0) { this.widgetConfig.attributeSettings.areaAttributes.push(attributeRef); } else { - this.widgetConfig.attributeSettings.areaAttributes.splice(this.widgetConfig.attributeSettings.areaAttributes.indexOf(attributeRef), 1); + this.widgetConfig.attributeSettings.areaAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.areaAttributes, attributeRef), 1); } this.notifyConfigUpdate(); break; case "arrange-send-backward": - if (!this.widgetConfig.attributeSettings.faintAttributes.includes(attributeRef)) { + if (findAttributeIndex(this.widgetConfig.attributeSettings.faintAttributes, attributeRef) < 0) { this.widgetConfig.attributeSettings.faintAttributes.push(attributeRef); } else { - this.widgetConfig.attributeSettings.faintAttributes.splice(this.widgetConfig.attributeSettings.faintAttributes.indexOf(attributeRef), 1); + this.widgetConfig.attributeSettings.faintAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.faintAttributes, attributeRef), 1); } this.notifyConfigUpdate(); break; - case "arrow-expand-right": - if (!this.widgetConfig.attributeSettings.extendedAttributes.includes(attributeRef)) { + case "arrow-expand-right": + if (findAttributeIndex(this.widgetConfig.attributeSettings.extendedAttributes, attributeRef) < 0) { this.widgetConfig.attributeSettings.extendedAttributes.push(attributeRef); } else { - this.widgetConfig.attributeSettings.extendedAttributes.splice(this.widgetConfig.attributeSettings.extendedAttributes.indexOf(attributeRef), 1); + this.widgetConfig.attributeSettings.extendedAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.extendedAttributes, attributeRef), 1); } this.notifyConfigUpdate(); break; default: - console.warn('Unknown action', action); + console.warn('Unknown attribute panel action:', action); } + console.log("end of onAttributeAction" + JSON.stringify(this.widgetConfig.attributeSettings)); } // When the list of attributeRefs is changed by the asset selector, @@ -390,11 +390,32 @@ export class ChartSettings extends WidgetSettings { // Also update the WidgetConfig attributeRefs field as usual protected onAttributesSelect(ev: AttributesSelectEvent) { const removedAttributeRefs = this.widgetConfig.attributeRefs.filter(ar => !ev.detail.attributeRefs.includes(ar)); - removedAttributeRefs.forEach(raf => this.removeFromRightAxis(raf)); + + removedAttributeRefs.forEach(raf => { + this.removeFromRightAxis(raf); + this.removeFromAttributeSettings(raf); + this.removeFromColorPickedAttributes(raf); + }); + this.widgetConfig.attributeRefs = ev.detail.attributeRefs; this.notifyConfigUpdate(); } + protected removeFromAttributeSettings(attributeRef: AttributeRef) { + const settings = this.widgetConfig.attributeSettings; + settings.smoothAttributes = settings.smoothAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); + settings.steppedAttributes = settings.steppedAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); + settings.areaAttributes = settings.areaAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); + settings.faintAttributes = settings.faintAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); + settings.extendedAttributes = settings.extendedAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); + } + + protected removeFromColorPickedAttributes(attributeRef: AttributeRef) { + this.widgetConfig.colorPickedAttributes = this.widgetConfig.colorPickedAttributes.filter( + item => item.attributeRef.id !== attributeRef.id || item.attributeRef.name !== attributeRef.name + ); + } + protected addToRightAxis(attributeRef: AttributeRef, notify = false) { if(!this.widgetConfig.rightAxisAttributes.includes(attributeRef)) { this.widgetConfig.rightAxisAttributes.push(attributeRef); From 960dedb3e0dd38bd6fd7cc13861e5cf516b4020d Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Tue, 11 Mar 2025 10:48:18 +0100 Subject: [PATCH 14/20] cleaned up some functions/vars --- ui/component/or-chart/src/index.ts | 14 +-- .../src/settings/chart-settings.ts | 116 ++++++------------ .../src/widgets/chart-widget.ts | 4 +- 3 files changed, 47 insertions(+), 87 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index cde53a3696..8d747cfa9d 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -357,8 +357,8 @@ export class OrChart extends translate(i18next)(LitElement) { @property({type: Object}) public assetAttributes: [number, Attribute][] = []; - @property({type: Array}) // List of AttributeRef that are shown on the right axis instead. - public rightAxisAttributes: AttributeRef[] = []; + //@property({type: Array}) // List of AttributeRef that are shown on the right axis instead. + //public rightAxisAttributes: AttributeRef[] = []; @property({type: Array}) public colorPickedAttributes: Array<{ attributeRef: AttributeRef; color: string }> = []; @@ -371,7 +371,6 @@ export class OrChart extends translate(i18next)(LitElement) { areaAttributes: AttributeRef[], faintAttributes: AttributeRef[], extendedAttributes: AttributeRef[], - //colorPickedAttributes: Array<{ attributeRef: AttributeRef; color: string }>; } = { rightAxisAttributes: [], smoothAttributes: [], @@ -379,7 +378,6 @@ export class OrChart extends translate(i18next)(LitElement) { areaAttributes: [], faintAttributes: [], extendedAttributes: [], - //colorPickedAttributes: [], }; @@ -493,7 +491,7 @@ export class OrChart extends translate(i18next)(LitElement) { } const reloadData = changedProperties.has('colorPickedAttributes') || changedProperties.has("datapointQuery") || changedProperties.has("timePresetKey") || changedProperties.has("timeframe") || - changedProperties.has("attributeSettings") || changedProperties.has("rightAxisAttributes") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); + changedProperties.has("attributeSettings") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); if (reloadData) { // console.log('reloadData triggered'); @@ -587,7 +585,7 @@ export class OrChart extends translate(i18next)(LitElement) { }, { type: 'value', - show: this.rightAxisAttributes.length > 0, + show: this.attributeSettings.rightAxisAttributes.length > 0, axisLine: { lineStyle: {color: this._style.getPropertyValue("--internal-or-chart-text-color")}}, boundaryGap: ['10%', '10%'], scale: true, @@ -791,7 +789,7 @@ export class OrChart extends translate(i18next)(LitElement) { const color = this.colorPickedAttributes.find(({ attributeRef }) => attributeRef.name === attr.name && attributeRef.id === asset.id)?.color; const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset!.type, attr.name, attr); const label = Util.getAttributeLabel(attr, descriptors[0], asset!.type, true); - const axisNote = (this.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; + const axisNote = (this.attributeSettings.rightAxisAttributes.find(ar => asset!.id === ar.id && attr.name === ar.name)) ? i18next.t('right') : undefined; const bgColor = ( color ?? this.colors[colourIndex] ) || ""; return html`
@@ -1219,7 +1217,7 @@ export class OrChart extends translate(i18next)(LitElement) { console.log('cheese:' + this.colorPickedAttributes); const asset = this.assets[assetIndex]; - const shownOnRightAxis = !!this.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); + const shownOnRightAxis = !!this.attributeSettings.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); const smooth = !!this.attributeSettings.smoothAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); const stepped = !!this.attributeSettings.steppedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); const area = !!this.attributeSettings.areaAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index c62dec7e10..bb146d72d8 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -46,37 +46,38 @@ export class ChartSettings extends WidgetSettings { const attributeFilter: (attr: Attribute) => boolean = (attr): boolean => { return ["boolean", "positiveInteger", "positiveNumber", "number", "long", "integer", "bigInteger", "negativeInteger", "negativeNumber", "bigNumber", "integerByte", "direction"].includes(attr.type!) }; + const attrSettings = this.widgetConfig.attributeSettings; const min = this.widgetConfig.chartOptions.options?.scales?.y?.min; const max = this.widgetConfig.chartOptions.options?.scales?.y?.max; - const isMultiAxis = this.widgetConfig.rightAxisAttributes.length > 0; + const isMultiAxis = attrSettings.rightAxisAttributes.length > 0; const samplingValue = Array.from(this.samplingOptions.entries()).find((entry => entry[1] === this.widgetConfig.datapointQuery.type))![0] const attributeLabelCallback = (asset: Asset, attribute: Attribute, attributeLabel: string) => { - const isOnRightAxis = isMultiAxis && this.widgetConfig.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; - const isFaint = this.widgetConfig.attributeSettings.faintAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; - const isSmooth = this.widgetConfig.attributeSettings.smoothAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; - const isStepped = this.widgetConfig.attributeSettings.steppedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; - const isArea = this.widgetConfig.attributeSettings.areaAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; - const isExtended = this.widgetConfig.attributeSettings.extendedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isOnRightAxis = isMultiAxis && attrSettings.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isFaint = attrSettings.faintAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isSmooth = attrSettings.smoothAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isStepped = attrSettings.steppedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isArea = attrSettings.areaAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; + const isExtended = attrSettings.extendedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name) !== undefined; return html` ${asset.name} ${attributeLabel} ${when(isOnRightAxis, () => html` - + `)} ${when(isFaint, () => html` - + `)} ${when(isSmooth, () => html` - + `)} ${when(isStepped, () => html` - + `)} ${when(isArea, () => html` - + `)} ${when(isExtended, () => html` - + `)} ` } @@ -116,7 +117,7 @@ export class ChartSettings extends WidgetSettings { }, { - icon: this.widgetConfig.rightAxisAttributes.includes(attributeRef) ? "arrow-right-bold" : "arrow-left-bold", + icon: this.widgetConfig.attributeSettings.rightAxisAttributes.includes(attributeRef) ? "arrow-right-bold" : "arrow-left-bold", tooltip: i18next.t('dashboard.toggleAxis'), disabled: false }, @@ -306,14 +307,7 @@ export class ChartSettings extends WidgetSettings { switch (action.icon) { case "arrow-right-bold": case "arrow-left-bold": - if (this.widgetConfig.attributeRefs.indexOf(attributeRef) >= 0) { - if (this.widgetConfig.rightAxisAttributes.includes(attributeRef)) { - this.removeFromRightAxis(attributeRef); - } else { - this.addToRightAxis(attributeRef); - } - this.notifyConfigUpdate(); - } + this.toggleAttributeSetting("rightAxisAttributes", attributeRef); break; case "palette": // Change color const colorInput = document.createElement('input'); @@ -340,44 +334,19 @@ export class ChartSettings extends WidgetSettings { colorInput.click(); break; case "chart-bell-curve-cumulative": - if (findAttributeIndex(this.widgetConfig.attributeSettings.smoothAttributes, attributeRef) < 0) { - this.widgetConfig.attributeSettings.smoothAttributes.push(attributeRef); - } else { - this.widgetConfig.attributeSettings.smoothAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.smoothAttributes, attributeRef), 1); - } - this.notifyConfigUpdate(); + this.toggleAttributeSetting("smoothAttributes", attributeRef); break; case "square-wave": - if (findAttributeIndex(this.widgetConfig.attributeSettings.steppedAttributes, attributeRef) < 0) { - this.widgetConfig.attributeSettings.steppedAttributes.push(attributeRef); - } else { - this.widgetConfig.attributeSettings.steppedAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.steppedAttributes, attributeRef), 1); - } - this.notifyConfigUpdate(); + this.toggleAttributeSetting("steppedAttributes", attributeRef); break; case "chart-areaspline-variant": - if (findAttributeIndex(this.widgetConfig.attributeSettings.areaAttributes, attributeRef) < 0) { - this.widgetConfig.attributeSettings.areaAttributes.push(attributeRef); - } else { - this.widgetConfig.attributeSettings.areaAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.areaAttributes, attributeRef), 1); - } - this.notifyConfigUpdate(); + this.toggleAttributeSetting("areaAttributes", attributeRef); break; case "arrange-send-backward": - if (findAttributeIndex(this.widgetConfig.attributeSettings.faintAttributes, attributeRef) < 0) { - this.widgetConfig.attributeSettings.faintAttributes.push(attributeRef); - } else { - this.widgetConfig.attributeSettings.faintAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.faintAttributes, attributeRef), 1); - } - this.notifyConfigUpdate(); + this.toggleAttributeSetting("faintAttributes", attributeRef); break; case "arrow-expand-right": - if (findAttributeIndex(this.widgetConfig.attributeSettings.extendedAttributes, attributeRef) < 0) { - this.widgetConfig.attributeSettings.extendedAttributes.push(attributeRef); - } else { - this.widgetConfig.attributeSettings.extendedAttributes.splice(findAttributeIndex(this.widgetConfig.attributeSettings.extendedAttributes, attributeRef), 1); - } - this.notifyConfigUpdate(); + this.toggleAttributeSetting("extendedAttributes", attributeRef); break; default: console.warn('Unknown attribute panel action:', action); @@ -392,7 +361,6 @@ export class ChartSettings extends WidgetSettings { const removedAttributeRefs = this.widgetConfig.attributeRefs.filter(ar => !ev.detail.attributeRefs.includes(ar)); removedAttributeRefs.forEach(raf => { - this.removeFromRightAxis(raf); this.removeFromAttributeSettings(raf); this.removeFromColorPickedAttributes(raf); }); @@ -403,35 +371,31 @@ export class ChartSettings extends WidgetSettings { protected removeFromAttributeSettings(attributeRef: AttributeRef) { const settings = this.widgetConfig.attributeSettings; - settings.smoothAttributes = settings.smoothAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); - settings.steppedAttributes = settings.steppedAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); - settings.areaAttributes = settings.areaAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); - settings.faintAttributes = settings.faintAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); - settings.extendedAttributes = settings.extendedAttributes.filter(ar => ar.id !== attributeRef.id || ar.name !== attributeRef.name); + (Object.keys(settings) as (keyof typeof settings)[]).forEach(key => { + settings[key] = settings[key].filter((ar: AttributeRef) => ar.id !== attributeRef.id || ar.name !== attributeRef.name); + }); } - protected removeFromColorPickedAttributes(attributeRef: AttributeRef) { - this.widgetConfig.colorPickedAttributes = this.widgetConfig.colorPickedAttributes.filter( - item => item.attributeRef.id !== attributeRef.id || item.attributeRef.name !== attributeRef.name + protected toggleAttributeSetting( + setting: keyof ChartWidgetConfig["attributeSettings"], + attributeRef: AttributeRef, + ): void { + const attributes = this.widgetConfig.attributeSettings[setting]; + const index = attributes.findIndex( + (item: AttributeRef) => item.id === attributeRef.id && item.name === attributeRef.name ); - } - - protected addToRightAxis(attributeRef: AttributeRef, notify = false) { - if(!this.widgetConfig.rightAxisAttributes.includes(attributeRef)) { - this.widgetConfig.rightAxisAttributes.push(attributeRef); - if(notify) { - this.notifyConfigUpdate(); - } + if (index < 0) { + attributes.push(attributeRef); + } else { + attributes.splice(index, 1); } + this.notifyConfigUpdate(); } - protected removeFromRightAxis(attributeRef: AttributeRef, notify = false) { - if(this.widgetConfig.rightAxisAttributes.includes(attributeRef)) { - this.widgetConfig.rightAxisAttributes.splice(this.widgetConfig.rightAxisAttributes.indexOf(attributeRef), 1); - if(notify) { - this.notifyConfigUpdate(); - } - } + protected removeFromColorPickedAttributes(attributeRef: AttributeRef) { + this.widgetConfig.colorPickedAttributes = this.widgetConfig.colorPickedAttributes.filter( + item => item.attributeRef.id !== attributeRef.id || item.attributeRef.name !== attributeRef.name + ); } protected onTimePresetSelect(ev: OrInputChangedEvent) { diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index 55f4240afc..cfdb801e1c 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -13,7 +13,6 @@ import "@openremote/or-chart"; export interface ChartWidgetConfig extends WidgetConfig { attributeRefs: AttributeRef[]; - rightAxisAttributes: AttributeRef[]; colorPickedAttributes: Array<{ attributeRef: AttributeRef; color: string }>; attributeSettings: { rightAxisAttributes: AttributeRef[], @@ -64,7 +63,6 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { const dates = dateFunc(new Date()); return { attributeRefs: [], - rightAxisAttributes: [], colorPickedAttributes: [], attributeSettings: { rightAxisAttributes: [], @@ -214,7 +212,7 @@ export class ChartWidget extends OrAssetWidget { `, () => { return html` - Date: Tue, 11 Mar 2025 18:16:34 +0100 Subject: [PATCH 15/20] added widgetsettings for lttb max datapoint query and show symbol treshold --- ui/app/shared/locales/en/or.json | 8 +++-- ui/component/or-chart/src/index.ts | 33 ++++++------------- .../src/settings/chart-settings.ts | 25 +++++++++++++- .../src/widgets/chart-widget.ts | 11 +++++-- 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/ui/app/shared/locales/en/or.json b/ui/app/shared/locales/en/or.json index e20c694b81..60b85e76a6 100644 --- a/ui/app/shared/locales/en/or.json +++ b/ui/app/shared/locales/en/or.json @@ -758,9 +758,11 @@ "smooth": "Smooth", "stepped": "Stepped", "fill": "Fill", - "extendData": "Extend last datapoint", - "extended": "(last known)", - "lineColor": "Line color" + "extendData": "Extend latest datapoint", + "lastKnown": "(last known)", + "lineColor": "Line color", + "maxConcurrentDatapoints": "Max concurrent datapoints", + "showSymbolMaxDatapoints": "Upper limit to show datapoint symbols" }, "south": "South", diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 8d747cfa9d..46113313f7 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -429,6 +429,12 @@ export class OrChart extends translate(i18next)(LitElement) { @property() public showToolBox: boolean = true; + @property() + public showSymbolMaxDatapoints: number = 30; + + @property() + public maxConcurrentDatapoints: number = 100; + @property() protected _loading: boolean = false; @@ -1212,9 +1218,6 @@ export class OrChart extends translate(i18next)(LitElement) { } else { this._dataAbortController = new AbortController(); promises = this.assetAttributes.map(async ([assetIndex, attribute], index) => { - console.log('Stepped attrs:' + JSON.stringify(this.attributeSettings.steppedAttributes)); - console.log('Collored attrs in chart:' + JSON.stringify(this.colorPickedAttributes)); - console.log('cheese:' + this.colorPickedAttributes); const asset = this.assets[assetIndex]; const shownOnRightAxis = !!this.attributeSettings.rightAxisAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); @@ -1224,7 +1227,6 @@ export class OrChart extends translate(i18next)(LitElement) { const faint = !!this.attributeSettings.faintAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); const extended = !!this.attributeSettings.extendedAttributes.find(ar => ar.id === asset.id && ar.name === attribute.name); const color = this.colorPickedAttributes.find(({ attributeRef }) => attributeRef.name === attribute.name && attributeRef.id === asset.id)?.color; - console.log('Picked color found: ' + color); const descriptors = AssetModelUtil.getAttributeAndValueDescriptors(asset.type, attribute.name, attribute); const label = Util.getAttributeLabel(attribute, descriptors[0], asset.type, false); const unit = Util.resolveUnits(Util.getAttributeUnits(attribute, descriptors[0], asset.type)); @@ -1244,8 +1246,7 @@ export class OrChart extends translate(i18next)(LitElement) { //Load Extended Data let bsNumber = 1; //inserted in from and to, however these are not used in _loadAttributeData anyway, the function references variables outside of it (bad practice) if (extended) { - console.log('Extended data requested'); - dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, color ?? this.colors[colourIndex], bsNumber, bsNumber, false, false, false, area, faint, extended, asset.name + " " + label + " " + i18next.t("dashboard.extended"), options, unit); + dataset = await this._loadAttributeData(this.assets[assetIndex], attribute, color ?? this.colors[colourIndex], bsNumber, bsNumber, false, false, false, area, faint, extended, asset.name + " " + label + " " + i18next.t("dashboard.lastKnown"), options, unit); data.push(dataset); } @@ -1337,22 +1338,8 @@ export class OrChart extends translate(i18next)(LitElement) { } if(query.type == 'lttb') { - // NEEDS TO BE REWORKED, ECHARTS HAS LTTB BUILT IN, BUT THIS CAN BE A SETTING IN CHART-SETTINGS - - // If amount of data points is set, only allow a maximum of 1 points per pixel in width - // Otherwise, dynamically set amount of data points based on chart width (1000px = 200 data points) - //if(query.amountOfPoints) { - // if(this._chartElem?.clientWidth > 0) { - // query.amountOfPoints = Math.min(query.amountOfPoints, this._chartElem?.clientWidth) - // } - //} else { - // if(this._chartElem?.clientWidth > 0) { - // query.amountOfPoints = Math.round(this._chartElem.clientWidth / 5) - // } else { - console.warn("Could not grab width of the Chart for estimating amount of data points. Using 100 points instead.") - query.amountOfPoints = 100; - // } - //} + + query.amountOfPoints = this.maxConcurrentDatapoints; } else if(query.type === 'interval' && !query.interval) { const diffInHours = (this.datapointQuery.toTimestamp! - this.datapointQuery.fromTimestamp!) / 1000 / 60 / 60; @@ -1380,7 +1367,7 @@ export class OrChart extends translate(i18next)(LitElement) { .map(point => ({ x: point.x, y: point.y } as ValueDatapoint)) dataset.data = data.map(point => [point.x, point.y]); - dataset.showSymbol = data.length <=30; //Only show symbols when there are 30 or fewer data points to be shown + dataset.showSymbol = data.length <= this.showSymbolMaxDatapoints; } diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index bb146d72d8..05726c43c6 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -163,7 +163,6 @@ export class ChartSettings extends WidgetSettings { @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onShowLegendToggle(ev)}" >
-
@@ -178,6 +177,13 @@ export class ChartSettings extends WidgetSettings { @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.onShowToolBoxToggle(ev)}" >
+ +
+ +
+
@@ -291,6 +297,13 @@ export class ChartSettings extends WidgetSettings { > `; } + case 'lttb': { + return html ` + + `; + } default: return html``; } @@ -452,4 +465,14 @@ export class ChartSettings extends WidgetSettings { this.widgetConfig.datapointQuery.type = this.samplingOptions.get(ev.detail.value)! as any; this.notifyConfigUpdate(); } + + protected onMaxConcurrentDatapointsValueChange(ev: OrInputChangedEvent) { + this.widgetConfig.maxConcurrentDatapoints = ev.detail.value; + this.notifyConfigUpdate(); + } + + protected onShowSymbolMaxDatapointsValueChange(ev: OrInputChangedEvent) { + this.widgetConfig.showSymbolMaxDatapoints = ev.detail.value; + this.notifyConfigUpdate(); + } } diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index cfdb801e1c..9780415d6c 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -23,12 +23,14 @@ export interface ChartWidgetConfig extends WidgetConfig { extendedAttributes: AttributeRef[], }, datapointQuery: AssetDatapointQueryUnion; - chartOptions?: any; // ChartConfiguration<"line", ScatterDataPoint[]> + chartOptions?: any; showTimestampControls: boolean; defaultTimePresetKey: string; showLegend: boolean; showZoomBar: boolean; showToolBox: boolean; + showSymbolMaxDatapoints: number; + maxConcurrentDatapoints: number; } function getDefaultTimePresetOptions(): Map { @@ -95,7 +97,10 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { defaultTimePresetKey: preset, showLegend: true, showZoomBar: false, - showToolBox: false + showToolBox: false, + showSymbolMaxDatapoints: 30, + maxConcurrentDatapoints: 100, + }; } @@ -221,6 +226,8 @@ export class ChartWidget extends OrAssetWidget { .attributeControls="${false}" .timestampControls="${!this.widgetConfig?.showTimestampControls}" .timePresetOptions="${getDefaultTimePresetOptions()}" .timePresetKey="${this.widgetConfig?.defaultTimePresetKey}" .datapointQuery="${this.datapointQuery}" .chartOptions="${this.widgetConfig?.chartOptions}" + .showSymbolMaxDatapoints="${this.widgetConfig?.showSymbolMaxDatapoints}" + .maxConcurrentDatapoints="${this.widgetConfig?.maxConcurrentDatapoints}" style="height: 100%" > `; From dc2f08193891d4b7e979b22f6f300534494c4800 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Thu, 13 Mar 2025 18:12:54 +0100 Subject: [PATCH 16/20] all functionality in basis completed --- ui/component/or-chart/src/index.ts | 189 +++++++++++++++--- .../src/settings/chart-settings.ts | 8 +- .../src/widgets/chart-widget.ts | 32 ++- 3 files changed, 200 insertions(+), 29 deletions(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 46113313f7..63a4de4020 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -357,9 +357,6 @@ export class OrChart extends translate(i18next)(LitElement) { @property({type: Object}) public assetAttributes: [number, Attribute][] = []; - //@property({type: Array}) // List of AttributeRef that are shown on the right axis instead. - //public rightAxisAttributes: AttributeRef[] = []; - @property({type: Array}) public colorPickedAttributes: Array<{ attributeRef: AttributeRef; color: string }> = []; @@ -417,6 +414,23 @@ export class OrChart extends translate(i18next)(LitElement) { @property() public timePresetKey?: string; + + + + + + @property() + public timeWindowOptions?: Map; + + @property() + public timePrefixKey?: string; + + @property() + public timeWindowKey?: string; + + + + @property() public showLegend: boolean = true; @@ -496,7 +510,7 @@ export class OrChart extends translate(i18next)(LitElement) { } } - const reloadData = changedProperties.has('colorPickedAttributes') || changedProperties.has("datapointQuery") || changedProperties.has("timePresetKey") || changedProperties.has("timeframe") || + const reloadData = changedProperties.has('colorPickedAttributes') || changedProperties.has("datapointQuery") || changedProperties.has("timePresetKey") || changedProperties.has("timeframe") || changedProperties.has("timePrefixKey") || changedProperties.has("timeWindowKey")|| changedProperties.has("attributeSettings") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); if (reloadData) { @@ -543,7 +557,7 @@ export class OrChart extends translate(i18next)(LitElement) { backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), tooltip: { trigger: 'axis', - axisPointer: { type: 'cross'}, + axisPointer: { type: 'cross', snap: true}, //formatter: (params: any) => { // if (Array.isArray(params) && params.length > 0) { // const yValue = params[0].value[1]; @@ -658,8 +672,6 @@ export class OrChart extends translate(i18next)(LitElement) { } } - // console.log(this._chartOptions); - // Initialize echarts instance this._chart = init(this._chartElem); // Set chart options to default @@ -717,6 +729,7 @@ export class OrChart extends translate(i18next)(LitElement) { } render() { + const disabled = false; // TEMP EDIT this._loading || this._latestError; return html`
@@ -738,23 +751,40 @@ export class OrChart extends translate(i18next)(LitElement) {
- ${this.timePresetOptions && this.timePresetKey ? html` + ${this.timePresetOptions && this.timePresetKey && this.timePrefixKey && this.timePrefixOptions && this.timeWindowKey && this.timeWindowOptions ? html` ${this.timestampControls ? html` + + + + ${getContentWithMenuTemplate( + html``, + this.timePrefixOptions.map((option) => ({ value: option } as ListItem)), + this.timePrefixKey, + (value: string | string[]) => { + this.timeframe = undefined; // remove any custom start & end times + this.timePrefixKey = value.toString(); + }, + undefined, + undefined, + undefined, + true + )} + ${getContentWithMenuTemplate( - html``, - Array.from(this.timePresetOptions!.keys()).map((key) => ({ value: key } as ListItem)), - this.timePresetKey, + html``, + Array.from(this.timeWindowOptions!.keys()).map((key) => ({ value: key } as ListItem)), + this.timePrefixKey, (value: string | string[]) => { this.timeframe = undefined; // remove any custom start & end times - this.timePresetKey = value.toString(); + this.timeWindowKey = value.toString(); }, undefined, undefined, undefined, true )} - - + + ` : html` `} @@ -843,9 +873,9 @@ export class OrChart extends translate(i18next)(LitElement) { let options = this._chart.getOption(); if (options.series && Array.isArray(options.series)) { options.series.forEach(function (series) { - if (series.lineStyle.opacity == 0.2) { + if (series.lineStyle.opacity == 0.2 || series.lineStyle.opacity == 0.99) { series.lineStyle.opacity = 0.31; - } else if (series.lineStyle.opacity != 0.31) { + } else { series.lineStyle.opacity = 1; } }); @@ -865,7 +895,8 @@ export class OrChart extends translate(i18next)(LitElement) { } else { series.lineStyle.opacity = 0.3; } - + } else if (series.lineStyle.opacity == 0.31) { // extra highlight if selected is faint + series.lineStyle.opacity = 0.99; } }); } @@ -877,7 +908,7 @@ export class OrChart extends translate(i18next)(LitElement) { async loadSettings(reset: boolean) { - // console.log('loadSettings triggered'); + console.log('loadSettings triggered'); if(this.assetAttributes == undefined || reset) { this.assetAttributes = []; @@ -894,6 +925,18 @@ export class OrChart extends translate(i18next)(LitElement) { this.timePresetKey = this.timePresetOptions.keys().next().value.toString(); } + if (!this.timeWindowOptions) { + this.timeWindowOptions = this._getDefaultTimeWindowOptions(); + } + + if (!this.timeWindowKey) { + this.timeWindowKey = this.timeWindowOptions.keys().next().value.toString(); + } + + if (!this.timePrefixKey) { + this.timePrefixKey = this.timePrefixOptions[0]; + } + if (!this.panelName) { return; } @@ -962,7 +1005,7 @@ export class OrChart extends translate(i18next)(LitElement) { } async saveSettings() { - // console.log('saveSettings triggered'); + console.log('saveSettings triggered'); if (!this.panelName) { return; @@ -1137,7 +1180,104 @@ export class OrChart extends translate(i18next)(LitElement) { } } - protected _getDefaultTimestampOptions(): Map { + + + + + + + protected timePrefixOptions: string[] = ["this", "last"]; + + + + protected _getDefaultTimeWindowOptions(): Map { + return new Map([ + ["5 minutes", ['minutes', 5]], + ["20 minutes", ['minutes', 20]], + ["60 minutes", ['minutes', 60]], + ["hour", ['hours', 1]], + ["6 hours", ['hours', 6]], + ["24 hours", ['hours', 24]], + ["day", ['days', 1]], + ["7 days", ['days', 7]], + ["week", ['weeks', 1]], + ["30 days", ['days', 30]], + ["month", ['months', 1]], + ["365 days", ['days', 365]], + ["year", ['years', 1]] + ]); + }; + + + protected _getTimeWindowSelected(timePrefixSelected: string, timeWindowSelected: string): [Date, Date] { + let startDate = moment(); + let endDate = moment(); + + const timeWindow: [moment.unitOfTime.DurationConstructor, number] | undefined = this.timeWindowOptions!.get(timeWindowSelected); + + if (!timeWindow) { + throw new Error(`Unsupported time window selected: ${timeWindowSelected}`); + } + + const [unit , value]: [moment.unitOfTime.DurationConstructor, number] = timeWindow; + + switch (timePrefixSelected) { + case "this": + if (value == 1) { // For singulars like this hour + startDate = moment().subtract(value, unit); + endDate = moment().endOf(unit); + } else { // For multiples like this 5 min, put now in the middle + startDate = moment().subtract(value*0.5, unit); + endDate = moment().add(value*0.5, unit); + } + break; + case "last": + startDate = moment().subtract(value, unit).startOf(unit); + if (value == 1) { // For singulars like last hour + endDate = moment().startOf(unit); + } else { //For multiples like last 5 min + endDate = moment(); + } + break; + } + return [startDate.toDate(), endDate.toDate()]; + } + + protected _shiftTimeframe(currentStart: Date, timeWindowSelected: string, direction: string) { + const timeWindow = this.timeWindowOptions!.get(timeWindowSelected); + + if (!timeWindow) { + throw new Error(`Unsupported time window selected: ${timeWindowSelected}`); + } + + const [unit, value] = timeWindow; + let newStart = moment(currentStart); + + direction === "previous" ? newStart.subtract(value, unit as moment.unitOfTime.DurationConstructor) : newStart.add(value, unit as moment.unitOfTime.DurationConstructor); + + let newEnd = moment(newStart).add(value, unit as moment.unitOfTime.DurationConstructor); + + this.timeframe = [newStart.toDate(), newEnd.toDate()]; + } + + + + + + + + + + + + + + + + + + + protected _getDefaultTimestampOptions(): Map { return new Map([ ["lastHour", (date) => [moment(date).subtract(1, 'hour').toDate(), date]], ["last24Hours", (date) => [moment(date).subtract(24, 'hours').toDate(), date]], @@ -1171,7 +1311,7 @@ export class OrChart extends translate(i18next)(LitElement) { } protected async _loadData() { - // console.log('loadData triggered'); + console.log('loadData triggered'); if ((this._data && !this._zoomChanged) || !this.assetAttributes || !this.assets || (this.assets.length === 0 && !this.dataProvider) || (this.assetAttributes.length === 0 && !this.dataProvider) || !this.datapointQuery) { return; } @@ -1187,7 +1327,7 @@ export class OrChart extends translate(i18next)(LitElement) { this._loading = true; - const dates: [Date, Date] = this.timePresetOptions!.get(this.timePresetKey!)!(new Date()); + const dates: [Date, Date] = this._getTimeWindowSelected(this.timePrefixKey!, this.timeWindowKey!); if(!this._zoomChanged || !this._startOfPeriod || !this._endOfPeriod) { // If zoom has changed, we want to keep the previous start and end of period @@ -1338,7 +1478,7 @@ export class OrChart extends translate(i18next)(LitElement) { } if(query.type == 'lttb') { - + // If the query type is lttb, we need to limit the amount of points to the maxConcurrentDatapoints query.amountOfPoints = this.maxConcurrentDatapoints; } else if(query.type === 'interval' && !query.interval) { @@ -1352,6 +1492,7 @@ export class OrChart extends translate(i18next)(LitElement) { response = await manager.rest.api.AssetPredictedDatapointResource.getPredictedDatapoints(asset.id, attribute.name, query, options); } else { if (extended) { + // if request is for extended dataset, we want to get the last known value only query.type = 'nearest'; query.timestamp = new Date().toISOString() } @@ -1432,6 +1573,6 @@ export class OrChart extends translate(i18next)(LitElement) { } } })) - }); + });; } } diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index 05726c43c6..5f039d27bd 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -318,10 +318,6 @@ export class ChartSettings extends WidgetSettings { }; switch (action.icon) { - case "arrow-right-bold": - case "arrow-left-bold": - this.toggleAttributeSetting("rightAxisAttributes", attributeRef); - break; case "palette": // Change color const colorInput = document.createElement('input'); colorInput.type = 'color'; @@ -346,6 +342,10 @@ export class ChartSettings extends WidgetSettings { }); colorInput.click(); break; + case "arrow-right-bold": + case "arrow-left-bold": + this.toggleAttributeSetting("rightAxisAttributes", attributeRef); + break; case "chart-bell-curve-cumulative": this.toggleAttributeSetting("smoothAttributes", attributeRef); break; diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index 9780415d6c..c987fa92bd 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -55,6 +55,34 @@ function getDefaultTimePresetOptions(): Map { ]); } +//function getDefaultWindowOptions(): Map { +// return new Map([ +// ["15Minutes", (date: Date) => []], +// ["Hour", (date: Date) => [moment(date).subtract(1, 'hour').toDate(), date]], +// ["24Hours", (date: Date) => [moment(date).subtract(24, 'hours').toDate(), date]], +// ["Day", (date: Date) => [moment(date).startOf('day').toDate(), moment(date).endOf('day').toDate()]], +// ["7Days", (date: Date) => [moment(date).subtract(7, 'days').toDate(), date]], +// ["Week", (date: Date) => [moment(date).startOf('isoWeek').toDate(), moment(date).endOf('isoWeek').toDate()]], +// ["30Days", (date: Date) => [moment(date).subtract(30, 'days').toDate(), date]], +// ["Month", (date: Date) => [moment(date).startOf('month').toDate(), moment(date).endOf('month').toDate()]], +// ["90Days", (date: Date) => [moment(date).subtract(90, 'days').toDate(), date]], +// ["6Months", (date: Date) => [moment(date).subtract(6, 'months').toDate(), date]], +// ["Year", (date: Date) => [moment(date).subtract(1, 'year').toDate(), date]] +// ]); +//} + +//function getDefaultPrefix(): Map { +// return new Map([ +// ["last", (date: Date) => [moment(date).subtract(1, 'hour').toDate(), date]], +// ["this", (date: Date) => [moment(date).startOf('hour').toDate(), moment(date).endOf('hour').toDate()]], +// ["previous", (date: Date) => [moment(date).subtract(1, 'week').startOf('isoWeek').toDate(), moment(date).subtract(1, 'week').endOf('isoWeek').toDate()]], +// ]); +//} + + + + + function getDefaultSamplingOptions(): Map { return new Map([["lttb", 'lttb'], ["withInterval", 'interval']]); } @@ -224,7 +252,9 @@ export class ChartWidget extends OrAssetWidget { .showZoomBar="${(this.widgetConfig?.showZoomBar != null) ? this.widgetConfig?.showZoomBar : true}" .showToolBox="${(this.widgetConfig?.showToolBox != null) ? this.widgetConfig?.showToolBox : true}" .attributeControls="${false}" .timestampControls="${!this.widgetConfig?.showTimestampControls}" - .timePresetOptions="${getDefaultTimePresetOptions()}" .timePresetKey="${this.widgetConfig?.defaultTimePresetKey}" + .timePresetOptions="${getDefaultTimePresetOptions()}" + .timePresetOptions2="${getDefaultTimePresetOptions()}" + .timePresetKey="${this.widgetConfig?.defaultTimePresetKey}" .datapointQuery="${this.datapointQuery}" .chartOptions="${this.widgetConfig?.chartOptions}" .showSymbolMaxDatapoints="${this.widgetConfig?.showSymbolMaxDatapoints}" .maxConcurrentDatapoints="${this.widgetConfig?.maxConcurrentDatapoints}" From 4edf979aafcccdcf556247933af15bd90d97ee48 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Mon, 17 Mar 2025 18:36:20 +0100 Subject: [PATCH 17/20] fixed eventlistener errors, bottomlegend trigger, std timeframe selection in chart-settings --- ui/app/shared/locales/en/or.json | 1 + ui/component/or-chart/src/index.ts | 135 +++++++----------- .../src/settings/chart-settings.ts | 29 +++- .../src/widgets/chart-widget.ts | 93 +++++------- 4 files changed, 110 insertions(+), 148 deletions(-) diff --git a/ui/app/shared/locales/en/or.json b/ui/app/shared/locales/en/or.json index 60b85e76a6..c9ec01ad3c 100644 --- a/ui/app/shared/locales/en/or.json +++ b/ui/app/shared/locales/en/or.json @@ -157,6 +157,7 @@ "previousYear": "Previous year", "period": "Period", "ending": "Ending", + "prefix": "Prefix", "timeframe": "Timeframe", "timeframeDefault": "Default timeframe", "dataSampling": "Data sampling", diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 63a4de4020..265675606c 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -390,7 +390,7 @@ export class OrChart extends translate(i18next)(LitElement) { @property({type: Object}) public config?: OrChartConfig; - @property({type: Object}) // WIDGET NEED TO CONVERT TO ECHARTS OPTION FORMAT, NOW WORKAROUND IN PLACE options that will get merged with our default chartjs configuration. + @property({type: Object}) public chartOptions?: any @property({type: String}) @@ -409,15 +409,7 @@ export class OrChart extends translate(i18next)(LitElement) { public timestampControls: boolean = true; @property() - public timePresetOptions?: Map; - - @property() - public timePresetKey?: string; - - - - - + protected timePrefixOptions?: string[]; @property() public timeWindowOptions?: Map; @@ -474,6 +466,9 @@ export class OrChart extends translate(i18next)(LitElement) { protected _stepSize?: number; protected _latestError?: string; protected _dataAbortController?: AbortController; + protected _zoomHandler?: any; + protected _resizeHandler?: any; + protected _containerResizeObserver?: ResizeObserver; constructor() { super(); @@ -510,7 +505,7 @@ export class OrChart extends translate(i18next)(LitElement) { } } - const reloadData = changedProperties.has('colorPickedAttributes') || changedProperties.has("datapointQuery") || changedProperties.has("timePresetKey") || changedProperties.has("timeframe") || changedProperties.has("timePrefixKey") || changedProperties.has("timeWindowKey")|| + const reloadData = changedProperties.has('colorPickedAttributes') || changedProperties.has("datapointQuery") || changedProperties.has("timeframe") || changedProperties.has("timePrefixKey") || changedProperties.has("timeWindowKey")|| changedProperties.has("attributeSettings") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); if (reloadData) { @@ -518,6 +513,8 @@ export class OrChart extends translate(i18next)(LitElement) { this._data = undefined; if (this._chart) { // console.log('releadData found _chart exists so disposing'); + // Remove event listeners + this._toggleChartEventListeners(false); this._chart.dispose(); this._chart = undefined; } @@ -557,14 +554,9 @@ export class OrChart extends translate(i18next)(LitElement) { backgroundColor: this._style.getPropertyValue("--internal-or-asset-tree-background-color"), tooltip: { trigger: 'axis', - axisPointer: { type: 'cross', snap: true}, - //formatter: (params: any) => { - // if (Array.isArray(params) && params.length > 0) { - // const yValue = params[0].value[1]; - // return yValue !== undefined ? yValue.toString() : ''; - // } - // return '' - //} + axisPointer: { + type: 'cross' + }, }, toolbox: {}, xAxis: { @@ -664,9 +656,9 @@ export class OrChart extends translate(i18next)(LitElement) { top: 0, feature: { dataView: {readOnly: true}, - magicType: { - type: ['line', 'bar'] - }, + //magicType: { + // type: ['line', 'bar'] + //}, saveAsImage: {} } } @@ -676,15 +668,7 @@ export class OrChart extends translate(i18next)(LitElement) { this._chart = init(this._chartElem); // Set chart options to default this._chart.setOption(this._chartOptions); - // Make chart size responsive - window.addEventListener("resize", () => this._chart!.resize()); - const resizeObserver = new ResizeObserver(() => this._chart!.resize()); - resizeObserver.observe(this._chartElem); - // Add event listener for zooming - this._chart!.on('datazoom', debounce((params: any) => { this._onZoomChange(params); }, 1500)); - // Add event listener for chart resize - this._chart!.on('resize', throttle(() => { this.applyChartResponsiveness(); }, 200)); - + this._toggleChartEventListeners(true); } if (changedProperties.has("_data")) { @@ -702,6 +686,7 @@ export class OrChart extends translate(i18next)(LitElement) { // Also sorts the attribute lists horizontally when it is below the chart applyChartResponsiveness(): void { if(this.shadowRoot) { + console.log('applyChartResponsiveness triggered'); const container = this.shadowRoot.getElementById('container'); if(container) { const bottomLegend: boolean = (container.clientWidth < 600); @@ -751,7 +736,7 @@ export class OrChart extends translate(i18next)(LitElement) {
- ${this.timePresetOptions && this.timePresetKey && this.timePrefixKey && this.timePrefixOptions && this.timeWindowKey && this.timeWindowOptions ? html` + ${this.timePrefixKey && this.timePrefixOptions && this.timeWindowKey && this.timeWindowOptions ? html` ${this.timestampControls ? html` @@ -786,7 +771,7 @@ export class OrChart extends translate(i18next)(LitElement) { ` : html` - + `} ` : undefined}
@@ -918,11 +903,8 @@ export class OrChart extends translate(i18next)(LitElement) { this.realm = manager.getRealm(); } - if (!this.timePresetOptions) { - this.timePresetOptions = this._getDefaultTimestampOptions(); - } - if (!this.timePresetKey) { - this.timePresetKey = this.timePresetOptions.keys().next().value.toString(); + if (!this.timePrefixOptions) { + this.timePrefixOptions = this._getDefaultTimePrefixOptions(); } if (!this.timeWindowOptions) { @@ -934,7 +916,7 @@ export class OrChart extends translate(i18next)(LitElement) { } if (!this.timePrefixKey) { - this.timePrefixKey = this.timePrefixOptions[0]; + this.timePrefixKey = this.timePrefixOptions[1]; } if (!this.panelName) { @@ -1051,31 +1033,6 @@ export class OrChart extends translate(i18next)(LitElement) { dialog.addEventListener(OrAttributePickerPickedEvent.NAME, (ev: any) => this._addAttribute(ev.detail)); } - protected _openTimeDialog(startTimestamp?: number, endTimestamp?: number) { - const startRef: Ref = createRef(); - const endRef: Ref = createRef(); - const dialog = showDialog(new OrMwcDialog() - .setHeading(i18next.t('timeframe')) - .setContent(() => html` -
- - -
- `) - .setActions([{ - actionName: "cancel", - content: "cancel" - }, { - actionName: "ok", - content: "ok", - action: () => { - if(this.timePresetOptions && startRef.value?.value && endRef.value?.value) { - this.timeframe = [new Date(startRef.value.value), new Date(endRef.value.value)]; - } - } - }]) - ) - } protected async _addAttribute(selectedAttrs?: AttributeRef[]) { if (!selectedAttrs) return; @@ -1111,6 +1068,7 @@ export class OrChart extends translate(i18next)(LitElement) { console.log('cleanup triggered'); if (this._chart) { //('cleanup found _chart exists so disposing'); + this._toggleChartEventListeners(false); this._chart.dispose(); this._chart = undefined; this.requestUpdate(); @@ -1183,18 +1141,13 @@ export class OrChart extends translate(i18next)(LitElement) { - - - - protected timePrefixOptions: string[] = ["this", "last"]; - + protected _getDefaultTimePrefixOptions(): string[] { + return ["this", "last"]; + } protected _getDefaultTimeWindowOptions(): Map { return new Map([ - ["5 minutes", ['minutes', 5]], - ["20 minutes", ['minutes', 20]], - ["60 minutes", ['minutes', 60]], ["hour", ['hours', 1]], ["6 hours", ['hours', 6]], ["24 hours", ['hours', 24]], @@ -1277,17 +1230,7 @@ export class OrChart extends translate(i18next)(LitElement) { - protected _getDefaultTimestampOptions(): Map { - return new Map([ - ["lastHour", (date) => [moment(date).subtract(1, 'hour').toDate(), date]], - ["last24Hours", (date) => [moment(date).subtract(24, 'hours').toDate(), date]], - ["last7Days", (date) => [moment(date).subtract(7, 'days').toDate(), date]], - ["last30Days", (date) => [moment(date).subtract(30, 'days').toDate(), date]], - ["last90Days", (date) => [moment(date).subtract(90, 'days').toDate(), date]], - ["last6Months", (date) => [moment(date).subtract(6, 'months').toDate(), date]], - ["lastYear", (date) => [moment(date).subtract(1, 'year').toDate(), date]] - ]); - } + protected _getInterval(diffInHours: number): [number, DatapointInterval] { @@ -1574,5 +1517,31 @@ export class OrChart extends translate(i18next)(LitElement) { } })) });; + } + + protected _toggleChartEventListeners(connect: boolean){ + if (connect) { + //Connect event listeners + // Make chart size responsive + //window.addEventListener("resize", () => this._chart!.resize()); + this._containerResizeObserver = new ResizeObserver(() => this._chart!.resize()); + this._containerResizeObserver.observe(this._chartElem); + // Add event listener for zooming + this._zoomHandler = this._chart!.on('datazoom', debounce((params: any) => { this._onZoomChange(params); }, 1500)); + // Add event listener for chart resize + this._resizeHandler = this._chart!.on('resize', throttle(() => { this.applyChartResponsiveness(); }, 200)); + } + else if (!connect) { + //Disconnect event listeners + this._chart!.off('datazoom', this._zoomHandler); + this._chart!.off('resize', this._resizeHandler); + this._containerResizeObserver?.disconnect(); + this._containerResizeObserver = undefined; + } + + + + + } } diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index 5f039d27bd..fd18330c19 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -10,6 +10,7 @@ import {ChartWidgetConfig} from "../widgets/chart-widget"; import {InputType, OrInputChangedEvent} from "@openremote/or-mwc-components/or-mwc-input"; import {TimePresetCallback} from "@openremote/or-chart"; import {when} from "lit/directives/when.js"; +import moment from "moment/moment"; const styling = css` .switch-container { @@ -27,11 +28,16 @@ export class ChartSettings extends WidgetSettings { - protected timePresetOptions: Map = new Map(); + protected timeWindowOptions: Map = new Map; + protected timePrefixOptions: string[] = []; protected samplingOptions: Map = new Map(); - public setTimePresetOptions(options: Map) { - this.timePresetOptions = options; + public setTimeWindowOptions(options: Map) { + this.timeWindowOptions = options; + } + + public setTimePrefixOptions(options: string[]) { + this.timePrefixOptions = options; } public setSamplingOptions(options: Map) { @@ -144,9 +150,13 @@ export class ChartSettings extends WidgetSettings {
+
@@ -411,8 +421,13 @@ export class ChartSettings extends WidgetSettings { ); } - protected onTimePresetSelect(ev: OrInputChangedEvent) { - this.widgetConfig.defaultTimePresetKey = ev.detail.value.toString(); + protected onTimePreFixSelect(ev: OrInputChangedEvent) { + this.widgetConfig.defaultTimePrefixKey = ev.detail.value.toString(); + this.notifyConfigUpdate(); + } + + protected onTimeWindowSelect(ev: OrInputChangedEvent) { + this.widgetConfig.defaultTimeWindowKey = ev.detail.value.toString(); this.notifyConfigUpdate(); } diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index c987fa92bd..eb1b8a3bee 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -25,7 +25,8 @@ export interface ChartWidgetConfig extends WidgetConfig { datapointQuery: AssetDatapointQueryUnion; chartOptions?: any; showTimestampControls: boolean; - defaultTimePresetKey: string; + defaultTimeWindowKey: string; + defaultTimePrefixKey: string; showLegend: boolean; showZoomBar: boolean; showToolBox: boolean; @@ -33,64 +34,37 @@ export interface ChartWidgetConfig extends WidgetConfig { maxConcurrentDatapoints: number; } -function getDefaultTimePresetOptions(): Map { - return new Map([ - ["lastHour", (date: Date) => [moment(date).subtract(1, 'hour').toDate(), date]], - ["last24Hours", (date: Date) => [moment(date).subtract(24, 'hours').toDate(), date]], - ["last7Days", (date: Date) => [moment(date).subtract(7, 'days').toDate(), date]], - ["last30Days", (date: Date) => [moment(date).subtract(30, 'days').toDate(), date]], - ["last90Days", (date: Date) => [moment(date).subtract(90, 'days').toDate(), date]], - ["last6Months", (date: Date) => [moment(date).subtract(6, 'months').toDate(), date]], - ["lastYear", (date: Date) => [moment(date).subtract(1, 'year').toDate(), date]], - ["thisHour", (date: Date) => [moment(date).startOf('hour').toDate(), moment(date).endOf('hour').toDate()]], - ["thisDay", (date: Date) => [moment(date).startOf('day').toDate(), moment(date).endOf('day').toDate()]], - ["thisWeek", (date: Date) => [moment(date).startOf('isoWeek').toDate(), moment(date).endOf('isoWeek').toDate()]], - ["thisMonth", (date: Date) => [moment(date).startOf('month').toDate(), moment(date).endOf('month').toDate()]], - ["thisYear", (date: Date) => [moment(date).startOf('year').toDate(), moment(date).endOf('year').toDate()]], - ["yesterday", (date: Date) => [moment(date).subtract(24, 'hours').startOf('day').toDate(), moment(date).subtract(24, 'hours').endOf('day').toDate()]], - ["thisDayLastWeek", (date: Date) => [moment(date).subtract(1, 'week').startOf('day').toDate(), moment(date).subtract(1, 'week').endOf('day').toDate()]], - ["previousWeek", (date: Date) => [moment(date).subtract(1, 'week').startOf('isoWeek').toDate(), moment(date).subtract(1, 'week').endOf('isoWeek').toDate()]], - ["previousMonth", (date: Date) => [moment(date).subtract(1, 'month').startOf('month').toDate(), moment(date).subtract(1, 'month').endOf('month').toDate()]], - ["previousYear", (date: Date) => [moment(date).subtract(1, 'year').startOf('year').toDate(), moment(date).subtract(1, 'year').endOf('year').toDate()]] +function getDefaultTimeWindowOptions(): Map { + return new Map([ + ["5 minutes", ['minutes', 5]], + ["20 minutes", ['minutes', 20]], + ["60 minutes", ['minutes', 60]], + ["hour", ['hours', 1]], + ["6 hours", ['hours', 6]], + ["24 hours", ['hours', 24]], + ["day", ['days', 1]], + ["7 days", ['days', 7]], + ["week", ['weeks', 1]], + ["30 days", ['days', 30]], + ["month", ['months', 1]], + ["365 days", ['days', 365]], + ["year", ['years', 1]] ]); } -//function getDefaultWindowOptions(): Map { -// return new Map([ -// ["15Minutes", (date: Date) => []], -// ["Hour", (date: Date) => [moment(date).subtract(1, 'hour').toDate(), date]], -// ["24Hours", (date: Date) => [moment(date).subtract(24, 'hours').toDate(), date]], -// ["Day", (date: Date) => [moment(date).startOf('day').toDate(), moment(date).endOf('day').toDate()]], -// ["7Days", (date: Date) => [moment(date).subtract(7, 'days').toDate(), date]], -// ["Week", (date: Date) => [moment(date).startOf('isoWeek').toDate(), moment(date).endOf('isoWeek').toDate()]], -// ["30Days", (date: Date) => [moment(date).subtract(30, 'days').toDate(), date]], -// ["Month", (date: Date) => [moment(date).startOf('month').toDate(), moment(date).endOf('month').toDate()]], -// ["90Days", (date: Date) => [moment(date).subtract(90, 'days').toDate(), date]], -// ["6Months", (date: Date) => [moment(date).subtract(6, 'months').toDate(), date]], -// ["Year", (date: Date) => [moment(date).subtract(1, 'year').toDate(), date]] -// ]); -//} - -//function getDefaultPrefix(): Map { -// return new Map([ -// ["last", (date: Date) => [moment(date).subtract(1, 'hour').toDate(), date]], -// ["this", (date: Date) => [moment(date).startOf('hour').toDate(), moment(date).endOf('hour').toDate()]], -// ["previous", (date: Date) => [moment(date).subtract(1, 'week').startOf('isoWeek').toDate(), moment(date).subtract(1, 'week').endOf('isoWeek').toDate()]], -// ]); -//} - - - - +function getDefaultTimePreFixOptions(): string[] { + return ["this", "last"]; +} function getDefaultSamplingOptions(): Map { return new Map([["lttb", 'lttb'], ["withInterval", 'interval']]); } function getDefaultWidgetConfig(): ChartWidgetConfig { - const preset = "last24Hours" - const dateFunc = getDefaultTimePresetOptions().get(preset) as TimePresetCallback; - const dates = dateFunc(new Date()); + const preset = "24 hours"; // Default time preset, "last" is hardcoded below. + const dateFunc = getDefaultTimeWindowOptions().get(preset); + const startDate = moment().subtract(dateFunc![1], dateFunc![0]).startOf(dateFunc![0]); + const endDate = dateFunc![1]== 1 ? moment().endOf(dateFunc![0]) : moment(); return { attributeRefs: [], colorPickedAttributes: [], @@ -104,8 +78,8 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { }, datapointQuery: { type: "lttb", - fromTimestamp: dates[0].getTime(), - toTimestamp: dates[1].getTime() + fromTimestamp: startDate.toDate().getTime(), + toTimestamp: endDate.toDate().getTime(), }, chartOptions: { options: { @@ -122,12 +96,13 @@ function getDefaultWidgetConfig(): ChartWidgetConfig { }, }, showTimestampControls: false, - defaultTimePresetKey: preset, + defaultTimeWindowKey: preset, + defaultTimePrefixKey: "last", showLegend: true, showZoomBar: false, showToolBox: false, showSymbolMaxDatapoints: 30, - maxConcurrentDatapoints: 100, + maxConcurrentDatapoints: 100 }; } @@ -157,7 +132,8 @@ export class ChartWidget extends OrAssetWidget { }, getSettingsHtml(config: ChartWidgetConfig): WidgetSettings { const settings = new ChartSettings(config); - settings.setTimePresetOptions(getDefaultTimePresetOptions()); + settings.setTimeWindowOptions(getDefaultTimeWindowOptions()); + settings.setTimePrefixOptions(getDefaultTimePreFixOptions()); settings.setSamplingOptions(getDefaultSamplingOptions()); return settings; }, @@ -252,9 +228,10 @@ export class ChartWidget extends OrAssetWidget { .showZoomBar="${(this.widgetConfig?.showZoomBar != null) ? this.widgetConfig?.showZoomBar : true}" .showToolBox="${(this.widgetConfig?.showToolBox != null) ? this.widgetConfig?.showToolBox : true}" .attributeControls="${false}" .timestampControls="${!this.widgetConfig?.showTimestampControls}" - .timePresetOptions="${getDefaultTimePresetOptions()}" - .timePresetOptions2="${getDefaultTimePresetOptions()}" - .timePresetKey="${this.widgetConfig?.defaultTimePresetKey}" + .timeWindowOptions="${getDefaultTimeWindowOptions()}" + .timePrefixOptions="${getDefaultTimePreFixOptions()}" + .timePrefixKey="${this.widgetConfig?.defaultTimePrefixKey}" + .timeWindowKey="${this.widgetConfig?.defaultTimeWindowKey}" .datapointQuery="${this.datapointQuery}" .chartOptions="${this.widgetConfig?.chartOptions}" .showSymbolMaxDatapoints="${this.widgetConfig?.showSymbolMaxDatapoints}" .maxConcurrentDatapoints="${this.widgetConfig?.maxConcurrentDatapoints}" From 53ec086bf6aa887efb674c0db67d6e9d586582e9 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Tue, 18 Mar 2025 17:01:02 +0100 Subject: [PATCH 18/20] ready for first review --- ui/app/shared/locales/en/or.json | 11 ++++ ui/component/or-chart/src/index.ts | 51 +++---------------- .../src/settings/chart-settings.ts | 23 +++++---- .../src/widgets/chart-widget.ts | 19 ++++--- 4 files changed, 41 insertions(+), 63 deletions(-) diff --git a/ui/app/shared/locales/en/or.json b/ui/app/shared/locales/en/or.json index c9ec01ad3c..8c6371a4b3 100644 --- a/ui/app/shared/locales/en/or.json +++ b/ui/app/shared/locales/en/or.json @@ -133,7 +133,17 @@ "filterInvite": "Filter with at least 3 characters", "location": "Location", "longPressSetLoc": "Long press to set location", + "this": "This", + "last": "Last", + "5Minutes": "5 minutes", + "20Minutes": "20 minutes", + "60Minutes": "60 minutes", "hour": "Hour", + "6Hours": "6 hours", + "24Hours": "24 hours", + "7Days": "7 days", + "30Days": "30 days", + "365Days": "365 days", "lastHour": "Last hour", "last24Hours": "Last 24 hours", "thisHour": "This hour", @@ -158,6 +168,7 @@ "period": "Period", "ending": "Ending", "prefix": "Prefix", + "prefixDefault": "Default Prefix", "timeframe": "Timeframe", "timeframeDefault": "Default timeframe", "dataSampling": "Data sampling", diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index 265675606c..d671aee2d1 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -58,8 +58,6 @@ export class OrChartEvent extends CustomEvent { } } -export type TimePresetCallback = (date: Date) => [Date, Date]; - export interface ChartViewConfig { attributeRefs?: AttributeRef[]; fromTimestamp?: number; @@ -476,25 +474,21 @@ export class OrChart extends translate(i18next)(LitElement) { } connectedCallback() { - console.log('connectedCallback triggered'); super.connectedCallback(); this._style = window.getComputedStyle(this); } disconnectedCallback(): void { - console.log('disconnectedCallback triggered'); super.disconnectedCallback(); this._cleanup(); } firstUpdated() { - console.log('firstUpdated triggered'); this.loadSettings(false); } updated(changedProperties: PropertyValues) { - // console.log('updated triggered'); super.updated(changedProperties); @@ -509,10 +503,8 @@ export class OrChart extends translate(i18next)(LitElement) { changedProperties.has("attributeSettings") || changedProperties.has("assetAttributes") || changedProperties.has("realm") || changedProperties.has("dataProvider"); if (reloadData) { - // console.log('reloadData triggered'); this._data = undefined; if (this._chart) { - // console.log('releadData found _chart exists so disposing'); // Remove event listeners this._toggleChartEventListeners(false); this._chart.dispose(); @@ -522,7 +514,6 @@ export class OrChart extends translate(i18next)(LitElement) { } if (!this._data) { - // console.log("Data is not loaded yet"); return; } @@ -572,7 +563,7 @@ export class OrChart extends translate(i18next)(LitElement) { showMinLabel: true, showMaxLabel: true, hideOverlap: true, - textStyle: {fontWeight: 'bold'}, + fontSize: 10, formatter: { year: '{yyyy}-{MMM}', month: '{yy}-{MMM}', @@ -686,7 +677,6 @@ export class OrChart extends translate(i18next)(LitElement) { // Also sorts the attribute lists horizontally when it is below the chart applyChartResponsiveness(): void { if(this.shadowRoot) { - console.log('applyChartResponsiveness triggered'); const container = this.shadowRoot.getElementById('container'); if(container) { const bottomLegend: boolean = (container.clientWidth < 600); @@ -758,7 +748,7 @@ export class OrChart extends translate(i18next)(LitElement) { ${getContentWithMenuTemplate( html``, Array.from(this.timeWindowOptions!.keys()).map((key) => ({ value: key } as ListItem)), - this.timePrefixKey, + this.timeWindowKey, (value: string | string[]) => { this.timeframe = undefined; // remove any custom start & end times this.timeWindowKey = value.toString(); @@ -893,7 +883,6 @@ export class OrChart extends translate(i18next)(LitElement) { async loadSettings(reset: boolean) { - console.log('loadSettings triggered'); if(this.assetAttributes == undefined || reset) { this.assetAttributes = []; @@ -987,8 +976,6 @@ export class OrChart extends translate(i18next)(LitElement) { } async saveSettings() { - console.log('saveSettings triggered'); - if (!this.panelName) { return; } @@ -1065,7 +1052,6 @@ export class OrChart extends translate(i18next)(LitElement) { } protected _cleanup() { - console.log('cleanup triggered'); if (this._chart) { //('cleanup found _chart exists so disposing'); this._toggleChartEventListeners(false); @@ -1149,14 +1135,14 @@ export class OrChart extends translate(i18next)(LitElement) { protected _getDefaultTimeWindowOptions(): Map { return new Map([ ["hour", ['hours', 1]], - ["6 hours", ['hours', 6]], - ["24 hours", ['hours', 24]], + ["6Hours", ['hours', 6]], + ["24Hours", ['hours', 24]], ["day", ['days', 1]], - ["7 days", ['days', 7]], + ["7Days", ['days', 7]], ["week", ['weeks', 1]], - ["30 days", ['days', 30]], + ["30Days", ['days', 30]], ["month", ['months', 1]], - ["365 days", ['days', 365]], + ["365Days", ['days', 365]], ["year", ['years', 1]] ]); }; @@ -1177,7 +1163,7 @@ export class OrChart extends translate(i18next)(LitElement) { switch (timePrefixSelected) { case "this": if (value == 1) { // For singulars like this hour - startDate = moment().subtract(value, unit); + startDate = moment().startOf(unit); endDate = moment().endOf(unit); } else { // For multiples like this 5 min, put now in the middle startDate = moment().subtract(value*0.5, unit); @@ -1214,24 +1200,6 @@ export class OrChart extends translate(i18next)(LitElement) { } - - - - - - - - - - - - - - - - - - protected _getInterval(diffInHours: number): [number, DatapointInterval] { if(diffInHours <= 1) { @@ -1254,7 +1222,6 @@ export class OrChart extends translate(i18next)(LitElement) { } protected async _loadData() { - console.log('loadData triggered'); if ((this._data && !this._zoomChanged) || !this.assetAttributes || !this.assets || (this.assets.length === 0 && !this.dataProvider) || (this.assetAttributes.length === 0 && !this.dataProvider) || !this.datapointQuery) { return; } @@ -1484,7 +1451,6 @@ export class OrChart extends translate(i18next)(LitElement) { protected _onZoomChange(params: any) { this._zoomChanged = true; - console.log('onZoomChange triggered'); const { start: zoomStartPercentage, end: zoomEndPercentage } = params.batch[0]; //Define the start and end of the period based on the zoomed area this._zoomStartOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomStartPercentage / 100); @@ -1496,7 +1462,6 @@ export class OrChart extends translate(i18next)(LitElement) { } protected _updateChartData(){ - console.log('updateChartData triggered'); this._chart!.setOption({ xAxis: { min: this._startOfPeriod, diff --git a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts index fd18330c19..106e283e01 100644 --- a/ui/component/or-dashboard-builder/src/settings/chart-settings.ts +++ b/ui/component/or-dashboard-builder/src/settings/chart-settings.ts @@ -8,7 +8,6 @@ import {AttributeAction, AttributeActionEvent, AttributesSelectEvent} from "../p import {Asset, AssetDatapointIntervalQuery, AssetDatapointIntervalQueryFormula, Attribute, AttributeRef} from "@openremote/model"; import {ChartWidgetConfig} from "../widgets/chart-widget"; import {InputType, OrInputChangedEvent} from "@openremote/or-mwc-components/or-mwc-input"; -import {TimePresetCallback} from "@openremote/or-chart"; import {when} from "lit/directives/when.js"; import moment from "moment/moment"; @@ -145,13 +144,13 @@ export class ChartSettings extends WidgetSettings { > - - + +
-
+
+
+ + + +
- -
- +
@@ -277,7 +280,7 @@ export class ChartSettings extends WidgetSettings { - +
!ev.detail.attributeRefs.includes(ar)); diff --git a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts index eb1b8a3bee..ff0995addb 100644 --- a/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts +++ b/ui/component/or-dashboard-builder/src/widgets/chart-widget.ts @@ -1,7 +1,6 @@ import {AssetDatapointLTTBQuery, AssetDatapointQueryUnion, Attribute, AttributeRef} from "@openremote/model"; import {html, PropertyValues, TemplateResult } from "lit"; import { when } from "lit/directives/when.js"; -import {TimePresetCallback} from "@openremote/or-chart"; import moment from "moment"; import {OrAssetWidget} from "../util/or-asset-widget"; import { customElement, state } from "lit/decorators.js"; @@ -36,18 +35,18 @@ export interface ChartWidgetConfig extends WidgetConfig { function getDefaultTimeWindowOptions(): Map { return new Map([ - ["5 minutes", ['minutes', 5]], - ["20 minutes", ['minutes', 20]], - ["60 minutes", ['minutes', 60]], + ["5Minutes", ['minutes', 5]], + ["20Minutes", ['minutes', 20]], + ["60Minutes", ['minutes', 60]], ["hour", ['hours', 1]], - ["6 hours", ['hours', 6]], - ["24 hours", ['hours', 24]], + ["6Hours", ['hours', 6]], + ["24Hours", ['hours', 24]], ["day", ['days', 1]], - ["7 days", ['days', 7]], + ["7Days", ['days', 7]], ["week", ['weeks', 1]], - ["30 days", ['days', 30]], + ["30Days", ['days', 30]], ["month", ['months', 1]], - ["365 days", ['days', 365]], + ["365Days", ['days', 365]], ["year", ['years', 1]] ]); } @@ -61,7 +60,7 @@ function getDefaultSamplingOptions(): Map { } function getDefaultWidgetConfig(): ChartWidgetConfig { - const preset = "24 hours"; // Default time preset, "last" is hardcoded below. + const preset = "24Hours"; // Default time preset, "last" prefix is hardcoded in startDate and endDate below. const dateFunc = getDefaultTimeWindowOptions().get(preset); const startDate = moment().subtract(dateFunc![1], dateFunc![0]).startOf(dateFunc![0]); const endDate = dateFunc![1]== 1 ? moment().endOf(dateFunc![0]) : moment(); From d3add93aeb34689be049f6e7bce4a87429c4803d Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Tue, 18 Mar 2025 17:11:33 +0100 Subject: [PATCH 19/20] remove temp compose files --- docker-compose.yml | 4 +--- profile/dev-ui.yml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 10c2682c16..779b2e8707 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,8 +39,6 @@ services: volumes: - postgresql-data:/var/lib/postgresql/data - manager-data:/storage - ports: - - 5432:5432 keycloak: restart: always @@ -66,7 +64,7 @@ services: ports: - "127.0.0.1:8405:8405" # Localhost metrics access environment: - OR_SETUP_TYPE: ${OR_SETUP_TYPE:-demo} + OR_SETUP_TYPE: OR_ADMIN_PASSWORD: OR_SETUP_RUN_ON_RESTART: OR_EMAIL_HOST: diff --git a/profile/dev-ui.yml b/profile/dev-ui.yml index e1adf3e178..8e99947654 100644 --- a/profile/dev-ui.yml +++ b/profile/dev-ui.yml @@ -52,7 +52,7 @@ services: volumes: - manager-data:/storage environment: - OR_SETUP_RUN_ON_RESTART: ${OR_SETUP_RUN_ON_RESTART:-false} + OR_SETUP_RUN_ON_RESTART: ${OR_SETUP_RUN_ON_RESTART:-true} OR_DEV_MODE: ${OR_DEV_MODE:-true} ports: - "8080:8080" From bee9b9dbbe7faae9d20c0309e4f8f44804b896b9 Mon Sep 17 00:00:00 2001 From: Hackerberg43 Date: Wed, 19 Mar 2025 14:38:20 +0100 Subject: [PATCH 20/20] fix datazoombar event data retrieval --- ui/component/or-chart/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/component/or-chart/src/index.ts b/ui/component/or-chart/src/index.ts index d671aee2d1..5a376cac9a 100644 --- a/ui/component/or-chart/src/index.ts +++ b/ui/component/or-chart/src/index.ts @@ -1451,7 +1451,8 @@ export class OrChart extends translate(i18next)(LitElement) { protected _onZoomChange(params: any) { this._zoomChanged = true; - const { start: zoomStartPercentage, end: zoomEndPercentage } = params.batch[0]; + const { start: zoomStartPercentage, end: zoomEndPercentage } = params.batch?.[0] ?? params; // Events triggered by scroll and zoombar return different structures + //Define the start and end of the period based on the zoomed area this._zoomStartOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomStartPercentage / 100); this._zoomEndOfPeriod = this._startOfPeriod! + ((this._endOfPeriod! - this._startOfPeriod!) * zoomEndPercentage / 100);