我正在尝试制作一个圆形水平图表 - 但代码中存在一个问题,当条形的直边接近 100% 时,其边界半径会发生扭曲。我还注意到它在 1% 和 4% 的低数字下也破裂了。
如何稳定代码以确保条形图部分被剪裁,但永远不会破坏容器
<html>
<head>
<title>multibar d3</title>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<style>
</style>
</head>
<body>
<script>
d3.select("body")
.selectAll('svg')
.remove();
var data = [{"label": "Toblerone", "value": 97}];
var w = parseInt(280, 10),
h = parseInt(120, 10);
var color = d3.scaleOrdinal().range(['#046395', '#d62828', '#f77f00', '#fcbf49', '#eae2b7']);
data.forEach(function(d) {
d.total = +d.value;
});
var margin = {top: 20, right: 10, bottom: 20, left: 20};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
x.domain([0, 100])
y.domain(data.map(function(d) { return d.total; }));
var svg = d3.select("body")
.append("svg")
.style('background','#5902f6')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr('class', 'barchart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var border =4;
var bars = svg.append('g').attr('class', 'bars');
bars.selectAll(".bg")
.data(data)
.enter().append("rect")
.attr("class", "bg")
.attr('rx',()=>{
const barHeight = y.bandwidth();
return barHeight/2
})
.attr('ry',()=>{
const barHeight = y.bandwidth();
return barHeight/2
})
.attr('fill','#5902f6')
.attr('stroke','white')
.attr('stroke-width',border-1)
.attr("width",x(100))
.attr("y", function(d) { return y(d.value); })
.attr("height", y.bandwidth());
;
bars.selectAll(".bar")
.data(data)
.enter()
.append("path")
.attr("class", "bar")
.attr("fill", "#2cc2d3")
.attr('stroke','white')
.attr('stroke-width',3.2)
.attr("d", function(d) {
const barWidth = x(d.total);
const barHeight = y.bandwidth();
const leftCornerRadius = barHeight/2;
let cornerRadius = barHeight/2;
if (d.total >= (100 - barHeight/5)) {
cornerRadius = (d.total - (100 - barHeight/5)) * 2.5;
return `
M ${leftCornerRadius} 0
H ${barWidth - cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 ${barWidth} ${cornerRadius}
V ${barHeight - cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 ${barWidth - cornerRadius} ${barHeight}
H ${leftCornerRadius}
A ${leftCornerRadius} ${leftCornerRadius} 0 0 1 0 ${barHeight - leftCornerRadius}
V ${leftCornerRadius}
A ${leftCornerRadius} ${leftCornerRadius} 0 0 1 ${leftCornerRadius} 0
Z
`;
} else {
return `
M ${cornerRadius} 0
H ${barWidth}
V ${barHeight}
H ${cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 0 ${barHeight - cornerRadius}
V ${cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 ${cornerRadius} 0
Z
`;
}
})
.attr("transform", function(d) {
return `translate(0, ${y(d.value)})`;
});
bars.selectAll(".label")
.data(data)
.enter().append("text")
.attr("class", "label")
.attr('text-anchor', function(d) {
return d.total <= 20 ? 'start' : 'end';
})
.attr("x", function(d) {
return d.total <= 20 ? x(d.total) + 20 : x(d.total) - 20;
})
.attr("y", function(d) {
return y(d.value) + y.bandwidth() / 2;
})
.attr("dy", 5)
.text(function(d) {
return d.label;
});
</script>
</body>
</html>
您遇到的条形图在低百分比时破裂和在高百分比时扭曲的问题是由于您计算路径中的角半径的方式造成的。
当总值接近100%时,角半径计算
cornerRadius = (d.total - (100 - barHeight/5)) * 2.5;
可能会导致负值或非常大的值,导致路径扭曲。同样,当总值接近 1% 时,角半径计算可能会得到非常小的值,从而导致路径断裂。
要解决此问题,您应该将拐角半径限制为最小值和最大值。您可以使用
Math.max
和 Math.min
函数来确保拐角半径永远不会小于某个值或大于另一个值。
<html>
<head>
<title>multibar d3</title>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<style>
</style>
</head>
<body>
<script>
d3.select("body")
.selectAll('svg')
.remove();
var data = [{"label": "Toblerone", "value": 97}];
var w = parseInt(280, 10),
h = parseInt(120, 10);
var color = d3.scaleOrdinal().range(['#046395', '#d62828', '#f77f00', '#fcbf49', '#eae2b7']);
data.forEach(function(d) {
d.total = +d.value;
});
var margin = {top: 20, right: 10, bottom: 20, left: 20};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
x.domain([0, 100])
y.domain(data.map(function(d) { return d.total; }));
var svg = d3.select("body")
.append("svg")
.style('background','#5902f6')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr('class', 'barchart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var border =4;
var bars = svg.append('g').attr('class', 'bars');
bars.selectAll(".bar")
.data(data)
.enter()
.append("path")
.attr("class", "bar")
.attr("fill", "#2cc2d3")
.attr('stroke','white')
.attr('stroke-width',3.2)
.attr("d", function(d) {
const barWidth = x(d.total);
const barHeight = y.bandwidth();
const leftCornerRadius = barHeight/2;
const minCornerRadius = barHeight / 5;
const maxCornerRadius = barHeight / 2;
let cornerRadius = barHeight/2;
if (d.total >= (100 - barHeight/5)) {
cornerRadius = Math.max(minCornerRadius, Math.min(maxCornerRadius, (d.total - (100 - barHeight/5)) * 2.5)); // notice this
return `
M ${leftCornerRadius} 0
H ${barWidth - cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 ${barWidth} ${cornerRadius}
V ${barHeight - cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 ${barWidth - cornerRadius} ${barHeight}
H ${leftCornerRadius}
A ${leftCornerRadius} ${leftCornerRadius} 0 0 1 0 ${barHeight - leftCornerRadius}
V ${leftCornerRadius}
A ${leftCornerRadius} ${leftCornerRadius} 0 0 1 ${leftCornerRadius} 0
Z
`;
} else {
return `
M ${cornerRadius} 0
H ${barWidth}
V ${barHeight}
H ${cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 0 ${barHeight - cornerRadius}
V ${cornerRadius}
A ${cornerRadius} ${cornerRadius} 0 0 1 ${cornerRadius} 0
Z
`;
}
})
.attr("transform", function(d) {
return `translate(0, ${y(d.value)})`;
});
bars.selectAll(".label")
.data(data)
.enter().append("text")
.attr("class", "label")
.attr('text-anchor', function(d) {
return d.total <= 20 ? 'start' : 'end';
})
.attr("x", function(d) {
return d.total <= 20 ? x(d.total) + 20 : x(d.total) - 20;
})
.attr("y", function(d) {
return y(d.value) + y.bandwidth() / 2;
})
.attr("dy", 5)
.text(function(d) {
return d.label;
});
</script>
</body>
</html>
在上面的代码片段中,
minCornerRadius
和maxCornerRadius
是拐角半径的最小值和最大值。您可以设置这些值以满足您的需要。例如,我将 minCornerRadius
设置为 barHeight/5
,将 maxCornerRadius
设置为 barHeight/2
,以确保角半径始终 至少是条形高度的一半,并且永远不会超过条形高度。