簡體   English   中英

獲取條形圖中每個y軸的唯一最大值

[英]Get unique max value for each y-axis in a bar chart

在下面的代碼中,我將添加多個svg元素,並從中獲取3個不同的圖表。

y.domain()的問題是,每個y軸從y.domain()的最大值是我所有數據中的最大值。

是否有一些巧妙的方法來獲取每個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> 

我認為,最好的方法是重構代碼,以根據傳遞給它的數據創建特定的SVG。 但是,根據您現在擁有的代碼, 新的慣用D3解決方案正在使用局部變量

實際上,根據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> 

我將以略有不同的方式重組數據。 這樣做可以使您在更改名稱,添加更多水果類型,更改totalgoal等字詞時更加靈活

無論哪種方式,您都可以遍歷初始數組並為數組中的每個對象創建一個單獨的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> 


Codepen

我將為每個變量創建不同的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()
 }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM