Adding a drag icon to Focus+Context via Brushing Chart - d3.js

I am trying to adapt Mike Bostock's Focus+Context via Brushing chart at: bl.ocks.org/mbostock/1667367 ‎ to include a drag icon on both vertical lines of the brush rectangle. These should appear once a selection is made and act as a visual cue to shrink or expand the selected/brushed area. I see the placement of the images being dynamic i.e. moving fluidly with the brushed area as opposed to an update after the brushed area is reset. What seems most reasonable to me would be to add an svg image to the context rectangle like so:
//original code
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7)
//additional code
.append("svg:image")
.attr("xlink:href", "icon.png")
.attr("width", 10)
.attr("height", 10)
.style("opacity",1)
I've tried playing around with the x and y positioning of both images with no luck getting them to appear, but i conceptually see it working as
y axis: height of context chart divided by 2
x axis: each image respectively corresponding to the min and max x values of the brushed area
Any help would be appreciated.

Lars, thanks for the pointer which generally led me in the right direction although I ended up directly adapting from an example at https://engineering.emcien.com/2013/05/7-d3-advanced-brush-styling. That example is a bit more complex so i borrowed from a subset relevant to my purposes.
Steps i followed include
1.) Creating two images appended to the context g element and initializing their position somewhere that doesn't give the impression that the chart is brushed on loading {i put them halfway (vertically) and close together around the end of the context(horizontally)}.
var leftHandle = context.append("image")
.attr("xlink:href", "icon.gif")
.attr("width", 11)
.attr("height", 27)
.attr("x",x2(data[data.length-6].date))
.attr("y", (height2/2)-15);
var rightHandle = context.append("image")
.attr("xlink:href", "icon.gif")
.attr("width", 11)
.attr("height", 27)
.attr("x",x2(data[data.length-1].date))
.attr("y", (height2/2)-15);
2.) Within the brush function, i created a variable to hold brush.extent(), then tied the x attributes of the images to its min and max x values.
var ext = brush.extent();
leftHandle.attr("x",x2(ext[0]));
rightHandle.attr("x",x2(ext[1]));
One things i'm not completely satisfied with is that when i initially created the images after the brush rectangle, they sat on top of it, preventing me from being able to brush if i hovered over the images (which is the intuitive reaction desired). I ended up placing the images behind the rectangle which has a low opacity. Not the 100% accurate visual representation sought, but it works just fine.

Related

How does Mike Bostock's (D3js's creator) pan & zoom example work?

I'm having a hard time understanding this example:
https://bl.ocks.org/mbostock/4e3925cdc804db257a86fdef3a032a45
I understand the general concept, all the points in the graph are put into a svg g-element, which is useful because now you can apply a transform on the group and have all the points scale accordingly. This transforms the whole coordinate system that these points are in. But this does not matter for the mouse-coordinate that is passed along for panning because those coordinates come from an invisible rect in the svg that does not get transformed.
What I don't understand is this:
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(d3.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed));
function zoomed() {
g.attr("transform", d3.event.transform);
}
Is the .call a member function of the default svg-rectangle? What does it do here? And d3.zoom is according to the documentation a 'behaviour'. What does that mean and how does the coupling between these two work here? And what is the purpose of the function .on, which gets passed 'zoom'? I assume it's for passing the browser event of zooming on an element to the event handler zoomed. But then how does panning get handled here? And is it just me or would it have had more sense to have .scaleExtent and .on as parameters of d3.zoom()?
The rect receives the browser zoom events.
The zoomed function applies the transform to the g element
Note we have the rect after the g otherwise the rect would behind the g not receiving events.
svg
- g
- rect

Is it possible to create a d3/svg circular canvas/container?

I'm trying to implement a circular canvas in d3. I've tried editing this example of a rectangular canvas...
http://jsfiddle.net/k92end80/1/
But whenever I add an svg:circle it simply blocks any nodes behind it - does anyone know if this is even possible in d3/svg?
I was naively thinking I could just replace the below attributes with a cx, cy, r attribute...
var container = d3.select("body")
.append("svg")
.attr("id", "svgcontainer")
.attr("width", 300) //replace with a cx
.attr("height", 300) //replace with a cy
//add a r attr
.style("background-color", "#aaaaee")
.call(zoom)
.append("g");
Thanks for any help!
The SVG 1.1 spec tells us:
The canvas is infinite for each dimension of the space, but rendering occurs relative to a finite rectangular region of the canvas. This finite rectangular region is called the SVG viewport.
Taking that for granted neither d3 nor any other framework will be able to establish a canvas or viewport of any other shape.

D3.js, position elements horizontally

I'm a D3.js newbie, so I beg your pardon if I ask something too basic.
I have a simple array as a dataset:
[0, 10, 15, 20, 24, 35, 58]
For every element of my array I want to create a circle and horizontally put those circles at the same distance (12 pixel) starting from x=10 (and y being constantly 50). How can I do?
Thanks in advance.
Basic things you need to know about d3.js is, it binds data to svg elements and creates visualization.
To get to the basics of d3. You can google around.
Here are some sites that I prefer for d3 learning.
d3 - Ofcourse the official website. With all the codes and examples.
Dashing D3 - Free for basic and you can also get a paid extension for further d3 tutorials.
d3noob - You get a free PDF with all the line by line briefing in the PDF. The one I prefer the most for the beginners.
Here's the answer for the question - fiddle.
It's very simple on creating circles in d3.
Here's the code I've used.
var svg = d3.select('.circle')
.append('svg')
.attr('height', 500)
.attr('width', 500)
First off I append the svg element to the .circle div.
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr("cx", function(d, i) { return 60 * i })
.attr("cy", 60)
.attr("r", 20)
And then I add the circles depending on the number of data using the above block of code.
Note : .attr is that I'm adding attribute.
Here I've fixed the radius to 20.
But you can change the radius according to the data like this. function(d) { return d; }.
And ofcourse you can color the circles by using .style("fill", "#ddd") and other css stuffs using .style().
You can get more of this info from the websites I've provided.
Hope this helps.

