我正在解决一个D3甜甜圈图,其图例位于右侧的问题。图例的文字不断被截断。它在容器外部可见,或者不在容器外部显示。无论哪种方式,它都不能容纳在容器中,即使我可以看到图例和甜甜圈图都是同一SVG的一部分。您可以在这张图片中看到我的意思:
我对使用D3非常陌生,但是我在这个问题上已经停留了一段时间。这不是我要修复的代码,但是在这里传递了生成SVG的选项:
const donutOptions: DonutChartOptions = {
showPercentageInDonut: false,
width: 500,
height: 260,
title: {text: '', textStyle: {fontSize: '14px'}},
margin: {top: 120, right: 10, bottom: 65, left: 100},
colors: [Theme.Emerald.hex, Theme.Lime.hex, Theme.Teal.hex, Theme.SkyBlue.hex,
Theme.Marigold.hex, Theme.Azure.hex, Theme.Red.hex, Theme.Orange.hex]
};
const legendTextWrapWidthEdge = 1440;
const donutEthnicityOptions: DonutChartOptionsExtended = {
showPercentageInDonut: false,
width: 470,
height: 260,
title: {text: '', textStyle: {fontSize: '14px'}},
margin: {top: 120, right: 10, bottom: 65, left: 85},
colors: [Theme.Emerald.hex, Theme.Lime.hex, Theme.Teal.hex, Theme.SkyBlue.hex,
Theme.Marigold.hex, Theme.Azure.hex, Theme.Red.hex, Theme.Orange.hex],
legend: {textStyle: {fontSize: '14px'}},
legendOptions: {
legendRectVSpace: 10,
legendPositionX: 110,
legendPositionY: 85,
legendPercentagePositionX: 46,
legendPercentagePositionY: 15,
legendTextPositionX: 20,
legendTextWidth: (!!this.browserScreenWidth && this.browserScreenWidth < legendTextWrapWidthEdge) ? 100 : 200
}
};
我尝试过尝试使用viewBox和preserveAspectRatio属性,但是显然我做的不正确。
这是使用上述选项实际创建图表的代码。如果可以避免的话,使用此代码是最后的选择。我将其视为仅用于上下文的黑匣子:
import { DataSet } from '../data-set';
import { DonutChartOptionsExtended, ILegendOptions } from '../interfaces/DonutChartOptionsExtended';
import { XYChartSettings } from '../interfaces/XYChartSettings';
import { DEFAULTS } from '../interfaces/DEFAULTS';
import { TextStyle } from '../interfaces/TextStyle';
import Defaults from 'lodash-es/defaults';
import { getColorScale, initializeSvg, wrapText } from '../d3-fns';
export class DonutChart {
options: any;
dataset: any;
draw(dataSet?: DataSet, options?: DonutChartOptionsExtended) {
Promise.all([
import(/* webpackChunkName: "d3" */ 'd3-shape'),
import(/* webpackChunkName: "d3" */ 'd3-interpolate'),
import(/* webpackChunkName: "d3" */ 'd3-selection'),
import(/* webpackChunkName: "d3" */ 'd3-scale'),
import(/* webpackChunkName: "d3" */ 'd3-transition')
]).then(([d3Shape, d3Interpolate, d3Select, d3Scale, trans]) => {
if (dataSet) {
this.dataset = dataSet;
}
if (options) {
this.options = options;
}
const pie = d3Shape.pie()
.value((d: any) => d.value)
.sort(null)
.padAngle(.03);
const width = this.options.width;
const outerRadius = (width - 300) / 2;
const innerRadius = outerRadius / 2;
const arc = d3Shape.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
const settings: any = new XYChartSettings(this.options);
const svg = initializeSvg(d3Select, settings);
const color = getColorScale(d3Scale, settings.colors);
const dataRows = this.dataset.dataRows;
const path = svg.selectAll('path')
.data(pie(dataRows as any))
.enter()
.append('path')
.attr('d', (d: any, i: number, groups: any[]) => arc(d))
.attr('fill', (d: any, i: number, groups: any[]) => String(color(String(d.data.label))));
if (options && options.showPieAnimation) {
path.transition()
.duration(1000)
.attrTween('d', function (d: any) {
const interpolate = d3Interpolate.interpolate({startAngle: 0, endAngle: 0}, d);
return function (t: any) {
return arc(interpolate(t));
};
});
}
const restOfTheData = (mydata: any) => {
try {
const legendOptions: ILegendOptions = this.options.legendOptions;
const legendRectSize = !!legendOptions && legendOptions.legendRectHeight ? legendOptions.legendRectHeight : 20;
const legendSpacing = !!legendOptions && legendOptions.legendRectVSpace ? legendOptions.legendRectVSpace : 7;
const legendHeight = legendRectSize + legendSpacing;
const positionx = !!legendOptions && legendOptions.legendPositionX ? legendOptions.legendPositionX : 115;
const positiony = !!legendOptions && legendOptions.legendPositionY ? legendOptions.legendPositionY : 65;
if (options && options.showPercentageInDonut) {
this.displayPercentageOnThePie(mydata, svg, pie, arc);
}
const defaultColor = getColorScale(d3Scale, settings.colors);
if (this.options.colors) {
this.displayPercentageNextToLegend(
mydata, svg, defaultColor, positionx,
positiony, legendHeight,
settings.legend.textStyle.fontSize || '14px'
);
this.displayLengend(
d3Select, mydata, svg, defaultColor, legendHeight,
positionx, positiony, legendRectSize,
settings.legend.textStyle.fontSize || '14px'
);
} else {
this.displayPercentageNextToLegendDefault(
mydata, svg, positionx, positiony, legendHeight,
settings.legend.textStyle.fontSize || '14px'
);
this.displayLengendDefault(
svg, defaultColor, legendHeight,
positionx, positiony, legendRectSize,
settings.legend.textStyle.fontSize || '14px'
);
}
this.displayTitle(svg, settings);
} catch (ex) {
console.log(ex);
}
};
setTimeout(restOfTheData(dataRows), 1000);
})
}
private displayPercentageOnThePie(mydata: any, svg: any, pie: any, arc: any) {
svg.selectAll('text')
.data(pie(mydata))
.enter()
.append('text')
.transition()
.duration(200)
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + arc.centroid(d) + ')')
.attr('dy', '.4em')
.attr('text-anchor', 'middle')
.text((d: any) => d.data.value + '%')
.style('fill', '#fff')
.style('font-size', '10px');
}
private displayPercentageNextToLegend(
mydata: any, svg: any, defaultColor: any, positionX: any,
positionY: any, legendHeight: any, fontSize: any) {
svg.selectAll('.percentage')
.data(mydata)
.enter().append('g')
.attr('class', 'percentage')
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 40) +
',' + ((i * legendHeight) - positionY) + ')')
.append('text')
.style('fill', (d: any, i: number, groups: any[]) => defaultColor(i))
.style('text-anchor', 'end')
.style('font-size', fontSize)
.text((d: any) => d.value + '%');
}
private displayPercentageNextToLegendDefault(mydata: any, svg: any, positionX: any, positionY: any, legendHeight: any, fontSize: any) {
svg.selectAll('.percentage')
.data(mydata)
.enter().append('g')
.attr('class', 'percentage')
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 40) +
',' + ((i * legendHeight) - positionY) + ')')
.append('text')
.style('fill', '#000')
.style('text-anchor', 'end')
.style('font-size', fontSize)
.text((d: any) => d.value + '%');
}
private displayLengend(d3Select: any, mydata: any, svg: any, defaultColor: any, legendHeight: any,
positionX: any, positionY: any, legendRectSize: any, fontSize: any) {
const legendOptions: ILegendOptions = this.options.legendOptions;
const legendRectWidth = !!legendOptions && legendOptions.legendRectWidth ? legendOptions.legendRectWidth : 10;
const percentageOffsetX = !!legendOptions && legendOptions.legendPercentagePositionX ? legendOptions.legendPercentagePositionX : 56;
const percentageOffsetY = !!legendOptions && legendOptions.legendPercentagePositionY ? legendOptions.legendPercentagePositionY : 15;
const textOffsetX = !!legendOptions && legendOptions.legendTextPositionX ? legendOptions.legendTextPositionX : 30;
const textOffsetY = !!legendOptions && legendOptions.legendTextPositionY ? legendOptions.legendTextPositionY : 15;
const textWidth = !!legendOptions && legendOptions.legendTextWidth ? legendOptions.legendTextWidth : 200;
const legend = svg.selectAll('.legend')
.data(mydata)
.enter()
.append('g')
.attr('class', 'legend')
// Just a calculation for x & y position
.attr('transform',
(d: any, i: number, groups: any[]) => `translate(${positionX
+ percentageOffsetX},${(i * legendHeight) - (positionY + percentageOffsetY)})`);
legend.append('rect')
.attr('width', legendRectWidth)
.attr('height', legendRectSize)
.attr('rx', 1)
.attr('ry', 1)
.style('fill', (d: any, i: number, groups: any[]) => defaultColor(i))
.style('stroke', (d: any, i: number, groups: any[]) => defaultColor(i));
legend.append('text')
.attr('x', textOffsetX)
.attr('y', textOffsetY)
.text((d: any) => d.label)
.style('fill', '#000')
.style('font-size', fontSize)
.call(wrapText, d3Select, textWidth);
}
private displayLengendDefault(svg: any, defaultColor: any, legendHeight: any,
positionX: any, positionY: any, legendRectSize: any, fontSize: any) {
const legendRectWidth = 10;
const legend = svg.selectAll('.legend')
.data(defaultColor.domain())
.enter()
.append('g')
.attr('class', 'legend')
// Just a calculation for x & y position
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 50) +
',' + ((i * legendHeight) - (positionY + 15)) + ')');
legend.append('rect')
.attr('width', legendRectWidth)
.attr('height', legendRectSize)
.attr('rx', 1)
.attr('ry', 1)
.style('fill', defaultColor)
.style('stroke', defaultColor);
legend.append('text')
.attr('x', 30)
.attr('y', 15)
.text((d: any) => d)
.style('fill', '#929DAF')
.style('font-size', fontSize);
}
private displayTitle(svg: any, settings: any) {
const textStyle = <TextStyle>Defaults(settings.title.textStyle || {}, DEFAULTS.textStyleTitle);
svg.append('text')
.attr('x', settings.widthInner / 2)
.attr('y', 0 - (settings.margin.top / 1.15 ))
.attr('text-anchor', 'middle')
.style('font-size', textStyle.fontSize)
.style('text-decoration', textStyle.textDecoration)
.text(settings.title.text);
}
}