Skip to content

Commit 39c37b6

Browse files
authored
Merge pull request #292 from yatbfm/dev/optimize-generate-chart
feat: add prompt and pipeline for linearProgress, heatmap and comparativeFunnel
2 parents b9bcf84 + 6a12f04 commit 39c37b6

File tree

9 files changed

+364
-6
lines changed

9 files changed

+364
-6
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { generateChart } from '../../src';
2+
3+
const MOCK_DATA_TABLE = [
4+
{ group: 'A', name: 'Category1', value: 10 },
5+
{ group: 'A', name: 'Category2', value: 15 },
6+
{ group: 'B', name: 'Category1', value: 20 },
7+
{ group: 'B', name: 'Category2', value: 25 }
8+
];
9+
10+
const layout = {
11+
type: 'grid',
12+
col: 3,
13+
row: 3,
14+
colWidth: [
15+
{
16+
index: 1,
17+
size: 120
18+
}
19+
],
20+
elements: [
21+
{
22+
modelId: 'title',
23+
col: 0,
24+
row: 0,
25+
colSpan: 3
26+
},
27+
{
28+
modelId: 'legend',
29+
col: 0,
30+
row: 1,
31+
colSpan: 3
32+
},
33+
{
34+
modelId: 'left',
35+
col: 0,
36+
row: 2
37+
},
38+
{
39+
modelId: 'right',
40+
col: 2,
41+
row: 2
42+
}
43+
]
44+
};
45+
46+
const region = [
47+
{
48+
id: 'left'
49+
},
50+
{
51+
id: 'right'
52+
}
53+
];
54+
55+
describe('generate comparative funnel chart', () => {
56+
it('should generate comparative funnel chart with dataTable', () => {
57+
const { spec } = generateChart('comparativeFunnel', {
58+
dataTable: MOCK_DATA_TABLE,
59+
cell: {
60+
x: 'name',
61+
y: 'value',
62+
category: 'group'
63+
},
64+
spec: {}
65+
});
66+
expect(spec.type).toBe('common');
67+
expect(spec.layout).toEqual(layout);
68+
expect(spec.region).toEqual(region);
69+
expect(spec.data).toEqual(
70+
Object.entries(
71+
MOCK_DATA_TABLE.reduce((acc, curr) => {
72+
if (!acc[curr.group]) {
73+
acc[curr.group] = [];
74+
}
75+
acc[curr.group].push(curr);
76+
return acc;
77+
}, {} as any)
78+
).map(([group, data]) => ({
79+
id: group,
80+
values: data
81+
}))
82+
);
83+
const serialize = (obj: any) => JSON.parse(JSON.stringify(obj));
84+
expect(serialize(spec.series)).toEqual(
85+
serialize([
86+
{
87+
type: 'funnel',
88+
dataIndex: 0,
89+
regionIndex: 0,
90+
isTransform: true,
91+
gap: 2,
92+
maxSize: '60%',
93+
shape: 'rect',
94+
funnelAlign: 'right',
95+
categoryField: 'name',
96+
valueField: 'value',
97+
heightRatio: 1.5,
98+
funnel: {
99+
style: {
100+
fill: { field: 'group', scale: 'color' },
101+
cornerRadius: 4
102+
}
103+
},
104+
transform: {
105+
style: {
106+
fill: { field: 'group', scale: 'color' },
107+
fillOpacity: 0.1
108+
}
109+
},
110+
outerLabel: {
111+
visible: true,
112+
line: { visible: false },
113+
style: {
114+
fontSize: 24,
115+
fontWeight: 'bold',
116+
fill: 'black',
117+
limit: Infinity
118+
}
119+
},
120+
extensionMark: [
121+
{
122+
type: 'text',
123+
dataIndex: 0,
124+
style: {
125+
fontSize: 24,
126+
fill: 'grey',
127+
textAlign: 'center'
128+
}
129+
}
130+
]
131+
},
132+
{
133+
type: 'funnel',
134+
dataIndex: 1,
135+
regionIndex: 1,
136+
isTransform: true,
137+
gap: 2,
138+
maxSize: '60%',
139+
shape: 'rect',
140+
funnelAlign: 'left',
141+
categoryField: 'name',
142+
valueField: 'value',
143+
heightRatio: 1.5,
144+
funnel: {
145+
style: {
146+
fill: { field: 'group', scale: 'color' },
147+
cornerRadius: 4
148+
}
149+
},
150+
transform: {
151+
style: {
152+
fill: { field: 'group', scale: 'color' },
153+
fillOpacity: 0.1
154+
}
155+
},
156+
outerLabel: {
157+
visible: true,
158+
line: { visible: false },
159+
style: {
160+
fontSize: 24,
161+
fontWeight: 'bold',
162+
fill: 'black',
163+
limit: Infinity
164+
}
165+
},
166+
extensionMark: []
167+
}
168+
])
169+
);
170+
});
171+
});

