GithubHelp home page GithubHelp logo

d3-brush's Introduction

d3-brush

Brushing is the interactive specification a one- or two-dimensional selected region using a pointing gesture, such as by clicking and dragging the mouse. Brushing is often used to select discrete elements, such as dots in a scatterplot or files on a desktop. It can also be used to zoom-in to a region of interest, or to select continuous regions for cross-filtering data or live histograms:

Mona Lisa Histogram

The d3-brush module implements brushing for mouse and touch events using SVG. Click and drag on the brush selection to translate the selection. Click and drag on one of the selection handles to move the corresponding edge (or edges) of the selection. Click and drag on the invisible overlay to define a new brush selection, or click anywhere within the brushable region while holding down the META (⌘) key. Holding down the ALT (⌥) key while moving the brush causes it to reposition around its center, while holding down SPACE locks the current brush size, allowing only translation.

Brushes also support programmatic control. For example, you can listen to end events, and then initiate a transition with brush.move to snap the brush selection to semantic boundaries:

Brush Snapping

Or you can have the brush recenter when you click outside the current selection:

Click-to-Recenter

Installing

If you use npm, npm install d3-brush. You can also download the latest release on GitHub. For vanilla HTML in modern browsers, import d3-brush from Skypack:

<script type="module">

import {brushX} from "https://cdn.skypack.dev/[email protected]";

const brush = brushX();

</script>

For legacy environments, you can load d3-brush’s UMD bundle from an npm-based CDN such as jsDelivr; a d3 global is exported:

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script>

const brush = d3.brushX();

</script>

Try d3-brush in your browser.

API Reference

# d3.brush() · Source, Examples

Creates a new two-dimensional brush.

# d3.brushX() · Source, Examples

Creates a new one-dimensional brush along the x-dimension.

# d3.brushY() · Source

Creates a new one-dimensional brush along the y-dimension.

# brush(group) · Source, Examples

Applies the brush to the specified group, which must be a selection of SVG G elements. This function is typically not invoked directly, and is instead invoked via selection.call. For example, to render a brush:

svg.append("g")
    .attr("class", "brush")
    .call(d3.brush().on("brush", brushed));

Internally, the brush uses selection.on to bind the necessary event listeners for dragging. The listeners use the name .brush, so you can subsequently unbind the brush event listeners as follows:

group.on(".brush", null);

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. To modify the brush selection programmatically, use brush.move.

# brush.move(group, selection[, event]) · Source, Examples

Sets the active selection of the brush on the specified group, which must be a selection or a transition of SVG G elements. The selection must be defined as an array of numbers, or null to clear the brush selection. For a two-dimensional brush, it must be defined as [[x0, y0], [x1, y1]], where x0 is the minimum x-value, y0 is the minimum y-value, x1 is the maximum x-value, and y1 is the maximum y-value. For an x-brush, it must be defined as [x0, x1]; for a y-brush, it must be defined as [y0, y1]. The selection may also be specified as a function which returns such an array; if a function, it is invoked for each selected element, being passed the current datum d and index i, with the this context as the current DOM element. The returned array defines the brush selection for that element.

# brush.clear(group[, event]) · Source, Examples

An alias for brush.move with the null selection.

# brush.extent([extent]) · Source, Examples

If extent is specified, sets the brushable extent to the specified array of points [[x0, y0], [x1, y1]], where [x0, y0] is the top-left corner and [x1, y1] is the bottom-right corner, and returns this brush. The extent may also be specified as a function which returns such an array; if a function, it is invoked for each selected element, being passed the current datum d and index i, with the this context as the current DOM element. If extent is not specified, returns the current extent accessor, which defaults to:

function defaultExtent() {
  var svg = this.ownerSVGElement || this;
  if (svg.hasAttribute("viewBox")) {
    svg = svg.viewBox.baseVal;
    return [[svg.x, svg.y], [svg.x + svg.width, svg.y + svg.height]];
  }
  return [[0, 0], [svg.width.baseVal.value, svg.height.baseVal.value]];
}

This default implementation requires that the owner SVG element have a defined viewBox, or width and height attributes. Alternatively, consider using element.getBoundingClientRect. (In Firefox, element.clientWidth and element.clientHeight is zero for SVG elements!)

The brush extent determines the size of the invisible overlay and also constrains the brush selection; the brush selection cannot go outside the brush extent.

# brush.filter([filter]) · Source, Examples

