Commit 1c029554 authored by 崔佳豪's avatar 崔佳豪

feat: 添加基本图表

parent 1ceb6d5e
......@@ -152,6 +152,8 @@
"@wisdom-utils/utils": "0.0.46",
"classnames": "^2.2.6",
"cross-spawn": "^7.0.3",
"echarts": "^5.4.0",
"echarts-for-react": "^3.0.2",
"form-render": "^0.9.12",
"highcharts": "^9.0.1",
"highcharts-react-official": "^3.0.0",
......
import React, { useMemo, forwardRef } from 'react';
import ECharts from './ECharts';
import * as echarts from 'echarts/core';
import propTypes from 'prop-types';
import _ from 'lodash';
// 基础柱状图
const isCategory = (category) => category !== undefined;
const isStack = (stack) => stack === undefined || stack === null;
const gradientColors = [
['#1685ff', '#74bcff'],
['#00b809', '#66d46b'],
['#ffa200', '#ffc766'],
['#00c4bd', '#66dccd'],
['#ff6b37', '#ff9773'],
['#986aff', '#c1a6ff'],
].map(
([from, to]) =>
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: from },
{ offset: 1, color: to },
]),
);
const BarChart = forwardRef(({ category, dataSource }, ref) => {
const option = useMemo(() => {
const xAxis = isCategory(category)
? {
type: 'category',
data: category,
}
: {
type: 'time',
};
const hasUnitItem = dataSource.find((item) => item.unit);
const yAxis = !!hasUnitItem ? { name: `(${hasUnitItem.unit})` } : {};
const series = dataSource.map((item, index) => {
const config = _.omit(item, ['type']);
const itemStyle = isStack(config.stack)
? {
color: gradientColors[index % gradientColors.length],
}
: {};
return {
type: 'bar',
...itemStyle,
...config,
};
});
return { xAxis, yAxis, series };
}, [category, dataSource]);
return <ECharts ref={ref} option={option} />;
});
BarChart.PropTypes = {
category: propTypes.array,
dataSource: propTypes.objectOf({
name: propTypes.string,
data: propTypes.array,
unit: propTypes.unit,
}),
};
export default BarChart;
......@@ -7,27 +7,105 @@ group:
path: /
---
# BasicChart 标准图表
## BasicChart 标准图表
基础组件
- 支持的图表类型有直线图、曲线图等 20 种图表,其中很多图表可以集成在同一个图形中形成混合图
## 何时使用
tooltip 规范
- 当需要展现图表时;
- 头部显示数据名\类目名
- 内容部分显示各系列数据信息:系列色、系列名称、系列值、系列单位
- 头部和内容通过分割线隔开
- 缺省的数据系列不显示在内容部分上
- 数值颜色跟随系列色
## 代码演示
xAxis 规范
### 常规使用
- 显示轴线、刻度线、文字标签,不显示网格线
- 轴线、刻度线、文字标签色彩样式一致
- 刻度线方向向内
- <font color="red">类目类型图表中,两边是否采用留白?</font>
<code src="./demos/Basic.tsx">
yAxis 规范
## API
- 不显示轴线、刻度线、文字标签,显示网格线
- 网格线使用虚线`dashed`类型
- 坐标轴名称显示在顶部,和坐标轴对齐
api 参考 highcharts 库 https://api.highcharts.com.cn/highcharts
axisPointer 规范
- 类目类型图表中,指示器采用阴影`shadow`类型
- 非类目类型图标中,指示器采用`line`类型,line 为虚线,在图形上方,标记下方
### 基本柱状图
`BasicChart.BarChart`拥有默认配置的柱状图,适用于仅包含柱状图的使用场景。
#### 类目类型柱状图
<code src="./demos/BaseBar.tsx">
#### 时间类型柱状图
<code src="./demos/TimeTypeBar.tsx">
#### 堆叠柱状图
<code src="./demos/StackBar.tsx">
### 基本折线图
`BasicChart.LineChart`拥有默认配置的折线图,适用于仅包含折线图的使用场景。
#### 类目类型折线图
<code src="./demos/BaseLine.tsx">
#### 面积图
<code src="./demos/AreaLine.tsx">
#### 时间类型折线图
<code src="./demos/TimeTypeLine.tsx">
### 自定义图表
`BasicChart`通用 echarts 图表,通过`option`传递配置项。 <code src="./demos/Basic.tsx">
### 获取 echarts 实例对象
通过传递`ref`可以获取到创建出来的 echarts 实例对象。 `BasicChart``BasicChart.BarChart``BasicChart.LineChart`都支持。 <code src="./demos/ChartInstance.tsx">
### API
#### BasicChart
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
| --------- | -------------------------- | ------ | ------ | ------ |
| className | 图表容器类名 | string | - | - |
| option | echarts 图表配置项`option` | object | - | - |
`option` 参考 echarts 库 https://echarts.apache.org/zh/index.html
#### BasicChart.BarChart(基本柱状图)
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
| --- | --- | --- | --- | --- | --- |
| className | 图表容器类名 | string | - | - |
| category | x 轴类目配置,有该配置则为`category`类型图表,否则为`time`类型的图表 | Array[string | number] | - | - |
| dataSource | 图表容器类名 | object | - | - |
dataSource | 参数 | 说明 | 类型 | 默认值 | 可选值 | | --- | --- | --- | --- | --- | | name | 数据系列名称,有 name 时才会显示图例 | string | - | - | | unit | 数据单位,显示在`tooltip``yAxis`上 | string | - | - | | data | 图表数据,`category`类型和 `time`类型有所区别,<font color="#008dff">详见案例</font> | Array[] | - | - |
#### BasicChart.LineChart(基本折线图)
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
| --- | --- | --- | --- | --- |
| constructorType | 标题 | string | chart | - 'chart' 用于 Highcharts 图表 <br/> - 'stockChart' 用于 Highstock 图表 <br/> - 'mapChart' 用于 Highmaps 图表 <br/> - 'ganttChart' 用于甘特图 |
| chartOptions`必需` | Highcharts 图表配置对象 | object | { } | - |
| --- | --- | --- | --- | --- | --- |
| className | 图表容器类名 | string | - | - |
| category | x 轴类目配置,有该配置则为`category`类型图表,否则为`time`类型的图表 | Array[string | number] | - | - |
| dataSource | 图表容器类名 | object | - | - |
dataSource | 参数 | 说明 | 类型 | 默认值 | 可选值 | | --- | --- | --- | --- | --- | | name | 数据系列名称,有 name 时才会显示图例 | string | - | - | | smooth | 平滑曲线模式 | boolean | true | - | | lienType | 折线图/面积图显示 | string | `line` | `line``area` | | unit | 数据单位,显示在`tooltip``yAxis`上 | string | - | - | | data | 图表数据,`category`类型和 `time`类型有所区别,<font color="#008dff">详见案例</font> | Array[] | - | - |
import React, { useEffect, useMemo } from 'react';
import { ConfigProvider } from 'antd';
import classNames from 'classnames';
import ReactEcharts from 'echarts-for-react';
import { omit } from 'lodash';
import buildOption from './utils';
const ECharts = React.forwardRef((props, ref) => {
const { getPrefixCls } = React.useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('basic-chart');
const chartProps = omit(props, ['className', 'option']);
const option = useMemo(() => {
return buildOption(props.option);
}, [props.option]);
// const chartProps = pick(props, ['option', 'notMerge', 'lazyUpdate', 'style', 'className', 'theme', 'onChartReady', 'loadingOption', 'showLoading', 'onEvents', 'opts']);
return (
<ReactEcharts
ref={ref}
className={classNames(prefixCls, props.className)}
option={option}
{...chartProps}
/>
);
});
export default ECharts;
import React, { forwardRef, useMemo } from 'react';
import ECharts from './ECharts';
import propTypes from 'prop-types';
import _ from 'lodash';
// 基础柱状图
const isCategory = (category) => category !== undefined;
const isArea = (lineType) => lineType === 'area';
const LineChart = forwardRef(({ category, lineType, smooth, dataSource }, ref) => {
const option = useMemo(() => {
const xAxis = isCategory(category)
? {
type: 'category',
data: category,
}
: {
type: 'time',
};
const hasUnitItem = dataSource.find((item) => item.unit);
const yAxis = !!hasUnitItem ? { name: `(${hasUnitItem.unit})` } : {};
const series = dataSource.map((item, index) => {
const config = _.omit(item, ['type']);
const itemStyle = isArea(lineType)
? {
areaStyle: {},
}
: {};
return {
type: 'line',
smooth,
...itemStyle,
...config,
};
});
return { xAxis, yAxis, series };
}, [category, smooth, lineType, dataSource]);
return <ECharts ref={ref} option={option} />;
});
LineChart.PropTypes = {
smooth: propTypes.boolean,
lineType: propTypes.oneOf(['line', 'area']),
category: propTypes.array,
dataSource: propTypes.objectOf({
name: propTypes.string,
data: propTypes.array,
unit: propTypes.unit,
}),
};
LineChart.defaultProps = {
smooth: true,
lineType: 'line',
};
export default LineChart;
import React from 'react';
import { BasicChart } from '../index';
const { LineChart } = BasicChart;
export default () => {
const category = ['11-01', '11-02', '11-03', '11-04', '11-05', '11-06', '11-07'];
const dataSource = [
{
name: '用水量',
unit: 'm³',
data: [120, 200, 150, 80, 70, 110, 130],
},
{
name: '供水量',
unit: 'm³',
data: [1120, 1200, 1150, 1180, 1170, 1110, 1130],
},
];
return <LineChart lineType="area" category={category} dataSource={dataSource} />;
};
import React from 'react';
import { BasicChart } from '../index';
const { BarChart } = BasicChart;
export default () => {
const category = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const dataSource = [
{
name: '用水量',
unit: 'm³',
data: [120, 200, 150, 80, 70, 110, 130],
},
{
name: '供水量',
unit: 'm³',
data: [120, 200, 150, 80, 70, 110, 130],
},
];
return <BarChart category={category} dataSource={dataSource} />;
};
import React from 'react';
import { BasicChart } from '../index';
const { LineChart } = BasicChart;
export default () => {
const category = ['11-01', '11-02', '11-03', '11-04', '11-05', '11-06', '11-07'];
const dataSource = [
{
name: '用水量',
unit: 'm³',
data: [120, 200, 150, 80, 70, 110, 130],
},
{
name: '供水量',
unit: 'm³',
data: [1120, 1200, 1150, 1180, 1170, 1110, 1130],
},
];
return <LineChart smooth={false} category={category} dataSource={dataSource} />;
};
import React from 'react';
import ECharts from '../ECharts';
const Demo = () => {
const option = {
title: {
text: 'Referer of a Website',
subtext: 'Fake Data',
left: 'center',
},
tooltip: {
trigger: 'item',
},
// xAxis: {
// data: [],
// },
legend: {
orient: 'vertical',
left: 'left',
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
};
return <ECharts option={option} />;
};
export default Demo;
import React from 'react';
import BasicChart from '../index';
import { BasicChart } from '../index';
const Demo = () => {
return (
<div>
<h3>折线图</h3>
<div style={{ height: '600px' }}>
<BasicChart chartOptions={chartOptions1} />
</div>
<h3 style={{ marginTop: '40px' }}>曲线面积图</h3>
<div style={{ height: '600px' }}>
<BasicChart chartOptions={chartOptions2} />
</div>
<h3 style={{ marginTop: '40px' }}>带滚动条曲线图</h3>
<div style={{ height: '600px' }}>
<BasicChart chartOptions={chartOptions3} constructorType={'stockChart'} />
</div>
</div>
);
};
export default Demo;
const chartOptions1 = {
// ...
series: [
const colors = ['#5470C6', '#91CC75', '#EE6666'];
const option = {
color: colors,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
},
},
grid: {
right: '20%',
},
toolbox: {
feature: {
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true },
},
},
legend: {
data: ['Evaporation', 'Precipitation', 'Temperature'],
},
xAxis: [
{
name: '今日供水量',
color: '#1884EC',
data: [
[1620720173000, 122],
[1620720473000, 122],
[1620720773000, 122],
[1620721073000, 123],
[1620721373000, 123],
[1620721673000, 123],
[1620721973000, 124],
[1620722274000, 124],
[1620722724000, 125],
[1620723024000, 125],
[1620723324000, 125],
],
type: 'category',
axisTick: {
alignWithLabel: true,
},
// prettier-ignore
data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
},
],
};
const chartOptions2 = {
// ...
series: [
yAxis: [
{
type: 'areaspline',
name: '出水瞬时流量',
color: '#68cbd1',
data: [
[1620720173000, 1.85],
[1620720473000, 8.22],
[1620720773000, 2.83],
[1620721073000, 2.62],
[1620721373000, 2.05],
[1620721673000, 8.7],
[1620721973000, 2.38],
[1620722274000, 3.31],
[1620722724000, 2.36],
[1620723024000, 2.98],
[1620723324000, 2.58],
],
marker: {
enabled: false,
type: 'value',
name: 'Evaporation',
position: 'right',
alignTicks: true,
axisLine: {
show: true,
lineStyle: {
color: colors[0],
},
},
],
plotOptions: {
areaspline: {
fillOpacity: 0.5,
axisLabel: {
formatter: '{value} ml',
},
},
{
type: 'value',
name: 'Precipitation',
position: 'right',
alignTicks: true,
offset: 80,
axisLine: {
show: true,
lineStyle: {
color: colors[1],
},
},
axisLabel: {
formatter: '{value} ml',
},
},
};
const chartOptions3 = {
// ...
series: [
{
type: 'spline',
name: '进水压力',
color: '#1884EC',
data: [
[1620720173000, 0.08],
[1620720473000, 0.09],
[1620720773000, 0.09],
[1620721073000, 0.09],
[1620721373000, 0.09],
[1620721673000, 0.08],
[1620721973000, 0.09],
[1620722274000, 0.09],
[1620722724000, 0.09],
[1620723024000, 0.08],
[1620723324000, 0.08],
type: 'value',
name: '温度',
position: 'left',
alignTicks: true,
axisLine: {
show: true,
lineStyle: {
color: colors[2],
},
},
axisLabel: {
formatter: '{value} °C',
},
},
],
series: [
{
name: 'Evaporation',
type: 'bar',
data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
},
{
name: 'Precipitation',
type: 'bar',
yAxisIndex: 1,
data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3],
},
{
name: 'Temperature',
type: 'line',
yAxisIndex: 2,
data: [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2],
},
],
};
return <BasicChart option={option} />;
};
export default Demo;
import React from 'react';
import { useEffect } from 'react';
import { useRef } from 'react';
import { BasicChart } from '../index';
const Demo = () => {
const ref = useRef();
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line',
},
],
};
useEffect(() => {
console.log(ref.current);
}, []);
return <BasicChart ref={ref} option={option} />;
};
export default Demo;
import React from 'react';
import { BasicChart } from '../index';
const { BarChart } = BasicChart;
export default () => {
const category = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const dataSource = [
{
name: '小区-A栋',
unit: 'm³',
data: [120, 200, 150, 80, 70, 110, 130],
stack: '分组A',
},
{
name: '小区-B栋',
unit: 'm³',
data: [120, 200, 150, 80, 70, 110, 130],
stack: '分组A',
},
{
name: '商区',
unit: 'm³',
data: [1120, 1200, 1150, 180, 170, 1110, 1130],
stack: '分组B',
},
];
return <BarChart category={category} dataSource={dataSource} />;
};
import React from 'react';
import { BasicChart } from '../index';
const { BarChart } = BasicChart;
// 数据格式: [[时间戳, value]]
const getData = () => {
let base = +new Date(2000, 9, 3);
let oneDay = 24 * 3600 * 1000;
let data = [[base, Math.random() * 300]];
for (let i = 1; i < 20; i++) {
let now = new Date((base += oneDay));
data.push([+now, Math.round(Math.random() * 20 + data[i - 1][1])]);
}
return data;
};
export default () => {
const dataSource = [
{
name: '用水量',
unit: 'm³',
data: getData(),
},
];
return <BarChart dataSource={dataSource} />;
};
import React from 'react';
import { BasicChart } from '../index';
const { LineChart } = BasicChart;
// 数据格式: [[时间戳, value]]
const getData = () => {
let base = +new Date(2000, 9, 3);
let oneDay = 24 * 3600 * 1000;
let data = [[base, Math.random() * 300]];
for (let i = 1; i < 20; i++) {
let now = new Date((base += oneDay));
data.push([+now, Math.round(Math.random() * 20 + data[i - 1][1])]);
}
return data;
};
export default () => {
const dataSource = [
{
name: '用水量',
unit: 'm³',
data: getData(),
},
];
return <LineChart lineType="area" dataSource={dataSource} />;
};
import React, { useContext, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import Highcharts from 'highcharts/highstock';
import HighchartsReact from 'highcharts-react-official';
import { ConfigProvider } from 'antd';
import './index.less';
let chartWidth = 0; // chart width
let chartHeight = 0; // chart height
const BasicChart = (props) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('basic-chart');
const { chartOptions, constructorType } = props;
const [options, setOptions] = useState(defaultOptions);
const container = useRef(null);
// 处理图表options
const handleChartOptions = () => {
const options = {
...defaultOptions,
...chartOptions,
};
if (container.current) {
if (container.current.offsetWidth !== 0) {
chartWidth = container.current.offsetWidth;
chartHeight = container.current.offsetHeight;
}
Highcharts.setOptions({
// 处理 chart 高度坍塌
chart: {
width: container.current.offsetWidth === 0 ? chartWidth : container.current.offsetWidth,
height:
container.current.offsetHeight === 0 ? chartHeight : container.current.offsetHeight,
},
});
}
Highcharts.setOptions({
global: { timezoneOffset: -8 * 60 },
});
return options;
};
useEffect(() => {
setOptions({ ...handleChartOptions() });
}, [chartOptions]);
return (
<div className={classNames(prefixCls)} ref={container}>
<HighchartsReact
immutable={true}
highcharts={Highcharts}
constructorType={constructorType}
options={options}
{...props}
/>
</div>
);
};
BasicChart.defaultProps = {
constructorType: 'chart',
chartOptions: {},
};
BasicChart.propTypes = {
columns: PropTypes.string,
chartOptions: PropTypes.object,
};
export default BasicChart;
const defaultOptions = {
chart: {
zoomType: 'x',
backgroundColor: 'rgba(255, 255, 255, 0.5)',
},
title: null,
credits: false,
rangeSelector: {
enabled: false,
},
xAxis: [
{
lineWidth: 0,
crosshair: true,
type: 'datetime',
dateTimeLabelFormats: {
second: '%H:%M:%S',
minute: '%H:%M',
hour: '%H:%M',
day: '%d',
week: '%d',
month: '%d',
year: '%Y',
},
},
],
yAxis: [
{
title: null,
opposite: false, // 即坐标轴会显示在对立面
lineWidth: 1,
},
],
tooltip: {
shared: true,
split: false,
valueDecimals: 3,
dateTimeLabelFormats: {
second: '%H:%M:%S',
minute: '%H:%M',
hour: '%H:%M',
day: '%d',
week: '%d',
month: '%d',
year: '%Y',
},
},
plotOptions: {
series: {
showInNavigator: true,
connectNulls: false,
zoneAxis: 'x',
},
},
legend: {
verticalAlign: 'top',
},
series: [],
};
import React, { useContext, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import Highcharts from 'highcharts/highstock';
import HighchartsReact from 'highcharts-react-official';
import { ConfigProvider } from 'antd';
import './index.less';
import * as echarts from 'echarts/core';
import Echarts from './ECharts';
import BarChart from './BarChart';
import LineChart from './LineChart';
let chartWidth = 0; // chart width
let chartHeight = 0; // chart height
const BasicChart = Echarts;
const BasicChart = (props) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('basic-chart');
BasicChart.BarChart = BarChart;
BasicChart.LineChart = LineChart;
const { chartOptions, constructorType } = props;
const [options, setOptions] = useState(defaultOptions);
const container = useRef(null);
// 处理图表options
const handleChartOptions = () => {
const options = {
...defaultOptions,
...chartOptions,
};
if (container.current) {
if (container.current.offsetWidth !== 0) {
chartWidth = container.current.offsetWidth;
chartHeight = container.current.offsetHeight;
}
Highcharts.setOptions({
// 处理 chart 高度坍塌
chart: {
width: container.current.offsetWidth === 0 ? chartWidth : container.current.offsetWidth,
height:
container.current.offsetHeight === 0 ? chartHeight : container.current.offsetHeight,
},
});
}
Highcharts.setOptions({
global: { timezoneOffset: -8 * 60 },
});
return options;
};
useEffect(() => {
setOptions({ ...handleChartOptions() });
}, [chartOptions]);
return (
<div className={classNames(prefixCls)} ref={container}>
<HighchartsReact
immutable={true}
highcharts={Highcharts}
constructorType={constructorType}
options={options}
{...props}
/>
</div>
);
};
BasicChart.defaultProps = {
constructorType: 'chart',
chartOptions: {},
};
BasicChart.propTypes = {
columns: PropTypes.string,
chartOptions: PropTypes.object,
};
export default BasicChart;
const defaultOptions = {
chart: {
zoomType: 'x',
backgroundColor: 'rgba(255, 255, 255, 0.5)',
},
title: null,
credits: false,
rangeSelector: {
enabled: false,
},
xAxis: [
{
lineWidth: 0,
crosshair: true,
type: 'datetime',
dateTimeLabelFormats: {
second: '%H:%M:%S',
minute: '%H:%M',
hour: '%H:%M',
day: '%d',
week: '%d',
month: '%d',
year: '%Y',
},
},
],
yAxis: [
{
title: null,
opposite: false, // 即坐标轴会显示在对立面
lineWidth: 1,
},
],
tooltip: {
shared: true,
split: false,
valueDecimals: 3,
dateTimeLabelFormats: {
second: '%H:%M:%S',
minute: '%H:%M',
hour: '%H:%M',
day: '%d',
week: '%d',
month: '%d',
year: '%Y',
},
},
plotOptions: {
series: {
showInNavigator: true,
connectNulls: false,
zoneAxis: 'x',
},
},
legend: {
verticalAlign: 'top',
},
series: [],
};
export { BasicChart, echarts };
import _ from 'lodash';
// 坐标轴图表类型
export const AXIS_CHART_TYPES = ['line', 'bar', 'scatter', 'effectScatter'];
/**
* 推断图表类型是否为坐标轴类图表
*
* @param {Object} option 图表配置项
* @returns Boolean: true坐标轴类图表, false 非坐标轴类图表
*/
export const isAxisChart = (option) => {
const { series: origin } = option;
const series = Array.isArray(origin) ? origin : _.isObject(origin) ? [origin] : [];
return !series.some((item) => AXIS_CHART_TYPES.indexOf(item.type) === -1);
};
export const isLineType = (series) => series.type === 'line';
export const isBarType = (series) => series.type === 'bar';
import _ from 'lodash';
import * as echarts from 'echarts/core';
/**
* 16进制颜色值转rgba颜色值
*
* @param {string} hex 16进制颜色值
* @param {number} opacity 透明度,缺省默认为1
* @returns Rgba颜色值
*/
export const hexToRgba = (hex, opacity = 1) => {
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return `rgba(${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(
result[3],
16,
)}, ${opacity})`;
};
// 图表基础配色
export const DEF_COLORS = [
'#1685ff',
'#00b809',
'#ffa200',
'#00d0c7',
'#ff6b37',
'#986aff',
'#2f54eb',
'#ff9e69',
'#ffcb66',
'#99ceff',
];
// 图表基础配色对应的线性渐变色, 适用面积曲线图
export const DEF_GRAD_COLORS = DEF_COLORS.map((color) => {
return new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: hexToRgba(color, 0.2),
},
{
offset: 1,
color: hexToRgba(color, 0.02),
},
]);
});
/**
* 根据系列的序列下标获取对应的颜色
*
* @param {number} index 系列所在的下标
* @param {Array} colors 配色方案
* @returns 颜色值,可以是16进制, rgb, LinearGradient
*/
export const colorOfSeries = (index, colors = DEF_COLORS) => {
return colors[index % colors.length];
};
import _ from 'lodash';
import { DEF_COLORS, DEF_GRAD_COLORS, colorOfSeries } from './color';
import { isAxisChart, isLineType } from './chart';
import { buildDefaultTooltip } from './tooltip';
// 默认标题配置
export const deafultTitle = { show: false };
export const defaultGrid = {
right: 40,
left: 60,
};
export const defaultLegend = {
show: true,
right: 40,
icon: 'react',
itemWidth: 14,
itemHeight: 8,
itemGap: 20,
textStyle: {
padding: [0, 0, 0, 4],
color: '#2d2d2d',
},
};
export const defaultDataZoom = [
{
type: 'inside',
},
];
// 直角坐标系图表x轴默认配置(非直角坐标系图表不能有xAxis配置,会报错)
export const buildDefaultXAxis = (axis) => {
const cfg = {
show: true,
axisLine: {
show: true,
lineStyle: {
color: '#e2e2e2',
},
},
axisTick: {
show: true,
inside: true,
lineStyle: {
color: '#e2e2e2',
},
},
axisLabel: {
show: true,
color: '#5b5b5b',
hideOverlap: true,
},
splitLine: {
show: false,
lineStyle: {
color: '#e2e2e2',
},
},
};
if (axis.type === 'time') {
cfg.axisLabel.formatter = {
year: '{yyyy}',
month: '{MM}月',
day: '{MM}-{dd}',
hour: '{HH}:{mm}',
minute: '{HH}:{mm}',
second: '{HH}:{mm}:{ss}',
millisecond: '{hh}:{mm}:{ss} {SSS}',
none: '{yyyy}-{MM}-{dd} {hh}:{mm}:{ss} {SSS}',
};
}
return cfg;
};
export const defaultXAxis = {
show: true,
axisLine: {
show: true,
lineStyle: {
color: '#e2e2e2',
},
},
axisTick: {
show: true,
inside: true,
lineStyle: {
color: '#e2e2e2',
},
},
axisLabel: {
show: true,
color: '#5b5b5b',
hideOverlap: true,
},
splitLine: {
show: false,
lineStyle: {
color: '#e2e2e2',
},
},
};
export const defaultYAxis = {
show: true,
alignTicks: true,
nameGap: 25,
nameTextStyle: {
align: 'right',
padding: 0,
color: '#8e8e8e',
},
axisLine: {
show: false,
lineStyle: {
color: '#e2e2e2',
},
},
axisTick: {
show: false,
inside: true,
lineStyle: {
color: '#e2e2e2',
},
},
axisLabel: {
show: true,
color: '#8e8e8e',
},
splitLine: {
show: true,
lineStyle: {
color: '#e2e2e2',
type: 'dashed',
},
},
};
export const buildAxisPointer = (option) => {
if (!option || !option.xAxis) return null;
const { xAxis: x } = option;
const xAxis = Array.isArray(x) ? x : _.isObject(x) ? [x] : [];
const type = xAxis.some((item) => item.type === 'category') ? 'shadow' : 'line';
const typeConfig =
type === 'line'
? {
lineStyle: {
type: 'solid',
color: 'rgba(22,133,255,0.3)',
},
}
: {};
return {
z: 3,
type,
...typeConfig,
};
};
// 折线图: 圆形标记点
// 曲线图: 不加标记点
// 折线面积图: 圆形标记点、面积渐变色配置
// 曲线面积图: 不加标记点、面积渐变色配置
const seriesItem = (series, index) => {
const cfg = {};
if (isLineType(series)) {
// cfg.symbol = 'ciecle';
cfg.symbolSize = 4;
cfg.showSymbol = !series.smooth;
cfg.areaStyle = series.areaStyle && {
opacity: 1,
color: colorOfSeries(index, DEF_GRAD_COLORS),
};
}
return _.merge(cfg, series);
};
export const buildDefaultSeries = (option) => {
const { series } = option;
const serieArr = Array.isArray(series) ? series : !!series ? [series] : [];
const defaultSeries = serieArr.map(seriesItem);
return Array.isArray(series) ? defaultSeries : !!series ? defaultSeries[0] : null;
};
export const buildDefaultOption = (option) => {
const exports = {};
exports.color = DEF_COLORS;
exports.title = deafultTitle;
(option.dataZoom == undefined || option.dataZoom === null) &&
(exports.dataZoom = defaultDataZoom);
exports.tooltip = buildDefaultTooltip(option);
return exports;
};
export const buildSpecificOption = (option) => {
const { xAxis, yAxis, grid, legend } = option;
// 直角坐标系类图标
if (isAxisChart(option)) {
return _.merge(
{},
{
xAxis: Array.isArray(xAxis)
? xAxis.map((item) => ({ ...buildDefaultXAxis(item) }))
: buildDefaultXAxis(xAxis),
yAxis: Array.isArray(yAxis) ? yAxis.map(() => ({ ...defaultYAxis })) : defaultYAxis,
grid: Array.isArray(grid) ? grid.map(() => ({ ...defaultGrid })) : defaultGrid,
legend: Array.isArray(legend) ? legend.map(() => ({ ...defaultLegend })) : defaultLegend,
series: buildDefaultSeries(option),
tooltip: {
axisPointer: buildAxisPointer(option),
},
},
);
}
};
import _ from 'lodash';
import { buildDefaultOption, buildSpecificOption } from './default';
import * as color from './color';
const buildOption = (option) => {
const exports = {};
const defaultOption = buildDefaultOption(option);
const specificOption = buildSpecificOption(option);
return _.merge(exports, defaultOption, specificOption, option);
};
export { buildOption, color };
export default buildOption;
import _ from 'lodash';
const isArray = (arr) => Array.isArray(arr);
/**
* 转换echarts线性渐变方向 成 css渐变角度 css线性渐变方向为从上往下时为0deg。(0: 上 -> 下)。
* 根据echarts渐变方向计算出角度,以水平方向为基准顺时针旋转的角度。(0: 左 -> 右) 所以需要多旋转90度,转为css角度。
*
* @param {any} x1 Echarts.graphic.LinearGradient.x
* @param {any} y1 Echarts.graphic.LinearGradient.y
* @param {any} x2 Echarts.graphic.LinearGradient.x2
* @param {any} y2 Echarts.graphic.LinearGradient.y2
* @returns Deg角度
*/
const toCssAngle = (x1, y1, x2, y2) => {
const deltaX = x2 - x1;
const deltaY = y2 - y1;
return Math.atan2(deltaY, deltaX) * (180 / Math.PI) + 90;
};
/**
* 获取系列真实值
*
* @param {Object} param Formatter 单个数据集
* @returns 系列真实值
*/
const valueAccessor = (param) => {
// 数据可能通过series配置,也可能通过dataset配置。
const { value, encode, dimensionNames } = param;
if (isArray(value)) {
return value[encode.y[0]];
} else if (_.isObject(value)) {
return value[dimensionNames[encode.y[0]]];
} else {
return value;
}
};
/**
* 获取系列单位
*
* @param {Object} option 图表配置项
* @param {Object} param Formatter 单个数据集
* @returns 系列单位
*/
const unitAccessor = (option, param) => {
const { yAxis, series } = option;
const { seriesIndex } = param;
// 规范 yAxis 和 series 都为数组, 方便通过下标访问
const axisArr = isArray(yAxis) ? yAxis : !!yAxis ? [yAxis] : [];
const serieArr = isArray(series) ? series : !!series ? [series] : [];
const serie = serieArr[seriesIndex];
// 构造单位
let unit = '';
if (serie && serie.showUnit !== false) {
unit =
serie.unit !== null && serie.unit !== undefined
? serie.unit
: _.get(axisArr, [_.get(serie, 'yAxisIndex', 0), 'name'], ''); // 根据serie去yAxis配置找name属性当做单位
}
return unit;
};
/**
* 获取可渐变背景颜色值
*
* @param {Object} param Formatter 单个数据集
* @returns 系列背景颜色
*/
const bgcolorAccessor = (param) => {
if (!param || !param.color) return null;
const { color } = param;
if (color.type === 'linear') {
const { x, y, x2, y2, colorStops } = color;
const angle = toCssAngle(x, y, x2, y2);
const hint = colorStops.map((item) => {
return `${item.color} ${item.offset * 100}%`;
});
return `linear-gradient(${angle}deg, ${hint.join(',')})`;
} else if (color.type === 'radial') {
const { x, y, r, colorStops } = color;
const hint = colorStops.map((item) => {
return `${item.color} ${item.offset * 100}%`;
});
return `radial-gradient(circle farthest-side at ${x} ${y}, ${hint.join(',')})`;
} else {
return color;
}
};
/**
* 获取文字颜色值
*
* @param {Object} param Formatter 单个数据集
* @returns 系列文字颜色
*/
const colorAccessor = (param) => {
if (!param || !param.color) return null;
const { color } = param;
if (color.type === 'linear' || color.type === 'radial') {
const { colorStops } = color;
return colorStops[0].color;
} else {
return color;
}
};
// const xlabelAccessor = (option, param) => {
// const { xAxis, series } = option;
// const { name, seriesIndex, axisValueLabel } = param;
// const axisArr = isArray(xAxis) ? xAxis : !!xAxis ? [xAxis] : [];
// const serieArr = isArray(series) ? series : !!series ? [series] : [];
// const serie = serieArr[seriesIndex];
// const type = _.get(axisArr, [_.get(serie, 'xAxisIndex', 0), 'type'], 'category');
// // const formatter = _.get(axisArr, [_.get(serie, 'xAxisIndex', 0), 'axisLabel', 'formatter'], '{yyyy}-{MM}-{dd}');
// if (type === 'time') {
// return axisValueLabel
// } else {
// return name;
// }
// };
/**
* Tooltip头部样式内容
*
* @param {Object} param Formatter 单个数据集
* @returns
*/
const headTemplate = (option, param) => {
if (!param) return '';
const { name, axisValueLabel } = param;
return `<div style="border-bottom: 1px solid #F0F0F0; color: #808080; margin-bottom: 5px; padding-bottom: 5px;">${
name || axisValueLabel
}</div>`;
};
/**
* Tooltip每个系列样式内容
*
* @param {Object} option 图表配置项
* @param {Object} param Formatter 单个数据集
* @returns
*/
const seriesTemplate = (option, param) => {
if (!param) return '';
const value = valueAccessor(param);
const unit = unitAccessor(option, param);
const bgcolor = bgcolorAccessor(param);
const color = colorAccessor(param);
if (value === void 0 || value === null) return '';
return `
<div>
<span style="display:inline-block;margin: 0 7px 2px 0;border-radius:5px;width:5px;height:5px;background:${bgcolor}"></span>
<span>${param.seriesName}</span><span style="display:inline-block;">:</span><span style="color:${color};margin-left:10px">${value}</span>
<span>${unit}</span>
</div>
`;
};
// 默认tooltip配置
export const buildDefaultTooltip = (option) => {
const cfg = {
show: true,
padding: [5, 10],
trigger: 'axis', // 一般柱状图/曲线图都用axis,饼图/散点图等无类目的用item
triggerOn: 'mousemove|click',
renderMode: 'html',
confine: true, // *是否限制在图表区域内
appendToBody: false, // *不挂到body标签下,挂载到图表容器节点
formatter: (params) => {
let tooltipHeader = '',
tooltipContent = '';
if (isArray(params)) {
tooltipHeader = headTemplate(option, params[0]);
params.forEach((param) => {
tooltipContent += seriesTemplate(option, param);
});
} else {
tooltipHeader = headTemplate(option, params);
tooltipContent += seriesTemplate(option, params);
}
return `
<div>
${tooltipHeader}
<div>${tooltipContent}</div>
</div>
`;
},
};
return cfg;
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment