/** @format */ import * as d3 from 'd3'; import React, { useEffect, useRef, useState } from 'react'; interface D3BarChartProps { size?: Record; [key: string]: any; showBackground?: boolean; backgroundStyle?: string; } const D3BarChart = (props: D3BarChartProps) => { const { size = { height: 500 }, data = { xData: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], yData: [ [10, 52, 200, 334, 400, 390, 390], [5, 80, 280, 304, 90, 400, 400], ], }, option = {}, } = props; // 空数据返回null if ( JSON.stringify(data) === '{}' || (data.xData.length === 0 && data.yData.length === 0) ) { return null; } const firstUpdate = useRef(true); const margin = option.grid; const WIDTH = size.width; const HEIGHT = size.height; const chartWidth = WIDTH - margin.left - margin.right; const chartHeight = HEIGHT - margin.top - margin.bottom; const svgRef = useRef(null); const sp = d3.schemeSet2; // x轴标尺 const xScale = d3 .scaleBand() .domain(data.xData) .range([0, chartWidth]) .paddingInner(option.series[0].barCategoryGap) .paddingOuter(0.4) .round(true); const bandwidth = xScale.bandwidth(); useEffect(() => { if (firstUpdate.current) { firstUpdate.current = false; onDraw(true); } else { onDraw(false); } return () => { d3.select(svgRef.current) .selectAll('g') .remove(); }; }, [size.height, size.width, option]); // 鼠标悬浮阴影效果 useEffect(() => { const rect = d3.selectAll('.bar'); rect.on('mouseover', (item: any) => { d3.select(svgRef.current) .append('rect') .attr('class', 'divergence') .attr('x', function() { return ( xScale(data.xData[item.path[1].attributes[1].value]) + -0.375 * bandwidth + margin.left // x轴对应的位置 ); }) // x轴对应的位置 .attr('y', margin.top) // y轴对应的位置 .attr('width', bandwidth * 2) .attr('height', chartHeight) .attr('fill', 'url(#shadow)') .on('mouseout', () => { d3.selectAll('.divergence').remove(); }); }); return () => { rect.on('mouseover', null); }; }, [size, option, margin]); function onDraw(isAnimate: boolean) { const svg = d3.select(svgRef.current); // x轴 const xAxis = d3 .axisBottom() .scale(xScale) .ticks(data.xData.length) // 分段 .tickPadding(30); //间距 // y轴标尺 const yScale = d3 .scaleLinear() .domain([ 0, d3.max(data.yData.map((d: any) => d.map((x: any) => x)).flat()), ]) // 多维数组转一维 .range([chartHeight, 0]) .nice(); // y轴 const yAxis = d3 .axisLeft() .scale(yScale) .ticks(data.yData[0].length) .tickPadding(30); //间距 if (option.yAxis.show) { const gy = svg .append('g') .call(yAxis) .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); gy.selectAll('text') .attr('x', -8) .attr('color', option.yAxis.axisLabel.color) .style('font-size', option.yAxis.axisLabel.fontSize) // 更改坐标轴字体大小 .style('font-family', option.yAxis.axisLabel.fontFamily) .style('font-weight', option.yAxis.axisLabel.fontWeight); gy.selectAll('line').attr('stroke', ' '); gy.selectAll('path').attr('stroke', ''); if (option.yAxis.axisLine.show) { gy.append('line') // 新增x轴线 .attr('x1', 0) .attr('y1', 0) .attr('x2', 0) .attr('y2', chartHeight) .attr('stroke', option.yAxis.axisLine.lineStyle.color); // 更改坐标轴颜色 } if (option.yAxis.splitLine && option.yAxis.splitLine.show) { // 添加指标线 gy.selectAll('.tick') .append('line') .attr('x1', 2) .attr('y1', 0) .attr('x2', function(d: any) { if (d > 0) { return chartWidth; } else { return 2; } }) .attr('y2', 0) .attr('stroke-dasharray', '4,4') .attr('stroke', option.yAxis.splitLine.lineStyle.color); } } // 柱子 data.yData.forEach((item: any, index: number) => { // 柱子颜色 let rbg; if (option.series[index].itemStyle.colorLinear) { // 是否渐变 rbg = svg .append('g') .append('linearGradient') .attr('id', `linearZebra${index}`) .attr('x1', 0) .attr('x2', '100%') .attr('y1', 0) .attr('y2', 0); rbg .append('stop') .attr('offset', '0%') .attr('stop-color', option.series[index].itemStyle.color1); rbg .append('stop') .attr('offset', '100%') .attr('stop-color', option.series[index].itemStyle.color2); } const bar = svg .selectAll('.bar' + index) .data(item) .enter() .append('g') .attr('class', 'bar') .attr('index', function(d: any, i: number) { return i; }) .attr('stroke-width', (bandwidth / data.yData.length) * 0.4) .attr('stroke', 'transparent'); // 实柱阴影 bar .append('g') .append('pattern') .attr('id', function(d: any) { return 'rect' + index + '_' + d; }) .attr('patternUnits', 'userSpaceOnUse') .attr('width', bandwidth / data.yData.length) .attr('height', 6) .attr('x', function(d: any, i: any) { const _d = (bandwidth / data.yData.length) * index * 1.5; return xScale(data.xData[i]) + _d + margin.left; }) .append('rect') .attr('width', bandwidth / data.yData.length) .attr('height', 3) .attr('ry', 1) .attr('fill', function() { if (option.series[index].itemStyle.colorLinear) { return 'url(#linearZebra' + index + ')'; } else { return option.series[index].itemStyle.color; } }); // 全柱阴影 bar .append('g') .append('pattern') .attr('id', function(d: any) { return 'rectX' + index + '_' + d; }) .attr('patternUnits', 'userSpaceOnUse') .attr('width', bandwidth / data.yData.length) .attr('height', 6) .attr('x', function(d: any, i: any) { const _d = (bandwidth / data.yData.length) * index * 1.5; return xScale(data.xData[i]) + _d + margin.left; }) .append('rect') .attr('width', bandwidth / data.yData.length) .attr('height', 3) .attr('ry', 1) .attr('fill', option.series[index].backgroundStyle.color); // 柱 if (isAnimate) { bar .append('rect') .attr('x', function(d: any, i: any) { const _d = (bandwidth / data.yData.length) * index * 1.5; return xScale(data.xData[i]) + _d + margin.left; }) .attr('y', function() { const min = yScale.domain()[0]; return yScale(min) + margin.top; }) .attr('width', bandwidth / data.yData.length) .attr('height', function() { return 0; }) .transition() .duration(1000) .ease(d3.easePolyInOut) .attr('height', function(d: any) { return chartHeight - yScale(d); }) .attr('y', function(d: any) { return yScale(d) + margin.top; }) .attr('fill', function(d: any) { return 'url(#rect' + index + '_' + d + ')'; }); } else { bar .append('rect') .attr('x', function(d: any, i: any) { const _d = (bandwidth / data.yData.length) * index * 1.5; return xScale(data.xData[i]) + _d + margin.left; }) .attr('width', bandwidth / data.yData.length) .attr('height', function(d: any) { return chartHeight - yScale(d); }) .attr('y', function(d: any) { return yScale(d) + margin.top; }) .attr('fill', function(d: any) { return 'url(#rect' + index + '_' + d + ')'; }); } // 全柱 bar .append('rect') .attr('x', function(d: any, i: any) { const _d = (bandwidth / data.yData.length) * index * 1.5; return xScale(data.xData[i]) + _d + margin.left; }) .attr('y', margin.top) .attr('width', bandwidth / data.yData.length) .attr('height', chartHeight) .attr('fill', function(d: AnimationPlaybackEventInit) { return 'url(#rectX' + index + '_' + d + ')'; }); // 文字 // bar // .append('text') // .attr('dy', '.75em') // .attr('y', -10) // .attr('x', bandwidth / (value.yData.length * 2)) // .attr('text-anchor', 'middle') // .attr('font-size', '8px') // .attr('fill', 'White') // .text(function(d) { // return d; // }); // 图例 if (option.legend.show) { const legend = svg.append('g'); const line = legend .append('rect') .attr('x', 0) .attr('y', 0) .attr('width', 10) .attr('height', 4) .attr('fill', function() { if (option.series[index].itemStyle.colorLinear) { return 'url(#linearZebra' + index + ')'; } else { return option.series[index].itemStyle.color; } }); const text = legend .append('text') .attr('class', 'legend-label') .style('text-anchor', 'start') .text(option.series[index].name) .attr('fill', option.legend.textStyle.color) .style('font-size', option.legend.textStyle.fontSize) .attr('transform', 'translate(' + 20 + ',' + 5 + ')'); legend.attr( 'transform', 'translate(' + (WIDTH - margin.left * 4 + index * 70) + ',' + margin.top / 2 + ')', ); } }); // X轴线 if (option.xAxis.show) { const gx = svg .append('g') .call(xAxis) .attr( 'transform', 'translate(' + margin.left + ',' + (HEIGHT - margin.bottom) + ')', ) .style('color', option.xAxis.axisLine.lineStyle.color) .attr('dx', '50px'); // 更改坐标轴颜色 gx.selectAll('text') .attr('color', option.xAxis.axisLabel.color) .attr('font-weight', option.xAxis.axisLabel.fontWeight) .style('font-family', option.xAxis.axisLabel.fontFamily) .style('font-size', option.xAxis.axisLabel.fontSize) // 更改坐标轴字体大小 .attr('y', 12); gx.selectAll('line').attr('stroke', ' '); gx.selectAll('path').attr('stroke', ''); if (option.xAxis.axisLine.show) { gx.append('line') // 新增x轴线 .attr('x1', 0) .attr('y1', 0) .attr('x2', chartWidth) .attr('y2', 0) .attr('stroke', option.xAxis.axisLine.lineStyle.color); } if (option.xAxis.axisLabel.fontSize > 14) { margin.bottom = 30 + option.xAxis.axisLabel.fontSize - 10; } } } return ( ); }; export default D3BarChart;