获取条形图中每个y轴的唯一最大值

时间:2018-09-28 12:56:24

标签: javascript d3.js

在下面的代码中,我要添加多个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>

3 个答案:

答案 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)

我将以略有不同的方式重组数据。这样做可以使您在更改名称,添加更多水果类型,更改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

答案 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()
 }