All Projects → ludwigschubert → d3-brush-multiple

ludwigschubert / d3-brush-multiple

Licence: other
This is an implementation of multiple brushes in D3js version 4.

Programming Languages

HTML
75241 projects

Projects that are alternatives of or similar to d3-brush-multiple

d3-force-reuse
Faster force-directed graph layouts by reusing force approximations
Stars: ✭ 110 (+547.06%)
Mutual labels:  d3js
D3partitionR
R package to visualise interactively hierarchical data.
Stars: ✭ 36 (+111.76%)
Mutual labels:  d3js
d3js-threejs-integration
D3js - threejs examples
Stars: ✭ 17 (+0%)
Mutual labels:  d3js
use-long-press
React hook for detecting click (or tap) and hold event
Stars: ✭ 97 (+470.59%)
Mutual labels:  mouse-events
stroke-dasharray-interpolation-talk
Slides from 2015 D3.js talk
Stars: ✭ 17 (+0%)
Mutual labels:  d3js
d3-geomap
A library for creating geographical maps based on D3.js
Stars: ✭ 124 (+629.41%)
Mutual labels:  d3js
phyd3
Phylogenetic tree viewer based on D3.js
Stars: ✭ 47 (+176.47%)
Mutual labels:  d3js
d3-force-apex-plugin
Oracle APEX Region Type Plugin: D3 Force Network Chart
Stars: ✭ 22 (+29.41%)
Mutual labels:  d3js
deloominator
Shed the light on your data!
Stars: ✭ 47 (+176.47%)
Mutual labels:  d3js
compareBars
Simplify comparative bar charts 📊
Stars: ✭ 28 (+64.71%)
Mutual labels:  d3js
github-visualization
simple github visualization
Stars: ✭ 106 (+523.53%)
Mutual labels:  d3js
d3js
Experiments with D3.js
Stars: ✭ 17 (+0%)
Mutual labels:  d3js
DraggableRegressionPoints
Draggable regression points in R Shiny
Stars: ✭ 18 (+5.88%)
Mutual labels:  d3js
disaster-crawler
Data sources from Kimono currently unavailable
Stars: ✭ 13 (-23.53%)
Mutual labels:  d3js
Vuesalize
Component library dedicated to simplifying interactive visualization building in Vue.js.
Stars: ✭ 24 (+41.18%)
Mutual labels:  d3js
skos-play
SKOS-Play allows to print SKOS files in HTML or PDF. It also embeds xls2rdf to generate RDF from Excel.
Stars: ✭ 58 (+241.18%)
Mutual labels:  d3js
3d-earth
🌏🌎🌍 3D Earth with Mapbox GL, D3.js and Three.js
Stars: ✭ 68 (+300%)
Mutual labels:  d3js
weltschmerz
Weltschmerz by age - "I am X years old and... [Google autocomplete]"
Stars: ✭ 23 (+35.29%)
Mutual labels:  d3js
D3Fire
Power your D3 visualizations with data from Firebase
Stars: ✭ 31 (+82.35%)
Mutual labels:  d3js
SortVis
https://airtucha.github.io/SortVis
Stars: ✭ 23 (+35.29%)
Mutual labels:  d3js

d3-brush Multiple Brushes

This is an implementation of multiple brushes in D3js version 4. While brushes give a good out-of-the-box behavior for single brushes, it is not immediately clear to me how to implement multiple selections/brushes. This is a "solution" primarily to elicit feedback. bl.ocks.org demo here.

This is a modified version of Jonathan Solichin's multiple no collision brush.

multiple brush selections

I wrote up this explanation for students taking a data visualization class I was TAing:

Explanatory piazza post to CS 448b students

Hey everyone;

as mentioned in class today some of your ran into d3-brush while implementing your assignment 3 homework. D3-brush is a library for movable, resizable "brushes" that allow selecting data. Sounds perfect for A3? Yes it does!

Unfortunately, brushes were not created with the idea of having multiple discontinous selections. So to use them in implementing the time searcher boxes behavior for A3, we will need to examine how d3-brush works behind the scenes. Once we understand their mechanics we can then tweak them to behave like we want them to.

The workings of d3-brush

The d3-brush documentation explains the internal structure of a brush like this:

