13

I am working on this plnkr. I have three lines at angle 30, 45 and 60. I want to apply a brush on these lines so that when the chart is brushed the lines get redrawn at where it crossed the brushed rectangle with appropriate the values on the axis. Any help or hint to solve this problem is greatly appreciated.

EDIT: If you have different solutions to draw the rotated lines and brush on top of them it is welcomed too. Please help.

var ga = d3.select("svg")
    .append("g")
    .attr("class", "a axis")
    .attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
    .selectAll("g")
    .data([30, 45, 60])
    .enter()
    .append("g")
    .attr("class", "rotatedlines")
    .attr("transform", function(d) { return "rotate(" + -d + ")"; })
    .attr("stroke-width", 1)
    .attr("stroke", "black")
    .attr("stroke-dasharray", "5,5");

Example result

approxiblue
  • 6,982
  • 16
  • 51
  • 59
somename
  • 978
  • 11
  • 30
  • Do you want to overwrite the existing chart with the new zoomed view, similar to the [SE reputation graph](http://stackexchange.com/users/4536689/d3-gxt-java?tab=reputation)? – approxiblue Oct 06 '15 at 17:46
  • yes - I want to overwrite exactly like you shown in the example. – somename Oct 06 '15 at 17:51
  • In you example you have made lines using rotation 30,45,60...is this the real dataset, or do you have some real x(date) and y(points) values like in here http://stackexchange.com/users/4536689/d3-gxt-java?tab=reputation – Cyril Cherian Oct 09 '15 at 06:51
  • When the chart loads the lines are drawn at the angle (30,45,60) that is my requirement - but when brush event happens the chart should redraw and show the rotated lines at where it intersected brushed area – somename Oct 09 '15 at 16:17
  • hmmm so there is no x domain or y domain – Cyril Cherian Oct 10 '15 at 00:17
  • In my comment i mean that is the angle the only input ...you did not mention anything about the start and end point of line is that not a part of the input? – Cyril Cherian Oct 10 '15 at 14:24
  • The final solution is at this location. http://plnkr.co/edit/PQWVMh7RNNtAMzn4dZjO?p=preview . Thank you. – somename Oct 10 '15 at 17:38

2 Answers2

5

To explain my solution:

The fundamental steps to take are as follows:

  • update the domains of the x and y scales to the brush extent
  • redraw the axes
  • compute the scale factor and translation for the lines
  • scale and translate the line containers accordingly
  • reset the brush

Note that steps 3 and 4 are only necessary because you're not using the scales to draw everything -- a better approach would be to define two points for each line as the data that's bound to the elements and then use the scales to redraw. This would make the code simpler.

With your approach it's still possible though. In order to facilitate it, I've made a few modifications to your code -- in particular, I've cleaned up the various nested g elements with different translations and defined the lines through their x1, x2, y1, y2 attributes rather than through translation of the containers. Both of these changes make the functionality you want easier to implement as only a single transformation takes places that doesn't need to consider multiple other transformations. I've also nested the lines in multiple g elements so that they can be scaled and translated more easily.

The brush handler function now looks like this:

// update scales, redraw axes
var extent = brush.extent();
x.domain(brush.empty() ? x2.domain() : [ extent[0][0], extent[1][0] ]);
y.domain(brush.empty() ? y2.domain() : [ extent[0][1], extent[1][1] ]);
xAxisG.call(xAxis);
yAxisG.call(yAxis);

This code should be fairly self-explanatory -- the domains of the scales are updated according to the current extent of the brush and the axes are redrawn.

// compute and apply scaling and transformation of the g elements containing the lines
var sx = (x2.domain()[1] - x2.domain()[0])/(x.domain()[1] - x.domain()[0]),
    sy = (y2.domain()[1] - y2.domain()[0])/(y.domain()[1] - y.domain()[0]),
    dx = -x2(x.domain()[0]) - x2.range()[0],
    dy = -y2(y.domain()[1]) - y2.range()[1];
d3.selectAll("g.container")
  .attr("transform", "translate(" + [sx * dx, sy * dy] + ")scale(" + [sx, sy] + ")");

This is the tricky part -- based on the new domains of the scales, we need to compute the scale and translation for the lines. The scaling factors are simply the ratio of the old extent to the new extent (note that I have made copies of the scales that are not modified), i.e. a number greater than 1. The translation determines the shift of the (0,0) coordinate and is computed through the difference of the old (0,0) coordinate (I get this from the range of the original scales) and the position of the new domain origin according to the original scales.

When applying the translation and scale at the same time, we need to multiply the offsets with the scaling factors.

// reset brush
brush.clear();
d3.select(".brush").call(brush);

Finally, we clear the brush and reset it to get rid of the grey rectangle.

Complete demo here.

Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204
2

You can access the brush extent via d3.event.target.extent(). The flow for drawing the scale is this:

  • Set scale
  • Set axis
  • Draw axis

As soon as the brush is done, you have to modify the scale and then re-draw the axis according to the current x and y domain. Is that what you meant?

I cleaned up the code a bit and made a little demonstration: http://plnkr.co/edit/epKbXbcBR2MiwUOMlU5A?p=preview

Rudolf
  • 1,856
  • 2
  • 19
  • 32
  • Thank you for your answer.. I need to redraw the rotated lines as well - where it intersected the brushed area. If the brush area does not intersect rotated lines it should not appear on the redrawn chart. Do you have any idea how to accomplish that? – somename Oct 09 '15 at 16:24
  • That's the simple part, I thought you might have solved that already :-) See this version: http://plnkr.co/edit/Aw6Gc1PNGtiVSWXNaJU2?p=preview On line 116. I split the canvas and the lines, then you can just scale the `g` element with something like `transform="translate(-50, 470) scale(2)"`. – Rudolf Oct 09 '15 at 20:33
  • Btw, d3 also has a zoom library that you could borrow the maths from. – Rudolf Oct 09 '15 at 20:33
  • translate(-50,470) being x0 and y0? See this plnkr http://plnkr.co/edit/ByyHVEzf5iRWNlDa2Lru?p=preview at line 156 I added the translate transform function. I am still missing how can we re-position the rotated lines? – somename Oct 09 '15 at 21:09
  • Think of the `g` element as your phone's screen. You can zoom in on the graph by pinching and pan around to adjust it correctly. That's exactly what you have to do - zoom in with `scale( factor )` and position it correctly with `translate( x, y )`. Do you understand what I mean? But to get to the correct position (the one your brush selects) you will need to to a little research :-) That's what I meant with the `zoom` behaviour: https://github.com/mbostock/d3/wiki/Zoom-Behavior They did these kind of computations in there, maybe the source code of this will help you. – Rudolf Oct 10 '15 at 06:39
  • ok you mean something like this http://plnkr.co/edit/PQWVMh7RNNtAMzn4dZjO?p=preview. – somename Oct 10 '15 at 17:41
  • Yep, exactly! You just need to refine it a bit, then you're done :-) – Rudolf Oct 11 '15 at 09:06