draw a grid or rectangles using a scale

I'm building my first line graph in d3:
http://jsfiddle.net/j94RZ/
I want to know how to utilize either the scale or axis allow me to draw a grid (of, presumably rectangles) where I can set a different background colour for each of the section of the grid...so I can alternate colours for each cell of the grid. I want the grid to be drawn and be constrained by the axes of my graph and then also adapt if the spacing of the axes ticks change (i.e. the axes changes like this: http://bl.ocks.org/mbostock/1667367). So if my graph has an x axis with 4 ticks and a y axis of 7 ticks then my graph will have a background grid that's 7 blocks high and 4 blocks wide.
I've been playing with the idea of using a range which starts at zero and ends at the full width of the graph but I don't know what value I can use for the step. Is there any way to sort of query the axis and return how many ticks there are?
var gridRange = d3.range(0, width, step?);
A better approach than your current solution would be to use scale.ticks() explicitly to get the tick values. The advantage of that is that it will still work if you change the number of ticks for some reason.
To get an alternating grid pattern instead of a single fill, you can use something like this code.
.attr("fill", function(d, i) {
return (i % 2) == 1 ? "green" : "blue";
})
Finally, to get the full grid pattern, you can either use an explicit loop as you've suggested, or nested selections. The idea here is to first pass in the y ticks, create a g element for each and then pass the x ticks to each one of these groups. In code, this looks something like this.
svg.selectAll("g.grid")
.data(y.ticks()).enter().append("g").attr("class", "grid")
.selectAll("rect")
.data(x.ticks()).enter().append("rect");
To set the position, you can access the indices within the top and bottom level data arrays like this.
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d, i, j) {
return yScale(j);
})
To set the x position, you need the index of the inner array (passed to the set of g elements), which can be accessed through the second argument of your callback. For the outer array, simply add another argument (j here).
And that's really all there is to it. Complete jsfiddle here. To update this grid dynamically, you would simply pass in the new tick values (gotten from scale.ticks()), match with the existing data, and handle the enter/update/exit selections in the usual manner.
If you want to do without the auxiliary scales (i.e. without .rangeBand()), you can calculate the width/height of the rectangles by taking the extent of the range of a scale and dividing it by the number of ticks minus 1. Altogether, this makes the code a bit uglier (mostly because you need one fewer rectangle than ticks and therefore need to subtract/remove), but a bit more general. A jsfiddle that takes this approach is here.
So after a few helpful comments above I've got close to a solution. Using Ordinal rangebands get me close to where I want to go.
I've created the range bands by using the number of ticks on my axis as a basis for the range of the input domain:
var xScale = d3.scale.ordinal()
.domain(d3.range(10))
.rangeRoundBands([0, width],0);
var yScale = d3.scale.ordinal()
.domain(d3.range(4))
.rangeRoundBands([0, height],0);
I've then tried drawing the rectangles out like so:
svg.selectAll("rect")
.data(p)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d,i) {
0
})
.attr("width", xScale.rangeBand())
.attr("height", yScale.rangeBand())
.attr("fill", "green").
attr('stroke','red');
This gets me the desired effect but for only one row deep:
http://jsfiddle.net/Ny2FJ/2/
I want,somehow to draw the green blocks for the whole table (and also without having to hard code the amount of ticks in the ordinal scales domain). I tried to then apply the range bands to the y axis like so (knowing that this wouldn't really work though) http://jsfiddle.net/Ny2FJ/3/
svg.selectAll("rect")
.data(p)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d,i) {
return yScale(i);
})
.attr("width", xScale.rangeBand())
.attr("height", yScale.rangeBand())
.attr("fill", "green").
attr('stroke','red');
The only way I can think to do this is to introduce a for loop to run the block of code in this fiddle http://jsfiddle.net/Ny2FJ/2/ for each tick of the y axis.

How to scale and translate together?

I want to scale and translate D3 force graph, both at the same time. E.g. On clicking a button it shoud scale to 400% and then make itself center on the screen. This should all happen with a smooth animation effect.
//animate vis to visible area
vis.transition()
.duration(2000)
.attr("transform", "scale(" + someScaleValue + ")" + "center("0,0)");
Doing this, scaling works fine, but graph is not centered. It shifts towards right-bottom corner.
vis.transition()
.duration(2000)
.attr("transform", "scale(" + someScaleValue + ")");
Why is scale is getting reset to 100% when I translate it second time.
I also tried using:
vis.transition()
.duration(2000)
.attr("transform", "scale(" + scaleValue + ")" + "translate(0,0)");`
This is not working too. Please help me.
center(0,0) is not a valid transform-definition to be used with transform, as per the spec.
If you want translate(0, 0) to take the object to the center of the screen (usually the top-left corner of vis), then you might want to set viewBox of the outer svg element to be: "-width/2 -height/2 width height". This would set the co-ordinate system inside the svg element such that the center lies at (0, 0). Alternatively, you can use translate(width/2, height/2).
Also, each time you call .attr('transform', ...), you overwrite the old value of the transform attribute. This is the possible reason why you are losing the original scaling on translating. The best solution would be to put the vis element inside a g which has the scaling in the transform attribute which remains constant.

Resources