D3:创建响应范围条形图

时间:2018-12-10 05:33:05

标签: d3.js responsive-design responsive

我正在尝试在D3中创建范围条形图(请参见a similar example)。

我发现了this answer,我能够完全按照自己的需要对其进行自定义,只是它使用了D3的较旧版本,数据是单独数组的愚蠢格式,并且没有响应(为什么那么多D3东西没有响应?)。

我放弃了这一尝试,并从头开始。现在,我主要使用最新的D3和上述SO问题的一些思想来工作,但是数据显示不正确。当我调整大小时,这些条会意外地增长和收缩,随着宽度的增加,最终会出现负宽度值。

我非常感谢您帮助我了解条形尺寸做错了什么。请注意,要观察不正确的行为,您必须扩展代码段并调整窗口大小。

let data = [
  { field: "Field Name 1", salary: [42, 65], bonusRange: [null, null] },
  { field: "Field Name 2", salary: [28, 58], bonusRange: [null, null] },
  { field: "Field Name 3", salary: [33, 57], bonusRange: [null, null] },
  { field: "Field Name 4", salary: [33, 50], bonusRange: [null, null] },
  { field: "Field Name 5", salary: [32, 44], bonusRange: [38, 52] },
  { field: "Field Name 6", salary: [27, 40], bonusRange: [null, null] }
];

const chartDiv = document.getElementById("chart");
const svg = d3.select(chartDiv).append("svg");

function redraw() {
  svg.selectAll("g").remove();

  const margin = 20;
  const width = chartDiv.clientWidth - margin * 2;
  const height = chartDiv.clientHeight - margin * 2;

  svg
    .attr("width", width)
    .attr("height", height)
    .attr("transform", `translate(${margin}, ${margin})`);

  const xScale = d3
    .scaleLinear()
    .range([120, width - 10])
    .domain([20, 70]);

  const xAxis = d3.axisBottom(xScale);

  const yScale = d3
    .scaleLinear()
    .range([0, height - margin])
    .domain([-0.5, (data.length - 1) * 1.1]);

  const yAxis = d3
    .axisLeft(yScale)
    .ticks(data.length)
    .tickFormat(function(d, i) {
      return data[i].field;
    });

  svg
    .append("g")
    .attr("class", "axis")
    .call(xAxis)
    .attr("transform", `translate(0, ${height - margin})`);

  svg
    .append("g")
    .attr("class", "axis")
    .call(yAxis)
    .attr("transform", "translate(120, 0)");

  svg
    .append("g")
    .attr("id", "bars")
    .selectAll(".bar")
    .data(data)
    .enter()
    .append("rect")
    .style("fill", "#f61166")
    .attr("class", "bar")
    .attr("x", function(d) {
      return xScale(d.salary[0]);
    })
    .attr("height", 25)
    .attr("width", function(d) {
      return xScale(d.salary[1] - d.salary[0]);
    })
    .attr("y", function(d, i) {
      return yScale(i) - margin * 0.5;
    });
}

redraw();
window.addEventListener("resize", redraw);
* {
  box-sizing: border-box;
}

body {
  padding: 10px;
}

#chart {
  outline: 1px solid fuchsia;
  height: 0;
  overflow: hidden;
  padding-top: 59.75%;
  background: white;
  position: relative;
}

#chart svg {
  position: absolute;
  top: 0;
  left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart" class=""></div>

1 个答案:

答案 0 :(得分:1)

您的代码需要大量重构,通常,只要您想更改数据(或在这种情况下,窗口大小为 ps”,就应该遵循d3 update pattern。我不确定100%确定这是一个好主意,因为我从来没有像这样使我的图表“响应”,但似乎效果很好

换句话说,当您要更新时,不要使用svg.selectAll("g").remove();,显然可以,但这并不是d3的习惯。

我从redraw函数中删除了所有附加项,并将它们放在包装函数中,以便我们可以按类名或变量引用它们。

我还将您的yScale变量设置为一个比例带,以便更轻松地根据数据调整图表的大小。

const yScale = d3.scaleBand()
    .domain(data.map(d => d.field))
    .padding(0.1)
    .paddingOuter(0.1)
    .paddingInner(0.1);

我在下面添加了一个代码段,希望应该清楚如何完成此操作。

let data = [
	{ field: "Field Name 1", salary: [42, 65], bonusRange: [null, null] },
	{ field: "Field Name 2", salary: [28, 58], bonusRange: [null, null] },
	{ field: "Field Name 3", salary: [33, 57], bonusRange: [null, null] },
	{ field: "Field Name 4", salary: [33, 50], bonusRange: [null, null] },
	{ field: "Field Name 5", salary: [32, 44], bonusRange: [38, 52] },
	{ field: "Field Name 6", salary: [27, 40], bonusRange: [null, null] }
];

chart(data);

function chart(data) {

	var svg = d3.select("#chart"),
		margin = {top: 55, bottom: 0, left: 85, right: 0},
		width  = parseInt(svg.style("width")) - margin.left - margin.right,
		height = parseInt(svg.style("height")) - margin.top - margin.bottom;
	
	const xScale = d3.scaleLinear()
		.domain([20, 70]);

	const yScale = d3.scaleBand()
		.domain(data.map(d => d.field))
		.padding(0.1)
		.paddingOuter(0.1)
		.paddingInner(0.1);

	const xAxis = svg.append("g")
		.attr("class", "x-axis")

	const yAxis = svg.append("g")
		.attr("class", "y-axis")

	redraw(width, height);

	function redraw(width, height) {

		yScale.range([margin.top, height - margin.bottom])
	
		svg.selectAll(".y-axis")
			.attr("transform", `translate(${margin.left},0)`)
			.call(d3.axisLeft(yScale)
				.ticks(data.length)
				.tickFormat(function(d, i) {
					return data[i].field;
    			}));
	
		xScale.range([margin.left, width - margin.right]);

		svg.selectAll(".x-axis").transition().duration(0)
			.attr("transform", `translate(0,${height})`)
			.call(d3.axisBottom(xScale));
	
		var bar = svg.selectAll(".bar")
			.data(data)

		bar.exit().remove();

		bar.enter().append("rect")
			.attr("class", "bar")
			.style("fill", "#f61166")
			.merge(bar)
		.transition().duration(0)
			.attr("width", function(d) {
				return xScale(d.salary[1] - d.salary[0]) - xScale(0);
			})
			.attr("height", yScale.bandwidth())
			.attr("y", d => yScale(d.field))
			.attr("x", function(d) {
				return xScale(d.salary[0]);
			});
	}

	d3.select(window).on('resize', function() {
		width = parseInt(svg.style("width")) - margin.left - margin.right,
		height = parseInt(svg.style("height")) - margin.top - margin.bottom;
		redraw(width, height);
	});
}
body {
	padding: 25px 25px 25px 25px;
	font: 18px arial;
}
#chart {
	outline: 1px solid fuchsia;
	position: absolute;
	width: 95%;
	height: 95%;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="chart"></svg>