If filter is specified, sets the filter to the specified function and returns the brush. If filter is not specified, returns the current filter, which defaults to:

function filter(event) {
  return !event.ctrlKey && !event.button;
}

If the filter returns falsey, the initiating event is ignored and no brush gesture is started. Thus, the filter determines which input events are ignored. The default filter ignores mousedown events on secondary buttons, since those buttons are typically intended for other purposes, such as the context menu.

# brush.touchable([touchable]) · Source

If touchable is specified, sets the touch support detector to the specified function and returns the brush. If touchable is not specified, returns the current touch support detector, which defaults to:

function touchable() {
  return navigator.maxTouchPoints || ("ontouchstart" in this);
}

Touch event listeners are only registered if the detector returns truthy for the corresponding element when the brush is applied. The default detector works well for most browsers that are capable of touch input, but not all; Chrome’s mobile device emulator, for example, fails detection.

# brush.keyModifiers([modifiers]) · Source

If modifiers is specified, sets whether the brush listens to key events during brushing and returns the brush. If modifiers is not specified, returns the current behavior, which defaults to true.

# brush.handleSize([size]) · Source

If size is specified, sets the size of the brush handles to the specified number and returns the brush. If size is not specified, returns the current handle size, which defaults to six. This method must be called before applying the brush to a selection; changing the handle size does not affect brushes that were previously rendered.

# brush.on(typenames[, listener]) · Source

If listener is specified, sets the event listener for the specified typenames and returns the brush. If an event listener was already registered for the same type and name, the existing listener is removed before the new listener is added. If listener is null, removes the current event listeners for the specified typenames, if any. If listener is not specified, returns the first currently-assigned listener matching the specified typenames, if any. When a specified event is dispatched, each listener will be invoked with the same context and arguments as selection.on listeners: the current event event and datum d, with the this context as the current DOM element.

The typenames is a string containing one or more typename separated by whitespace. Each typename is a type, optionally followed by a period (.) and a name, such as brush.foo and brush.bar; the name allows multiple listeners to be registered for the same type. The type must be one of the following:

  • start - at the start of a brush gesture, such as on mousedown.
  • brush - when the brush moves, such as on mousemove.
  • end - at the end of a brush gesture, such as on mouseup.

See dispatch.on and Brush Events for more.

# d3.brushSelection(node) · Source, Examples

Returns the current brush selection for the specified node. Internally, an element’s brush state is stored as element.__brush; however, you should use this method rather than accessing it directly. If the given node has no selection, returns null. Otherwise, the selection is defined as an array of numbers. For a two-dimensional brush, it is [[x0, y0], [x1, y1]], where x0 is the minimum x-value, y0 is the minimum y-value, x1 is the maximum x-value, and y1 is the maximum y-value. For an x-brush, it is [x0, x1]; for a y-brush, it is [y0, y1].

Brush Events

When a brush event listener is invoked, it receives the current brush event. The event object exposes several fields:

  • target - the associated brush behavior.
  • type - the string “start”, “brush” or “end”; see brush.on.
  • selection - the current brush selection.
  • sourceEvent - the underlying input event, such as mousemove or touchmove.
  • mode - the string “drag”, “space”, “handle” or “center”; the mode of the brush.

d3-brush's People

Contributors

alexmacy avatar dependabot[bot] avatar fil avatar mbostock avatar woutervh- avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

d3-brush's Issues

Disable Shift key behavior?

Is there a best-practices way to disable the shift key behavior?

In my app the brush is enabled only when the user holds shift. I.e. they hold shift and then drag to select nodes in a force layout. This means that event.shiftKey is always true during the brush drag. I understand that this is a feature of the brush, but if possible I would like to disable it for this use case.

I scanned through the code and saw that if I change this line to the following everything works fine:

shifting = false, // Disable `shifting` entirely

But I wasn't sure how I might hook into this functionality without modifying the source. Any suggestions would be much appreciated. Thanks!

brush.touchable

We don’t have an equivalent to drag.touchable or zoom.touchable for brushes, and we should, so that a touchstart listener is only registered when we want to support touch events. Likewise, we can use this flag to determine whether to set touch-action and -webkit-tap-highlight-color styles.

Swap "e" and "w" resize controls to align DOM with actual view

Right now "east resize" goes first (example from v3 but v4 has the same order):

image

Impact

