转换宽度为负值的条形图
Transitioning a bar chart with negative values for the width
我正在使用 d3 创建水平条形图。我在启动时使用动画 "grow" 图表。这是代码。
// Create the svg element
d3.select("#chart-area")
.append("svg")
.attr("height", 800)
.attr("width", 800);
.data(dataValues) // This data is previously prepared
.enter().append("rect")
.style("fill", "blue")
.attr("x", function () { return xScale(0); }) // xScale is defined earlier
.attr("y", function (d) { return yScale(d); }) // yScale is defined earlier
.attr("height", yScale.bandwidth()) // yScale is defined earlier
// Initial value of "width" (before animation)
.attr("width", 0)
// Start of animation transition
.transition()
.duration(5000) // 5 seconds
.ease (d3.easeLinear);
// Final value of "width" (after animation)
.attr("width", function(d) { return Math.abs(xScale(d) - xScale(0)); })
上面的代码可以正常工作,并且线条会按预期增长,在 5 秒内从 0 到任意宽度。
现在,如果我们将缓动线更改为以下内容
// This line changed
.ease (d3.easeElasticIn);
然后,在达到最终正值之前,ease 会尝试将宽度变为负值。 As you can see here, d3.easeElasticIn
returns 随着时间的推移负值,然后回到正值,导致动画中某些点的宽度为负值。因此条形图无法正确呈现(因为 SVG 规范规定如果宽度为负,则使用 0)
我尝试了所有解决方案来让条形负增长然后退出。但找不到任何。我该如何解决这个问题?
谢谢。
如您所知,在您的特定代码中使用 d3.easeElasticIn
将为矩形的宽度创建负值,这是不允许的。
这个基本演示重现了这个问题,控制台(浏览器的控制台,而不是代码段的控制台)填充了错误消息,如下所示:
Error: Invalid negative value for attribute width="-85.90933910798789"
看看:
const svg = d3.select("svg");
const margin = 50;
const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")
const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});
const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);
const xScale = d3.scaleLinear()
.range([margin, 300]);
const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attr("width", function(d) {
return xScale(d.x) - margin
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
那么,解决方案是什么?
其中之一是在生成负值时捕获这些负值,然后将矩形向左移动(使用 x
属性)并将这些负数转换为正数。
为此,我们必须在过渡选择中使用 attrTween 而不是 attr
。
像这样:
.attrTween("width", function(d) {
return function(t){
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t){
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})
在上面的代码片段中,margin
只是我创建的边距,因此您可以看到轴左侧的条形。
这是演示:
const svg = d3.select("svg");
const margin = 100;
const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")
const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});
const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);
const xScale = d3.scaleLinear()
.range([0, 300 - margin]);
const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attrTween("width", function(d) {
return function(t) {
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t) {
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
我正在使用 d3 创建水平条形图。我在启动时使用动画 "grow" 图表。这是代码。
// Create the svg element
d3.select("#chart-area")
.append("svg")
.attr("height", 800)
.attr("width", 800);
.data(dataValues) // This data is previously prepared
.enter().append("rect")
.style("fill", "blue")
.attr("x", function () { return xScale(0); }) // xScale is defined earlier
.attr("y", function (d) { return yScale(d); }) // yScale is defined earlier
.attr("height", yScale.bandwidth()) // yScale is defined earlier
// Initial value of "width" (before animation)
.attr("width", 0)
// Start of animation transition
.transition()
.duration(5000) // 5 seconds
.ease (d3.easeLinear);
// Final value of "width" (after animation)
.attr("width", function(d) { return Math.abs(xScale(d) - xScale(0)); })
上面的代码可以正常工作,并且线条会按预期增长,在 5 秒内从 0 到任意宽度。
现在,如果我们将缓动线更改为以下内容
// This line changed
.ease (d3.easeElasticIn);
然后,在达到最终正值之前,ease 会尝试将宽度变为负值。 As you can see here, d3.easeElasticIn
returns 随着时间的推移负值,然后回到正值,导致动画中某些点的宽度为负值。因此条形图无法正确呈现(因为 SVG 规范规定如果宽度为负,则使用 0)
我尝试了所有解决方案来让条形负增长然后退出。但找不到任何。我该如何解决这个问题?
谢谢。
如您所知,在您的特定代码中使用 d3.easeElasticIn
将为矩形的宽度创建负值,这是不允许的。
这个基本演示重现了这个问题,控制台(浏览器的控制台,而不是代码段的控制台)填充了错误消息,如下所示:
Error: Invalid negative value for attribute width="-85.90933910798789"
看看:
const svg = d3.select("svg");
const margin = 50;
const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")
const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});
const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);
const xScale = d3.scaleLinear()
.range([margin, 300]);
const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attr("width", function(d) {
return xScale(d.x) - margin
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
那么,解决方案是什么?
其中之一是在生成负值时捕获这些负值,然后将矩形向左移动(使用 x
属性)并将这些负数转换为正数。
为此,我们必须在过渡选择中使用 attrTween 而不是 attr
。
像这样:
.attrTween("width", function(d) {
return function(t){
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t){
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})
在上面的代码片段中,margin
只是我创建的边距,因此您可以看到轴左侧的条形。
这是演示:
const svg = d3.select("svg");
const margin = 100;
const line = svg.append("line")
.attr("x1", margin)
.attr("x2", margin)
.attr("y1", 0)
.attr("y2", 150)
.style("stroke", "black")
const data = d3.range(10).map(function(d) {
return {
y: "bar" + d,
x: Math.random()
}
});
const yScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.y
}))
.range([0, 150])
.padding(0.2);
const xScale = d3.scaleLinear()
.range([0, 300 - margin]);
const bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", margin)
.attr("width", 0)
.style("fill", "steelblue")
.attr("y", function(d) {
return yScale(d.y)
})
.attr("height", yScale.bandwidth())
.transition()
.duration(2000)
.ease(d3.easeElasticIn)
.attrTween("width", function(d) {
return function(t) {
return Math.abs(xScale(d.x) * t);
};
})
.attrTween("x", function(d) {
return function(t) {
return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
};
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>