Skip to content

Commit e5d4c15

Browse files
authored
Merge pull request #1438 from merico-dev/1401-add-reference_lines-to-pareto-chart
1401 add reference lines to pareto chart
2 parents 9b4ee38 + db5b7fb commit e5d4c15

File tree

12 files changed

+278
-7
lines changed

12 files changed

+278
-7
lines changed

api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/api",
3-
"version": "13.14.0",
3+
"version": "13.15.0",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/dashboard",
3-
"version": "13.14.0",
3+
"version": "13.15.0",
44
"license": "Apache-2.0",
55
"publishConfig": {
66
"access": "public",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { useMemo } from 'react';
2+
import { Control, UseFormWatch } from 'react-hook-form';
3+
import { useTranslation } from 'react-i18next';
4+
import { FieldArrayTabs } from '~/components/plugins/editor-components';
5+
import { ITemplateVariable } from '~/utils';
6+
import { IParetoChartConf } from '../../type';
7+
import { ReferenceLineField } from './reference-line';
8+
import { ICartesianReferenceLine } from '../../../cartesian/type';
9+
10+
interface IReferenceLinesField {
11+
control: Control<IParetoChartConf, $TSFixMe>;
12+
watch: UseFormWatch<IParetoChartConf>;
13+
variables: ITemplateVariable[];
14+
}
15+
16+
export function ReferenceLinesField({ control, watch, variables }: IReferenceLinesField) {
17+
const { t } = useTranslation();
18+
19+
const getItem = () => {
20+
const item: ICartesianReferenceLine = {
21+
name: '',
22+
template: '',
23+
variable_key: '',
24+
orientation: 'horizontal',
25+
lineStyle: {
26+
type: 'dashed',
27+
width: 1,
28+
color: '#868E96',
29+
},
30+
show_in_legend: false,
31+
yAxisIndex: 0,
32+
};
33+
return item;
34+
};
35+
36+
const renderTabName = (field: ICartesianReferenceLine, index: number) => {
37+
const n = field.name.trim();
38+
return n ? n : index + 1;
39+
};
40+
41+
const variableOptions = useMemo(() => {
42+
return variables.map((v) => ({
43+
label: v.name,
44+
value: v.name,
45+
}));
46+
}, [variables]);
47+
48+
const yAxisNames = watch(['bar.name', 'line.name']);
49+
50+
const yAxisOptions = useMemo(() => {
51+
return yAxisNames.map((name, index) => ({
52+
label: name,
53+
value: index.toString(),
54+
}));
55+
}, [yAxisNames]);
56+
57+
return (
58+
<FieldArrayTabs<IParetoChartConf, ICartesianReferenceLine>
59+
control={control}
60+
watch={watch}
61+
name="reference_lines"
62+
getItem={getItem}
63+
addButtonText={t('chart.reference_line.add')}
64+
deleteButtonText={t('chart.reference_line.delete')}
65+
renderTabName={renderTabName}
66+
>
67+
{({ field, index }) => (
68+
<ReferenceLineField
69+
control={control}
70+
index={index}
71+
watch={watch}
72+
variableOptions={variableOptions}
73+
yAxisOptions={yAxisOptions}
74+
/>
75+
)}
76+
</FieldArrayTabs>
77+
);
78+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Checkbox, Divider, Group, NumberInput, Select, Stack, Text, TextInput } from '@mantine/core';
2+
import { Control, Controller, UseFormWatch } from 'react-hook-form';
3+
import { useTranslation } from 'react-i18next';
4+
import { MantineColorSelector } from '~/components/panel/settings/common/mantine-color';
5+
import { LineTypeSelector } from '~/components/plugins/common-echarts-fields/line-type';
6+
import { OrientationSelector } from '~/components/plugins/common-echarts-fields/orientation';
7+
import { IParetoChartConf } from '../../type';
8+
9+
interface IReferenceLineField {
10+
control: Control<IParetoChartConf, $TSFixMe>;
11+
index: number;
12+
watch: UseFormWatch<IParetoChartConf>;
13+
variableOptions: { label: string; value: string }[];
14+
yAxisOptions: {
15+
label: string;
16+
value: string;
17+
}[];
18+
}
19+
20+
export function ReferenceLineField({ control, index, watch, variableOptions, yAxisOptions }: IReferenceLineField) {
21+
const { t } = useTranslation();
22+
const orientation = watch(`reference_lines.${index}.orientation`);
23+
return (
24+
<Stack my={0} p={0} sx={{ position: 'relative' }}>
25+
<Group grow noWrap>
26+
<Controller
27+
name={`reference_lines.${index}.name`}
28+
control={control}
29+
render={({ field }) => (
30+
<TextInput
31+
label={t('common.name')}
32+
placeholder={t('chart.reference_line.name_placeholder')}
33+
required
34+
sx={{ flex: 1 }}
35+
{...field}
36+
/>
37+
)}
38+
/>
39+
<Controller
40+
name={`reference_lines.${index}.variable_key`}
41+
control={control}
42+
render={({ field }) => (
43+
// @ts-expect-error type of onChange
44+
<Select label={t('common.data_field')} required data={variableOptions} sx={{ flex: 1 }} {...field} />
45+
)}
46+
/>
47+
</Group>
48+
<Controller
49+
name={`reference_lines.${index}.template`}
50+
control={control}
51+
render={({ field }) => (
52+
<TextInput
53+
label={t('chart.content_template.label')}
54+
placeholder={t('chart.content_template.hint')}
55+
sx={{ flex: 1 }}
56+
{...field}
57+
/>
58+
)}
59+
/>
60+
<Group grow>
61+
<Stack>
62+
<Controller
63+
name={`reference_lines.${index}.orientation`}
64+
control={control}
65+
render={({ field }) => (
66+
<OrientationSelector label={t('chart.orientation.label')} sx={{ flex: 1 }} {...field} />
67+
)}
68+
/>
69+
</Stack>
70+
{orientation === 'horizontal' && (
71+
<Controller
72+
name={`reference_lines.${index}.yAxisIndex`}
73+
control={control}
74+
render={({ field: { value, onChange, ...rest } }) => (
75+
<Select
76+
label={t('chart.y_axis.label')}
77+
data={yAxisOptions}
78+
disabled={yAxisOptions.length === 0}
79+
{...rest}
80+
value={value?.toString() ?? ''}
81+
onChange={(value: string | null) => {
82+
if (!value) {
83+
onChange(0);
84+
return;
85+
}
86+
onChange(Number(value));
87+
}}
88+
sx={{ flex: 1 }}
89+
/>
90+
)}
91+
/>
92+
)}
93+
</Group>
94+
<Divider mb={-10} mt={10} variant="dashed" label={t('chart.style.label')} labelPosition="center" />
95+
<Group grow>
96+
<Controller
97+
name={`reference_lines.${index}.lineStyle.type`}
98+
control={control}
99+
render={({ field }) => <LineTypeSelector sx={{ flexGrow: 1 }} {...field} />}
100+
/>
101+
<Controller
102+
name={`reference_lines.${index}.lineStyle.width`}
103+
control={control}
104+
render={({ field }) => (
105+
// @ts-expect-error type of onChange
106+
<NumberInput label={t('chart.series.line.line_width')} min={1} max={10} sx={{ flexGrow: 1 }} {...field} />
107+
)}
108+
/>
109+
</Group>
110+
<Stack spacing={4}>
111+
<Text size="sm">{t('chart.color.label')}</Text>
112+
<Controller
113+
name={`reference_lines.${index}.lineStyle.color`}
114+
control={control}
115+
render={({ field }) => <MantineColorSelector {...field} />}
116+
/>
117+
</Stack>
118+
</Stack>
119+
);
120+
}

dashboard/src/components/plugins/viz-components/pareto-chart/option/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ import { getTooltip } from './tooltip';
66
import { getFormatters } from './utils';
77
import { getXAxis } from './x-axis';
88
import { getYAxes } from './y-axes';
9+
import { getReferenceLines } from './reference_lines';
10+
import { getVariableValueMap } from '../../cartesian/option/utils/variables';
911

10-
export function getOption(conf: IParetoChartConf, data: TPanelData, _variables: ITemplateVariable[]) {
12+
export function getOption(conf: IParetoChartConf, data: TPanelData, variables: ITemplateVariable[]) {
13+
const variableValueMap = getVariableValueMap(data, variables);
1114
const formatters = getFormatters(conf);
1215

1316
const option = {
1417
dataZoom: getEchartsDataZoomOption(conf.dataZoom),
1518
tooltip: getTooltip(conf, formatters),
1619
xAxis: getXAxis(conf),
1720
yAxis: getYAxes(conf, formatters),
18-
series: getSeries(conf, data, formatters),
21+
series: [
22+
...getSeries(conf, data, formatters),
23+
...getReferenceLines(conf.reference_lines, variables, variableValueMap, data),
24+
],
1925
grid: {
2026
top: 50,
2127
left: 30,
@@ -24,5 +30,6 @@ export function getOption(conf: IParetoChartConf, data: TPanelData, _variables:
2430
containLabel: true,
2531
},
2632
};
33+
console.log(option);
2734
return option;
2835
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ITemplateVariable, templateToString } from '~/utils';
2+
import { ICartesianReferenceLine } from '../../cartesian/type';
3+
4+
function getInvisibleScatterData(value: string | number, isHorizontal: boolean) {
5+
if (!isHorizontal) {
6+
return [];
7+
}
8+
return [value];
9+
}
10+
11+
export function getReferenceLines(
12+
reference_lines: ICartesianReferenceLine[],
13+
variables: ITemplateVariable[],
14+
variableValueMap: Record<string, string | number>,
15+
data: TPanelData,
16+
) {
17+
return reference_lines.map((r) => {
18+
const value = variableValueMap[r.variable_key];
19+
const isHorizontal = r.orientation === 'horizontal';
20+
const keyOfAxis = isHorizontal ? 'yAxis' : 'xAxis';
21+
const position = isHorizontal ? 'insideEndTop' : 'end';
22+
const seriesData = getInvisibleScatterData(value, isHorizontal);
23+
return {
24+
name: r.name,
25+
type: 'scatter',
26+
hide_in_legend: !r.show_in_legend,
27+
xAxisId: 'main-x-axis',
28+
yAxisIndex: r.yAxisIndex,
29+
data: seriesData,
30+
symbol: 'none',
31+
silent: true,
32+
tooltip: { show: false },
33+
markLine: {
34+
data: [
35+
{
36+
name: r.name,
37+
[keyOfAxis]: value,
38+
},
39+
],
40+
silent: true,
41+
symbol: ['none', 'none'],
42+
lineStyle: r.lineStyle,
43+
label: {
44+
formatter: function () {
45+
if (!r.template) {
46+
return '';
47+
}
48+
return templateToString(r.template, variables, data);
49+
},
50+
position,
51+
},
52+
},
53+
};
54+
});
55+
}

dashboard/src/components/plugins/viz-components/pareto-chart/option/x-axis.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export function getXAxis(conf: IParetoChartConf) {
99
return [
1010
defaultEchartsOptions.getXAxis({
1111
type: 'category',
12+
id: 'main-x-axis',
1213
name: name,
1314
nameLocation: 'middle',
1415
nameGap: 30,

dashboard/src/components/plugins/viz-components/pareto-chart/type.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
getDefaultXAxisLabelFormatter,
1111
IXAxisLabelFormatter,
1212
} from '../../common-echarts-fields/x-axis-label-formatter';
13+
import { ICartesianReferenceLine } from '../cartesian/type';
1314

1415
export const DEFAULT_PARETO_MARK_LINE = {
1516
label_template: '${percentage.x} of ${x_axis.name} causes ${percentage.y} of ${bar.name}',
@@ -43,6 +44,7 @@ export interface IParetoChartConf {
4344
label_template: string;
4445
color: string;
4546
};
47+
reference_lines: ICartesianReferenceLine[];
4648
}
4749

4850
export const DEFAULT_CONFIG: IParetoChartConf = {
@@ -69,4 +71,5 @@ export const DEFAULT_CONFIG: IParetoChartConf = {
6971
},
7072
dataZoom: DEFAULT_DATA_ZOOM_CONFIG,
7173
markLine: DEFAULT_PARETO_MARK_LINE,
74+
reference_lines: [],
7275
};

dashboard/src/components/plugins/viz-components/pareto-chart/viz-pareto-chart-editor.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import { XAxisField } from './editors/x-axis';
1414
import { YAxisField } from './editors/y-axis';
1515
import { DEFAULT_CONFIG, IParetoChartConf } from './type';
1616
import { useTranslation } from 'react-i18next';
17+
import { ReferenceLinesField } from './editors/reference-lines';
1718

1819
export function VizParetoChartEditor({ context }: VizConfigProps) {
1920
const { t } = useTranslation();
2021
const { value: conf, set: setConf } = useStorageData<IParetoChartConf>(context.instanceData, 'config');
22+
const { variables } = context;
2123
const defaultValues = useMemo(() => defaults({}, conf, DEFAULT_CONFIG), [conf]);
2224

2325
const { control, handleSubmit, watch, getValues, reset } = useForm<IParetoChartConf>({ defaultValues });
@@ -55,6 +57,7 @@ export function VizParetoChartEditor({ context }: VizConfigProps) {
5557
<Tabs.Tab value="Bar">{t('chart.series.bar.label')}</Tabs.Tab>
5658
<Tabs.Tab value="Line">{t('chart.series.line.label')}</Tabs.Tab>
5759
<Tabs.Tab value="80-20 Line">{t('viz.pareto_chart.line_80_20.label')}</Tabs.Tab>
60+
<Tabs.Tab value="Reference Lines">{t('chart.reference_line.labels')}</Tabs.Tab>
5861
<Tabs.Tab value="Zooming">{t('chart.zooming.label')}</Tabs.Tab>
5962
</Tabs.List>
6063

@@ -78,6 +81,10 @@ export function VizParetoChartEditor({ context }: VizConfigProps) {
7881
<MarkLineField control={control} watch={watch} />
7982
</Tabs.Panel>
8083

84+
<Tabs.Panel value="Reference Lines">
85+
<ReferenceLinesField variables={variables} control={control} watch={watch} />
86+
</Tabs.Panel>
87+
8188
<Tabs.Panel value="Zooming">
8289
<Controller name="dataZoom" control={control} render={({ field }) => <EchartsZoomingField {...field} />} />
8390
</Tabs.Panel>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/root",
3-
"version": "13.14.0",
3+
"version": "13.15.0",
44
"private": true,
55
"workspaces": [
66
"api",

0 commit comments

Comments
 (0)