@kubex/zinc 1.0.106 → 1.0.107
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/custom-elements.json +183 -83
- package/dist/vscode.html-custom-data.json +19 -10
- package/dist/web-types.json +56 -45
- package/dist/zn.d.ts +66 -34
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +1006 -1907
- package/docs/pages/components/chart.md +134 -8
- package/docs/pages/components/simple-chart.md +20 -5
- package/docs/pages/components/stat.md +531 -401
- package/package.json +5 -6
- package/src/components/chart/builders.test.ts +211 -0
- package/src/components/chart/builders.ts +221 -0
- package/src/components/chart/chart.component.ts +182 -173
- package/src/components/chart/chart.scss +0 -1
- package/src/components/chart/chart.test.ts +48 -4
- package/src/components/editor/modules/context-menu/context-menu.ts +2 -1
- package/src/components/rating/rating.component.ts +2 -1
- package/src/components/simple-chart/simple-chart.component.ts +72 -180
|
@@ -1,208 +1,217 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import * as echarts from 'echarts/core';
|
|
2
|
+
import { BarChart, LineChart, SankeyChart } from 'echarts/charts';
|
|
3
|
+
import {
|
|
4
|
+
buildAreaOption,
|
|
5
|
+
buildBarOption,
|
|
6
|
+
type BuilderProps,
|
|
7
|
+
buildLineOption,
|
|
8
|
+
buildSankeyOption,
|
|
9
|
+
type ChartType,
|
|
10
|
+
type SeriesItem,
|
|
11
|
+
} from './builders';
|
|
12
|
+
import { CanvasRenderer } from 'echarts/renderers';
|
|
13
|
+
import { type CSSResultGroup, html, type PropertyValues, unsafeCSS } from 'lit';
|
|
14
|
+
import {
|
|
15
|
+
DataZoomComponent,
|
|
16
|
+
GridComponent,
|
|
17
|
+
LegendComponent,
|
|
18
|
+
TitleComponent,
|
|
19
|
+
TooltipComponent,
|
|
20
|
+
} from 'echarts/components';
|
|
21
|
+
import { property } from 'lit/decorators.js';
|
|
6
22
|
import styles from './chart.scss';
|
|
23
|
+
import ZincElement from '../../internal/zinc-element';
|
|
24
|
+
import type { ECharts } from 'echarts/core';
|
|
25
|
+
|
|
26
|
+
echarts.use([
|
|
27
|
+
BarChart,
|
|
28
|
+
LineChart,
|
|
29
|
+
SankeyChart,
|
|
30
|
+
GridComponent,
|
|
31
|
+
TooltipComponent,
|
|
32
|
+
LegendComponent,
|
|
33
|
+
TitleComponent,
|
|
34
|
+
DataZoomComponent,
|
|
35
|
+
CanvasRenderer,
|
|
36
|
+
]);
|
|
7
37
|
|
|
8
38
|
/**
|
|
9
|
-
* @summary
|
|
39
|
+
* @summary Chart component powered by Apache ECharts.
|
|
10
40
|
* @documentation https://zinc.style/components/data-chart
|
|
11
41
|
* @status experimental
|
|
12
42
|
* @since 1.0
|
|
13
43
|
*
|
|
14
|
-
* @dependency zn-example
|
|
15
|
-
*
|
|
16
|
-
* @event zn-event-name - Emitted as an example.
|
|
17
|
-
*
|
|
18
|
-
* @slot - The default slot.
|
|
19
|
-
* @slot example - An example slot.
|
|
20
|
-
*
|
|
21
44
|
* @csspart base - The component's base wrapper.
|
|
22
|
-
*
|
|
23
|
-
* @cssproperty --example - An example CSS custom property.
|
|
24
45
|
*/
|
|
25
46
|
export default class ZnChart extends ZincElement {
|
|
26
47
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
27
48
|
|
|
28
|
-
@property() type:
|
|
29
|
-
@property({type: Array}) data:
|
|
30
|
-
@property({type: Array}) categories: string
|
|
31
|
-
|
|
32
|
-
@property() xAxis:
|
|
33
|
-
@property({type: Number, attribute: 'd-size'}) datapointSize: number = 1;
|
|
34
|
-
@property({type: Boolean}) stacked = false;
|
|
49
|
+
@property() type: ChartType = 'bar';
|
|
50
|
+
@property({ type: Array }) data: SeriesItem[] = [];
|
|
51
|
+
@property({ type: Array }) categories: string[] = [];
|
|
52
|
+
|
|
53
|
+
@property({ attribute: 'xaxis' }) xAxis: 'datetime' | 'category' | 'numeric';
|
|
54
|
+
@property({ type: Number, attribute: 'd-size' }) datapointSize: number = 1;
|
|
55
|
+
@property({ type: Boolean }) stacked = false;
|
|
56
|
+
|
|
57
|
+
@property({ type: Boolean }) live = false;
|
|
58
|
+
@property({ attribute: 'data-url' }) dataUrl = '';
|
|
59
|
+
@property({ attribute: 'live-interval', type: Number }) liveInterval = 1000;
|
|
60
|
+
@property({ type: Number, reflect: true }) height = 300;
|
|
61
|
+
|
|
62
|
+
@property({
|
|
63
|
+
attribute: 'enable-animations',
|
|
64
|
+
converter: {
|
|
65
|
+
fromAttribute: (value: string | null) => {
|
|
66
|
+
if (value === null) return false;
|
|
67
|
+
if (value === '' || value === 'true') return true;
|
|
68
|
+
const num = parseFloat(value);
|
|
69
|
+
return Number.isNaN(num) ? true : num;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}) enableAnimations: boolean | number = false;
|
|
73
|
+
@property({ attribute: 'y-axis-append' }) yAxisAppend: string;
|
|
74
|
+
|
|
75
|
+
@property({ type: Array }) colors?: string[];
|
|
76
|
+
@property({ attribute: 'sync-group' }) syncGroup?: string;
|
|
77
|
+
@property({ type: Boolean }) smooth = false;
|
|
78
|
+
@property({
|
|
79
|
+
converter: {
|
|
80
|
+
fromAttribute: (value: string | null) => {
|
|
81
|
+
if (value === null) return false;
|
|
82
|
+
if (value === '' || value === 'true') return true;
|
|
83
|
+
const num = parseFloat(value);
|
|
84
|
+
return Number.isNaN(num) ? true : num;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}) scale: boolean | number = false;
|
|
35
88
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@property({attribute: 'live-interval', type: Number}) liveInterval = 1000;
|
|
40
|
-
@property({type: Number, reflect: true}) height = 300;
|
|
89
|
+
private chart?: ECharts;
|
|
90
|
+
private resizeObserver?: ResizeObserver;
|
|
91
|
+
private liveTimer?: number;
|
|
41
92
|
|
|
42
|
-
|
|
93
|
+
protected firstUpdated(_changedProperties: PropertyValues) {
|
|
94
|
+
this.initChart();
|
|
95
|
+
super.firstUpdated(_changedProperties);
|
|
96
|
+
}
|
|
43
97
|
|
|
44
|
-
|
|
98
|
+
private getTheme(): 'light' | 'dark' {
|
|
99
|
+
return this.getAttribute('t') === 'dark' ? 'dark' : 'light';
|
|
100
|
+
}
|
|
45
101
|
|
|
46
|
-
private
|
|
102
|
+
private getTextColor(): string | undefined {
|
|
103
|
+
const cs = getComputedStyle(this);
|
|
104
|
+
const rgb = cs.getPropertyValue('--zn-text').trim();
|
|
105
|
+
if (!rgb) return undefined;
|
|
106
|
+
const opacity = cs.getPropertyValue('--zn-text-opacity').trim() || '1';
|
|
107
|
+
return `rgba(${rgb}, ${opacity})`;
|
|
108
|
+
}
|
|
47
109
|
|
|
48
|
-
|
|
49
|
-
|
|
110
|
+
private getBorderColor(): string | undefined {
|
|
111
|
+
const rgb = getComputedStyle(this).getPropertyValue('--zn-border-color').trim();
|
|
112
|
+
return rgb ? `rgb(${rgb})` : undefined;
|
|
113
|
+
}
|
|
50
114
|
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
enabled: false,
|
|
68
|
-
},
|
|
69
|
-
events: {}
|
|
70
|
-
},
|
|
71
|
-
dataLabels: {
|
|
72
|
-
enabled: false,
|
|
73
|
-
},
|
|
74
|
-
markers: {
|
|
75
|
-
size: this.datapointSize,
|
|
76
|
-
strokeWidth: 0,
|
|
77
|
-
hover: {
|
|
78
|
-
sizeOffset: 2
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
yaxis: {
|
|
82
|
-
labels: {
|
|
83
|
-
formatter: (value: number) => {
|
|
84
|
-
if (this.yAxisAppend) {
|
|
85
|
-
return value + this.yAxisAppend;
|
|
86
|
-
}
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
tooltip: {
|
|
92
|
-
theme: theme,
|
|
93
|
-
y: {
|
|
94
|
-
formatter: (value: number,) => {
|
|
95
|
-
if (this.yAxisAppend) {
|
|
96
|
-
return value + this.yAxisAppend;
|
|
97
|
-
}
|
|
98
|
-
return value;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
grid: {
|
|
103
|
-
borderColor: theme === 'dark' ? "rgb(50, 50, 60)" : 'rgb(224, 221, 233)',
|
|
104
|
-
},
|
|
105
|
-
//colors: theme === 'dark' ? this.darkColors : this.lightColors,
|
|
106
|
-
legend: {
|
|
107
|
-
position: 'top',
|
|
108
|
-
horizontalAlign: 'right',
|
|
109
|
-
fontSize: '12px',
|
|
110
|
-
fontFamily: '"Inter", sans',
|
|
111
|
-
fontWeight: 400,
|
|
112
|
-
labels: {
|
|
113
|
-
colors: theme == 'dark' ? "rgb(149, 163, 184)" : "rgb(123, 112, 151)",
|
|
114
|
-
},
|
|
115
|
-
markers: {
|
|
116
|
-
shape: 'circle',
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
stroke: {
|
|
120
|
-
width: 1.5
|
|
121
|
-
},
|
|
122
|
-
fill: {
|
|
123
|
-
type: 'solid',
|
|
124
|
-
opacity: this.type === 'area' ? 0.1 : 0.8
|
|
125
|
-
},
|
|
126
|
-
series: this.data,
|
|
127
|
-
xaxis: {},
|
|
115
|
+
private buildOption() {
|
|
116
|
+
const props: BuilderProps = {
|
|
117
|
+
type: this.type,
|
|
118
|
+
data: this.data ?? [],
|
|
119
|
+
categories: Array.isArray(this.categories) ? this.categories : [],
|
|
120
|
+
xAxisType: this.xAxis,
|
|
121
|
+
yAxisAppend: this.yAxisAppend,
|
|
122
|
+
stacked: this.stacked,
|
|
123
|
+
enableAnimations: this.enableAnimations,
|
|
124
|
+
datapointSize: this.datapointSize,
|
|
125
|
+
colors: this.colors,
|
|
126
|
+
theme: this.getTheme(),
|
|
127
|
+
smooth: this.smooth,
|
|
128
|
+
scale: this.scale,
|
|
129
|
+
textColor: this.getTextColor(),
|
|
130
|
+
borderColor: this.getBorderColor(),
|
|
128
131
|
};
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
options.xaxis = {
|
|
136
|
-
categories: this.categories,
|
|
137
|
-
};
|
|
132
|
+
switch (this.type) {
|
|
133
|
+
case 'bar': return buildBarOption(props);
|
|
134
|
+
case 'line': return buildLineOption(props);
|
|
135
|
+
case 'area': return buildAreaOption(props);
|
|
136
|
+
case 'sankey': return buildSankeyOption(props);
|
|
137
|
+
default: return buildBarOption(props);
|
|
138
138
|
}
|
|
139
|
+
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
speed: 400
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
options.chart.events = {
|
|
151
|
-
animationEnd: function (chartCtx: any, opts: any) {
|
|
152
|
-
const newData1 = chartCtx.w.config.series[0].data.slice();
|
|
153
|
-
newData1.shift();
|
|
154
|
-
const newData2 = chartCtx.w.config.series[1].data.slice();
|
|
155
|
-
newData2.shift();
|
|
156
|
-
|
|
157
|
-
// check animation end event for just 1 series to avoid multiple updates
|
|
158
|
-
if (opts.el.node.getAttribute('index') === '0') {
|
|
159
|
-
window.setTimeout(function () {
|
|
160
|
-
chartCtx.updateOptions({
|
|
161
|
-
series: [{
|
|
162
|
-
data: newData1
|
|
163
|
-
}, {
|
|
164
|
-
data: newData2
|
|
165
|
-
}],
|
|
166
|
-
}, false, false);
|
|
167
|
-
}, 300);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
}
|
|
141
|
+
private initChart() {
|
|
142
|
+
const host = this.shadowRoot?.getElementById('chart') as HTMLElement | null;
|
|
143
|
+
if (!host) return;
|
|
144
|
+
host.style.height = `${this.height}px`;
|
|
145
|
+
this.chart = echarts.init(host);
|
|
146
|
+
this.chart.setOption(this.buildOption());
|
|
173
147
|
|
|
174
|
-
if (
|
|
175
|
-
this.chart =
|
|
176
|
-
this.
|
|
148
|
+
if (this.syncGroup) {
|
|
149
|
+
this.chart.group = this.syncGroup;
|
|
150
|
+
echarts.connect(this.syncGroup);
|
|
177
151
|
}
|
|
178
152
|
|
|
153
|
+
let firstResize = true;
|
|
154
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
155
|
+
if (firstResize) { firstResize = false; return; }
|
|
156
|
+
this.chart?.resize();
|
|
157
|
+
});
|
|
158
|
+
this.resizeObserver.observe(host);
|
|
179
159
|
|
|
180
|
-
|
|
160
|
+
if (this.live && this.dataUrl) this.startLive();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private startLive() {
|
|
164
|
+
this.liveTimer = window.setInterval(async () => {
|
|
165
|
+
try {
|
|
166
|
+
const res = await fetch(this.dataUrl);
|
|
167
|
+
const newData = await res.json() as SeriesItem[];
|
|
168
|
+
this.data = newData;
|
|
169
|
+
this.chart?.setOption({ ...this.buildOption() });
|
|
170
|
+
} catch {
|
|
171
|
+
/* swallow transient fetch errors */
|
|
172
|
+
}
|
|
173
|
+
}, this.liveInterval);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private reinit() {
|
|
177
|
+
this.chart?.dispose();
|
|
178
|
+
this.chart = undefined;
|
|
179
|
+
this.initChart();
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
attributeChangedCallback(name: string, _old: string | null, value: string | null) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
super.attributeChangedCallback(name, _old, value);
|
|
184
|
+
if (!this.chart) return;
|
|
185
|
+
|
|
186
|
+
if (name === 't') { this.reinit(); return; }
|
|
187
|
+
if (name === 'type') { this.reinit(); return; }
|
|
188
|
+
if (name === 'height') {
|
|
189
|
+
const host = this.shadowRoot?.getElementById('chart') as HTMLElement | null;
|
|
190
|
+
if (host) host.style.height = `${this.height}px`;
|
|
191
|
+
this.chart.resize();
|
|
192
|
+
return;
|
|
188
193
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
194
|
+
if (name === 'sync-group') {
|
|
195
|
+
if (value) {
|
|
196
|
+
this.chart.group = value;
|
|
197
|
+
echarts.connect(value);
|
|
198
|
+
} else {
|
|
199
|
+
this.chart.group = '';
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
196
202
|
}
|
|
203
|
+
this.chart.setOption({ ...this.buildOption() });
|
|
204
|
+
}
|
|
197
205
|
|
|
198
|
-
|
|
206
|
+
disconnectedCallback() {
|
|
207
|
+
super.disconnectedCallback();
|
|
208
|
+
if (this.liveTimer) clearInterval(this.liveTimer);
|
|
209
|
+
this.resizeObserver?.disconnect();
|
|
210
|
+
this.chart?.dispose();
|
|
211
|
+
this.chart = undefined;
|
|
199
212
|
}
|
|
200
213
|
|
|
201
214
|
protected render(): unknown {
|
|
202
|
-
return html
|
|
203
|
-
<div class="chart">
|
|
204
|
-
<div id="chart"></div>
|
|
205
|
-
</div>
|
|
206
|
-
`;
|
|
215
|
+
return html`<div class="chart"><div id="chart"></div></div>`;
|
|
207
216
|
}
|
|
208
|
-
}
|
|
217
|
+
}
|
|
@@ -1,10 +1,54 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any */
|
|
1
2
|
import '../../../dist/zn.min.js';
|
|
2
3
|
import { expect, fixture, html } from '@open-wc/testing';
|
|
3
4
|
|
|
4
5
|
describe('<zn-chart>', () => {
|
|
5
|
-
it('
|
|
6
|
-
const el = await fixture(html
|
|
7
|
-
|
|
6
|
+
it('renders a component', async () => {
|
|
7
|
+
const el = await fixture(html`<zn-chart></zn-chart>`);
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
it('renders a bar chart with data and categories', async () => {
|
|
12
|
+
const el: any = await fixture(html`
|
|
13
|
+
<zn-chart
|
|
14
|
+
type="bar"
|
|
15
|
+
.data=${[{ name: 'S', data: [1, 2, 3] }]}
|
|
16
|
+
.categories=${['A', 'B', 'C']}
|
|
17
|
+
></zn-chart>
|
|
18
|
+
`);
|
|
19
|
+
await el.updateComplete;
|
|
20
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
21
|
+
const canvas = el.shadowRoot.querySelector('canvas');
|
|
22
|
+
expect(canvas).to.exist;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders a sankey chart', async () => {
|
|
26
|
+
const el: any = await fixture(html`
|
|
27
|
+
<zn-chart
|
|
28
|
+
type="sankey"
|
|
29
|
+
.data=${[{
|
|
30
|
+
name: 'Flow',
|
|
31
|
+
data: [
|
|
32
|
+
{ source: 'A', target: 'B', value: 10 },
|
|
33
|
+
{ source: 'B', target: 'C', value: 5 },
|
|
34
|
+
],
|
|
35
|
+
}]}
|
|
36
|
+
></zn-chart>
|
|
37
|
+
`);
|
|
38
|
+
await el.updateComplete;
|
|
39
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
40
|
+
const canvas = el.shadowRoot.querySelector('canvas');
|
|
41
|
+
expect(canvas).to.exist;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('joins a sync-group when the attribute is set', async () => {
|
|
45
|
+
const a: any = await fixture(html`
|
|
46
|
+
<zn-chart sync-group="g1" type="bar"
|
|
47
|
+
.data=${[{ name: 'S', data: [1, 2] }]}
|
|
48
|
+
.categories=${['A', 'B']}></zn-chart>
|
|
49
|
+
`);
|
|
50
|
+
await a.updateComplete;
|
|
51
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
52
|
+
expect(a.chart?.group).to.equal('g1');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -2,7 +2,8 @@ import './context-menu-component';
|
|
|
2
2
|
import {html} from "lit";
|
|
3
3
|
import {litToHTML} from "../../../../utilities/lit-to-html";
|
|
4
4
|
import {type ResultItem} from "./context-menu-component";
|
|
5
|
-
import Quill
|
|
5
|
+
import Quill from "quill";
|
|
6
|
+
import Delta from "quill-delta";
|
|
6
7
|
import ZnEditorQuickAction from "./quick-action";
|
|
7
8
|
import type {EditorFeatureConfig} from "../../editor.component";
|
|
8
9
|
import type ContextMenuComponent from "./context-menu-component";
|
|
@@ -5,9 +5,10 @@ import {FormControlController, validValidityState} from "../../internal/form";
|
|
|
5
5
|
import {property, query, state} from 'lit/decorators.js';
|
|
6
6
|
import {styleMap} from 'lit/directives/style-map.js';
|
|
7
7
|
import {unsafeHTML} from "lit/directives/unsafe-html.js";
|
|
8
|
-
import type {ZincFormControl} from '../../internal/zinc-element';
|
|
9
8
|
import ZincElement from '../../internal/zinc-element';
|
|
10
9
|
|
|
10
|
+
import type {ZincFormControl} from '../../internal/zinc-element';
|
|
11
|
+
|
|
11
12
|
import styles from './rating.scss';
|
|
12
13
|
|
|
13
14
|
/**
|