Git Product home page Git Product 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.

Resources

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  avatar

d3-brush's Issues

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.

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.)

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.

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.

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)

[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.

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

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.

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.

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.

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.

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>

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?

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 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?

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

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

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.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.

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 :)

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!

Support multitouch?

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

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

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.

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

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.