When extending resize controls to be focusable, focus/screen reader still follows DOM order and as a result goes in reverse order:

brush-taborder2

Potential fix

Swap "e" and "w" in array of handles to align DOM order with actual view.

Select brushX or brushY based on mouse direction ?

In v4, I am trying to implement brush (to zoom selected area) for a line chart. I want the brush in X or Y direction to be selected based on the direction the mouse moves first.

If the mouse moves to left or right, brushX needs to be invoked and if mouse moves first in top or bottom direction, brushY needs to be invoked.

Is there a way to implement this?

Brush loses functionality if mouseup occurs outside frame

Typically clicking on a brush context results creates an "end" event with a null selection. When these conditions are seen the brush can be re-initialized to the entire range of the context area as is done in this example.

If a brush event is started but the user releases the mouse outside of the frame, then the brush event will still be going on when the user mouses back into the frame. The user then must click to end the brush event, but in doing so the brush selection rect disappears.

At this point clicking the brush context will no longer create an "end" event with a null selection to reset the brush selection to the entire range of the context area.

Weird behaviour when nesting brushes

I did go to stackoverflow first, but I think this one needs to be reported here.

I'm creating 2 nested brushes using d3. The inner brush is bound by the outer brush. I also limit the behavior of the brushes using filter function. Clicking on the outer brush selection creates the inner brush, and double clicking the selection of the inner brush removes it.

I see that the brushes behave perfectly in all but one instance.

If I squish the outer brush into the inner brush, and then try to alter the height of the inner brush, d3 is not respecting the extent of the inner brush. It seems like d3 is multiplying the height by 10, which seems to be causing this.

Below is the whole code:

<!DOCTYPE html>

<style>
.handle--w, .selection, .overlay {
  cursor: auto;
}

.brush2>.handle--n {
  cursor: auto;
}

.brush2>.selection {
  fill: red;
}

</style>
<svg width=1200 height=500></svg>

<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script type="text/javascript">
var rectHeight = 80, rectWidth = 180,
  svg = d3.select("svg"),
  mainGroup = svg.append("g");

var xScale = d3.scaleLinear()
    .domain([0,1])
    .range([0,rectWidth]);

// function for outer brush
var brush1 = d3.brushX()
    .extent([[0, 0], [rectWidth-10, rectHeight]])
    .handleSize([4])
    .filter(function(){
      // keep only the right side expansion/contraction
      return !d3.event.button
        && !d3.select(d3.event.target).classed("handle--w")
        && !d3.select(d3.event.target).classed("overlay")
        && !d3.select(d3.event.target).classed("selection")
        ;
    })
    .on("brush", brushed1)
    ;

// when brush1 is "move"d
function brushed1() {
  var w = d3.select(this).select(".selection").attr("width")-3;
  //console.log("in brushed1 -- "+w);
  if(!d3.select(this.nextSibling).empty()){
    // handle the inner brush, if present
    //console.log("found inner brush");
    var innerBrush = d3.select(this.nextSibling);
    brush2.extent([[0,0],[w,rectHeight]]);
    innerBrush.call(brush2);
    if (w < innerBrush.select(".selection").attr("width")){
      var innerHeight = innerBrush.select(".selection").attr("height");
      innerBrush.call(brush2.move, [[0,0],[w,innerHeight]]);
    }
  }
}

// function for inner brush
var brush2 = d3.brush()
  .handleSize([4])
  .filter(function(){
    return !d3.event.button
      && !d3.select(d3.event.target).classed("overlay")
      && !d3.select(d3.event.target).classed("selection")
      ;
  });

// create outer brush
mainGroup.append("g")
  .attr("class", "brush1")
  .call(brush1)
  .call(brush1.move, [0,0.65].map(xScale));

// add function for outer brush selection click
mainGroup.selectAll("rect.selection")
  .on("mousedown touchstart", function() {
    var w = d3.select(this).attr("width")-3;
    //console.log("Selection clicked  -- " + w);
    var p = d3.select(this.parentNode.parentNode);

    // Create inner brush if one doesn't exist
    if (d3.select(this.parentNode.nextSibling).empty()) {
      //console.log("creating element");
      //console.log(`w ${w} rectHeight ${rectHeight}`);
      brush2.extent([[0,0], [w,rectHeight]]);
      var innerBrush = p.append("g")
        .attr("class", "brush2")
        .call(brush2)
        .call(brush2.move, [[0,0],[w/2,rectHeight/2]])
        ;

      // remove inner brush if double clicked
      innerBrush.select("rect.selection")
        .on("dblclick touchstart", function(){
          //console.log("removing element");
          d3.select(this.parentNode).remove();
        })
    }
  });