The brush also creates the SVG elements necessary to display the brush selection and to receive input events for interaction. You can add, remove or modify these elements as desired to change the brush appearance; you can also apply stylesheets to modify the brush appearance. The structure of a two-dimensional brush is as follows:

 <g class="brush" fill="none" pointer-events="all" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">
 <rect class="overlay" pointer-events="all" cursor="crosshair" x="0" y="0" width="960" height="500"></rect>
 <rect class="selection" cursor="move" fill="#777" fill-opacity="0.3" stroke="#fff" shape-rendering="crispEdges" x="112" y="194" width="182" height="83"></rect>
 <rect class="handle handle--n" cursor="ns-resize" x="107" y="189" width="192" height="10"></rect>
 <rect class="handle handle--e" cursor="ew-resize" x="289" y="189" width="10" height="93"></rect>
 <rect class="handle handle--s" cursor="ns-resize" x="107" y="272" width="192" height="10"></rect>
 <rect class="handle handle--w" cursor="ew-resize" x="107" y="189" width="10" height="93"></rect>
 <rect class="handle handle--nw" cursor="nwse-resize" x="107" y="189" width="10" height="10"></rect>
 <rect class="handle handle--ne" cursor="nesw-resize" x="289" y="189" width="10" height="10"></rect>
 <rect class="handle handle--se" cursor="nwse-resize" x="289" y="272" width="10" height="10"></rect>
 <rect class="handle handle--sw" cursor="nesw-resize" x="107" y="272" width="10" height="10"></rect>
 </g>

The overlay rect covers the brushable area defined by brush.extent. The selection rect covers the area defined by the current brush selection. The handle rects cover the edges and corners of the brush selection, allowing the corresponding value in the brush selection to be modified interactively.

Allright, that's a lot of svg elements for a simple brushing behavior. But look at them in turn, and every element makes sense:

Diagram of brush DOM elements

  • g.brush — this is simply a group to keep all the elements together
  • rect.overlay — a transparent overlay that captures mouse events for creating the brush selection; this will be important for multiple brushes later on
  • rect.selection — a rectangle indicating the selected area, this will be what's actually visible on screen when you click and drag to filter
  • a bunch of rect.handle — these are invisible boxes to allow resizing on all edges and corners

So when we call d3.brush() on our svg element, we already create all of this—even though selection will be empty, and thus invisible (0 by 0 pixels), the overlay already exists and captures mouse events. Keep this in mind for part 2:

Implementing multiple brushes

So we've understood d3.brush, called it on our svg element; what do we see? At first everything seems good: clicking and dragging creates a rectangle on screen; we can move and resize it, and even programmatically query its dimensions and position to use in our filtering logic. The trouble only starts once we want to create a second filter. Clicking and dragging on the seemingly empty space next to our first brush does not create another brush; the first one seems to disappear.

You might have considered simply having an onmousedown handler on your svg element to create an extra brush; or maybe to simply call d3.brush() on your svg element again, but to your surprise your handler doesn't even get called anymore. Time to step back and think about the structure of the DOM elements our original call to d3.brush created.

I'm sure some of you have already noticed: the reason we're not getting events anymore and that the brush seems to disappear is that the original brush's overlay never stopped listening to mouse events. Consequently, the solution lies in:

  • Disabling mouse events  (pointer events in CSS parlance) on the brushes overlay layer once it has been created, and
  • Calling d3.brush() on the svg element again after we have created a brush and disabled its overlay layer's mouse events.

The latter will create a new overlay layer for the second brush, ready to capture mouse events to draw a second selection. Mouse events inside the selection rectangle of the original brush will still be handled by it, as we only disabled the mouse events on its overlay layer:

Diagram of two overlaid brush DOM elements

See it with sourcecode here. Go ahead and read through the implementation; it's not overly long and it's commented.

That is the entire recipe, and I hope this writeup helped you understand what is going on. I believe there are some takeways here:

  • Abstractions are "leaky", even if they're good abstractions. You will always need to learn some parts of the underlying layers to understand how to effectively work with higher level tools.
  • D3 is supremely "hackable" thanks to it basically being SVG elements and JS event handlers. Was it "easy" to immediately see how to implement this? Not necessarily! But once I sat down and took a look at what D3 was doing, everything made sense and there was no wacky magic I had to work around.

When I wrote the original example solution for A3 I meant for you to implement resizing and dragging of filters yourselves. This was because I did not know about d3-brush, not because I wanted you to reimplement existing behavior—but I enjoyed how this turned into an exercise in exploring the inner workings of D3 libraries and hope it leaves you with a deeper understanding and appreciation of the design of D3.

As always, come to us with any questions you still have—D3 or otherwise—and now go forth and create a prototype for those final projects!

Ludwig

About

Created 2016-10-30 by Ludwig Schubert for Stanford's Fall 2016 offering of CS 448b—Data Visualization.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].