首页 文章

D3:创建响应范围条形图

提问于
浏览
1

我正在尝试在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 回答

  • 1

    你的代码需要大量的重构,你通常应该按照d3 update pattern来改变数据(或者在这种情况下是窗口的大小,ps . 我不是100%肯定这是一个好主意,因为我从来没有我的图表"responsive"就像这样,但似乎工作得很好)

    换句话说,当你想要更新时不要使用 svg.selectAll("g").remove(); ,你可以显然但这对d3来说并不是真正的惯用语 .

    我删除了重绘函数的所有追加并将它们放在包装函数中,因此我们可以通过类名或变量引用它们 .

    我还将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>
    

相关问题