在水平条形图中放大画笔

时间:2018-06-30 09:00:19

标签: javascript d3.js

在下面的代码中,我设法使画笔与zoom事件保持一致,但是现在我不得不尝试在使用画笔时使条形图正确缩放。

我的brushed函数基于this答案,但无法将逻辑转换为我自己的代码。

我收到错误消息:无法读取未定义的属性'transform',我也不知道为什么。

这是我所有的代码:

const data = [
	{month: "jan", value: 12},
	{month: "feb", value: 25},
	{month: "mar", value: 10},
	{month: "apr", value: 15}
];

var svg = d3.select("svg"),
	margin = {top: 10, right: 35, bottom: 10, left: 35},
	width = +svg.attr("width") - margin.left - margin.right,
	height = +svg.attr("height") - margin.top - margin.bottom;

var g = svg.append("g")
	.attr("transform", 
		"translate(" + margin.left + "," + margin.top + ")");

// === Bar ===

var x = d3.scaleLinear()
	.domain([0, d3.max(data, d => d.value)]).nice()
	.range([margin.left * 2, width])

var y = d3.scaleBand()
	.domain(data.map(d => d.month))
	.range([height, 0])
	.padding(0.1)

var yAxis = g => g
	.attr("transform", `translate(${margin.left * 2},0)`)
	.call(d3.axisLeft(y).tickSizeOuter(0))

g.append("g").selectAll(".bar")
	.data(data).enter().append("rect")
	.attr("fill", "steelblue")
	.attr("class", "bar")
	.attr("x", x(0))
	.attr("y", d => y(d.month))
	.attr("width", d => x(d.value) - x(0))
	.attr("height", y.bandwidth());

g.append("g")
	.attr("class", "y-axis")
	.call(yAxis);

// === Brush ===

var xB = d3.scaleLinear()
	.domain([0, d3.max(data, d => d.value)])
	.range([0, margin.left]);

var yB = d3.scaleBand()
	.domain(data.map(d => d.month))
	.range([height, 0])
	.padding(0.1);

var brush = d3.brushY()
	.extent([[0, 0],[margin.left, height]])
	.on("start brush", brushed);

var yAxisB = g => g
	.call(d3.axisLeft(yB).tickSizeOuter(0))

g.append("g")
	.attr("class", "brush")
 	.call(brush)
 	.call(brush.move, yB.range().reverse())

g.append("g").selectAll(".brushBar")
	.data(data).enter().append("rect")
	.attr("fill", "steelblue")
	.attr("class", "brushBar")
	.attr("x", xB(0))
	.attr("y", d => yB(d.month))
	.attr("width", d => xB(d.value) - xB(0))
	.attr("height", yB.bandwidth());

g.append("g")
	.call(yAxisB);

// === Brush & Zoom ===

var bExtent = [[0, 0], [width, height]]

var zoom = d3.zoom()
	.scaleExtent([1, 2])
	.translateExtent(bExtent)
	.extent(bExtent)
	.on("zoom", zoomed)

g.append("rect")
	.attr("class", "zoom")
	.attr("width", width)
	.attr("height", height)
	.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
	.call(zoom);

function zoomed() {

	if (d3.event.sourceEvent && 
		d3.event.sourceEvent.type === "brush") return;

	var t = d3.event.transform;

	y.range([height, 0]
		.map(d => d3.event.transform.applyY(d)));

	g.selectAll(".bar")
		.attr("y", d => y(d.month))
		.attr("height", y.bandwidth());

	g.selectAll(".y-axis").call(yAxis);

	g.select(".brush").call(brush.move, 
		yB.range().reverse().map(t.invertY, t))
}