</script>

handles created as <use> tags not working on IE 10

When I create handles with a <use> tag (.append("use")) there is an error on IE10 when I try to drag and drop the handle.

The error happens here (brush.js, 296):

type = event.target.__data__.type,

The object contained in event.target is slightly different on IE than on other browsers. It contains an extra level called correspondingUseElement This seems to be related to how IE and the other browsers implement SVG Elements.

This solves the problem, but I am not super satisfied with this solution:

type = typeof event.target.__data__ !== "undefined" ? event.target.__data__.type : event.target.correspondingUseElement.__data__.type,

Can someone explain me why there is this difference on IE and if is there another way to get it to work on IE?

Thanks

Double click to remove brush

Is there any way to disable single click to remove brush selection. Instead, the brush selection should be removed by double click event listener only.

How to fix one end of the Brush in D3.js?

I have a Slider created using D3 and want to fix one end of the slider to Starting of the Brush extent whereas want the other end to be draggable so user can change the selection.

I am able to disable the entire dragging but not sure how to keep dragging enabled but fix one end of the brush.

Any help is highly appreciated.

Thanks :)

SHIFT modifier?

Holding down the SHIFT key should lock to resizing along x or y based on the difference between the mouse position before SHIFT and the mouse position after SHIFT. (If the mouse moves more horizontally than vertically, resizing should be horizontal-only, e.g.)

Crash on move after brush.move sets selection to null.

Reproduction is here: https://jsfiddle.net/sxrxaxrL/1/

Lines 76-81:

    if(x1 === x2){
        //x2 = x1 + .1; // UNCOMMENT THIS TO FIX
    }
    var moveTo = [x(x1), x(x2)];
    console.log('move to = ' + moveTo);
    bg.call(brush.move, moveTo);   

Calling brush.move with x1 == x2 seems to work ok, but then in a d3 callback on a different call stack, it crashes:

d3.v4.js:12809 Uncaught TypeError: Cannot read property '0' of null

