responsive d3 area graph stretches circle interaction points - javascript

I have been experimenting with a simple d3 google analytics-style area graph demo. I'd like to make it stretch to the full width of its container, which I have managed to do. However the little circles are of course stretching out of shape too. I'd like their positions to be responsive, but not their dimensions (so they remain circular).
Fiddle link: http://jsfiddle.net/46PfK/2/
I'm trying to use the SVGPanUnscale.js script. I have tried calling it with unscaleEach('.dot'); and [].forEach.call($('.dot'),unscale); but neither appear to do anything.
This example is responsive in a similar way to mine and uses the script to 'unscale' the axis labels: http://meloncholy.com/static/demos/responsive-svg-graph-1/
This example also uses circle elements:
http://phrogz.net/svg/scale-independent-elements.svg
I looked at solutions involving the css attribute:
circle {
vector-effect: non-scaling-stroke;
}
which created a circular stroke on an ellipse - weird.
A CSS solution would be preferable to a JS one, provided it works across browsers.
Any suggestions?

Thanks to #leMoisela for pointing me in the right direction. I fixed my issue nicely using JS to redraw the graph on resize:
http://jsfiddle.net/46PfK/4/
window.onresize = function(e){
draw_graph();
};

There's a good example on resizing with D3 https://blog.safaribooksonline.com/2014/02/17/building-responsible-visualizations-d3-js/
After you update your scales, only thing left to resize your circles would be something like this:
/* Force D3 to recalculate and update the points */
svg.selectAll('circle')
.attr('cx', function (d) { return xScale(d.x); })
.attr('cy', function (d) { return yScale(d.y); })
.attr('r', 4);

Related

Vertical line in D3 plot move on zoom/pan

I am trying to draw a vertical line marker in my graph in D3. It is modeled off of this example: https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
My issue is that after I draw my line, it doesn't move as I zoom/scroll the graph. An example is shown below:
Currently, I have it calculated as a d3.area().
this.pastDateArea = d3.area()
.x(function(d) { return this.x(this.props.pastDate.toDate()) }.bind(this))
.y0(0)
.y1(function(d) { return this.height }.bind(this))
It is appended as
var pastDateData = [{x:this.props.pastDate.toDate(), y:150}]
this.focus.append("path")
.datum(pastDateData)
.attr("class", "area")
.attr("d", this.pastDateArea)
and zoomed/brushed using
//zoom
var t = d3.event.transform;
this.x.domain(t.rescaleX(this.x2).domain());
//brush
this.svg.select(".zoom").call(this.zoom.transform, d3.zoomIdentity
.scale(this.width / (s[1] - s[0]))
.translate(-s[0], 0));
I know there are similar questions to this one (namely, Draw a vertical line representing the current date in d3 gantt chart) but none of them include the zooming/panning features I have in my graph.
Please let me know if you need more information and thanks!
The issue is that you are not updating the vertical bar with each zoom event. Using the code of the example you show, several things are done when the chart is zoomed, including as you note:
x.domain(t.rescaleX(x2).domain()); // update x scale
focus.select(".area").attr("d", area); // redraw chart area
While you do give the new area the class of area, d3.select will only pick the first matching element. So, on zoom, only one .area element is updated (the first encountered, generally the first appended). But, replacing this with d3.selectAll(".area") will not generate the intended results as the area function referenced (.attr("d",area) ) is only used for the first area (that of the graph, not of the vertical bar).
A solution is to select each area (the chart and the bar) independently and update the area with their respective area generators. To do so, append the vertical bar with a unique class name, or an id and use that to select it later. Then when updating the graph on zoom or brush you can use:
x.domain(s.map(x2.invert, x2)); // update x scale
focus.select(".area").attr("d", area); // redraw chart area
focus.select(".bar").attr("d", pastDateArea);// redraw vertical bar
Remember that this needs to be done for both zoom and brush. Also, in the given example, a clip path is assigned in the css for .area, so you need to keep that in mind as well.
Here's a modified example.

Zoomable sunburst chart in percentages