function brushed() {

	if (d3.event.sourceEvent && 
		d3.event.sourceEvent.type === "zoom") return;

	var s = d3.event.selection,
		nD = [];

	yB.domain().forEach((d) => {
		var pos = yB(d) + yB.bandwidth() / 2;
		if (pos > s[0] && pos < s[1]){
			nD.push(d);
		}
	});

	y.domain(nD);

	g.selectAll(".y-axis").call(yAxis);

	g.selectAll(".bar")
		.attr("y", d => y(d.month))
		.attr("height", y.bandwidth());

	//g.select(".zoom").call(zoom.transform, d3.zoomIdentity
	//	.scale(2)
	//	.translate(-s[0], 0));
}
.zoom {
	cursor: move;
	fill: none;
	pointer-events: all;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="540" height="120"></svg>

1 个答案:

答案 0 :(得分:1)

首先,我没有收到您描述的错误。

您的代码段中的问题似乎是,拉出的条形堆积在SVG的顶部。这是由于您正在更改y标度的域,并且由于某些条形图会为undefined获得y(d.month)。因此,它们只是从0的{​​{1}}位置而不是从视图中消失了。

最好的选择是重新考虑所有的画笔/缩放代码。但是,为了在代码中进行最少的更改,一种快速而肮脏的解决方案就是将具有y位置的undefined的条变成透明:

y

此外,为笔刷中的条形设置.style("opacity", d => y(d.month) ? 1 : 0) ,以提供更好的用户体验:

pointer-events: none;

这是您所做的更改的代码:

.attr("pointer-events", "none")
const data = [{
    month: "jan",
    value: 12
  },
  {
    month: "feb",
    value: 25
  },
  {
    month: "mar",
    value: 10
  },
  {
    month: "apr",
    value: 15
  }
];

var svg = d3.select("svg"),
  margin = {
    top: 10,
    right: 35,
    bottom: 10,
    left: 35
  },
  width = +svg.attr("width") - margin.left - margin.right,
  height = +svg.attr("height") - margin.top - margin.bottom;

var g = svg.append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")");

// === Bar ===

var x = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)]).nice()
  .range([margin.left * 2, width])

var y = d3.scaleBand()
  .domain(data.map(d => d.month))
  .range([height, 0])
  .padding(0.1)

var yAxis = g => g
  .attr("transform", `translate(${margin.left * 2},0)`)
  .call(d3.axisLeft(y).tickSizeOuter(0))

g.append("g").selectAll(".bar")
  .data(data).enter().append("rect")
  .attr("fill", "steelblue")
  .attr("class", "bar")
  .attr("x", x(0))
  .attr("y", d => y(d.month))
  .attr("width", d => x(d.value) - x(0))
  .attr("height", y.bandwidth());

g.append("g")
  .attr("class", "y-axis")
  .call(yAxis);

// === Brush ===

var xB = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([0, margin.left]);

var yB = d3.scaleBand()
  .domain(data.map(d => d.month))
  .range([height, 0])
  .padding(0.1);

var brush = d3.brushY()
  .extent([
    [0, 0],
    [margin.left, height]
  ])
  .on("start brush", brushed);

var yAxisB = g => g
  .call(d3.axisLeft(yB).tickSizeOuter(0))

g.append("g")
  .attr("class", "brush")
  .call(brush)
  .call(brush.move, yB.range().reverse())

g.append("g").selectAll(".brushBar")
  .data(data).enter().append("rect")
  .attr("fill", "steelblue")
  .attr("class", "brushBar")
  .attr("pointer-events", "none")
  .attr("x", xB(0))
  .attr("y", d => yB(d.month))
  .attr("width", d => xB(d.value) - xB(0))
  .attr("height", yB.bandwidth());

g.append("g")
  .call(yAxisB);

// === Brush & Zoom ===

var bExtent = [
  [0, 0],
  [width, height]
]

var zoom = d3.zoom()
  .scaleExtent([1, 2])
  .translateExtent(bExtent)
  .extent(bExtent)
  .on("zoom", zoomed)

g.append("rect")
  .attr("class", "zoom")
  .attr("width", width)
  .attr("height", height)
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
  .call(zoom);

function zoomed() {

  if (d3.event.sourceEvent &&
    d3.event.sourceEvent.type === "brush") return;

  var t = d3.event.transform;

  y.range([height, 0]
    .map(d => d3.event.transform.applyY(d)));

  g.selectAll(".bar")
    .attr("y", d => y(d.month))
    .attr("height", y.bandwidth());

  g.selectAll(".y-axis").call(yAxis);

  g.select(".brush").call(brush.move,
    yB.range().reverse().map(t.invertY, t))
}

function brushed() {

  if (d3.event.sourceEvent &&
    d3.event.sourceEvent.type === "zoom") return;

  var s = d3.event.selection,
    nD = [];

  yB.domain().forEach((d) => {
    var pos = yB(d) + yB.bandwidth() / 2;
    if (pos > s[0] && pos < s[1]) {
      nD.push(d);
    }
  });

  y.domain(nD);

  g.selectAll(".y-axis").call(yAxis);

  g.selectAll(".bar")
    .attr("y", d => y(d.month))
    .style("opacity", d => y(d.month) ? 1 : 0)
    .attr("height", y.bandwidth());

  //g.select(".zoom").call(zoom.transform, d3.zoomIdentity
  //	.scale(2)
  //	.translate(-s[0], 0));
}
.zoom {
  cursor: move;
  fill: none;
  pointer-events: all;
}