Totem
Totem

Reputation: 474

NVD3 chart controls don't work when hooking mouse events on svg elements

I have an area chart in nvd3:

var chart = nv.models.stackedAreaChart()
    .x(function (d) { return d[0] })
    .y(function (d) { return Math.round(d[1]) })
    .clipEdge(true)
    .showControls(true)
    .useInteractiveGuideline(true);

As you can see, I have enabled showControls, which displays three small buttons (Stacked, Stream and Expanded) in the top left corner of the chart.

Since it was desired to select subsections of the chart by dragging the mouse over, I implemented the following solution by hooking up mouseup, mousedown and mousemove events on the SVG element that contains the chart.

    var mouseDown = false;
    var mouseDownCoords;
    var rect = svg.append("rect")
        .attr("x", 0).attr("y", 0)
        .attr("width", 0).attr("height", 0)
        .attr("fill", "rgba(43,48,87,0.3)");

    svg.on('mousedown', function () {
        var height = svg[0][0].height;

        mouseDownCoords = d3.mouse(this);
        mouseDown = true;

        rect.attr("x", mouseDownCoords[0]);
        rect.attr("height", height.animVal.value);

        // Register mousemove when the mouse button is down
        svg.on('mousemove', function () {
            var coords = d3.mouse(this);
            rect.attr("width", Math.max(coords[0] - mouseDownCoords[0], 0));
        });
    });

    svg.on('mouseup', function () {
        if (mouseDown) {
            var coords = d3.mouse(this);
            var width = Math.max(coords[0] - mouseDownCoords[0], 0);

            mouseDown = false;
            rect.attr("width", 0);

            if (width > 0) {
                var totalWidth = svg[0][0].width.animVal.value;
                var totalPeriod = dateTo.getTime() - dateFrom.getTime();
                var newDateFrom = new Date(Math.floor(dateFrom.getTime() + totalPeriod * mouseDownCoords[0] / totalWidth));
                var newDateTo = new Date(Math.floor(newDateFrom.getTime() + totalPeriod * width / totalWidth));
                window.setSearchTimeframe(newDateFrom, newDateTo);
            }
        }

        // Unregister mousemove
        svg.on('mousemove', null);
    });

However, registering these event callbacks stops the control buttons from working. When I click on them, nothing happens, even if the pointer correctly changes when I hover them.

Upvotes: 1

Views: 286

Answers (1)

Luc
Luc

Reputation: 534

You're right, registering events on elements outside NVD3's built-in event system really seems to destroy things internally (which shouldn't be the case, in my opinion). You could work around this by positioning an invisible element over the part of the chart that needs custom behaviour.

Demo

The red rectangle is the part of the chart with custom behaviour (click it).

var chartElement = d3.select("#chart svg");
var chart;

nv.addGraph(function() {
  chart = nv.models.pieChart()
    .x(function(d) {
      return d.label
    })
    .y(function(d) {
      return d.value
    })
    .showLabels(true);

  var chartData = [{
    label: "Foo",
    value: 67
  }, {
    label: "Bar",
    value: 33
  }];

  chartElement
    .datum(chartData)
    .call(chart);

  $("#customUI").on("mousedown", function() {
    alert("Some custom behaviour...");
  });

  return chart;
});
#wrapper {
  position: relative;
}
#chart {
  position: absolute;
  height: 500px;
}
#customUI {
  position: absolute;
  background: red;
  opacity: 0.2;
  width: 100px;
  height: 100px;
  left: 100px;
  top: 200px;
}
#customUI:hover {
  opacity: 0.5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.css" rel="stylesheet" />
<div id="wrapper">
  <div id="chart">
    <svg>
    </svg>
  </div>
  <div id="customUI">
  </div>
</div>

Upvotes: 1

Related Questions