feat: Charts support time comparisons
This commit is contained in:
parent
500afafdf3
commit
0769b02549
|
@ -3,6 +3,7 @@ import { Table, Input, Button, Modal } from 'antd';
|
|||
import { ColumnProps, TableRowSelection } from 'antd/es/table';
|
||||
import Color from 'color';
|
||||
import _ from 'lodash';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import clipboard from '@common/clipboard';
|
||||
import ContextMenu from '@cpts/ContextMenu';
|
||||
import { SerieInterface, PointInterface } from '../interface';
|
||||
|
@ -35,7 +36,7 @@ interface LegendDataItem extends SerieInterface {
|
|||
last: number | null,
|
||||
}
|
||||
|
||||
export default class Legend extends Component<Props, State> {
|
||||
class Legend extends Component<Props, State> {
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
series: [],
|
||||
|
@ -125,7 +126,7 @@ export default class Legend extends Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { onSelectedChange } = this.props;
|
||||
const { comparisonOptions, onSelectedChange } = this.props;
|
||||
const { searchText, selectedKeys, highlightedKeys } = this.state;
|
||||
const counterSelectedKeys = highlightedKeys;
|
||||
const data = this.filterData();
|
||||
|
@ -148,7 +149,7 @@ export default class Legend extends Component<Props, State> {
|
|||
filterDropdownVisible: this.state.filterDropdownVisible,
|
||||
onFilterDropdownVisibleChange: (visible: boolean) => this.setState({ filterDropdownVisible: visible }),
|
||||
render: (text, record) => {
|
||||
const legendName = getLengendName(record);
|
||||
const legendName = getLengendName(record, comparisonOptions, this.props.intl);
|
||||
return (
|
||||
<span
|
||||
title={text}
|
||||
|
@ -258,12 +259,13 @@ export default class Legend extends Component<Props, State> {
|
|||
|
||||
export function normalizeLegendData(series: SerieInterface[] = []) {
|
||||
const tableData = _.map(series, (serie) => {
|
||||
const { id, metric, tags, data } = serie;
|
||||
const { id, metric, tags, data, comparison } = serie;
|
||||
const { last, avg, max, min, sum } = getLegendNums(data);
|
||||
return {
|
||||
id,
|
||||
metric,
|
||||
tags,
|
||||
comparison,
|
||||
last,
|
||||
avg,
|
||||
max,
|
||||
|
@ -348,9 +350,17 @@ function getLegendNums(points: PointInterface[]) {
|
|||
return { last, avg, max, min, sum };
|
||||
}
|
||||
|
||||
function getLengendName(serie: SerieInterface) {
|
||||
const { tags } = serie;
|
||||
function getLengendName(serie: SerieInterface, comparisonOptions: any, intl: any) {
|
||||
const { tags, comparison } = serie;
|
||||
let lname = tags;
|
||||
// display comparison
|
||||
if (comparison && typeof comparison === 'number') {
|
||||
const currentComparison = _.find(comparisonOptions, { value: `${comparison}000` });
|
||||
if (currentComparison && currentComparison.label) {
|
||||
const postfix = intl.locale === 'en' ? currentComparison.labelEn : `环比${currentComparison.label}`;
|
||||
lname += ` ${postfix}`;
|
||||
}
|
||||
}
|
||||
// shorten name
|
||||
if (lname.length > 80) {
|
||||
const leftStr = lname.substr(0, 40);
|
||||
|
@ -369,3 +379,5 @@ function isEqualSeries(series: SerieInterface[], nextSeries: SerieInterface[]) {
|
|||
});
|
||||
return _.isEqual(pureSeries, pureNextSeries);
|
||||
}
|
||||
|
||||
export default injectIntl(Legend);
|
||||
|
|
|
@ -93,6 +93,7 @@ export default class Graph extends Component<Props, State> {
|
|||
|| aggrFuncChanged
|
||||
|| aggrGroupChanged
|
||||
|| consolFuncChanged
|
||||
|| !_.isEqual(nextData.comparison, thisData.comparison)
|
||||
) {
|
||||
const isFetchCounter = selectedNsChanged || selectedMetricChanged || selectedTagkvChanged;
|
||||
this.fetchData(nextProps.data, isFetchCounter, (series: SerieInterface[]) => {
|
||||
|
@ -256,6 +257,8 @@ export default class Graph extends Component<Props, State> {
|
|||
return util.getTooltipsContent({
|
||||
points,
|
||||
chartWidth: this.graphWrapEle.offsetWidth - 40,
|
||||
comparison: graphConfig.comparison,
|
||||
isComparison: !!_.get(graphConfig.comparison, 'length'),
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -292,6 +295,8 @@ export default class Graph extends Component<Props, State> {
|
|||
return util.getTooltipsContent({
|
||||
points,
|
||||
chartWidth: this.graphWrapEle.offsetWidth - 40,
|
||||
comparison: graphConfig.comparison,
|
||||
isComparison: !!_.get(graphConfig.comparison, 'length'),
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -391,6 +396,7 @@ export default class Graph extends Component<Props, State> {
|
|||
style={{ display: graphConfig.legend ? 'block' : 'none' }}
|
||||
series={this.getZoomedSeries()}
|
||||
onSelectedChange={this.handleLegendRowSelectedChange}
|
||||
comparisonOptions={graphConfig.comparisonOptions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Icon, Button, Select, Popover, Input, InputNumber } from 'antd';
|
||||
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
|
||||
import { injectIntl } from 'react-intl';
|
||||
|
||||
interface Props {
|
||||
size: 'small' | 'default' | 'large' | undefined,
|
||||
comparison: string[],
|
||||
relativeTimeComparison: boolean,
|
||||
comparisonOptions: any[],
|
||||
graphConfig: any,
|
||||
onChange: (values: any) => void,
|
||||
}
|
||||
|
||||
interface State {
|
||||
customValue?: number,
|
||||
customType: string,
|
||||
errorText: string,
|
||||
}
|
||||
|
||||
const Option = Select.Option;
|
||||
const customTypeOptions = [
|
||||
{
|
||||
value: 'hour',
|
||||
label: '小时',
|
||||
labelEn: 'hour',
|
||||
ms: 3600000,
|
||||
}, {
|
||||
value: 'day',
|
||||
label: '天',
|
||||
labelEn: 'day',
|
||||
ms: 86400000,
|
||||
},
|
||||
];
|
||||
|
||||
class Comparison extends Component<Props, State> {
|
||||
static defaultProps = {
|
||||
size: 'small',
|
||||
comparison: [],
|
||||
relativeTimeComparison: false,
|
||||
comparisonOptions: [],
|
||||
graphConfig: null,
|
||||
onChange: _.noop,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
customValue: undefined, // 自定义环比值(不带单位)
|
||||
customType: 'hour', // 自定义环比值单位 hour | day
|
||||
errorText: '', // 错误提示文本
|
||||
};
|
||||
}
|
||||
|
||||
refresh = () => {
|
||||
const { graphConfig } = this.props;
|
||||
if (graphConfig) {
|
||||
const now = moment();
|
||||
const start = (Number(now.format('x')) - Number(graphConfig.end)) + Number(graphConfig.start) + '';
|
||||
const end = now.format('x');
|
||||
|
||||
return { now: end, start, end };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
handleComparisonChange = (value: string[]) => {
|
||||
const { onChange, relativeTimeComparison, comparisonOptions } = this.props;
|
||||
onChange({
|
||||
...this.refresh(),
|
||||
comparison: value,
|
||||
relativeTimeComparison,
|
||||
comparisonOptions,
|
||||
});
|
||||
}
|
||||
|
||||
handleRelativeTimeComparisonChange = (e: CheckboxChangeEvent) => {
|
||||
const { onChange, comparison, comparisonOptions } = this.props;
|
||||
onChange({
|
||||
...this.refresh(),
|
||||
comparison,
|
||||
relativeTimeComparison: e.target.checked,
|
||||
comparisonOptions,
|
||||
});
|
||||
}
|
||||
|
||||
handleCustomValueChange = (value: number | undefined) => {
|
||||
if (value) {
|
||||
this.setState({
|
||||
customValue: value,
|
||||
errorText: '',
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
customValue: value,
|
||||
errorText: 'Custom value is required',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleCustomTypeChange = (value: string) => {
|
||||
this.setState({ customType: value });
|
||||
}
|
||||
|
||||
handleCustomBtnClick = () => {
|
||||
const { onChange, comparison, relativeTimeComparison, comparisonOptions } = this.props;
|
||||
const { customValue, customType } = this.state;
|
||||
const currentCustomTypeObj = _.find(customTypeOptions, { value: customType });
|
||||
|
||||
if (!customValue || !currentCustomTypeObj) {
|
||||
this.setState({
|
||||
errorText: 'Custom value is required',
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
errorText: '',
|
||||
}, () => {
|
||||
const ms = currentCustomTypeObj.ms * customValue;
|
||||
const comparisonOptionsClone = _.cloneDeep(comparisonOptions);
|
||||
const comparisonClone = _.cloneDeep(comparison);
|
||||
comparisonClone.push(_.toString(ms));
|
||||
comparisonOptionsClone.push({
|
||||
label: `${customValue}${currentCustomTypeObj.label}`,
|
||||
value: _.toString(ms),
|
||||
});
|
||||
const newComparisonOptions = _.unionBy(comparisonOptionsClone, 'value');
|
||||
onChange({
|
||||
...this.refresh(),
|
||||
comparison: comparisonClone,
|
||||
relativeTimeComparison,
|
||||
comparisonOptions: newComparisonOptions,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { size, comparison, comparisonOptions } = this.props;
|
||||
const { customValue, customType, errorText } = this.state;
|
||||
console.log(this.props.intl.locale);
|
||||
const addonUid = _.uniqueId('inputNumber-addon-');
|
||||
return (
|
||||
<div className="graph-config-inner-comparison">
|
||||
<Select
|
||||
mode="multiple"
|
||||
dropdownMatchSelectWidth={false}
|
||||
size={size}
|
||||
style={{ minWidth: 80, width: 'auto', verticalAlign: 'middle' }}
|
||||
value={comparison}
|
||||
onChange={this.handleComparisonChange}>
|
||||
{
|
||||
_.map(comparisonOptions, o => {
|
||||
return (
|
||||
<Option key={o.value} value={o.value}>
|
||||
{this.props.intl.locale === 'en' ? o.labelEn : o.label}
|
||||
</Option>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
<Popover placement="bottom" title="Enter a custom value" trigger="click" content={
|
||||
<div>
|
||||
<div style={{ display: 'inline-block', width: 160, marginRight: 10, verticalAlign: 'top' }}>
|
||||
<Input.Group className="ant-select-wrapper" size="default">
|
||||
<InputNumber value={customValue} onChange={this.handleCustomValueChange} />
|
||||
<span className="ant-input-group-addon" id={addonUid}>
|
||||
<Select
|
||||
style={{ width: 70 }}
|
||||
getPopupContainer={() => document.getElementById(addonUid) as HTMLElement}
|
||||
value={customType}
|
||||
onChange={this.handleCustomTypeChange}
|
||||
>
|
||||
{
|
||||
_.map(customTypeOptions, item => (
|
||||
<Option key={item.value}>
|
||||
{this.props.intl.locale === 'en' ? item.labelEn : item.label}
|
||||
</Option>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</span>
|
||||
</Input.Group>
|
||||
</div>
|
||||
<Button onClick={this.handleCustomBtnClick}>
|
||||
{this.props.intl.locale === 'en' ? 'ok' : '确认'}
|
||||
</Button>
|
||||
<p style={{ color: '#f50' }}>{errorText}</p>
|
||||
</div>
|
||||
}>
|
||||
<span className="ant-input-group-addon select-addon" style={{
|
||||
padding: size === 'default' ? 7 : 5,
|
||||
left: size === 'default' ? -5 : -3,
|
||||
height: size === 'default' ? 32 : 24,
|
||||
lineHeight: size === 'default' ? '18px' : '10px',
|
||||
}}>
|
||||
<Icon type="plus-circle-o" />
|
||||
</span>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(Comparison);
|
|
@ -9,6 +9,7 @@ import { normalizeTreeData, renderTreeNodes } from '@cpts/Layout/utils';
|
|||
import request from '@common/request';
|
||||
import api from '@common/api';
|
||||
import Tagkv from './Tagkv';
|
||||
import Comparison from './Comparison';
|
||||
import * as config from '../config';
|
||||
import { getTimeLabelVal } from '../util';
|
||||
import hasDtag from '../util/hasDtag';
|
||||
|
@ -213,6 +214,18 @@ export default class GraphConfigForm extends Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
handleCommonFieldChange = (changedObj) => {
|
||||
const newChangedObj = {};
|
||||
_.each(changedObj, (val, key) => {
|
||||
newChangedObj[key] = {
|
||||
$set: val,
|
||||
};
|
||||
});
|
||||
this.setState(update(this.state, {
|
||||
graphConfig: newChangedObj,
|
||||
}));
|
||||
}
|
||||
|
||||
handleNsChange = async (selectedNid: number[], currentMetricObj: MetricInterface) => {
|
||||
try {
|
||||
this.setLoading(true);
|
||||
|
@ -835,6 +848,30 @@ export default class GraphConfigForm extends Component<Props, State> {
|
|||
] : false
|
||||
}
|
||||
</FormItem>
|
||||
<FormItem
|
||||
labelCol={{ span: 3 }}
|
||||
wrapperCol={{ span: 21 }}
|
||||
label={<FormattedMessage id="graph.config.comparison" />}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Comparison
|
||||
size="default"
|
||||
comparison={graphConfig.comparison}
|
||||
relativeTimeComparison={graphConfig.relativeTimeComparison}
|
||||
comparisonOptions={graphConfig.comparisonOptions}
|
||||
graphConfig={graphConfig}
|
||||
onChange={(values) => {
|
||||
this.handleCommonFieldChange({
|
||||
start: values.start,
|
||||
end: values.end,
|
||||
now: values.now,
|
||||
comparison: values.comparison,
|
||||
relativeTimeComparison: values.relativeTimeComparison,
|
||||
comparisonOptions: values.comparisonOptions,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
{this.renderMetrics()}
|
||||
<FormItem
|
||||
labelCol={{ span: 3 }}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Icon, Button, Select, Checkbox, Tooltip, DatePicker } from 'antd';
|
|||
import * as config from '../config';
|
||||
import * as util from '../util';
|
||||
import Tagkv from './Tagkv';
|
||||
import Comparison from './Comparison';
|
||||
import { GraphDataInterface, GraphDataChangeFunc } from '../interface';
|
||||
|
||||
interface Props {
|
||||
|
@ -77,6 +78,18 @@ export default class GraphConfigInner extends Component<Props> {
|
|||
});
|
||||
}
|
||||
|
||||
handleComparisonChange = (values: any) => {
|
||||
const { data, onChange } = this.props;
|
||||
onChange('update', data.id, {
|
||||
start: values.start,
|
||||
end: values.end,
|
||||
now: values.now,
|
||||
comparison: values.comparison,
|
||||
relativeTimeComparison: values.relativeTimeComparison,
|
||||
comparisonOptions: values.comparisonOptions,
|
||||
});
|
||||
}
|
||||
|
||||
handleconsolFuncChange = (val: string) => {
|
||||
const { data, onChange } = this.props;
|
||||
onChange('update', data.id, {
|
||||
|
@ -178,7 +191,7 @@ export default class GraphConfigInner extends Component<Props> {
|
|||
|
||||
render() {
|
||||
const { data, onChange } = this.props;
|
||||
const { now, start, end } = data;
|
||||
const { now, start, end, comparison } = data;
|
||||
const timeLabel = now === end ? util.getTimeLabelVal(start, end, 'label') : '其他';
|
||||
const timeVal = now === end ? util.getTimeLabelVal(start, end, 'value') : 'custom';
|
||||
const datePickerStartVal = moment(Number(start)).format(config.timeFormatMap.moment);
|
||||
|
@ -284,21 +297,23 @@ export default class GraphConfigInner extends Component<Props> {
|
|||
</Select>
|
||||
</div> : null
|
||||
}
|
||||
{/* <div className="graph-config-inner-item">
|
||||
采样函数:
|
||||
<Select
|
||||
allowClear
|
||||
size="small"
|
||||
style={{ width: 85 }}
|
||||
placeholder="无"
|
||||
value={_.get(data.metrics, '[0].consolFunc')}
|
||||
onChange={this.handleconsolFuncChange}
|
||||
>
|
||||
<Option value="AVERAGE">均值</Option>
|
||||
<Option value="MAX">最大值</Option>
|
||||
<Option value="MIN">最小值</Option>
|
||||
</Select>
|
||||
</div> */}
|
||||
<div className="graph-config-inner-item">
|
||||
<FormattedMessage id="graph.config.comparison" />:
|
||||
<Comparison
|
||||
comparison={comparison}
|
||||
relativeTimeComparison={data.relativeTimeComparison}
|
||||
comparisonOptions={data.comparisonOptions}
|
||||
graphConfig={data}
|
||||
onChange={this.handleComparisonChange}
|
||||
/>
|
||||
<input
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: -10000,
|
||||
}}
|
||||
id={`hiddenInput${data.id}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="graph-config-inner-item">
|
||||
<Checkbox checked={!!data.legend} onChange={this.legendChange}>
|
||||
Legend
|
||||
|
|
|
@ -2,21 +2,26 @@ import PropTypes from 'prop-types';
|
|||
import moment from 'moment';
|
||||
|
||||
const now = moment();
|
||||
export const comparison = [
|
||||
export const comparisonOptions = [
|
||||
{
|
||||
label: '1小时',
|
||||
labelEn: '1 hour',
|
||||
value: '3600000',
|
||||
}, {
|
||||
label: '2小时',
|
||||
labelEn: '2 hours',
|
||||
value: '7200000',
|
||||
}, {
|
||||
label: '1天',
|
||||
labelEn: '1 day',
|
||||
value: '86400000',
|
||||
}, {
|
||||
label: '2天',
|
||||
labelEn: '2 days',
|
||||
value: '172800000',
|
||||
}, {
|
||||
label: '7天',
|
||||
labelEn: '7 days',
|
||||
value: '604800000',
|
||||
},
|
||||
];
|
||||
|
@ -74,6 +79,7 @@ export const graphDefaultConfig = {
|
|||
now: now.clone().format('x'),
|
||||
start: now.clone().subtract(3600000, 'ms').format('x'),
|
||||
end: now.clone().format('x'),
|
||||
comparisonOptions,
|
||||
threshold: undefined,
|
||||
legend: false,
|
||||
shared: false,
|
||||
|
|
|
@ -32,20 +32,38 @@ export default function getTooltipsContent(activeTooltipData: ActiveTooltipData)
|
|||
tooltipContent += getHeaderStr(activeTooltipData);
|
||||
|
||||
_.each(sortedPoints, (point) => {
|
||||
tooltipContent += singlePoint(point);
|
||||
tooltipContent += singlePoint(point, activeTooltipData);
|
||||
});
|
||||
|
||||
return `<div style="table-layout: fixed;max-width: ${chartWidth}px;word-wrap: break-word;white-space: normal;">${tooltipContent}</div>`;
|
||||
}
|
||||
|
||||
function singlePoint(pointData = {} as PointInterface) {
|
||||
const { color, filledNull, serieOptions = {} } = pointData;
|
||||
const { tags } = serieOptions as any;
|
||||
function singlePoint(pointData = {}, activeTooltipData) {
|
||||
const { color, filledNull, serieOptions = {}, timestamp } = pointData;
|
||||
const { comparison: comparisons, isComparison } = activeTooltipData;
|
||||
const { tags } = serieOptions;
|
||||
const value = numeral(pointData.value).format('0,0[.]000');
|
||||
let name = tags;
|
||||
|
||||
// 对比情况下 name 特殊处理
|
||||
if (isComparison) {
|
||||
const mDate = serieOptions.comparison && typeof serieOptions.comparison === 'number' ? moment(timestamp).subtract(serieOptions.comparison, 'seconds') : moment(timestamp);
|
||||
const isAllDayLevelComparison = _.every(comparisons, (o) => {
|
||||
return _.isInteger(Number(o) / 86400000);
|
||||
});
|
||||
|
||||
if (isAllDayLevelComparison) {
|
||||
const dateStr = mDate.format('YYYY-MM-DD');
|
||||
name = `${dateStr}`;
|
||||
} else {
|
||||
const dateStr = mDate.format(fmt);
|
||||
name = `${dateStr} ${name}`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
`<span style="color:${color}">● </span>
|
||||
${_.escape(tags)}:<strong>${value}${filledNull ? '(空值填补,仅限看图使用)' : ''}</strong><br />`
|
||||
${name}:<strong>${value}${filledNull ? '(空值填补,仅限看图使用)' : ''}</strong><br />`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ export function transformMsToS(ts: string) {
|
|||
return Number(ts.substring(0, ts.length - 3));
|
||||
}
|
||||
|
||||
export function processComparison(comparison: number[]) {
|
||||
export function processComparison(comparison: string[]) {
|
||||
const newComparison = [0];
|
||||
_.each(comparison, (o) => {
|
||||
newComparison.push(transformMsToS(String(o)));
|
||||
|
@ -14,6 +14,7 @@ export function processComparison(comparison: number[]) {
|
|||
}
|
||||
|
||||
export default function normalizeEndpointCounters(graphConfig: GraphDataInterface, counterList: CounterInterface[]) {
|
||||
const newComparison = processComparison(graphConfig.comparison);
|
||||
const firstMetric = _.get(graphConfig, 'metrics[0]', {});
|
||||
const { aggrFunc, aggrGroup: groupKey, consolFunc } = firstMetric;
|
||||
const start = transformMsToS(_.toString(graphConfig.start));
|
||||
|
@ -27,6 +28,7 @@ export default function normalizeEndpointCounters(graphConfig: GraphDataInterfac
|
|||
aggrFunc,
|
||||
groupKey,
|
||||
consolFunc,
|
||||
comparisons: newComparison,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { SerieInterface } from '../interface';
|
|||
export default function normalizeSeries(data: any[]) {
|
||||
const series = [] as SerieInterface[];
|
||||
_.each(_.sortBy(data, ['counter', 'endpoint']), (o, i) => {
|
||||
const { endpoint } = o;
|
||||
const { endpoint, comparison } = o;
|
||||
const color = getSerieColor(o, i);
|
||||
const separatorIdx = o.counter.indexOf('/');
|
||||
|
||||
|
@ -23,6 +23,7 @@ export default function normalizeSeries(data: any[]) {
|
|||
lineWidth: 2,
|
||||
color,
|
||||
oldColor: color,
|
||||
comparison,
|
||||
} as SerieInterface;
|
||||
series.push(serie);
|
||||
});
|
||||
|
|
|
@ -260,6 +260,7 @@ export default {
|
|||
'graph.config.aggr.max': 'max',
|
||||
'graph.config.aggr.min': 'min',
|
||||
'graph.config.aggr.group': 'groupBy',
|
||||
'graph.config.comparison': 'comparison',
|
||||
'graph.config.series': 'series',
|
||||
'graph.config.series.unit': 'pcs',
|
||||
'graph.config.cate': 'cate',
|
||||
|
|
|
@ -261,6 +261,7 @@ export default {
|
|||
'graph.config.aggr.max': '最大值',
|
||||
'graph.config.aggr.min': '最小值',
|
||||
'graph.config.aggr.group': '聚合维度',
|
||||
'graph.config.comparison': '环比',
|
||||
'graph.config.series': '曲线',
|
||||
'graph.config.series.unit': '条',
|
||||
'graph.config.cate': '分类',
|
||||
|
|
Loading…
Reference in New Issue