让点鼠标交互和画笔协同工作
Getting point mouse interaction and brushes to work together
我正在尝试让鼠标事件在散点图中的不同元素之间进行协作。 D3 的 brush
组件将一些侦听器添加到被调用的元素(例如 svg.call(brush)
)。我还想显示绑定在 SVG 上的点,就像散点图一样,并且这些点支持 mouseover
事件(用于工具提示和其他交互)。
A previous solution suggests drawing points before calling the brush, which supports mouseover on points while allowing the brush to be drawn and the extent modified. However, if the dragging motion for the brush starts upon a point (which I anticipate in very dense graphs), the brush component misbehaves when an extent is already active (translating the brush resizes the extent instead). You can try it out on this example,其中已实施上述建议的解决方案。
我已将问题缩小到如何在 d3.svg.brush
组件内部的 d3 brushstart()
函数中处理事件。这是刷子正常工作时相关变量的样子。
this eventTarget dragging resizing
-------------- ------------------------------------- ---------- ----------
Translating extent brush parent rect.extent true 0
Resizing extent brush parent rect (invisible rects for resizing) false e.g. "e"
Redrawing brush parent rect.background false 0
这是目前的样子,上面的解决方案:
this eventTarget dragging resizing
-------------------- -------------- ------------- ---------- ----------------
Translating extent brush parent circle false circle.datum()
Resizing extent brush parent circle false circle.datum()
Redrawing brush parent circle false circle.datum()
真正的问题是:如何伪造 d3.event.target 的来源以匹配第一个 table?如果我能做到这一点,我就能得到我想要的行为。感谢您的帮助!
如果您错过了,这里是 bl.ock 这个难题的实际应用:http://bl.ocks.org/yelper/d38ddf461a0175ebd927946d15140947
这是工作示例:
https://jsfiddle.net/paradite/rpqusqdc/2/
基本上我使用了我之前使用 drag
事件而不是 brush
的编码范围选择工具:http://bl.ocks.org/paradite/71869a0f30592ade5246
它不会干扰你的圈子。因此,您只需要获取当前的 rect
维度并相应地更新您的圈子:
// select your points here
var e = selectionRect.getCurrentAttributes();
svg.selectAll('circle').classed('hidden', function(d) {
return e.x1 > x(d[0]) || x(d[0]) > e.x2 || e.y1 > y(d[1]) || y(d[1]) > e.y2;
});
当然,您可以删除其中的部分逻辑,因为您的情况不需要其中的大部分内容。
这里有一个可以纠正该行为的快速技巧:
.on('mousedown', function(d){
var e = brush.extent(),
m = d3.mouse(svg.node()), // pointer position with respect to g
p = [x.invert(m[0]), y.invert(m[1])]; // position in user space
if ( brush.empty() || // if there is no brush
(e[0][0] > d[0] || d[0] > e[1][0]
|| e[0][1] > d[1] || d[1] > e[1][1] ) // or our current circle is outside the bounds of the brush
) {
brush.extent([p,p]); // set brush to current position
} else {
d3.select(this).classed('extent', true); // else we are moving the brush, so fool d3 (I got this from looking at source code, it's how d3 determines a drag)
}
});
下面的工作代码,更新块 here。
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.hidden {
opacity: 0.3;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 50, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width])
.domain([0, 10]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 10]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var curPt = d3.select('body')
.append('p')
.html("Current point: ")
.append('span')
.attr('id', 'curPt');
var svg = d3.select('body').insert('svg', 'p')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,'+height+')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brush", function() {
var e = brush.extent(),
c = svg.selectAll('circle');
c.classed('extent', false);
c.classed('hidden', function(d) {
return e[0][0] > d[0] || d[0] > e[1][0]
|| e[0][1] > d[1] || d[1] > e[1][1];
}
);
})
.on("brushend", function() {
if (brush.empty()) svg.selectAll('circle').classed('hidden', false);
});
svg.call(brush);
var data = d3.range(50).map(function() { return [Math.random() * 10, Math.random() * 10]; });
svg.append('g')
.attr('class', 'points')
.selectAll('circle')
.data(data).enter()
.append('circle')
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.attr('r', 10)
.style('fill', 'steelblue')
.on('mouseover', function(d) {
curPt.html("[" + d[0] + ", " + d[1] + "]");
})
.on('mouseout', function(d) {
curPt.html("");
})
.on('mousedown', function(d){
var e = brush.extent(),
m = d3.mouse(svg.node()),
p = [x.invert(m[0]), y.invert(m[1])];
if ( brush.empty() ||
(e[0][0] > d[0] || d[0] > e[1][0]
|| e[0][1] > d[1] || d[1] > e[1][1] )
) {
brush.extent([p,p]);
} else {
d3.select(this).classed('extent', true);
}
});
</script>
我正在尝试让鼠标事件在散点图中的不同元素之间进行协作。 D3 的 brush
组件将一些侦听器添加到被调用的元素(例如 svg.call(brush)
)。我还想显示绑定在 SVG 上的点,就像散点图一样,并且这些点支持 mouseover
事件(用于工具提示和其他交互)。
A previous solution suggests drawing points before calling the brush, which supports mouseover on points while allowing the brush to be drawn and the extent modified. However, if the dragging motion for the brush starts upon a point (which I anticipate in very dense graphs), the brush component misbehaves when an extent is already active (translating the brush resizes the extent instead). You can try it out on this example,其中已实施上述建议的解决方案。
我已将问题缩小到如何在 d3.svg.brush
组件内部的 d3 brushstart()
函数中处理事件。这是刷子正常工作时相关变量的样子。
this eventTarget dragging resizing
-------------- ------------------------------------- ---------- ----------
Translating extent brush parent rect.extent true 0
Resizing extent brush parent rect (invisible rects for resizing) false e.g. "e"
Redrawing brush parent rect.background false 0
这是目前的样子,上面的解决方案:
this eventTarget dragging resizing
-------------------- -------------- ------------- ---------- ----------------
Translating extent brush parent circle false circle.datum()
Resizing extent brush parent circle false circle.datum()
Redrawing brush parent circle false circle.datum()
真正的问题是:如何伪造 d3.event.target 的来源以匹配第一个 table?如果我能做到这一点,我就能得到我想要的行为。感谢您的帮助!
如果您错过了,这里是 bl.ock 这个难题的实际应用:http://bl.ocks.org/yelper/d38ddf461a0175ebd927946d15140947
这是工作示例: https://jsfiddle.net/paradite/rpqusqdc/2/
基本上我使用了我之前使用 drag
事件而不是 brush
的编码范围选择工具:http://bl.ocks.org/paradite/71869a0f30592ade5246
它不会干扰你的圈子。因此,您只需要获取当前的 rect
维度并相应地更新您的圈子:
// select your points here
var e = selectionRect.getCurrentAttributes();
svg.selectAll('circle').classed('hidden', function(d) {
return e.x1 > x(d[0]) || x(d[0]) > e.x2 || e.y1 > y(d[1]) || y(d[1]) > e.y2;
});
当然,您可以删除其中的部分逻辑,因为您的情况不需要其中的大部分内容。
这里有一个可以纠正该行为的快速技巧:
.on('mousedown', function(d){
var e = brush.extent(),
m = d3.mouse(svg.node()), // pointer position with respect to g
p = [x.invert(m[0]), y.invert(m[1])]; // position in user space
if ( brush.empty() || // if there is no brush
(e[0][0] > d[0] || d[0] > e[1][0]
|| e[0][1] > d[1] || d[1] > e[1][1] ) // or our current circle is outside the bounds of the brush
) {
brush.extent([p,p]); // set brush to current position
} else {
d3.select(this).classed('extent', true); // else we are moving the brush, so fool d3 (I got this from looking at source code, it's how d3 determines a drag)
}
});
下面的工作代码,更新块 here。
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.hidden {
opacity: 0.3;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 50, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width])
.domain([0, 10]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 10]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var curPt = d3.select('body')
.append('p')
.html("Current point: ")
.append('span')
.attr('id', 'curPt');
var svg = d3.select('body').insert('svg', 'p')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,'+height+')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brush", function() {
var e = brush.extent(),
c = svg.selectAll('circle');
c.classed('extent', false);
c.classed('hidden', function(d) {
return e[0][0] > d[0] || d[0] > e[1][0]
|| e[0][1] > d[1] || d[1] > e[1][1];
}
);
})
.on("brushend", function() {
if (brush.empty()) svg.selectAll('circle').classed('hidden', false);
});
svg.call(brush);
var data = d3.range(50).map(function() { return [Math.random() * 10, Math.random() * 10]; });
svg.append('g')
.attr('class', 'points')
.selectAll('circle')
.data(data).enter()
.append('circle')
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.attr('r', 10)
.style('fill', 'steelblue')
.on('mouseover', function(d) {
curPt.html("[" + d[0] + ", " + d[1] + "]");
})
.on('mouseout', function(d) {
curPt.html("");
})
.on('mousedown', function(d){
var e = brush.extent(),
m = d3.mouse(svg.node()),
p = [x.invert(m[0]), y.invert(m[1])];
if ( brush.empty() ||
(e[0][0] > d[0] || d[0] > e[1][0]
|| e[0][1] > d[1] || d[1] > e[1][1] )
) {
brush.extent([p,p]);
} else {
d3.select(this).classed('extent', true);
}
});
</script>