Example for Cooperative Brushing and Tooltips in D3.
The completed chart, with both tooltips and brushing working cooperatively. You can start a brush-zoom on either the background or a data point.
Example for Cooperative Brushing and Tooltips in D3.
The completed chart, with both tooltips and brushing working cooperatively. You can start a brush-zoom on either the background or a data point.
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| .point { | |
| fill: #2f225d; | |
| stroke: #afa2dc; | |
| } | |
| .selected { | |
| fill: #afa2dc; | |
| stroke: #2f225d; | |
| } | |
| .axis { | |
| font: 10px sans-serif; | |
| } | |
| p { | |
| font: 12px sans-serif; | |
| margin: 0 0 2px 0; | |
| padding: 0; | |
| } | |
| .clear-button { | |
| font: 14px sans-serif; | |
| cursor: pointer; | |
| } | |
| .axis path, | |
| .axis line { | |
| fill: none; | |
| stroke: #000; | |
| shape-rendering: crispEdges; | |
| } | |
| .brush .extent { | |
| stroke: #fff; | |
| fill-opacity: .125; | |
| shape-rendering: crispEdges; | |
| } | |
| </style> | |
| <body> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script> | |
| d3.helper = {}; | |
| d3.helper.tooltip = function(){ | |
| var tooltipDiv; | |
| var bodyNode = d3.select('body').node(); | |
| function tooltip(selection){ | |
| selection.on('mouseover.tooltip', function(pD, pI){ | |
| // Clean up lost tooltips | |
| d3.select('body').selectAll('div.tooltip').remove(); | |
| // Append tooltip | |
| tooltipDiv = d3.select('body') | |
| .append('div') | |
| .attr('class', 'tooltip') | |
| var absoluteMousePos = d3.mouse(bodyNode); | |
| tooltipDiv.style({ | |
| left: (absoluteMousePos[0] + 10)+'px', | |
| top: (absoluteMousePos[1] - 40)+'px', | |
| 'background-color': '#d8d5e4', | |
| width: '65px', | |
| height: '30px', | |
| padding: '5px', | |
| position: 'absolute', | |
| 'z-index': 1001, | |
| 'box-shadow': '0 1px 2px 0 #656565' | |
| }); | |
| var first_line = '<p>X-Value: ' + pD.index + '</p>' | |
| var second_line = '<p>Y-Value: ' + pD.value + '</p>' | |
| tooltipDiv.html(first_line + second_line) | |
| }) | |
| .on('mousemove.tooltip', function(pD, pI){ | |
| // Move tooltip | |
| var absoluteMousePos = d3.mouse(bodyNode); | |
| tooltipDiv.style({ | |
| left: (absoluteMousePos[0] + 10)+'px', | |
| top: (absoluteMousePos[1] - 40)+'px' | |
| }); | |
| }) | |
| .on('mouseout.tooltip', function(pD, pI){ | |
| // Remove tooltip | |
| tooltipDiv.remove(); | |
| }); | |
| } | |
| tooltip.attr = function(_x){ | |
| if (!arguments.length) return attrs; | |
| attrs = _x; | |
| return this; | |
| }; | |
| tooltip.style = function(_x){ | |
| if (!arguments.length) return styles; | |
| styles = _x; | |
| return this; | |
| }; | |
| return tooltip; | |
| }; | |
| var data = []; | |
| var values = []; | |
| for (var i = 2; i <= 50; i++) { | |
| var val = Math.floor(Math.random() * (50 - 5 + 1) + 5); | |
| data.push({index: i, value: val}); | |
| values.push(val); | |
| } | |
| var margin = {top: 20, right: 20, bottom: 60, left: 40}, | |
| width = 960 - margin.left - margin.right, | |
| height = 500 - margin.top - margin.bottom; | |
| var x = d3.scale.linear() | |
| .range([0, width]) | |
| .domain([0, 50]); | |
| var y = d3.scale.linear() | |
| .range([height, 0]) | |
| .domain([0, d3.max(values) + 5]); | |
| var brush = d3.svg.brush() | |
| .x(x) | |
| .on("brush", brushmove) | |
| .on("brushend", brushend); | |
| var xAxis = d3.svg.axis() | |
| .scale(x) | |
| .orient("bottom"); | |
| var yAxis = d3.svg.axis() | |
| .scale(y) | |
| .orient("left") | |
| .ticks(11); | |
| var svg = d3.select("body").append("svg") | |
| .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("clip-path", "url(#clip)") | |
| .attr("transform", "translate(0," + height + ")") | |
| .call(xAxis); | |
| svg.append("g") | |
| .attr("class", "y axis") | |
| .call(yAxis); | |
| svg.append("g") | |
| .attr("class", "brush") | |
| .call(brush) | |
| .selectAll('rect') | |
| .attr('height', height); | |
| svg.append("defs").append("clipPath") | |
| .attr("id", "clip") | |
| .append("rect") | |
| .attr("width", width) | |
| .attr("height", height + 20); | |
| points = svg.selectAll(".point") | |
| .data(data) | |
| .enter().append("circle") | |
| .attr("class", "point") | |
| .attr("clip-path", "url(#clip)") | |
| .attr("r", function(d){return Math.floor(Math.random() * (20 - 5 + 1) + 5);}) | |
| .attr("cx", function(d) { return x(d.index); }) | |
| .attr("cy", function(d) { return y(d.value); }) | |
| .call(d3.helper.tooltip()); | |
| points.on('mousedown', function(){ | |
| brush_elm = svg.select(".brush").node(); | |
| new_click_event = new Event('mousedown'); | |
| new_click_event.pageX = d3.event.pageX; | |
| new_click_event.clientX = d3.event.clientX; | |
| new_click_event.pageY = d3.event.pageY; | |
| new_click_event.clientY = d3.event.clientY; | |
| brush_elm.dispatchEvent(new_click_event); | |
| }); | |
| function brushmove() { | |
| var extent = brush.extent(); | |
| points.classed("selected", function(d) { | |
| is_brushed = extent[0] <= d.index && d.index <= extent[1]; | |
| return is_brushed; | |
| }); | |
| } | |
| function brushend() { | |
| get_button = d3.select(".clear-button"); | |
| if(get_button.empty() === true) { | |
| clear_button = svg.append('text') | |
| .attr("y", 460) | |
| .attr("x", 825) | |
| .attr("class", "clear-button") | |
| .text("Clear Brush"); | |
| } | |
| x.domain(brush.extent()); | |
| transition_data(); | |
| reset_axis(); | |
| points.classed("selected", false); | |
| d3.select(".brush").call(brush.clear()); | |
| clear_button.on('click', function(){ | |
| x.domain([0, 50]); | |
| transition_data(); | |
| reset_axis(); | |
| clear_button.remove(); | |
| }); | |
| } | |
| function transition_data() { | |
| svg.selectAll(".point") | |
| .data(data) | |
| .transition() | |
| .duration(500) | |
| .attr("cx", function(d) { return x(d.index); }); | |
| } | |
| function reset_axis() { | |
| svg.transition().duration(500) | |
| .select(".x.axis") | |
| .call(xAxis); | |
| } | |
| </script> |
Thanks, this would be very useful, but with the newest version of d3 I cannot seem to create the right synthetic event, could you please update this if you have time? I couldn't manage to get it working