D3: Создание адаптивной линейчатой ​​диаграммы диапазона - PullRequest
0 голосов
/ 10 декабря 2018

Я пытаюсь создать гистограмму диапазона в D3 (см. аналогичный пример ).

Я нашел этот ответ , и я смогнастройте его именно на то, что мне нужно, за исключением того, что он использует древнюю версию 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 голосов
/ 10 декабря 2018

Ваш код нуждается в значительном рефакторинге, в общем случае вы должны следовать шаблону обновления d3 всякий раз, когда вы хотите изменить данные (или размер окна в этом случае, ps. Я не уверен на 100%, что это хорошая идея, так как я никогда не делаю свои графики "отзывчивыми", как это, но, похоже, работает нормально )

Другими словами, не* Если вы хотите обновить *, используйте 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>
...