d3 source: (crash happens on the final line)

        selection = state.selection; // May be set by brush.move!

        if (lockX) w1 = selection[0][0], e1 = selection[1][0];
        if (lockY) n1 = selection[0][1], s1 = selection[1][1];

        if (selection[0][0] !== w1

If it's not allowed to call brush.move with x1 == x2, then at least it should throw an error immediately explaining this instead of later on a different call stack

Brush with handle

Trying to follow this example to add handle to the brush using:

brushEl.selectAll(".handle").append("path").attr("d", _handlePath)  //_handlePath generates the handle graphic

The path doesn't show up since .handle element is a rect instead of g. As soon as I change the d3-brush source code here to handle.enter().append("g") it works.

Am I doing something wrong or the .handle needs a wrapping g?

Thank you

Brush clear

Hello, I've been having issue while trying to clear brush in v4:

var selectRectangle = svg.append("g").attr("class", "brush");

var rectSelectionBehavior = d3.brush();
rectSelectionBehavior.on("end", function() {
    selectRectangle.call(rectSelectionBehavior.move, null);
});

selectRectangle.call(rectSelectionBehavior);

I've noticed that "end" handler keeps on firing to infinity, and brush keeps being drawn.
Any help is welcome, as I'm out of ideas. Thanks!

Unable to capture events before they are consumed by d3-brush

I understand that (since d3v4), d3-brush stops propagation of the events that it has handled, and that there are good reasons for this. However, I have been unable to use the usual rules of event propagation to get around this, as suggested in this comment on an issue raised when this new behaviour was introduced.

The behaviour I expect is that if I assign a capturing event listener on to the parent of an element upon which I have called a d3-brush object, that event should be handled before the propagation is stopped by d3-brush. This however does not work:

<!DOCTYPE html>
<meta charset="utf-8" />

<script src="https://d3js.org/d3-color.v1.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.js"></script>
<script src="https://d3js.org/d3-ease.v1.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.js"></script>
<script src="https://d3js.org/d3-timer.v1.js"></script>
<script src="https://d3js.org/d3-selection.v1.js"></script>
<script src="https://d3js.org/d3-transition.v1.js"></script>
<script src="https://d3js.org/d3-drag.v1.js"></script>
<script src="https://d3js.org/d3-zoom.v1.js"></script>
<script src="https://d3js.org/d3-brush.v1.js"></script>

<div id="parent">
  <svg id="child" width="200" height="200"></svg>
</div>

<script>
  function parentListener(event) {
    console.log("Hello from parentListener");
  }
  document
    .querySelector("#parent")
    .addEventListener("click", parentListener, true);
  d3.select("#child").call(d3.brush());
</script>

The parentListener is not fired. This is contrasted with the following example:

<!DOCTYPE html>
<meta charset="utf-8" />

<div id="parent">
  <svg id="child" width="200" height="200"></svg>
</div>

<script>
  function childListener(event) {
    console.log("Hello from childListener");
    event.stopImmediatePropagation();
  }
  function parentListener(event) {
    console.log("Hello from parentListener");
  }
  document.querySelector("#child").addEventListener("click", childListener);
  document
    .querySelector("#parent")
    .addEventListener("click", parentListener, true);
</script>

In this case, both listeners fire even though the childListener stops event propagation.

If this is not the behaviour I should expect, I apologize for misunderstanding and ask if there is any recommended way to use d3-brush along with other mouse event handlers for the events of which it stops propagation.

This Stackoverflow question from 2016 asks essentially exactly what I am asking here, except for d3-zoom. Unfortunately it has not been answered.

Thank you very much for you time.

How do you "clear" a brush?

So far the only way I've found to clear a brush is to do something like:

brushArea.node().__brush.selection = null;
brushArea.call(brushInstance)

Calling `brush.move` does not update the internal state's selection

When e.g. resizing a figure, we call brush.move on active brush selectors to adjust to the resized figure.

The internal state of the brush is not updated by the call to move causing the interval to jump upon drag.

Our current workaround is to make another call to brush() to update the internal state.

There is no way to disable clearing of brush

I was looking for solutions to disable clearing the brush when the background outside the brush extent area is clicked. But I couldn't find any and I have been trying various work arounds for it. Currently, there isn't any way to remember the previous brush extent area and not disable the brush on click outside the brush extent area.

defaultExtent not working in firefox

The current way of calculating the defaultExtent looks like this:

 function defaultExtent() {
    var svg = this.ownerSVGElement || this;
    return [[0, 0], [svg.width.baseVal.value, svg.height.baseVal.value]];
  }

In Chrome this works fine, but it seems that this does not work in firefox (and some mobile browsers aswell?), because svg.width and svg.height do not work as expected.

This causes my brush to not receive any mouse events at all (the overlay rect has dimensions 1x1 px).

FYI: In my case, I can get away with just hard-wiring the extent to something "big enough".

brush.extent does not appear to resize the invisible overlay

We expect (maybe wrongly) that a call to brush.extent would resize the invisible overlay capturing mouse events but it does not appear to be the case.

Our usecase for making such a call is to adjust the overlay when the figure is resized. We also move the brush to correspond to the previously selected values.

Custom handle causing confusing error

I have created a custom handle for my brush, following some of the V4 examples, but when I click on the handle to manipulate it when the selection has the same values, it gives an error.

EXCEPTION: Cannot read property '0' of null

If I go to the source, seems to be caused in this area of the d3 code, at the w0 variable area.

    if (type === "overlay") {
      state.selection = selection$$1 = [
        [w0 = dim === Y ? W : point0[0], n0 = dim === X ? N : point0[1]],
        [e0 = dim === Y ? E : w0, s0 = dim === X ? S : n0]
      ];
    } else {
      w0 = selection$$1[0][0];
      n0 = selection$$1[0][1];
      e0 = selection$$1[1][0];
      s0 = selection$$1[1][1];
    }

If I disable pointerevents for my custom handle, everything works fine with no error.

Maybe it's just an issue with the way I am trying to accomodate single date values for my selection, and to not have the selection area disappear.

Creating the brush items

this.brush = d3.brushX()
            .extent([[0, 0], [this.rootWidth, this.rootHeight / 2]])
            .on("start brush", this.brushmoved);

        this.brushGroup = this.root.append("g")
            .attr("transform", "translate(0," + this.rootHeight / 2 + ")")
            .attr("class", "brush")
            .call(this.brush);

        this.handle = this.brushGroup.selectAll(".handle-custom")
            .data([{ type: "w" }, { type: "e" }])
            .enter().append("path")
            .attr("class", function (d: any) {
                return "handle-custom " + d.type;
            })
            .attr("display", null)
            .attr("d", this.TREFOIL_HALF);

Updating my brush, on resizes or otherwise.

updateBrush = (ctx: DateSelectorComponent) => {
        this.brushGroup.call(this.brush.extent([[0, 0], [this.rootWidth, this.rootHeight / 2]]));
        this.brushGroup.call(this.brush.move,
            [this.selectedFromDate,
            this.selectedThroughDate].map(this.xScale));
    };

Updating my handles

    updateHandle = (ctx: DateSelectorComponent) => {
        this.handle
            .attr("transform", function (d, i) {
                let from = ctx.xScale(ctx.selectedFromDate);
                let through = ctx.xScale(ctx.selectedThroughDate);
                return "translate(" + (d.type === "w" ? from : through) + "," + ctx.rootHeight / 4 + ")" +
                    (d.type === "w" ? "scale(1.25, 1.25)" : "scale(-1.25, 1.25)");
            });
    };

brushmoved function

    brushmoved = () => {
        // make sure date range is updated and handles are moved to match the selection extent
        if (!d3.event.sourceEvent || d3.event.sourceEvent.type === "brush") return;
        this.updateDates(this);
        this.updateHandle(this);
    };

Everything works fine except clicking on my custom handle area again.
rangeselector

Handle orphaned gestures.

Reproduction: https://jsfiddle.net/njjd40vj/1/ (modified from http://bl.ocks.org/mbostock/b0d0aa4df3b5c3c0fa37d4b3f2127740 just to log start/end events)

IE 11:

  1. open console, brush a bit, notice "start" and "end" brush events get logged
  2. mouse down, mouse off browser,
  3. mouse up. start is logged, but end is not
  4. mouse back on browser, try to keep brushing. start and end events no longer get logged.

Chrome:

Change 3) to be
3) mouse over another window. right click (left button is still down). end is not logged and start/end events no longer fire

