堆叠条形图的 Y 比例域最小值/最小值不为零; X 轴溢出
Posted
技术标签:
【中文标题】堆叠条形图的 Y 比例域最小值/最小值不为零; X 轴溢出【英文标题】:Y Scale domain minimum / min NOT zero for stacked bar chart; overflowing X-Axis 【发布时间】:2021-09-29 04:00:40 【问题描述】:我对 D3 非常陌生,并试图弄清楚当域不是 [0,someMaxNumber] 时如何调整我的堆叠条形图。我尝试了一些不同的方法,但无济于事。
我试过了……
.attr("y", sequence => yScale(sequence[1] + yMin))
:这确实使事情变得正确,但现在我的第一个数据点不再从 100 开始,而是现在高于它。那是不正确的,所以我没有留在那里。
.attr("height", sequence => const [lower, upper] = sequence; return yScale(lower + yMin) - yScale(upper); )
:这解决了我之前的问题(事情没有从正确的 y 轴标记开始),但是使粉色和绿色层的高度不正确。
我还尝试了许多其他修复方法,但我就是无法让它发挥作用。我发誓这与rect
元素的高度有关。
如果您想知道,我的目标是让 Y 轴不从 0 开始,而是从某个 min
开始,例如最小 rect
高度值的 50%。任何帮助将不胜感激!
更新 将上面的第 2 点更改为以下代码可以解决我的问题,但我认为应该有更好的解决方案。请告诉我。
.attr("height", (sequence, other, otherother) =>
const [lower, upper] = sequence;
const firstBarAdjustment = lower === 0 ? yMin : 0;
return yScale(lower + firstBarAdjustment) - yScale(upper);
)
class D3StackedBarChart extends React.Component<Props, State>
state: State =
data: [
year: 1993, males: 100, females: 95, pets: 12,
year: 1994, males: 80, females: 88, pets: 8,
year: 1995, males: 111, females: 122, pets: 32,
year: 1996, males: 25, females: 25, pets: 64,
year: 1997, males: 13, females: 45, pets: 72,
],
;
componentDidMount()
const data = this.state;
const keys = ["males", "females", "pets"];
const colors =
males: "blue",
females: "pink",
pets: "green",
;
const width = 1000;
const height = 1000;
const margin = top: 80, right: 180, bottom: 80, left: 180;
// const margin = top: 0, right: 0, bottom: 0, left: 0;
const padding = 0.1;
const stackGenerator = d3.stack().keys(keys); // now a function
const layers = stackGenerator(data); // now a function
// Origin of an SVG is in the TOP LEFT corner
const svg = d3
.select("#test")
.append("svg") // append an svg element to our div#test
.attr("height", height - margin.top - margin.bottom)
.attr("width", width - margin.left - margin.right)
.attr("viewBox", [0, 0, width, height]);
// SCALE
const xScale = d3
.scaleBand()
.domain(data.map(d => d.year))
.range([margin.left, width - margin.right])
.padding(padding);
// looking at second value / y value
const extent = [
0.5 *
d3.min(layers, layer => d3.min(layer, sequence => sequence[1])),
1.1 *
d3.max(layers, layer => d3.max(layer, sequence => sequence[1])),
];
const [yMin, yMax] = extent;
const yScale = d3
.scaleLinear()
.domain(extent)
.range([height - margin.bottom, margin.top]); // range from bottom up
// AXIS
const xAxis = g =>
// bottom align it
g.attr("transform", `translate(0, $height - margin.bottom)`)
.call(d3.axisBottom(xScale))
.attr("font-size", "20px");
;
const yAxis = g =>
g.attr("transform", `translate($margin.left, 0)`)
.call(d3.axisLeft(yScale))
.attr("font-size", "20px");
;
// Create tooltip
const Tooltip = d3
.select("#test")
.append("div")
.style("opacity", 0)
.attr("class", css(styles.tooltip))
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px");
// Three function that change the tooltip when user hover / move / leave a cell
const mouseover = function(event, data)
Tooltip.style("opacity", 1);
d3.select(this)
.style("stroke", "black")
.style("opacity", 1);
;
const mousemove = function(event, data)
const 0: start, 1: end, data: d = data;
Tooltip.html(`The year: $d.year<br> The value: $end - start`)
.style("left", event.layerX + 3 + "px")
.style("top", event.layerY - 3 + "px");
;
const mouseleave = function(event, data)
Tooltip.style("opacity", 0);
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8);
;
// Creating Legend
const legend = svg
.append("g")
.attr("class", "legend")
.attr("transform", d => "translate(0, 0)")
.attr("font-size", "12px")
.attr("text-anchor", "start")
.selectAll("g")
.data(keys)
.join("g") // Create 3 "g" elements that are initially empty
.attr("transform", (d, i) => "translate(0," + i * 30 + ")");
// Add square and their color
legend
.append("rect") // append a rect to each individual g
.attr("fill", d => colors[d])
.attr("x", width - margin.right)
.attr("rx", 3)
.attr("width", 19)
.attr("height", 19);
// Add text next to squares
legend
.append("text")
.attr("x", width - margin.right + 40)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(d => d);
// Add header
const legendHeader = d3
.select(".legend")
.append("g")
.attr("transform", (d, i) => "translate(0, -20)")
.lower()
.append("text")
.attr("x", width - margin.right)
.attr("font-size", "12px")
.text(() =>
const text = "Master Levels";
return text.toLocaleUpperCase();
);
// Get coordinates and height of legend to add border
const
x: legendX,
y: legendY,
width: legendWidth,
height: legendHeight,
= d3
.select(".legend")
.node()
.getBBox();
const borderPadding = 20;
// Create border for legend
// Adding a "border" manually
const legendBox = svg
.select(".legend")
.append("rect")
.lower()
.attr("class", "legend-box")
.attr("x", legendX - borderPadding)
.attr("y", legendY - borderPadding)
.attr("width", legendWidth + borderPadding * 2)
.attr("height", legendHeight + borderPadding * 2)
.attr("fill", "white")
.attr("stroke", "black")
.attr("opacity", 0.8);
// Rendering
// first, second, and third refer to `layers`
// first --> layers
// second --> edge1, edge2, and data
svg.selectAll(".layer")
.data(layers) // first
.join("g") // create new element for each layer
.attr("class", "layer")
// .attr("class", css(styles.rectangle))
.attr("fill", layer => colors[layer.key])
.selectAll("rect")
.data(layer => layer) // second
.join("rect")
.attr("x", sequence => xScale(sequence.data.year))
.attr("y", sequence => yScale(sequence[1]))
.attr("width", xScale.bandwidth())
.attr("height", sequence =>
const [lower, upper] = sequence;
return yScale(lower) - yScale(upper);
)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
svg.node();
render(): React.Node
return (
<View>
<LabelLarge>i18n.doNotTranslate("D3.js")</LabelLarge>
<Strut size=Spacing.xLarge_32 />
<div id="test" />
</View>
);
const styles = StyleSheet.create(
tooltip:
position: "absolute",
,
rectangle:
":hover":
opacity: 0.66,
,
,
);
export default D3StackedBarChart;
【问题讨论】:
【参考方案1】:yScale
的domain
应在 0 到堆栈值的最大总和的范围内:
const maxStackValue = data.reduce((m, d) => Math.max(m, keys.reduce((s, k) => s + d[k], 0)), 0);
const yScale = d3
.scaleLinear()
.domain([0, maxStackValue])
.range([height - margin.bottom, margin.top]);
查看它在 sn-p 中的工作:
const data = [
year: 1993, males: 100, females: 95, pets: 12,
year: 1994, males: 80, females: 88, pets: 8,
year: 1995, males: 111, females: 122, pets: 32,
year: 1996, males: 25, females: 25, pets: 64,
year: 1997, males: 13, females: 45, pets: 72,
];
const keys = ["males", "females", "pets"];
const maxStackValue = data.reduce((m, d) => Math.max(m, keys.reduce((s, k) => s + d[k], 0)), 0);
const colors =
males: "blue",
females: "pink",
pets: "green",
;
const width = 1000;
const height = 1000;
const margin = top: 80, right: 180, bottom: 80, left: 180;
// const margin = top: 0, right: 0, bottom: 0, left: 0;
const padding = 0.1;
const stackGenerator = d3.stack().keys(keys); // now a function
const layers = stackGenerator(data); // now a function
// Origin of an SVG is in the TOP LEFT corner
const svg = d3
.select("#test")
.append("svg") // append an svg element to our div#test
.attr("height", height - margin.top - margin.bottom)
.attr("width", width - margin.left - margin.right)
.attr("viewBox", [0, 0, width, height]);
// SCALE
const xScale = d3
.scaleBand()
.domain(data.map(d => d.year))
.range([margin.left, width - margin.right])
.padding(padding);
const yScale = d3
.scaleLinear()
.domain([0, maxStackValue])
.range([height - margin.bottom, margin.top]); // range from bottom up
// AXIS
const xAxis = g =>
// bottom align it
g.attr("transform", `translate(0, $height - margin.bottom)`)
.call(d3.axisBottom(xScale))
.attr("font-size", "20px");
;
const yAxis = g =>
g.attr("transform", `translate($margin.left, 0)`)
.call(d3.axisLeft(yScale))
.attr("font-size", "20px");
;
const mousemove = function(event, data)
;
const mouseleave = function(event, data)
;
// Creating Legend
const legend = svg
.append("g")
.attr("class", "legend")
.attr("transform", d => "translate(0, 0)")
.attr("font-size", "12px")
.attr("text-anchor", "start")
.selectAll("g")
.data(keys)
.join("g") // Create 3 "g" elements that are initially empty
.attr("transform", (d, i) => "translate(0," + i * 30 + ")");
// Add square and their color
legend
.append("rect") // append a rect to each individual g
.attr("fill", d => colors[d])
.attr("x", width - margin.right)
.attr("rx", 3)
.attr("width", 19)
.attr("height", 19);
// Add text next to squares
legend
.append("text")
.attr("x", width - margin.right + 40)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(d => d);
// Add header
const legendHeader = d3
.select(".legend")
.append("g")
.attr("transform", (d, i) => "translate(0, -20)")
.lower()
.append("text")
.attr("x", width - margin.right)
.attr("font-size", "12px")
.text(() =>
const text = "Master Levels";
return text.toLocaleUpperCase();
);
// Get coordinates and height of legend to add border
const
x: legendX,
y: legendY,
width: legendWidth,
height: legendHeight,
= d3
.select(".legend")
.node()
.getBBox();
const borderPadding = 20;
// Create border for legend
// Adding a "border" manually
const legendBox = svg
.select(".legend")
.append("rect")
.lower()
.attr("class", "legend-box")
.attr("x", legendX - borderPadding)
.attr("y", legendY - borderPadding)
.attr("width", legendWidth + borderPadding * 2)
.attr("height", legendHeight + borderPadding * 2)
.attr("fill", "white")
.attr("stroke", "black")
.attr("opacity", 0.8);
// Rendering
// first, second, and third refer to `layers`
// first --> layers
// second --> edge1, edge2, and data
svg.selectAll(".layer")
.data(layers) // first
.join("g") // create new element for each layer
.attr("class", "layer")
.attr("fill", layer => colors[layer.key])
.selectAll("rect")
.data(layer => layer) // second
.join("rect")
.attr("x", sequence => xScale(sequence.data.year))
.attr("y", sequence => yScale(sequence[1]))
.attr("width", xScale.bandwidth())
.attr("height", sequence =>
const [lower, upper] = sequence;
return (yScale(lower) - yScale(upper));
)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<div id="test" />
【讨论】:
以上是关于堆叠条形图的 Y 比例域最小值/最小值不为零; X 轴溢出的主要内容,如果未能解决你的问题,请参考以下文章
R语言ggplot2可视化堆叠的条形图(stacked bar plot)并在每一个条形图的的中间添加对应的数值值标签定位在geom_col堆叠的条形图中的每个条形段的中间
R语言ggplot2可视化:计算dataframe中每个数据列缺失值的个数使用堆叠的条形图(Stacked Barplot)可视化每个数据列的缺失值的情况(自定义堆叠条形图的形式)