@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubex/zinc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.107",
|
|
4
4
|
"description": "A collection of web components for building web applications based off of @shoelace-style/Shoelace",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web components",
|
|
@@ -46,9 +46,8 @@
|
|
|
46
46
|
"@lit/task": "^1.0.1",
|
|
47
47
|
"@shoelace-style/localize": "^3.2.1",
|
|
48
48
|
"air-datepicker": "^3.5.3",
|
|
49
|
-
"apexcharts": "^4.5.0",
|
|
50
|
-
"chart.js": "^4.4.8",
|
|
51
49
|
"composed-offset-position": "^0.0.6",
|
|
50
|
+
"echarts": "^6.0.0",
|
|
52
51
|
"emoji-mart": "^5.6.0",
|
|
53
52
|
"lit": "^2.7.6",
|
|
54
53
|
"marked": "^18.0.2",
|
|
@@ -77,9 +76,9 @@
|
|
|
77
76
|
"custom-element-vs-code-integration": "^1.5.0",
|
|
78
77
|
"custom-element-vuejs-integration": "^1.4.0",
|
|
79
78
|
"del": "^8.0.0",
|
|
80
|
-
"esbuild": "^0.
|
|
79
|
+
"esbuild": "^0.27.7",
|
|
81
80
|
"esbuild-plugin-replace": "^1.4.0",
|
|
82
|
-
"esbuild-sass-plugin": "^3.
|
|
81
|
+
"esbuild-sass-plugin": "^3.7.0",
|
|
83
82
|
"eslint": "^8.57.1",
|
|
84
83
|
"eslint-plugin-chai-expect": "^3.1.0",
|
|
85
84
|
"eslint-plugin-chai-friendly": "^0.7.2",
|
|
@@ -94,7 +93,7 @@
|
|
|
94
93
|
"jsdom": "^26.0.0",
|
|
95
94
|
"lodash": "^4.17.21",
|
|
96
95
|
"lunr": "^2.3.9",
|
|
97
|
-
"markdown-it": "^14.1.
|
|
96
|
+
"markdown-it": "^14.1.1",
|
|
98
97
|
"markdown-it-container": "^4.0.0",
|
|
99
98
|
"markdown-it-ins": "^4.0.0",
|
|
100
99
|
"markdown-it-kbd": "^2.2.2",
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */
|
|
2
|
+
import {
|
|
3
|
+
buildAreaOption,
|
|
4
|
+
buildBarOption,
|
|
5
|
+
type BuilderProps,
|
|
6
|
+
buildLineOption,
|
|
7
|
+
buildSankeyOption,
|
|
8
|
+
} from './builders';
|
|
9
|
+
import { expect } from '@open-wc/testing';
|
|
10
|
+
|
|
11
|
+
const baseProps: BuilderProps = {
|
|
12
|
+
type: 'bar',
|
|
13
|
+
data: [{ name: 'Sales', data: [10, 20, 30] }],
|
|
14
|
+
categories: ['Q1', 'Q2', 'Q3'],
|
|
15
|
+
stacked: false,
|
|
16
|
+
enableAnimations: false,
|
|
17
|
+
datapointSize: 1,
|
|
18
|
+
theme: 'light',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('buildBarOption', () => {
|
|
22
|
+
it('maps a single series into bar type with categories', () => {
|
|
23
|
+
const opt = buildBarOption(baseProps);
|
|
24
|
+
expect(opt.series).to.have.lengthOf(1);
|
|
25
|
+
expect((opt.series as any[])[0].type).to.equal('bar');
|
|
26
|
+
expect((opt.series as any[])[0].name).to.equal('Sales');
|
|
27
|
+
expect((opt.series as any[])[0].data).to.deep.equal([10, 20, 30]);
|
|
28
|
+
expect((opt.xAxis as any).type).to.equal('category');
|
|
29
|
+
expect((opt.xAxis as any).data).to.deep.equal(['Q1', 'Q2', 'Q3']);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('applies stacking when stacked=true', () => {
|
|
33
|
+
const opt = buildBarOption({
|
|
34
|
+
...baseProps,
|
|
35
|
+
stacked: true,
|
|
36
|
+
data: [
|
|
37
|
+
{ name: 'A', data: [1, 2] },
|
|
38
|
+
{ name: 'B', data: [3, 4] },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
expect((opt.series as any[])[0].stack).to.equal('total');
|
|
42
|
+
expect((opt.series as any[])[1].stack).to.equal('total');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not stack when stacked=false', () => {
|
|
46
|
+
const opt = buildBarOption({
|
|
47
|
+
...baseProps,
|
|
48
|
+
data: [
|
|
49
|
+
{ name: 'A', data: [1, 2] },
|
|
50
|
+
{ name: 'B', data: [3, 4] },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
expect((opt.series as any[])[0].stack).to.be.undefined;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('appends y-axis suffix via formatter', () => {
|
|
57
|
+
const opt = buildBarOption({ ...baseProps, yAxisAppend: '%' });
|
|
58
|
+
const formatter = ((opt.yAxis as any).axisLabel.formatter) as (v: number) => string;
|
|
59
|
+
expect(formatter(42)).to.equal('42%');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('maps per-series color to itemStyle.color', () => {
|
|
63
|
+
const opt = buildBarOption({
|
|
64
|
+
...baseProps,
|
|
65
|
+
data: [{ name: 'Sales', data: [1, 2, 3], color: '#ff0000' }],
|
|
66
|
+
});
|
|
67
|
+
expect((opt.series as any[])[0].itemStyle.color).to.equal('#ff0000');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('maps colors array to option.color', () => {
|
|
71
|
+
const opt = buildBarOption({ ...baseProps, colors: ['#abc', '#def'] });
|
|
72
|
+
expect(opt.color).to.deep.equal(['#abc', '#def']);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('disables animation by default', () => {
|
|
76
|
+
const opt = buildBarOption(baseProps);
|
|
77
|
+
expect(opt.animation).to.equal(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('enables animation when enableAnimations=true', () => {
|
|
81
|
+
const opt = buildBarOption({ ...baseProps, enableAnimations: true });
|
|
82
|
+
expect(opt.animation).to.equal(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('buildLineOption', () => {
|
|
87
|
+
const baseLineProps: BuilderProps = {
|
|
88
|
+
type: 'line',
|
|
89
|
+
data: [{ name: 'Revenue', data: [10, 20, 30] }],
|
|
90
|
+
categories: ['Jan', 'Feb', 'Mar'],
|
|
91
|
+
stacked: false,
|
|
92
|
+
enableAnimations: false,
|
|
93
|
+
datapointSize: 3,
|
|
94
|
+
theme: 'light',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
it('maps a series to line type', () => {
|
|
98
|
+
const opt = buildLineOption(baseLineProps);
|
|
99
|
+
expect((opt.series as any[])[0].type).to.equal('line');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('applies symbolSize from datapointSize', () => {
|
|
103
|
+
const opt = buildLineOption(baseLineProps);
|
|
104
|
+
expect((opt.series as any[])[0].symbolSize).to.equal(3);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('uses datetime x-axis when xAxisType=datetime', () => {
|
|
108
|
+
const opt = buildLineOption({ ...baseLineProps, xAxisType: 'datetime' });
|
|
109
|
+
expect((opt.xAxis as any).type).to.equal('time');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('does not apply areaStyle for plain line', () => {
|
|
113
|
+
const opt = buildLineOption(baseLineProps);
|
|
114
|
+
expect((opt.series as any[])[0].areaStyle).to.be.undefined;
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('buildAreaOption', () => {
|
|
119
|
+
const baseAreaProps: BuilderProps = {
|
|
120
|
+
type: 'area',
|
|
121
|
+
data: [{ name: 'Traffic', data: [100, 200, 300] }],
|
|
122
|
+
categories: ['Mon', 'Tue', 'Wed'],
|
|
123
|
+
stacked: false,
|
|
124
|
+
enableAnimations: false,
|
|
125
|
+
datapointSize: 1,
|
|
126
|
+
theme: 'light',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
it('maps a series to line type with areaStyle set', () => {
|
|
130
|
+
const opt = buildAreaOption(baseAreaProps);
|
|
131
|
+
expect((opt.series as any[])[0].type).to.equal('line');
|
|
132
|
+
expect((opt.series as any[])[0].areaStyle).to.be.an('object');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('applies stacking when stacked=true', () => {
|
|
136
|
+
const opt = buildAreaOption({
|
|
137
|
+
...baseAreaProps,
|
|
138
|
+
stacked: true,
|
|
139
|
+
data: [
|
|
140
|
+
{ name: 'A', data: [1, 2] },
|
|
141
|
+
{ name: 'B', data: [3, 4] },
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
expect((opt.series as any[])[0].stack).to.equal('total');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('buildSankeyOption', () => {
|
|
149
|
+
const sankeyProps: BuilderProps = {
|
|
150
|
+
type: 'sankey',
|
|
151
|
+
data: [{
|
|
152
|
+
name: 'Flow',
|
|
153
|
+
data: [
|
|
154
|
+
{ source: 'A', target: 'B', value: 10 },
|
|
155
|
+
{ source: 'A', target: 'C', value: 5 },
|
|
156
|
+
{ source: 'B', target: 'D', value: 7 },
|
|
157
|
+
],
|
|
158
|
+
}],
|
|
159
|
+
categories: [],
|
|
160
|
+
stacked: false,
|
|
161
|
+
enableAnimations: false,
|
|
162
|
+
datapointSize: 1,
|
|
163
|
+
theme: 'light',
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
it('builds one sankey series with links mapped from edges', () => {
|
|
167
|
+
const opt = buildSankeyOption(sankeyProps);
|
|
168
|
+
const series = (opt.series as any[])[0];
|
|
169
|
+
expect(series.type).to.equal('sankey');
|
|
170
|
+
expect(series.links).to.deep.equal([
|
|
171
|
+
{ source: 'A', target: 'B', value: 10 },
|
|
172
|
+
{ source: 'A', target: 'C', value: 5 },
|
|
173
|
+
{ source: 'B', target: 'D', value: 7 },
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('derives unique nodes from edge source/target values', () => {
|
|
178
|
+
const opt = buildSankeyOption(sankeyProps);
|
|
179
|
+
const series = (opt.series as any[])[0];
|
|
180
|
+
const names = series.data.map((n: any) => n.name).sort();
|
|
181
|
+
expect(names).to.deep.equal(['A', 'B', 'C', 'D']);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('omits xAxis and yAxis (sankey has no cartesian axes)', () => {
|
|
185
|
+
const opt = buildSankeyOption(sankeyProps);
|
|
186
|
+
expect(opt.xAxis).to.be.undefined;
|
|
187
|
+
expect(opt.yAxis).to.be.undefined;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('passes through explicit nodes when provided on the series object', () => {
|
|
191
|
+
const withNodes = {
|
|
192
|
+
...sankeyProps,
|
|
193
|
+
data: [{
|
|
194
|
+
name: 'Flow',
|
|
195
|
+
nodes: [
|
|
196
|
+
{ name: 'A', itemStyle: { color: '#f00' } },
|
|
197
|
+
{ name: 'B' }, { name: 'C' }, { name: 'D' },
|
|
198
|
+
],
|
|
199
|
+
data: sankeyProps.data[0].data,
|
|
200
|
+
}] as any,
|
|
201
|
+
};
|
|
202
|
+
const opt = buildSankeyOption(withNodes);
|
|
203
|
+
const series = (opt.series as any[])[0];
|
|
204
|
+
expect(series.data[0]).to.deep.equal({ name: 'A', itemStyle: { color: '#f00' } });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('uses tooltip trigger=item (not axis) for sankey', () => {
|
|
208
|
+
const opt = buildSankeyOption(sankeyProps);
|
|
209
|
+
expect((opt.tooltip as any).trigger).to.equal('item');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// src/components/chart/builders.ts
|
|
2
|
+
import type { EChartsOption } from 'echarts';
|
|
3
|
+
|
|
4
|
+
export type ChartType = 'area' | 'bar' | 'line' | 'sankey';
|
|
5
|
+
|
|
6
|
+
export interface SeriesItem {
|
|
7
|
+
name: string;
|
|
8
|
+
data: any[];
|
|
9
|
+
color?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SankeyEdge {
|
|
13
|
+
source: string;
|
|
14
|
+
target: string;
|
|
15
|
+
value: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface BuilderProps {
|
|
19
|
+
type: ChartType;
|
|
20
|
+
data: SeriesItem[];
|
|
21
|
+
categories: string[];
|
|
22
|
+
xAxisType?: 'datetime' | 'category' | 'numeric';
|
|
23
|
+
yAxisAppend?: string;
|
|
24
|
+
stacked: boolean;
|
|
25
|
+
enableAnimations: boolean | number;
|
|
26
|
+
datapointSize: number;
|
|
27
|
+
colors?: string[];
|
|
28
|
+
theme: 'light' | 'dark';
|
|
29
|
+
smooth?: boolean;
|
|
30
|
+
scale?: boolean | number;
|
|
31
|
+
textColor?: string;
|
|
32
|
+
borderColor?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function commonOption(props: BuilderProps): EChartsOption {
|
|
36
|
+
const fallback = props.theme === 'dark' ? 'rgb(161, 161, 170)' : 'rgb(113, 113, 122)';
|
|
37
|
+
const textColor = props.textColor ?? fallback;
|
|
38
|
+
const animEnabled = props.enableAnimations !== false && props.enableAnimations !== 0;
|
|
39
|
+
const animDuration = typeof props.enableAnimations === 'number' ? props.enableAnimations : 1500;
|
|
40
|
+
return {
|
|
41
|
+
animation: animEnabled,
|
|
42
|
+
animationDuration: animDuration,
|
|
43
|
+
animationEasing: 'cubicOut',
|
|
44
|
+
...(props.colors ? { color: props.colors } : {}),
|
|
45
|
+
textStyle: { color: textColor },
|
|
46
|
+
tooltip: {
|
|
47
|
+
trigger: 'axis',
|
|
48
|
+
valueFormatter: props.yAxisAppend
|
|
49
|
+
? (v: number | string) => `${v}${props.yAxisAppend}`
|
|
50
|
+
: undefined,
|
|
51
|
+
},
|
|
52
|
+
legend: {
|
|
53
|
+
top: 0,
|
|
54
|
+
right: 0,
|
|
55
|
+
icon: 'circle',
|
|
56
|
+
textStyle: { color: textColor },
|
|
57
|
+
},
|
|
58
|
+
grid: {
|
|
59
|
+
left: 40,
|
|
60
|
+
right: 20,
|
|
61
|
+
top: 40,
|
|
62
|
+
bottom: 30,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasNoData(data: SeriesItem[]): boolean {
|
|
68
|
+
return data.length === 0 || data.every((s) => !s.data || s.data.length === 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildYAxis(props: BuilderProps) {
|
|
72
|
+
const emptyOpts = hasNoData(props.data) ? { min: 0, max: 6 } : {};
|
|
73
|
+
const scaleOpts = props.scale
|
|
74
|
+
? {
|
|
75
|
+
scale: true,
|
|
76
|
+
...(typeof props.scale === 'number'
|
|
77
|
+
? { boundaryGap: [`${props.scale}%`, `${props.scale}%`] as [string, string] }
|
|
78
|
+
: {}),
|
|
79
|
+
}
|
|
80
|
+
: {};
|
|
81
|
+
const splitLineOpts = props.borderColor
|
|
82
|
+
? { splitLine: { lineStyle: { color: props.borderColor } } }
|
|
83
|
+
: {};
|
|
84
|
+
return {
|
|
85
|
+
type: 'value' as const,
|
|
86
|
+
...splitLineOpts,
|
|
87
|
+
...emptyOpts,
|
|
88
|
+
...scaleOpts,
|
|
89
|
+
axisLabel: props.yAxisAppend
|
|
90
|
+
? { formatter: (v: number) => `${v}${props.yAxisAppend}` }
|
|
91
|
+
: {},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildXAxis(props: BuilderProps, edgeToEdge = false) {
|
|
96
|
+
const axisLineOpts = props.borderColor
|
|
97
|
+
? { axisLine: { lineStyle: { color: props.borderColor } } }
|
|
98
|
+
: {};
|
|
99
|
+
if (props.xAxisType === 'datetime') return { type: 'time' as const, ...axisLineOpts };
|
|
100
|
+
if (props.xAxisType === 'numeric') return { type: 'value' as const, ...axisLineOpts };
|
|
101
|
+
return {
|
|
102
|
+
type: 'category' as const,
|
|
103
|
+
data: props.categories,
|
|
104
|
+
...(edgeToEdge ? { boundaryGap: false } : {}),
|
|
105
|
+
...axisLineOpts,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */
|
|
110
|
+
function normalizeData(data: any[]): any[] {
|
|
111
|
+
return data.map((d: any) => {
|
|
112
|
+
if (d && typeof d === 'object' && !Array.isArray(d) && 'x' in d && 'y' in d) {
|
|
113
|
+
return [d.x, d.y];
|
|
114
|
+
}
|
|
115
|
+
return d;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */
|
|
119
|
+
|
|
120
|
+
function seriesFromProps(
|
|
121
|
+
props: BuilderProps,
|
|
122
|
+
seriesType: 'bar' | 'line',
|
|
123
|
+
extra: (s: SeriesItem) => Record<string, unknown> = () => ({}),
|
|
124
|
+
) {
|
|
125
|
+
return props.data.map((s) => ({
|
|
126
|
+
type: seriesType,
|
|
127
|
+
name: s.name,
|
|
128
|
+
data: normalizeData(s.data),
|
|
129
|
+
...(s.color ? { itemStyle: { color: s.color } } : {}),
|
|
130
|
+
...(props.stacked ? { stack: 'total' } : {}),
|
|
131
|
+
...(props.enableAnimations !== false && props.enableAnimations !== 0 && seriesType === 'bar'
|
|
132
|
+
? { animationDelay: (idx: number) => idx * 50 }
|
|
133
|
+
: {}),
|
|
134
|
+
...extra(s),
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function buildBarOption(props: BuilderProps): EChartsOption {
|
|
139
|
+
return {
|
|
140
|
+
...commonOption(props),
|
|
141
|
+
xAxis: buildXAxis(props),
|
|
142
|
+
yAxis: buildYAxis(props),
|
|
143
|
+
series: seriesFromProps(props, 'bar'),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function buildLineOption(props: BuilderProps): EChartsOption {
|
|
148
|
+
return {
|
|
149
|
+
...commonOption(props),
|
|
150
|
+
xAxis: buildXAxis(props, true),
|
|
151
|
+
yAxis: buildYAxis(props),
|
|
152
|
+
series: seriesFromProps(props, 'line', () => ({
|
|
153
|
+
symbolSize: props.datapointSize,
|
|
154
|
+
smooth: props.smooth,
|
|
155
|
+
})),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function buildAreaOption(props: BuilderProps): EChartsOption {
|
|
160
|
+
return {
|
|
161
|
+
...commonOption(props),
|
|
162
|
+
xAxis: buildXAxis(props, true),
|
|
163
|
+
yAxis: buildYAxis(props),
|
|
164
|
+
series: seriesFromProps(props, 'line', () => ({
|
|
165
|
+
symbolSize: props.datapointSize,
|
|
166
|
+
smooth: props.smooth,
|
|
167
|
+
areaStyle: { opacity: 0.1 },
|
|
168
|
+
})),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function deriveNodes(edges: SankeyEdge[]): { name: string }[] {
|
|
173
|
+
const seen = new Set<string>();
|
|
174
|
+
const nodes: { name: string }[] = [];
|
|
175
|
+
for (const edge of edges) {
|
|
176
|
+
if (!seen.has(edge.source)) {
|
|
177
|
+
seen.add(edge.source);
|
|
178
|
+
nodes.push({ name: edge.source });
|
|
179
|
+
}
|
|
180
|
+
if (!seen.has(edge.target)) {
|
|
181
|
+
seen.add(edge.target);
|
|
182
|
+
nodes.push({ name: edge.target });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return nodes;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function buildSankeyOption(props: BuilderProps): EChartsOption {
|
|
189
|
+
const first = props.data[0] ?? { name: '', data: [] };
|
|
190
|
+
const edges = (first.data ?? []) as SankeyEdge[];
|
|
191
|
+
const explicitNodes = (first as { nodes?: { name: string }[] }).nodes;
|
|
192
|
+
const nodes = explicitNodes ?? deriveNodes(edges);
|
|
193
|
+
const fallback = props.theme === 'dark' ? 'rgb(161, 161, 170)' : 'rgb(113, 113, 122)';
|
|
194
|
+
const textColor = props.textColor ?? fallback;
|
|
195
|
+
|
|
196
|
+
const animEnabled = props.enableAnimations !== false && props.enableAnimations !== 0;
|
|
197
|
+
const animDuration = typeof props.enableAnimations === 'number' ? props.enableAnimations : 1500;
|
|
198
|
+
return {
|
|
199
|
+
animation: animEnabled,
|
|
200
|
+
animationDuration: animDuration,
|
|
201
|
+
animationEasing: 'cubicOut',
|
|
202
|
+
...(props.colors ? { color: props.colors } : {}),
|
|
203
|
+
textStyle: { color: textColor },
|
|
204
|
+
tooltip: { trigger: 'item' },
|
|
205
|
+
series: [{
|
|
206
|
+
type: 'sankey',
|
|
207
|
+
name: first.name,
|
|
208
|
+
data: nodes,
|
|
209
|
+
links: edges,
|
|
210
|
+
label: {
|
|
211
|
+
color: textColor,
|
|
212
|
+
textBorderWidth: 0,
|
|
213
|
+
},
|
|
214
|
+
lineStyle: {
|
|
215
|
+
color: 'source',
|
|
216
|
+
opacity: props.theme === 'dark' ? 0.4 : 0.3,
|
|
217
|
+
},
|
|
218
|
+
emphasis: { focus: 'adjacency' },
|
|
219
|
+
}],
|
|
220
|
+
};
|
|
221
|
+
}
|