Brush losing event handlers

I'm seeing a possibly related issue to #18, but not sure so filing separately. I modified the following b.lock to include start/end logging:

http://bl.ocks.org/zelitzsi/8b72cbd79863f04b5bcf7cb5d1e52320

If I brush in succession without letting the previous brush transition finish, I get the same 'start' event without 'end' event that #18 is seeing. However, this is repeatable across Firefox/Chrome on Mac. Interrupting the transition in this way breaks the brush - snapping doesn't occur and no brush events fire. Perhaps there is something about brush.move that breaks the previous transition?

[Feature Request] Use transform attribute to position all shape elements instead of x and y attributes.

While building custom handles for a brush I noticed an interesting side-effect where a custom handle is assigned an x and y attribute that automatically updates (relative to the brush extent) as long as the shape element has a "handle" class and an appropriate value for the "cursor" attribute.

For shape elements such as <rect> or <circle> this side-effect is a boon as they support the x and y attribute (no need to set the position manually). However this does not hold for <path> elements which do not use x or y attributes according to the SVG spec. As a result, path-based shapes cannot benefit from these automatic position updates (which also affects the use of d3.symbol types since they are all path-based shapes).

The current workaround (as seen in this sample) is to update the position of a custom handle in a brush event listener.

But I think there is a better approach. What path elements do share with shape elements is a transform attribute. Replacing the x & y attributes with a translate transform would allow path elements to benefit from these automatic position updates relative to the brush selection's extents; removing the need to update position in an event handler.


Another thing. When a child element of a brush (classed) element has children of its own; its position (x and y) attributes are not set. This becomes problematic with <g> elements where one might want to retain any automatic transformations applied at the group level.

So in addition to generalizing position updates by leveraging translate transforms, please retain transforms for child <g> elements contained within a brush classed element.

Translate d3.svg.brush code to d3-brush?

Trying out the version of this library pushed to npm yesterday. My code, like most d3 brush code to control the time axis of a chart, looks more or less like this:

var timeScale = d3.time.scale()...
var brush = d3.svg.brush()
    .x(timeScale)
    .on("brush", brushed);

There doesn't seem to be an x(scale) method on brush anymore. Is there an example anywhere?

My actual code is here.

Support multitouch?

We don’t currently handle multitouch very well. It would be better to at least ignore anything other than the first touch.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.