类似页面如下:
屏幕截图.png
平时在各个页面中可能会遇到类似的饼状图显示,这里采用d3 (v4版) 绘制,就上面的饼状图可以抽象成一个通用的React组件,使用时只需要传入数据源即可,另外,更新操作也已经封装,直接更新传入的数据源即可。
- 将d3中的饼状图封装成一个ES6的类
//饼状图类
export default class PieChart {
constructor(
wrapper,
dataSource,
color,
width = wrapper.offsetWidth,
height = wrapper.offsetHeight
) {
this.wrapper = wrapper; //容器元素
this.dataSource = this.handleDataBefore(dataSource);
this.width = width;
this.height = height;
this.radius = Math.min(this.width, this.height) / 2;
this.color = d3
.scaleOrdinal()
.domain(d3.range(this.handleColorBefore(color).length))
.range(this.handleColorBefore(color));
this.initChart();
}
// 添加默认显示
handleDataBefore(data) {
const total = data.reduce((total, current) => (total + current.count), 0)
if (total === 0) {
data[data.length] = { region: 'default', count: 1 };
} else {
data[data.length] = { region: 'default', count: 0 };
}
return data;
}
// 添加默认颜色显示
handleColorBefore(color) {
color[color.length] = '#B6B9C2';
return color;
}
initChart() {
const pie = d3
.pie()
.value(function (d) {
return d.count;
})
.padAngle(0.02)
.sort(null);
const arc = d3
.arc()
.innerRadius(this.radius * 0.85)
.outerRadius(this.radius)
.cornerRadius(5);
const svg = d3
.select(this.wrapper)
.append('svg')
.attr('width', this.width)
.attr('height', this.height)
.attr('viewBox', '0 0 ' + this.width + ' ' + this.height) //当宽高改变时,图表响应式
.attr('style', 'width:100%')
.append('g')
.attr(
'transform',
'translate(' + this.width / 2 + ',' + this.height / 2 + ')'
);
this.pie = pie;
this.arc = arc;
this.svg = svg;
this.drawChart(this.dataSource);
}
drawChart(data) {
const color = this.color;
const pie = this.pie;
const arc = this.arc;
const svg = this.svg;
let path = svg.selectAll('path');
const data0 = path.data();
const data1 = pie(data);
path = path.data(data1, key);
path
.transition()
.duration(800)
.attrTween('d', arcTween);
path
.enter()
.append('path')
.each(function (d, i) {
var narc = findNeighborArc(i, data0, data1, key);
if (narc) {
this._current = narc;
this._previous = narc;
} else {
this._current = d;
}
})
.attr('fill', (d, i) => {
return color(i);
})
.transition()
.duration(600)
.attrTween('d', arcTween);
path
.exit()
.transition()
.duration(300)
// .delay(2000)
.attrTween('d', function (d, index) {
var currentIndex = this._previous.data.region;
var i = d3.interpolateObject(d, this._previous);
return function (t) {
return arc(i(t));
};
})
.remove();
function key(d) {
return d.data.region;
}
function findNeighborArc(i, data0, data1, key) {
var d;
if ((d = findPreceding(i, data0, data1, key))) {
const obj = cloneObj(d);
obj.startAngle = d.endAngle;
return obj;
} else if ((d = findFollowing(i, data0, data1, key))) {
const obj = cloneObj(d);
obj.endAngle = d.startAngle;
return obj;
}
return null;
}
// Find the element in data0 that joins the highest preceding element in data1.
function findPreceding(i, data0, data1, key) {
const m = data0.length;
while (--i >= 0) {
var k = key(data1[i]);
for (var j = 0; j < m; ++j) {
if (key(data0[j]) === k) return data0[j];
}
}
}
// Find the element in data0 that joins the lowest following element in data1.
function findFollowing(i, data0, data1, key) {
const n = data1.length,
m = data0.length;
while (++i < n) {
const k = key(data1[i]);
for (var j = 0; j < m; ++j) {
if (key(data0[j]) === k) return data0[j];
}
}
}
function arcTween(d) {
const i = d3.interpolate(this._current, d);
this._current = i(0);
return function (t) {
return arc(i(t));
};
}
function cloneObj(obj) {
const o = {};
for (var i in obj) {
o[i] = obj[i];
}
return o;
}
}
updateChart(data) {
this.drawChart(this.handleDataBefore(data));
}
destory() {
d3.select(this.wrapper).remove(); //移除图表元素
}
}
- 封装为React通用组件
import React from 'react';
import Chart from './pie.js';
import PropTypes from 'prop-types';
export default class PieChart extends React.PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
dataSource: PropTypes.array.isRequired,
color: PropTypes.array.isRequired,
width: PropTypes.number,
height: PropTypes.number
}
constructor(props) {
super(props);
this.chartRef = true;
}
componentWillUnmount() {
this.destoryChart();
this.chartRef = false;
}
componentWillReceiveProps(nextProps) {
if (nextProps.dataSource !== this.props.dataSource) {
this.updateChart(nextProps);
}
}
drawChart = node => {
if (this.chartRef) {
const { dataSource, color } = this.props;
this.chart = new Chart(node, dataSource, color);
}
};
updateChart = props => {
this.chart.updateChart(props.dataSource);
};
destoryChart = () => {
this.chart.destory();
this.chart = null;
};
render() {
const { id, width = 135, height = 120 } = this.props;
return <div id={id} style={{ width, height }} ref={this.drawChart} />;
}
}
- 使用
import React from 'react';
import PieChart from 'components/PieChart'
export default class Demo extends React.Component{
state={
inline:380,
outline:120
}
render(){
const {inline,outline}=this.state;
const dataSource = [
{ region: 'inline', count: inline },
{ region: 'outline', count: outline }
]
const color=['#fa919c', '#f83d59'];
return(
<div>
<PieChart id='device' dataSource={dataSource} color={color} />
</div>
)
}
}
github地址:https://github.com/Hfimy/component/tree/master/components/PieChart
网友评论