packages/generate-vchart/__tests__/transformers/heatmap.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ describe('generateChart', () => {
4848
grid: {
4949
visible: false
5050
},
51+
label: {
52+
style: {
53+
angle: 90
54+
}
55+
},
5156
domainLine: {
5257
visible: false
5358
}
@@ -261,6 +266,11 @@ describe('generateChart', () => {
261266
grid: {
262267
visible: false
263268
},
269+
label: {
270+
style: {
271+
angle: 90
272+
}
273+
},
264274
domainLine: {
265275
visible: false
266276
},

packages/generate-vchart/src/pipeline.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const {
3030
pipelineWaterfall,
3131
pipelineWordCloud,
3232
pipelineBidirectionalBar,
33+
pipelineComparativeFunnel,
3334
addSimpleComponents,
3435
theme
3536
} = allTransformers;
@@ -70,7 +71,8 @@ const pipelineMap: {
7071
gauge: { type: 'gauge', aliasName: 'Gauge Chart', pipline: pipelineGauge },
7172
heatmap: { type: 'heatmap', aliasName: 'Basic Heat Map', pipline: pipelineBasicHeatMap },
7273
venn: { type: 'venn', aliasName: 'Venn Chart', pipline: pipelineVenn },
73-
bidirectionalBar: { type: 'common', aliasName: 'Bidirectional Bar Chart', pipline: pipelineBidirectionalBar }
74+
bidirectionalBar: { type: 'common', aliasName: 'Bidirectional Bar Chart', pipline: pipelineBidirectionalBar },
75+
comparativeFunnel: { type: 'common', aliasName: 'Comparative Funnel Chart', pipline: pipelineComparativeFunnel }
7476
};
7577

7678
export const findPipelineByType = (type: string) => {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { array } from '@visactor/vutils';
2+
import { DataItem, GenerateChartInput } from '../types';
3+
import { data, discreteLegend, formatXFields } from './common';
4+
5+
const title = (context: GenerateChartInput) => {
6+
const { spec } = context;
7+
const { title } = spec;
8+
spec.title = {
9+
...title,
10+
id: 'title'
11+
};
12+
return { spec };
13+
};
14+
15+
const layout = (context: GenerateChartInput) => {
16+
const { spec } = context;
17+
spec.layout = {
18+
type: 'grid',
19+
col: 3,
20+
row: 3,
21+
colWidth: [{ index: 1, size: 120 }],
22+
elements: [
23+
{ modelId: 'title', col: 0, row: 0, colSpan: 3 },
24+
{ modelId: 'legend', col: 0, row: 1, colSpan: 3 },
25+
{ modelId: 'left', col: 0, row: 2 },
26+
{ modelId: 'right', col: 2, row: 2 }
27+
]
28+
};
29+
spec.region = [{ id: 'left' }, { id: 'right' }];
30+
return { spec };
31+
};
32+
33+
const comparativeFunnelSeries = (context: GenerateChartInput) => {
34+
const { spec, cell } = context;
35+
const generateSeries = (index: number, align: 'left' | 'right') => {
36+
return {
37+
type: 'funnel',
38+
dataIndex: index,
39+
regionIndex: index,
40+
isTransform: true,
41+
gap: 2,
42+
maxSize: '60%',
43+
shape: 'rect',
44+
funnelAlign: align,
45+
categoryField: cell.x,
46+
valueField: cell.y,
47+
heightRatio: 1.5,
48+
funnel: {
49+
style: {
50+
fill: { field: cell.category, scale: 'color' },
51+
cornerRadius: 4
52+
}
53+
},
54+
transform: {
55+
style: {
56+
fill: { field: cell.category, scale: 'color' },
57+
fillOpacity: 0.1
58+
}
59+
},
60+
outerLabel: {
61+
visible: true,
62+
line: { visible: false },
63+
formatMethod: (data: any, datum: DataItem) => datum[array(cell.y)[0]],
64+
style: {
65+
fontSize: 24,
66+
fontWeight: 'bold',
67+
fill: 'black',
68+
limit: Infinity
69+
}
70+
},
71+
extensionMark: [
72+
index === 0
73+
? {
74+
type: 'text',
75+
dataIndex: 0,
76+
style: {
77+
text: (data: any) => data[array(cell.x)[0]],
78+
fontSize: 24,
79+
fill: 'grey',
80+
textAlign: 'center',
81+
x: (data: any, ctx: any) => {
82+
const { vchart } = ctx;
83+
return vchart.getCurrentSize().width / 2 - 10;
84+
},
85+
y: (data: DataItem, ctx: any) => {
86+
const { getPoints } = ctx;
87+
const [tl, tr, br, bl] = getPoints(data);
88+
return (tl.y + bl.y) / 2;
89+
}
90+
}
91+
}
92+
: null
93+
].filter(Boolean)
94+
};
95+
};
96+
spec.series = [generateSeries(0, 'right'), generateSeries(1, 'left')];
97+
return { spec };
98+
};
99+
100+
const comparativeFunnelLegend = (context: GenerateChartInput) => {
101+
const { spec, cell } = context;
102+
spec.seriesField = cell.category;
103+
spec.legends = array(spec.legends).map(legend => ({
104+
...legend,
105+
id: 'legend',
106+
orient: 'top'
107+
}));
108+
return { spec };
109+
};
110+
111+
const comparativeFunnelData = (context: GenerateChartInput) => {
112+
const { spec, dataTable } = context;
113+
const mp = {};
114+
dataTable.forEach(item => {
115+
const { group } = item;
116+
if (!mp[group]) {
117+
mp[group] = [];
118+
}
119+
mp[group].push(item);
120+
});
121+
spec.data = Object.entries(mp).map(([group, data]) => ({ id: group, values: data }));
122+
return { spec };
123+
};
124+
125+
export const pipelineComparativeFunnel = [
126+
title,
127+
formatXFields,
128+
layout,
129+
comparativeFunnelData,
130+
comparativeFunnelSeries,
131+
discreteLegend,
132+
comparativeFunnelLegend
133+
];

packages/generate-vchart/src/transformers/heatmap.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ export const basicHeatMapAxes = (context: GenerateChartInput) => {
7474
}
7575
},
7676
userConfig: {
77-
type: 'band'
77+
type: 'band',
78+
label: {
79+
style: {
80+
angle: 90
81+
}
82+
}
7883
},
7984
filters: [axis => axis.orient === 'bottom', axis => axis.orient === 'top']
8085
},

packages/generate-vchart/src/transformers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ export * from './waterfall';
2929
export * from './wordcloud';
3030

3131
export * from './bidirectionalBar';
32+
export * from './comparativeFunnel';

0 commit comments

Comments
 (0)