zz-chart
Version:
Alauda Chart components by Alauda Frontend Team
156 lines • 6.51 kB
JavaScript
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