@record-evolution/widget-linechart 1.6.34 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/widget-linechart.js +565 -427
- package/dist/widget-linechart.js.map +1 -1
- package/package.json +1 -1
- package/src/default-data.json +14 -11
- package/src/definition-schema.d.ts +30 -5
- package/src/definition-schema.json +65 -4
- package/src/widget-linechart.ts +250 -43
|
@@ -21,6 +21,10 @@ export type XAxisLabel = string;
|
|
|
21
21
|
* Label text displayed beside the vertical axis. Describes the measured values (e.g., 'Temperature (°C)', 'Count', 'Revenue ($)').
|
|
22
22
|
*/
|
|
23
23
|
export type YAxisLabel = string;
|
|
24
|
+
/**
|
|
25
|
+
* Label text displayed at the top of the secondary vertical axis on the right side of the chart. Describes the values measured on the right axis (e.g., 'Pressure (bar)', 'Humidity (%)'). The right y-axis only appears when at least one data series has its 'Y-Axis Assignment' (dataseries[].yAxis) set to 'right'; this label is ignored otherwise.
|
|
26
|
+
*/
|
|
27
|
+
export type RightYAxisLabel = string;
|
|
24
28
|
/**
|
|
25
29
|
* When enabled, displays a legend identifying each data series by color and label. Essential for multi-series charts.
|
|
26
30
|
*/
|
|
@@ -38,9 +42,13 @@ export type ShowBoxFrame = boolean;
|
|
|
38
42
|
*/
|
|
39
43
|
export type TimeseriesChart = boolean;
|
|
40
44
|
/**
|
|
41
|
-
*
|
|
45
|
+
* The direction in which bars grow and the chart is read. 'vertical' (the default) places the x-values (categories or timestamps) along the horizontal axis at the bottom and the measured y-values along the vertical axis, so bars grow upwards. 'horizontal' swaps the two axes: the x-values run down the left-hand vertical axis, first data point at the top, and the y-values grow to the right, producing a horizontal bar chart. Choose 'horizontal' for ranked or frequency charts, and whenever category names are long enough to be unreadable when squeezed under a vertical axis. This setting affects only the drawing direction, not the meaning of the other options: 'X-Axis Label' always names the x-value dimension and 'Y-Axis Label' always names the measured values, whichever side of the chart they end up on. Applies to all series in the chart, including line and scatter series.
|
|
42
46
|
*/
|
|
43
|
-
export type
|
|
47
|
+
export type BarDirection = "vertical" | "horizontal";
|
|
48
|
+
/**
|
|
49
|
+
* Controls how SEPARATE charts are arranged relative to each other when the series are grouped into more than one chart via 'Chart Name' (dataseries[].advanced.chartName). Enabled = the charts are stacked one above the other, Disabled = the charts sit side by side. This is a page-layout option and has no effect on a single chart; to change the direction bars grow in, use 'Bar Direction' (axis.orientation) instead.
|
|
50
|
+
*/
|
|
51
|
+
export type StackMultipleChartsVertically = boolean;
|
|
44
52
|
/**
|
|
45
53
|
* When enabled, displays the x-axis line, ticks, and labels. Disable to remove bottom axis and its padding for minimal designs.
|
|
46
54
|
*/
|
|
@@ -57,6 +65,10 @@ export type ShowYAxis = boolean;
|
|
|
57
65
|
* When enabled, the y-axis range automatically adjusts to fit the data (min to max). When disabled, the y-axis always starts at 0, which is better for comparing absolute values.
|
|
58
66
|
*/
|
|
59
67
|
export type YAxisScaling = boolean;
|
|
68
|
+
/**
|
|
69
|
+
* When enabled, the right y-axis range automatically adjusts to fit its assigned series (min to max). When disabled (the default), the right y-axis always starts at 0, which is better for comparing absolute values. This setting is independent of the left axis 'Y-Axis Scaling' and only has an effect when at least one data series is assigned to the right axis.
|
|
70
|
+
*/
|
|
71
|
+
export type RightYAxisScaling = boolean;
|
|
60
72
|
/**
|
|
61
73
|
* Display name for this data series shown in legends and tooltips. Should clearly identify what data this series represents.
|
|
62
74
|
*/
|
|
@@ -65,6 +77,10 @@ export type Label = string;
|
|
|
65
77
|
* The visual representation type: 'line' for continuous line charts (best for trends), 'bar' for vertical bar charts (best for comparisons), 'scatter' for individual data points (best for correlation analysis).
|
|
66
78
|
*/
|
|
67
79
|
export type DrawingStyle = "bar" | "line" | "scatter";
|
|
80
|
+
/**
|
|
81
|
+
* Selects which vertical axis this series is plotted against: 'left' (the default) uses the primary y-axis on the left side of the chart, 'right' uses a secondary y-axis on the right side with its own independent scale. Assign 'right' to series whose value range differs strongly from the other series (e.g., temperature 0-40 °C on the left and pressure 900-1100 hPa on the right) so each group remains readable. The right axis appears automatically as soon as one series in a chart is set to 'right' and is labeled/scaled via the global 'Right Y-Axis Label' and 'Right Y-Axis Scaling' settings. Note: all series in one chart share a single inferred axis data type (numeric or categorical), so do not mix numeric and categorical y-values across the two axes.
|
|
82
|
+
*/
|
|
83
|
+
export type YAxisAssignment = "left" | "right";
|
|
68
84
|
/**
|
|
69
85
|
* When enabled for line charts, fills the area between the line and the x-axis, creating an area chart. Useful for visualizing volume or cumulative values.
|
|
70
86
|
*/
|
|
@@ -81,6 +97,10 @@ export type LineDashStyle = "solid" | "dashed" | "dotted";
|
|
|
81
97
|
* Shape of data point markers: 'circle', 'rect' (square), 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', or 'none' to hide markers. Different shapes help distinguish series.
|
|
82
98
|
*/
|
|
83
99
|
export type SymbolStyle = "circle" | "rect" | "roundRect" | "triangle" | "diamond" | "pin" | "arrow" | "none";
|
|
100
|
+
/**
|
|
101
|
+
* When enabled, each data point's y-value is printed as text directly on the chart: above the top of every bar in a vertical bar chart, just past the end of the bar in a horizontal one, and above the marker for line and scatter series. Enable this whenever the reader is meant to read exact numbers off the chart rather than estimate them against the axis — the usual expectation for bar charts of counts, totals or frequencies, and the setting to use when someone asks for the values to be shown on or above the bars. Numbers are rounded to two decimal places; non-numeric values are printed unchanged. Leave it disabled for charts with many data points, where the labels would overlap each other.
|
|
102
|
+
*/
|
|
103
|
+
export type ShowValueLabels = boolean;
|
|
84
104
|
/**
|
|
85
105
|
* Z-index controlling which series appears on top when overlapping. Lower numbers are drawn on top. Use to ensure important series aren't hidden behind others.
|
|
86
106
|
*/
|
|
@@ -123,6 +143,7 @@ export type Dataseries = {
|
|
|
123
143
|
type?: DrawingStyle;
|
|
124
144
|
backgroundColor?: FillColor;
|
|
125
145
|
borderColor?: LineColor;
|
|
146
|
+
yAxis?: YAxisAssignment;
|
|
126
147
|
styling?: Styling;
|
|
127
148
|
advanced?: AdvancedSettings;
|
|
128
149
|
data?: Data;
|
|
@@ -130,9 +151,9 @@ export type Dataseries = {
|
|
|
130
151
|
}[];
|
|
131
152
|
|
|
132
153
|
/**
|
|
133
|
-
* A versatile chart widget for visualizing data as line charts, bar charts, or scatter plots. Use this widget for time-series data, trend analysis, comparisons, and general data visualization. Supports multiple data series with different chart types in the same view, automatic pivot-based series generation, zoom controls, and flexible styling options. The ECharts-based rendering provides smooth animations and interactive tooltips. Ideal for monitoring dashboards, historical data analysis, and any scenario requiring visual data representation over continuous or categorical dimensions.
|
|
154
|
+
* A versatile chart widget for visualizing data as line charts, bar charts, or scatter plots. Despite the package name, this is also THE bar chart widget: set dataseries[].type to 'bar' for vertical bars and axis.orientation to 'horizontal' for horizontal ones. Use this widget for time-series data, trend analysis, comparisons, frequency distributions, and general data visualization. Supports multiple data series with different chart types in the same view, automatic pivot-based series generation, zoom controls, and flexible styling options. The ECharts-based rendering provides smooth animations and interactive tooltips. Ideal for monitoring dashboards, historical data analysis, and any scenario requiring visual data representation over continuous or categorical dimensions.
|
|
134
155
|
*/
|
|
135
|
-
export interface
|
|
156
|
+
export interface ChartConfiguration {
|
|
136
157
|
title?: Title;
|
|
137
158
|
subTitle?: Subtitle;
|
|
138
159
|
axis?: Configuration;
|
|
@@ -145,15 +166,18 @@ export interface InputData {
|
|
|
145
166
|
export interface Configuration {
|
|
146
167
|
xAxisLabel?: XAxisLabel;
|
|
147
168
|
yAxisLabel?: YAxisLabel;
|
|
169
|
+
yAxisLabelRight?: RightYAxisLabel;
|
|
148
170
|
showLegend?: ShowLegend;
|
|
149
171
|
showTitle?: ShowTitle;
|
|
150
172
|
showBox?: ShowBoxFrame;
|
|
151
173
|
timeseries?: TimeseriesChart;
|
|
152
|
-
|
|
174
|
+
orientation?: BarDirection;
|
|
175
|
+
columnLayout?: StackMultipleChartsVertically;
|
|
153
176
|
showXAxis?: ShowXAxis;
|
|
154
177
|
xAxisZoom?: XAxisZoomTool;
|
|
155
178
|
showYAxis?: ShowYAxis;
|
|
156
179
|
yAxisScaling?: YAxisScaling;
|
|
180
|
+
yAxisScalingRight?: RightYAxisScaling;
|
|
157
181
|
[k: string]: unknown;
|
|
158
182
|
}
|
|
159
183
|
/**
|
|
@@ -176,6 +200,7 @@ export interface Styling {
|
|
|
176
200
|
borderWidth?: LineWidth;
|
|
177
201
|
borderDash?: LineDashStyle;
|
|
178
202
|
pointStyle?: SymbolStyle;
|
|
203
|
+
showValueLabels?: ShowValueLabels;
|
|
179
204
|
[k: string]: unknown;
|
|
180
205
|
}
|
|
181
206
|
/**
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
"title": "
|
|
3
|
-
"description": "A versatile chart widget for visualizing data as line charts, bar charts, or scatter plots. Use this widget for time-series data, trend analysis, comparisons, and general data visualization. Supports multiple data series with different chart types in the same view, automatic pivot-based series generation, zoom controls, and flexible styling options. The ECharts-based rendering provides smooth animations and interactive tooltips. Ideal for monitoring dashboards, historical data analysis, and any scenario requiring visual data representation over continuous or categorical dimensions.",
|
|
2
|
+
"title": "Chart Configuration",
|
|
3
|
+
"description": "A versatile chart widget for visualizing data as line charts, bar charts, or scatter plots. Despite the package name, this is also THE bar chart widget: set dataseries[].type to 'bar' for vertical bars and axis.orientation to 'horizontal' for horizontal ones. Use this widget for time-series data, trend analysis, comparisons, frequency distributions, and general data visualization. Supports multiple data series with different chart types in the same view, automatic pivot-based series generation, zoom controls, and flexible styling options. The ECharts-based rendering provides smooth animations and interactive tooltips. Ideal for monitoring dashboards, historical data analysis, and any scenario requiring visual data representation over continuous or categorical dimensions.",
|
|
4
|
+
"aiSelection": {
|
|
5
|
+
"dataShape": "One or more series of (x, y) pairs. x is a timestamp, a number, or a category label and defines the position along the x-value dimension; y is the measured quantity. All series in a chart share one x dimension. An optional third column bound to data[].pivot splits a single series into one line or bar per distinct value of that column, and an optional data[].r sizes the marker for a bubble chart. Needs at least two columns of data; a single scalar has no shape to plot.",
|
|
6
|
+
"useWhen": [
|
|
7
|
+
"A quantity is measured repeatedly over time and the trend, shape or direction of change is the point (axis.timeseries: true, dataseries[].type: 'line').",
|
|
8
|
+
"A quantity is compared across a handful of discrete categories — counts, totals, frequency distributions, per-machine or per-site comparisons (dataseries[].type: 'bar', axis.timeseries: false). This is the correct widget for any bar chart; there is no separate bar chart widget.",
|
|
9
|
+
"Categories are being ranked, or their names are long enough to be unreadable side by side under a vertical axis (dataseries[].type: 'bar', axis.orientation: 'horizontal').",
|
|
10
|
+
"Two numeric variables are being correlated as a cloud of individual points (dataseries[].type: 'scatter'), optionally with data[].r encoding a third variable as marker size.",
|
|
11
|
+
"Several quantities with different units or wildly different ranges belong on one chart (assign the second group dataseries[].yAxis: 'right').",
|
|
12
|
+
"The same measurement is broken down by an entity — one line per machine, per site, per program (bind the breakdown column to data[].pivot)."
|
|
13
|
+
],
|
|
14
|
+
"notFor": [
|
|
15
|
+
"Parts of a whole, shares, or percentages that add up to 100% — use widget-doughnut.",
|
|
16
|
+
"A single current number, reading or KPI with no second dimension to plot — use widget-value, or widget-gauge when it should be read against a min/max range.",
|
|
17
|
+
"Records the user needs to read row by row, or data with more columns than an x and a y — use widget-table.",
|
|
18
|
+
"Intensity or density across two categorical dimensions at once, i.e. a matrix or calendar grid — use widget-heatmap.",
|
|
19
|
+
"How long an entity stayed in each discrete state over time (state timelines, machine mode history, up/down periods drawn as spans). A bar chart of state durations is the wrong shape — use widget-statehistory.",
|
|
20
|
+
"Anything positioned by geographic coordinates — use widget-mapbox.",
|
|
21
|
+
"A single binary or few-valued status shown as an indicator rather than plotted — use widget-signal, or widget-switch when the user must also be able to toggle it.",
|
|
22
|
+
"Free text, headings or explanatory prose — use widget-label, widget-textbox or widget-markdown."
|
|
23
|
+
]
|
|
24
|
+
},
|
|
4
25
|
"type": "object",
|
|
5
26
|
"properties": {
|
|
6
27
|
"title": {
|
|
@@ -35,6 +56,13 @@
|
|
|
35
56
|
"dataDrivenDisabled": false,
|
|
36
57
|
"order": 1
|
|
37
58
|
},
|
|
59
|
+
"yAxisLabelRight": {
|
|
60
|
+
"title": "Right Y-Axis Label",
|
|
61
|
+
"description": "Label text displayed at the top of the secondary vertical axis on the right side of the chart. Describes the values measured on the right axis (e.g., 'Pressure (bar)', 'Humidity (%)'). The right y-axis only appears when at least one data series has its 'Y-Axis Assignment' (dataseries[].yAxis) set to 'right'; this label is ignored otherwise.",
|
|
62
|
+
"type": "string",
|
|
63
|
+
"dataDrivenDisabled": false,
|
|
64
|
+
"order": 1.05
|
|
65
|
+
},
|
|
38
66
|
"showLegend": {
|
|
39
67
|
"title": "Show Legend",
|
|
40
68
|
"description": "When enabled, displays a legend identifying each data series by color and label. Essential for multi-series charts.",
|
|
@@ -66,9 +94,18 @@
|
|
|
66
94
|
"dataDrivenDisabled": true,
|
|
67
95
|
"order": 4
|
|
68
96
|
},
|
|
97
|
+
"orientation": {
|
|
98
|
+
"title": "Bar Direction",
|
|
99
|
+
"description": "The direction in which bars grow and the chart is read. 'vertical' (the default) places the x-values (categories or timestamps) along the horizontal axis at the bottom and the measured y-values along the vertical axis, so bars grow upwards. 'horizontal' swaps the two axes: the x-values run down the left-hand vertical axis, first data point at the top, and the y-values grow to the right, producing a horizontal bar chart. Choose 'horizontal' for ranked or frequency charts, and whenever category names are long enough to be unreadable when squeezed under a vertical axis. This setting affects only the drawing direction, not the meaning of the other options: 'X-Axis Label' always names the x-value dimension and 'Y-Axis Label' always names the measured values, whichever side of the chart they end up on. Applies to all series in the chart, including line and scatter series.",
|
|
100
|
+
"type": "string",
|
|
101
|
+
"enum": ["vertical", "horizontal"],
|
|
102
|
+
"default": "vertical",
|
|
103
|
+
"dataDrivenDisabled": true,
|
|
104
|
+
"order": 4.5
|
|
105
|
+
},
|
|
69
106
|
"columnLayout": {
|
|
70
|
-
"title": "
|
|
71
|
-
"description": "
|
|
107
|
+
"title": "Stack Multiple Charts Vertically",
|
|
108
|
+
"description": "Controls how SEPARATE charts are arranged relative to each other when the series are grouped into more than one chart via 'Chart Name' (dataseries[].advanced.chartName). Enabled = the charts are stacked one above the other, Disabled = the charts sit side by side. This is a page-layout option and has no effect on a single chart; to change the direction bars grow in, use 'Bar Direction' (axis.orientation) instead.",
|
|
72
109
|
"type": "boolean",
|
|
73
110
|
"dataDrivenDisabled": true,
|
|
74
111
|
"order": 5
|
|
@@ -104,6 +141,13 @@
|
|
|
104
141
|
"type": "boolean",
|
|
105
142
|
"dataDrivenDisabled": true,
|
|
106
143
|
"order": 11
|
|
144
|
+
},
|
|
145
|
+
"yAxisScalingRight": {
|
|
146
|
+
"title": "Right Y-Axis Scaling",
|
|
147
|
+
"description": "When enabled, the right y-axis range automatically adjusts to fit its assigned series (min to max). When disabled (the default), the right y-axis always starts at 0, which is better for comparing absolute values. This setting is independent of the left axis 'Y-Axis Scaling' and only has an effect when at least one data series is assigned to the right axis.",
|
|
148
|
+
"type": "boolean",
|
|
149
|
+
"dataDrivenDisabled": true,
|
|
150
|
+
"order": 11.1
|
|
107
151
|
}
|
|
108
152
|
}
|
|
109
153
|
},
|
|
@@ -146,6 +190,15 @@
|
|
|
146
190
|
"color": true,
|
|
147
191
|
"order": 4
|
|
148
192
|
},
|
|
193
|
+
"yAxis": {
|
|
194
|
+
"title": "Y-Axis Assignment",
|
|
195
|
+
"description": "Selects which vertical axis this series is plotted against: 'left' (the default) uses the primary y-axis on the left side of the chart, 'right' uses a secondary y-axis on the right side with its own independent scale. Assign 'right' to series whose value range differs strongly from the other series (e.g., temperature 0-40 °C on the left and pressure 900-1100 hPa on the right) so each group remains readable. The right axis appears automatically as soon as one series in a chart is set to 'right' and is labeled/scaled via the global 'Right Y-Axis Label' and 'Right Y-Axis Scaling' settings. Note: all series in one chart share a single inferred axis data type (numeric or categorical), so do not mix numeric and categorical y-values across the two axes.",
|
|
196
|
+
"type": "string",
|
|
197
|
+
"enum": ["left", "right"],
|
|
198
|
+
"default": "left",
|
|
199
|
+
"dataDrivenDisabled": true,
|
|
200
|
+
"order": 4.5
|
|
201
|
+
},
|
|
149
202
|
"styling": {
|
|
150
203
|
"title": "Styling",
|
|
151
204
|
"description": "Additional visual styling options for the series.",
|
|
@@ -195,6 +248,14 @@
|
|
|
195
248
|
"none"
|
|
196
249
|
],
|
|
197
250
|
"order": 5
|
|
251
|
+
},
|
|
252
|
+
"showValueLabels": {
|
|
253
|
+
"title": "Show Value Labels",
|
|
254
|
+
"description": "When enabled, each data point's y-value is printed as text directly on the chart: above the top of every bar in a vertical bar chart, just past the end of the bar in a horizontal one, and above the marker for line and scatter series. Enable this whenever the reader is meant to read exact numbers off the chart rather than estimate them against the axis — the usual expectation for bar charts of counts, totals or frequencies, and the setting to use when someone asks for the values to be shown on or above the bars. Numbers are rounded to two decimal places; non-numeric values are printed unchanged. Leave it disabled for charts with many data points, where the labels would overlap each other.",
|
|
255
|
+
"type": "boolean",
|
|
256
|
+
"default": false,
|
|
257
|
+
"dataDrivenDisabled": true,
|
|
258
|
+
"order": 6
|
|
198
259
|
}
|
|
199
260
|
}
|
|
200
261
|
},
|
package/src/widget-linechart.ts
CHANGED
|
@@ -30,7 +30,7 @@ echarts.use([
|
|
|
30
30
|
LegacyGridContainLabel
|
|
31
31
|
])
|
|
32
32
|
|
|
33
|
-
import {
|
|
33
|
+
import { ChartConfiguration } from './definition-schema'
|
|
34
34
|
import { EChartsOption, SeriesOption } from 'echarts'
|
|
35
35
|
import { TitleOption } from 'echarts/types/dist/shared'
|
|
36
36
|
|
|
@@ -39,11 +39,23 @@ type Theme = {
|
|
|
39
39
|
theme_object: any
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
type SeriesOptionX = SeriesOption & {
|
|
42
|
+
type SeriesOptionX = SeriesOption & {
|
|
43
|
+
minDate?: number
|
|
44
|
+
maxDate?: number
|
|
45
|
+
drawOrder: number
|
|
46
|
+
xAxisIndex?: number
|
|
47
|
+
yAxisIndex?: number
|
|
48
|
+
// Which value axis (0 = primary, 1 = secondary) this series belongs to,
|
|
49
|
+
// independent of orientation. In a vertical chart the value axes are the
|
|
50
|
+
// y axes (left/right), in a horizontal one they are the x axes (bottom/top),
|
|
51
|
+
// so xAxisIndex/yAxisIndex alone cannot be read without knowing the
|
|
52
|
+
// orientation — this field can.
|
|
53
|
+
valueAxisIndex?: number
|
|
54
|
+
}
|
|
43
55
|
@customElement('widget-linechart-versionplaceholder')
|
|
44
56
|
export class WidgetLinechart extends LitElement {
|
|
45
57
|
@property({ type: Object })
|
|
46
|
-
inputData?:
|
|
58
|
+
inputData?: ChartConfiguration
|
|
47
59
|
|
|
48
60
|
@property({ type: Object })
|
|
49
61
|
theme?: Theme
|
|
@@ -143,21 +155,45 @@ export class WidgetLinechart extends LitElement {
|
|
|
143
155
|
fontSize: 14
|
|
144
156
|
}
|
|
145
157
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
+
// index 0 = left (primary) axis, index 1 = right (secondary) axis.
|
|
159
|
+
// Must stay function-free: the template is copied via structuredClone.
|
|
160
|
+
yAxis: [
|
|
161
|
+
{
|
|
162
|
+
type: 'value',
|
|
163
|
+
nameLocation: 'middle',
|
|
164
|
+
name: 'Temperature (°C)',
|
|
165
|
+
nameGap: 30,
|
|
166
|
+
position: 'left',
|
|
167
|
+
axisLabel: {
|
|
168
|
+
fontSize: 14
|
|
169
|
+
},
|
|
170
|
+
axisLine: {
|
|
171
|
+
lineStyle: {
|
|
172
|
+
width: undefined
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
scale: false
|
|
158
176
|
},
|
|
159
|
-
|
|
160
|
-
|
|
177
|
+
{
|
|
178
|
+
type: 'value',
|
|
179
|
+
position: 'right',
|
|
180
|
+
show: false,
|
|
181
|
+
name: '',
|
|
182
|
+
nameGap: 30,
|
|
183
|
+
axisLabel: {
|
|
184
|
+
fontSize: 14
|
|
185
|
+
},
|
|
186
|
+
axisLine: {
|
|
187
|
+
lineStyle: {
|
|
188
|
+
width: undefined
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
splitLine: {
|
|
192
|
+
show: false
|
|
193
|
+
},
|
|
194
|
+
scale: false
|
|
195
|
+
}
|
|
196
|
+
],
|
|
161
197
|
series: [
|
|
162
198
|
{
|
|
163
199
|
name: 'Highest',
|
|
@@ -241,9 +277,49 @@ export class WidgetLinechart extends LitElement {
|
|
|
241
277
|
echarts.registerTheme(theme.theme_name, filteredTheme)
|
|
242
278
|
}
|
|
243
279
|
|
|
280
|
+
/** True when bars grow to the right instead of upwards (axis.orientation). */
|
|
281
|
+
isHorizontal(): boolean {
|
|
282
|
+
return this.inputData?.axis?.orientation === 'horizontal'
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Index of the measured y-value inside a data point's `value` tuple.
|
|
287
|
+
*
|
|
288
|
+
* A point is stored as `[x, y, r]` for a vertical chart. A horizontal chart
|
|
289
|
+
* swaps the first two entries to `[y, x, r]`, because ECharts always maps
|
|
290
|
+
* tuple index 0 to the x axis and index 1 to the y axis — and in a
|
|
291
|
+
* horizontal chart the measured value is what belongs on the x axis.
|
|
292
|
+
*/
|
|
293
|
+
private valueIndex(): number {
|
|
294
|
+
return this.isHorizontal() ? 0 : 1
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Index of the x-value (category/timestamp) inside a data point's `value` tuple. */
|
|
298
|
+
private categoryIndex(): number {
|
|
299
|
+
return this.isHorizontal() ? 1 : 0
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Formatter for the on-chart value labels (styling.showValueLabels).
|
|
304
|
+
* Rounds to two decimals to match the numeric axis labels, and leaves
|
|
305
|
+
* categorical y-values untouched.
|
|
306
|
+
*/
|
|
307
|
+
private valueLabelFormatter(valueIdx: number) {
|
|
308
|
+
return (params: any) => {
|
|
309
|
+
const raw = Array.isArray(params?.value) ? params.value[valueIdx] : params?.value
|
|
310
|
+
if (raw === undefined || raw === null || raw === '') return ''
|
|
311
|
+
const num = Number(raw)
|
|
312
|
+
return isNaN(num) ? String(raw) : String(Math.round(num * 100) / 100)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
244
316
|
transformData() {
|
|
245
317
|
if (!this?.inputData?.dataseries?.length) return
|
|
246
318
|
|
|
319
|
+
const horizontal = this.isHorizontal()
|
|
320
|
+
const valueIdx = this.valueIndex()
|
|
321
|
+
const categoryIdx = this.categoryIndex()
|
|
322
|
+
|
|
247
323
|
// reset all existing chart dataseries
|
|
248
324
|
this.canvasList.forEach((chartM) => {
|
|
249
325
|
chartM.series = []
|
|
@@ -280,22 +356,29 @@ export class WidgetLinechart extends LitElement {
|
|
|
280
356
|
: derivedBgColors[i]
|
|
281
357
|
: undefined
|
|
282
358
|
const data = distincts.length === 1 ? ds.data : ds.data?.filter((d) => d.pivot === piv)
|
|
359
|
+
// The value tuple is [x, y, r] for a vertical chart and [y, x, r]
|
|
360
|
+
// for a horizontal one — ECharts always reads index 0 as the x
|
|
361
|
+
// axis, and in a horizontal chart the measured value lives there.
|
|
362
|
+
const toTuple = (x: any, y: any, r: any) => (horizontal ? [y, x, r] : [x, y, r])
|
|
283
363
|
let data2 = this.inputData?.axis?.timeseries
|
|
284
|
-
? (data?.map((d) => ({
|
|
285
|
-
|
|
286
|
-
|
|
364
|
+
? (data?.map((d) => ({
|
|
365
|
+
name: d.x,
|
|
366
|
+
value: toTuple(new Date(d.x ?? '').getTime(), d.y, d.r)
|
|
367
|
+
})) ?? [])
|
|
368
|
+
: (data?.map((d) => ({ name: d.x, value: toTuple(d.x, d.y, d.r) })) ?? [])
|
|
287
369
|
|
|
288
370
|
let minDate: number = 0,
|
|
289
371
|
maxDate: number = 0,
|
|
290
372
|
extraData: (string | number | undefined)[][] = []
|
|
291
373
|
if (this.xAxisType() === 'time' && data2) {
|
|
292
|
-
const dates = data2.map((d: any) => d.value[
|
|
374
|
+
const dates = data2.map((d: any) => d.value[categoryIdx] as number)
|
|
293
375
|
minDate = Math.min(...dates)
|
|
294
376
|
maxDate = Math.max(...dates)
|
|
295
377
|
// extraData = (pds?.data as any[][])?.filter((d: any) => d[0] < minDate) ?? []
|
|
296
378
|
// data2.unshift(...extraData) // add old data to the beginning of the new data
|
|
297
379
|
// data2 = [...extraData, ...data2] // leave old data in for smooth shifting animation and delete after draw
|
|
298
380
|
}
|
|
381
|
+
const valueAxisIndex = ds.yAxis === 'right' ? 1 : 0
|
|
299
382
|
const pds: SeriesOptionX = {
|
|
300
383
|
id: name,
|
|
301
384
|
name: name,
|
|
@@ -317,8 +400,21 @@ export class WidgetLinechart extends LitElement {
|
|
|
317
400
|
symbol: ds.styling?.pointStyle ?? 'circle',
|
|
318
401
|
symbolSize: (d: any[]) => d[2] ?? 0,
|
|
319
402
|
showSymbol: ds.styling?.pointStyle === 'none' ? false : true,
|
|
403
|
+
label: {
|
|
404
|
+
show: ds.styling?.showValueLabels ?? false,
|
|
405
|
+
// Bars grow upwards when vertical and rightwards when
|
|
406
|
+
// horizontal, so the label sits past the growing end.
|
|
407
|
+
position: horizontal ? 'right' : 'top',
|
|
408
|
+
fontSize: 12,
|
|
409
|
+
formatter: this.valueLabelFormatter(valueIdx)
|
|
410
|
+
},
|
|
320
411
|
data: data2 ?? [],
|
|
321
|
-
drawOrder: ds.advanced?.drawOrder ?? 0
|
|
412
|
+
drawOrder: ds.advanced?.drawOrder ?? 0,
|
|
413
|
+
valueAxisIndex: valueAxisIndex,
|
|
414
|
+
// The secondary value axis is the right y axis when vertical
|
|
415
|
+
// and the top x axis when horizontal.
|
|
416
|
+
xAxisIndex: horizontal ? valueAxisIndex : 0,
|
|
417
|
+
yAxisIndex: horizontal ? 0 : valueAxisIndex
|
|
322
418
|
}
|
|
323
419
|
let chartName = ds.advanced?.chartName ?? ''
|
|
324
420
|
chartName = chartName.replace('#split#', prefix)
|
|
@@ -455,6 +551,7 @@ export class WidgetLinechart extends LitElement {
|
|
|
455
551
|
const modifier = 1
|
|
456
552
|
// Sort chartContainer children by drawOrder and label
|
|
457
553
|
if (!this.chartContainer) return
|
|
554
|
+
const horizontal = this.isHorizontal()
|
|
458
555
|
for (const canvas of this.canvasList.values()) {
|
|
459
556
|
if (canvas.element) this.chartContainer.appendChild(canvas.element)
|
|
460
557
|
}
|
|
@@ -479,10 +576,18 @@ export class WidgetLinechart extends LitElement {
|
|
|
479
576
|
yAxisLabel: this.inputData?.axis?.yAxisLabel,
|
|
480
577
|
xAxisZoom: this.inputData?.axis?.xAxisZoom,
|
|
481
578
|
yAxisScaling: this.inputData?.axis?.yAxisScaling,
|
|
579
|
+
yAxisLabelRight: this.inputData?.axis?.yAxisLabelRight,
|
|
580
|
+
yAxisScalingRight: this.inputData?.axis?.yAxisScalingRight,
|
|
482
581
|
xAxisType: this.xAxisType(),
|
|
483
582
|
yAxisType: this.yAxisType(),
|
|
583
|
+
// Flipping the orientation swaps which option key holds the
|
|
584
|
+
// single category axis and which holds the value-axis array,
|
|
585
|
+
// so it must force a notMerge rebuild rather than a merge.
|
|
586
|
+
orientation: this.inputData?.axis?.orientation ?? 'vertical',
|
|
484
587
|
seriesCount: chart.series.length,
|
|
485
|
-
seriesNames: chart.series.map((s) => s.name).join(',')
|
|
588
|
+
seriesNames: chart.series.map((s) => s.name).join(','),
|
|
589
|
+
seriesAxes: chart.series.map((s) => s.valueAxisIndex ?? 0).join(','),
|
|
590
|
+
seriesLabels: chart.series.map((s) => ((s as any).label?.show ? 1 : 0)).join(',')
|
|
486
591
|
})
|
|
487
592
|
const configChanged = chart.lastConfig !== currentConfig
|
|
488
593
|
chart.lastConfig = currentConfig
|
|
@@ -520,31 +625,114 @@ export class WidgetLinechart extends LitElement {
|
|
|
520
625
|
}
|
|
521
626
|
}
|
|
522
627
|
|
|
523
|
-
|
|
524
|
-
option.
|
|
628
|
+
const showZoom = this.inputData?.axis?.xAxisZoom ?? false
|
|
629
|
+
option.dataZoom[0].show = showZoom
|
|
630
|
+
option.toolbox.show = showZoom
|
|
631
|
+
|
|
632
|
+
// Y axes: index 0 = left (primary), index 1 = right (secondary).
|
|
633
|
+
// The template holds an array, but getOption() (merge path) also returns
|
|
634
|
+
// component options normalized to arrays — handle both shapes.
|
|
635
|
+
const yAxes: any[] = Array.isArray(option.yAxis) ? option.yAxis : [option.yAxis ?? {}]
|
|
636
|
+
while (yAxes.length < 2) yAxes.push({})
|
|
637
|
+
option.yAxis = yAxes
|
|
638
|
+
|
|
639
|
+
const hasValueLabels = chart.series.some((s) => !!(s as any).label?.show)
|
|
640
|
+
const rightAxisUsed = chart.series.some((s) => (s.valueAxisIndex ?? 0) === 1)
|
|
641
|
+
const leftAxisUsed =
|
|
642
|
+
chart.series.length === 0 || chart.series.some((s) => (s.valueAxisIndex ?? 0) === 0)
|
|
643
|
+
const showLeftAxis = showYAxis && leftAxisUsed
|
|
644
|
+
const showRightAxis = showYAxis && rightAxisUsed
|
|
525
645
|
|
|
526
646
|
const yAxisLabel = this.inputData?.axis?.yAxisLabel ?? ''
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
647
|
+
const yAxisLabelRight = this.inputData?.axis?.yAxisLabelRight ?? ''
|
|
648
|
+
const hasYAxisLabel = showLeftAxis && !!yAxisLabel
|
|
649
|
+
const hasYAxisLabelRight = showRightAxis && !!yAxisLabelRight
|
|
650
|
+
|
|
651
|
+
const yType = this.yAxisType()
|
|
652
|
+
const numericAxisLabel = ['value', 'log'].includes(yType ?? '')
|
|
653
|
+
? { fontSize: 14, formatter: (value: number) => Math.round(value * 100) / 100 }
|
|
654
|
+
: undefined
|
|
655
|
+
|
|
656
|
+
Object.assign(yAxes[0], {
|
|
657
|
+
type: yType,
|
|
658
|
+
name: yAxisLabel,
|
|
659
|
+
scale: this.inputData?.axis?.yAxisScaling ?? false,
|
|
660
|
+
show: showLeftAxis,
|
|
661
|
+
position: 'left',
|
|
662
|
+
nameLocation: 'end',
|
|
663
|
+
nameGap: 10,
|
|
664
|
+
nameTextStyle: { align: 'left' },
|
|
665
|
+
axisLine: { show: true }
|
|
666
|
+
})
|
|
667
|
+
if (numericAxisLabel) yAxes[0].axisLabel = numericAxisLabel
|
|
668
|
+
|
|
669
|
+
Object.assign(yAxes[1], {
|
|
670
|
+
type: yType,
|
|
671
|
+
name: showRightAxis ? yAxisLabelRight : '',
|
|
672
|
+
scale: this.inputData?.axis?.yAxisScalingRight ?? false,
|
|
673
|
+
show: showRightAxis,
|
|
674
|
+
position: 'right',
|
|
675
|
+
nameLocation: 'end',
|
|
676
|
+
nameGap: 10,
|
|
677
|
+
nameTextStyle: { align: 'right' },
|
|
678
|
+
axisLine: { show: true },
|
|
679
|
+
splitLine: { show: false }
|
|
680
|
+
})
|
|
681
|
+
if (numericAxisLabel) yAxes[1].axisLabel = numericAxisLabel
|
|
682
|
+
|
|
683
|
+
// Orientation swap. Everything above builds one category axis (the
|
|
684
|
+
// x-values) and a two-entry array of value axes (the y-values). A
|
|
685
|
+
// vertical chart puts them where they were built; a horizontal one
|
|
686
|
+
// moves the category axis onto `yAxis` and the value axes onto
|
|
687
|
+
// `xAxis`. The axis *labels* keep their meaning either way:
|
|
688
|
+
// 'X-Axis Label' names the x-value dimension wherever it is drawn.
|
|
689
|
+
if (horizontal) {
|
|
690
|
+
const catAxis: any = Array.isArray(option.xAxis) ? option.xAxis[0] : option.xAxis
|
|
691
|
+
option.yAxis = {
|
|
692
|
+
...catAxis,
|
|
693
|
+
position: 'left',
|
|
694
|
+
// Read top-to-bottom, so the first data point is the top
|
|
695
|
+
// bar — the convention for ranked horizontal bar charts.
|
|
696
|
+
inverse: true,
|
|
697
|
+
// 'start' is the top of an inverted axis, which keeps the
|
|
698
|
+
// axis name clear of the value axis running along the bottom.
|
|
699
|
+
// When a secondary value axis is in use it is drawn along
|
|
700
|
+
// the top, so the name has to clear its tick labels too.
|
|
701
|
+
nameLocation: 'start',
|
|
702
|
+
nameGap: showRightAxis ? 30 : 10,
|
|
703
|
+
nameTextStyle: { align: 'left' }
|
|
540
704
|
}
|
|
705
|
+
option.xAxis = yAxes.map((axis: any, index: number) => ({
|
|
706
|
+
...axis,
|
|
707
|
+
position: index === 0 ? 'bottom' : 'top',
|
|
708
|
+
nameLocation: 'middle',
|
|
709
|
+
nameGap: 27,
|
|
710
|
+
nameTextStyle: { align: 'center' }
|
|
711
|
+
}))
|
|
712
|
+
// Zooming still applies to the x-value dimension, which now
|
|
713
|
+
// lives on the y axis.
|
|
714
|
+
option.dataZoom = [
|
|
715
|
+
{ ...(option.dataZoom?.[0] ?? {}), xAxisIndex: undefined, yAxisIndex: [0] }
|
|
716
|
+
]
|
|
717
|
+
}
|
|
541
718
|
|
|
542
719
|
option.series = chart.series
|
|
543
720
|
option.legend.show = showLegend
|
|
544
721
|
|
|
545
722
|
// Dynamic grid padding based on visible elements
|
|
546
|
-
// Add extra top space when Y-axis label is shown at 'end' position
|
|
547
|
-
|
|
723
|
+
// Add extra top space when Y-axis label is shown at 'end' position.
|
|
724
|
+
// The right axis name and the legend both live in the top-right corner,
|
|
725
|
+
// so reserve an extra row when both are visible.
|
|
726
|
+
const topPadding =
|
|
727
|
+
(showTitle || hasYAxisLabel || hasYAxisLabelRight ? 30 : 0) +
|
|
728
|
+
(showLegend && hasYAxisLabelRight ? 25 : 0)
|
|
729
|
+
// An ECharts slider dataZoom is ~30px thick and sits outside the
|
|
730
|
+
// grid's own bookkeeping, so nothing reserves room for it. Left
|
|
731
|
+
// unaccounted it is drawn straight over the plot, clipping the foot
|
|
732
|
+
// of every bar and washing out the axis labels underneath.
|
|
733
|
+
const ZOOM_THICKNESS = 40
|
|
734
|
+
const zoomPadBottom = showZoom && !horizontal ? ZOOM_THICKNESS : 0
|
|
735
|
+
const zoomPadRight = showZoom && horizontal ? ZOOM_THICKNESS : 0
|
|
548
736
|
option.grid = {
|
|
549
737
|
...option.grid,
|
|
550
738
|
show: showBox,
|
|
@@ -552,12 +740,31 @@ export class WidgetLinechart extends LitElement {
|
|
|
552
740
|
borderWidth: showBox ? 1 : 0,
|
|
553
741
|
borderColor: this.themeTitleColor ?? '#ccc',
|
|
554
742
|
top: topPadding,
|
|
555
|
-
bottom
|
|
556
|
-
left
|
|
557
|
-
|
|
743
|
+
// The category axis sits at the bottom when vertical and on the
|
|
744
|
+
// left when horizontal; the value axis is the other way round.
|
|
745
|
+
// The zoom slider is not accounted for by `containLabel`, so its
|
|
746
|
+
// thickness has to be reserved here as well — see ZOOM_THICKNESS.
|
|
747
|
+
bottom: ((horizontal ? showLeftAxis : showXAxis) ? 20 : 0) + zoomPadBottom,
|
|
748
|
+
left: (horizontal ? showXAxis : showLeftAxis) ? 20 : 0,
|
|
749
|
+
// A horizontal chart grows towards the right edge, so the last
|
|
750
|
+
// axis tick and any value label printed past the end of a bar
|
|
751
|
+
// need room that `containLabel` does not reserve for them.
|
|
752
|
+
right: (horizontal ? (hasValueLabels ? 45 : 15) : 0) + zoomPadRight,
|
|
558
753
|
containLabel: showXAxis || showYAxis
|
|
559
754
|
}
|
|
560
755
|
|
|
756
|
+
// Anchor the slider to the edge whose dimension it zooms: the bottom
|
|
757
|
+
// for a vertical chart, the right-hand side for a horizontal one
|
|
758
|
+
// (where it controls the y axis and ECharts draws it upright).
|
|
759
|
+
if (showZoom) {
|
|
760
|
+
Object.assign(
|
|
761
|
+
option.dataZoom[0],
|
|
762
|
+
horizontal
|
|
763
|
+
? { right: 0, top: topPadding, bottom: 20, left: undefined }
|
|
764
|
+
: { bottom: 0, left: undefined, right: undefined, top: undefined }
|
|
765
|
+
)
|
|
766
|
+
}
|
|
767
|
+
|
|
561
768
|
// Calculate animation duration based on update frequency
|
|
562
769
|
const animationDuration = this.updateThresholdMs //this.calculateAnimationDuration(chart, label)
|
|
563
770
|
option.animation = true
|