I'm new to D3 and I hope you can help out. I'm working on a sunburst chart divided into inner and outer layers, where inner layer represents a group and outer layers represent subgroups. Here's a working example for reference: jsfiddle.net/9gpL308y/1/ (and here's the original fiddle I used as a starting point: jsfiddle.net/j9WnB/64/)
Currently, each inner layer adds up to a number based on its categories' values in the outer layer and categories behave the same with their subcategories. What I need is for each layer to display as percentage (out of 100 that all groups on the same layer should add up to) and scale to appropriate size. Take this image for example:
image of the wanted chart
Working example would be extremely helpful. I found some topics on this problem but I couldn't get it to work with provided advice. To be honest I still don't understand fully what's going on in here (I was never good at geometry).
tl;dr: How to make sunburst chart from the fiddle above display data as percentage and have arcs scale appropriately?
Thanks.
There is a little to do with geometry in this case (except positioning your label, but this is what arc.centroid provides). To get your percentages you just need to divide an extent of the child node by the extent of its parent.
var center = arc.centroid(d);
g.append("text")
.attr("text-anchor", "middle")
.attr("transform", "translate(" + center + ")")
.text(function(_) {
if (d.parent == null)
{
return;
}
var percentage = 100 * d.dx / d.parent.dx; // that's it!
return d3.format(",.2f")(percentage) + "%";
})
Working example here: http://jsfiddle.net/9gpL308y/2/
For educational purposes you can dump all the nodes of the partition layout and then find how do they match visual picture. You can also read about those parent, dx, etc. at the documentation on partition layout: https://github.com/d3/d3-3.x-api-reference/blob/master/Partition-Layout.md

d3 drag behavior of a node in a force layout with semantic zoom not working as expected

I have created a force layout as shown in this plunker and have implemented semantic zooming as defined by Mike Bostock. That side of things works perfectly in my layout, however when I come to drag a node after zooming (in or out) it doesn't move as expected. When zoomed in, it moves more than the mouse moves, and when zoomed out, it moves less than the mouse. This make me think it's a scaling issue either in the tick function or in the zoom function. I don't think it has anything to do with the dragged function as the code here has no bearing on the position of the node. I.e:
function dragged(d) {
var mouse = d3.mouse(svg.node());
//account for translation and scale
d.x = X.invert(mouse[0]);
d.y = Y.invert(mouse[1]);
tick()
}
has the same effect as:
function dragged(d) {
tick()
}
I have gone through AmeliaBR very comprehensive answer about semantic zooming found here and a whole load of other examples found online and here on SO, but I cannot figure out this bug! Can someone else see what I am doing wrong?

How do I override d3.js chart function behavior?

There is a popular library in d3.js that provides a bullet chart implementation. I am in the process of making a small amendment to the default behaviour of this chart.
A bullet chart consists of one or more markers, parallel lines to identify targets, and I wish to programatically make one of the markers slightly shorter in length than the other. I'm pretty close to the solution and I have shared my work at Bullet Fiddle. To see my issue you have to hit the Year and Quarter buttons and notice how that the line shrinks and grows and then shrinks again. I want it to remain at the shorter length.
It seems that the default behaviour of the bullet is trigged at each update which is causing it to reset the marker to it's original size. I would love some help to understand how I can override this default behaviour and keep the line at a custom size.
This is a sample of the code that I use to update the marker:
d3.selectAll(".bullet .marker.s1").attr("y1", 10).attr("y2", 35);
This is the code in the library that is causing your changes to be overwritten:
marker.transition()
.duration(duration)
.attr("x1", x1)
.attr("x2", x1)
.attr("y1", height / 6) // the y1 and y2 values get updated here
.attr("y2", height * 5 / 6);
So the easiest way would just be to fork the library and delete the couple of lines. Otherwise, I didn't see that the marker definitions get exposed as part of the chart for you to remove the transition.

D3.js - Data and Axis Not Synchronized During Pan

I created a scatter plot using D3.js that updates every 20 seconds. It also pans and zooms. The problem is the data lags behind the axis during the pan. I've looked for examples of a similar implementation but all I can find are ones that either do zoom/pan or do intervals, not both. I can't find the source of the problem. A simplified demo of my code can be found here: http://jsbin.com/yurik/1/edit. Any help is appreciated.
The synchronization issue comes from the fact that you are using a transition to move the circles and not using a transition to update the x-axis. Here's the relevant snippet from the draw function:
circles.transition()
.attr("cx", function(d) { return x(dateFn(d)) })
.attr("cy", function(d) { return yValueFn(d) });
svg.selectAll("g.x.axis").call(xAxis);
Because D3 has a default transition duration of 250 milliseconds, the circles are lagging behind the axis, which is updated instantly. You can synchronize the two by reducing the transition duration to 0 like this:
circles.transition().duration(0)
That should make the x-axis and circles move synchronously.

Categories