UNPKG

zz-chart

Version:

Alauda Chart components by Alauda Frontend Team

156 lines 6.51 kB
import { select } from 'd3'; import * as d3 from 'd3'; import { ChartEvent } from '../../types/index.js'; import { createSvg, generateName, getChartColor, PolarShapeType, } from '../../utils/index.js'; import { PolarShape } from './index.js'; import { get, isNumber } from 'lodash-es'; import { measureText } from '../../strategy/utils.js'; import { UPLOT_DEFAULT_OPTIONS } from '../../strategy/config.js'; /** * 堆叠 柱状图 */ export default class BarStacked extends PolarShape { constructor() { super(...arguments); this.type = PolarShapeType.BarStacked; this.data = this.getData(); } get nullData() { return this.data.every(d => !isNumber(d.value)); } get totalValue() { return this.data.reduce((prev, item) => prev + item.value, 0); } get colorVar() { return this.ctrl.getTheme().colorVar; } init() { // do nothing. } getTextsWidth(texts) { const widths = texts.map(text => measureText(text, 14).width); return Math.max(...widths); } render() { this.ctrl.container.style.display = 'flex'; this.ctrl.container.style.flexDirection = 'column'; this.option = get(this.ctrl.getOption(), this.type, {}); this.svgEl = this.svgEl || createSvg(select(this.ctrl.container)); this.container = this.container || this.svgEl.append('g'); this.categories = get(this.ctrl.getOption(), 'axis.x.categories', []) || []; const margin = { top: 30, right: 180, bottom: 30, left: this.getTextsWidth(this.categories), }; const { clientWidth, clientHeight } = this.ctrl.container; const legendRef = this.ctrl.components.get('legend'); this.ctrl.emit(ChartEvent.U_PLOT_READY); requestAnimationFrame(() => { const legendEl = this.ctrl.chartContainer.querySelector(`.${generateName('legend')}`); const position = get(this.ctrl.getOption().legend, 'position', ''); const chartWidth = clientWidth - margin.left - margin.right; const chartHeight = clientHeight - margin.bottom; this.container.attr('transform', `translate(${margin.left},${margin.top})`); const legendH = position.includes('bottom') ? legendEl?.clientHeight : 0; const headerH = this.ctrl.chartContainer.querySelector(`.${generateName('header')}`) ?.clientHeight || 0; const height = chartHeight - legendH - headerH; this.renderBar(chartWidth, height); // this.renderLabel(); this.ctrl.on(ChartEvent.LEGEND_ITEM_CLICK, () => { this.data = this.getData().filter(d => !legendRef.inactivatedSet.has(d.name)); this.renderBar(chartWidth, height); }); }); } renderBar(clientWidth, clientHeight) { this.legendRef = this.ctrl.components.get('legend'); this.yScale = d3 .scaleBand() .domain(this.categories) .range([0, clientHeight]) .padding(0.25); this.updateBar(clientWidth, clientHeight); } updateBar(width, height) { this.container.selectAll('*').remove(); const data = this.getData(); // 过滤掉隐藏的系列 const visibleSeries = data.filter(s => !this.legendRef.inactivatedSet.has(s.name)); const stackGroups = d3.group(visibleSeries, d => d.stack || d.name); const groupCount = stackGroups.size; const groupHeight = (this.yScale.bandwidth() / groupCount) * 0.8; // 给堆叠组之间留空隙 const groupPadding = (this.yScale.bandwidth() / groupCount) * 0.2; const maxValue = d3.max(this.categories, (_cat, i) => { let maxForCat = 0; for (let [_stackName, items] of stackGroups) { const sum = items.reduce((s, item) => s + item.values[i].y, 0); maxForCat = Math.max(maxForCat, sum); } return maxForCat; }) || 0; const x = d3.scaleLinear().domain([0, maxValue]).range([0, width]).nice(); // X 轴 this.container .append('g') .attr('class', 'x-grid') .attr('transform', `translate(0,${height})`) .call(d3.axisBottom(x).tickSize(-height)) .call(g => g .selectAll('line') .attr('stroke', this.ctrl.getTheme().yAxis.gridStroke) .attr('stroke-dasharray', '4,4')) .selectAll('text') .style('font', UPLOT_DEFAULT_OPTIONS.axes[0].font); let groupIndex = 0; for (let [stackName, items] of stackGroups) { const keys = items.map(s => s.name); const data = this.categories.map((cat, i) => { const obj = { category: cat }; items.forEach(s => { obj[s.name] = s.values[i].y; obj.color = s.color; }); return obj; }); const stack = d3.stack().keys(keys); const stackedSeries = stack(data); this.container .selectAll(`g.stack-${stackName}`) .data(stackedSeries) .join('g') .attr('fill', (d, index) => { return (items.find(item => item.name === d.key).color || getChartColor(index)); }) .selectAll('rect') .data(d => d) .join('rect') .attr('y', d => this.yScale(d.data.category) + groupIndex * (groupHeight + groupPadding)) .attr('x', d => x(d[0])) .attr('width', d => x(d[1]) - x(d[0])) .attr('height', groupHeight); groupIndex++; } // Y 轴 this.container .append('g') .call(d3.axisLeft(this.yScale)) .selectAll('text') .style('font', UPLOT_DEFAULT_OPTIONS.axes[1].font); this.container .selectAll('text') .style('fill', this.ctrl.getTheme().yAxis.stroke); this.container .selectAll('line') .style('stroke', this.ctrl.getTheme().yAxis.tickStroke); this.container .selectAll('.domain') .style('stroke', this.ctrl.getTheme().yAxis.gridStroke); } redraw() { } } //# sourceMappingURL=stacked-bar.js.map