在下面的代码中,我要添加多个svg元素,并从中获取3个不同的图表。
我遇到的问题是,从y.domain()
得出的每个y轴的最大值是我所有数据的最大值。
是否有一些聪明的方法来获取每个svg的最大值并将其设置为每个y轴的最大值?还是我必须制作3个不同的y比例尺?
代码如下:
var data = [
{category: "Apples", total: 10, goal: 8},
{category: "Oranges", total: 20, goal: 18},
{category: "Bananas", total: 20, goal: 25},
];
chart(data);
function chart(result) {
var margin = {bottom: 25, right: 25, top: 25, left: 25},
width = 180 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom;
var svg = d3.select("#chart").selectAll("svg")
.data(result)
.enter().append("svg")
.attr("width", width)
.attr("height", height)
var x = d3.scaleBand()
.range([margin.left, width - margin.right])
.domain(["Total", "Goal"])
.padding(.1)
.paddingOuter(.2)
var y = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, d3.max(result, d => Math.max(d.total, d.goal))]).nice()
var xAxis = g => g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
var yAxis = g => g
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y))
svg.append("g")
.attr("class", "x-axis")
.call(xAxis);
svg.append("g")
.attr("class", "y-axis")
.call(yAxis);
var total = svg.append("rect")
.attr("fill", "steelblue")
.attr("width", x.bandwidth())
.attr("x", x("Total"))
.attr("y", function() {
var d = d3.select(this.parentNode).datum()
return y(d.total)
})
.attr("height", function() {
var d = d3.select(this.parentNode).datum()
return y(0) - y(d.total)
})
var goal = svg.append("rect")
.attr("fill", "orange")
.attr("width", x.bandwidth())
.attr("x", x("Goal"))
.attr("y", function() {
var d = d3.select(this.parentNode).datum()
return y(d.goal)
})
.attr("height", function() {
var d = d3.select(this.parentNode).datum()
return y(0) - y(d.goal)
})
var text = svg.append("text")
.attr("dx", width / 2)
.attr("dy", 15)
.attr("text-anchor", "middle")
.text(function() {
return d3.select(this.parentNode).datum().category
})
}
<meta charset ="utf-8">
<script src="https://d3js.org/d3.v5.min.js "></script>
<div id="chart"></div>
答案 0 :(得分:3)
我认为这里最好的主意是重构代码,以基于传递给它的数据创建特定的SVG。但是,鉴于您现在拥有的代码, new 惯用的D3解决方案正在使用local variables。
实际上,根据Mike Bostock的说法...
例如,当渲染时间序列数据的较小倍数时,您可能希望所有图表使用相同的x比例,但要使用不同的y比例来比较每个指标的相对性能。
...局部变量是您确切情况的解决方案!
因此,您所需要做的就是设置本地...
var local = d3.local();
svg.each(function(d) {
var y = local.set(this, d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, Math.max(d.total, d.goal)]).nice())
});
...并获取它来创建轴和条。例如:
.attr("y", function(d) {
return local.get(this)(d.total);
})
请记住,您不需要那个var d = d3.select(this.parentNode).datum()
来获取基准!
这是您所做的更改的代码:
var data = [{
category: "Apples",
total: 10,
goal: 8
},
{
category: "Oranges",
total: 20,
goal: 18
},
{
category: "Bananas",
total: 20,
goal: 25
},
];
var local = d3.local();
chart(data);
function chart(result) {
var margin = {
bottom: 25,
right: 25,
top: 25,
left: 25
},
width = 180 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom;
var svg = d3.select("#chart").selectAll("svg")
.data(result)
.enter().append("svg")
.attr("width", width)
.attr("height", height)
var x = d3.scaleBand()
.range([margin.left, width - margin.right])
.domain(["Total", "Goal"])
.padding(.1)
.paddingOuter(.2);
svg.each(function(d) {
var y = local.set(this, d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, Math.max(d.total, d.goal)]).nice())
});
var xAxis = g => g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
svg.append("g")
.attr("class", "x-axis")
.call(xAxis);
svg.each(function() {
var y = local.get(this);
var yAxis = g => g
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y));
d3.select(this).append("g")
.attr("class", "y-axis")
.call(yAxis);
})
var total = svg.append("rect")
.attr("fill", "steelblue")
.attr("width", x.bandwidth())
.attr("x", x("Total"))
.attr("y", function(d) {
return local.get(this)(d.total);
})
.attr("height", function(d) {
return local.get(this)(0) - local.get(this)(d.total)
})
var goal = svg.append("rect")
.attr("fill", "orange")
.attr("width", x.bandwidth())
.attr("x", x("Goal"))
.attr("y", function(d) {
return local.get(this)(d.goal)
})
.attr("height", function(d) {
return local.get(this)(0) - local.get(this)(d.goal)
})
var text = svg.append("text")
.attr("dx", width / 2)
.attr("dy", 15)
.attr("text-anchor", "middle")
.text(function() {
return d3.select(this.parentNode).datum().category
})
}
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js "></script>
<div id="chart"></div>
答案 1 :(得分:1)
我将以略有不同的方式重组数据。这样做可以使您在更改名称,添加更多水果类型,更改total
和goal
等单词时更加灵活
无论哪种方式,您都可以遍历初始数组,并为数组中的每个对象创建一个单独的SVG(每个都有自己的yScales)。
const data = [{
"category": "Apples",
"bars": [{
"label": "total",
"val": 10
},
{
"label": "goal",
"val": 8
}
]
},
{
"category": "Oranges",
"bars": [{
"label": "total",
"val": 20
},
{
"label": "goal",
"val": 18
}
]
},
{
"category": "Bananas",
"bars": [{
"label": "total",
"val": 20
},
{
"label": "goal",
"val": 25
}
]
}
]
data.forEach((d) => chart(d))
function chart(result) {
const margin = {
bottom: 25,
right: 25,
top: 25,
left: 25
},
width = 180 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom
const svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
const x = d3.scaleBand()
.range([margin.left, width - margin.right])
.domain(result.bars.map((d) => d.label))
.padding(.1)
.paddingOuter(.2)
const y = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, d3.max(result.bars.map(z => z.val))])
const xAxis = g => g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
const yAxis = g => g
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y))
svg.append("g")
.attr("class", "x-axis")
.call(xAxis)
svg.append("g")
.attr("class", "y-axis")
.call(yAxis)
const barEnter = svg.selectAll("rect")
.data(result.bars)
.enter()
.append("rect")
.attr("fill", d => (d.label === 'total' ? "steelblue" : "green"))
.attr("width", x.bandwidth())
.attr("x", (d) => x(d.label))
.attr("y", (d) => y(d.val))
.attr("height", (d) => y(0) - y(d.val))
const text = svg.append("text")
.attr("dx", width / 2)
.attr("dy", 15)
.attr("text-anchor", "middle")
.text(result.category)
}
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js "></script>
<div id="chart"></div>
更新。如果您无法优化数据结构,则可以采用这种方式
const data = [{
category: "Apples",
total: 10,
goal: 8
},
{
category: "Oranges",
total: 20,
goal: 18
},
{
category: "Bananas",
total: 20,
goal: 25
}
]
data.forEach((d) => chart(d))
function chart(result) {
const margin = {
bottom: 25,
right: 25,
top: 25,
left: 25
},
width = 180 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom
const svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
const x = d3.scaleBand()
.range([margin.left, width - margin.right])
.domain(["total", "goal"])
.padding(.1)
.paddingOuter(.2)
const y = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, d3.max([result.total, result.goal])])
const xAxis = g => g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0))
const yAxis = g => g
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y))
svg.append("g")
.attr("class", "x-axis")
.call(xAxis)
svg.append("g")
.attr("class", "y-axis")
.call(yAxis)
const totalBarEnter = svg.selectAll(".total")
.data([result.total])
.enter()
.append("rect")
.attr("class", "total")
.attr("fill", "steelblue")
.attr("width", x.bandwidth())
.attr("x", (d) => x("total"))
.attr("y", (d) => y(d))
.attr("height", (d) => y(0) - y(d))
const goalBarEnter = svg.selectAll(".goal")
.data([result.goal])
.enter()
.append("rect")
.attr("class", "goal")
.attr("fill", "green")
.attr("width", x.bandwidth())
.attr("x", (d) => x("goal"))
.attr("y", (d) => y(d))
.attr("height", (d) => y(0) - y(d))
const text = svg.append("text")
.attr("dx", width / 2)
.attr("dy", 15)
.attr("text-anchor", "middle")
.text(result.category)
}
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js "></script>
<div id="chart"></div>
答案 2 :(得分:-1)
我将为每个变量创建不同的y_scale:
//build unique categories set
var categories = d3.set(function(d){return d.category}).values();
//define y scale
var y_scales = {};
//loop through categories, filter data and set y_scale on max.
for (c in categories){
var filtered_result = result.filter(function(d){if(d.category == categories[c]){return d});
y_scales[categories[c]] = d3.scaleLinear()
.range([height - margin.bottom, margin.top])
.domain([0, d3.max(filtered_result, d => Math.max(d.total, d.goal))]).nice()
}