nvd3 multiBarChart with line along x axis - d3.js

I need to add a line with multiple values along the x axis of a multiBarChart. I cannot use another nvd3 control like multiChart or linePlusBarChart due to existing functionality breaking if i switch.
The following code and fiddle show what i have so far.
var limits = [[60,166990904656],[300,154990904656],[500,142990904656]];
var lineFunction = d3.svg.line()
.x(function(d) { return d[0] })
.y(function(d) {
d = d[1] / 1000000000;
return d
}).interpolate('step-after');
//The line SVG Path we draw
d3.select("#chart svg")
.append("path")
.attr("d", lineFunction(limits))
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("fill", "none");
https://jsfiddle.net/s2vemzht/11/
I am facing 3 issues: First is the x axis placement of the line. At the moment i have hardcoded this value into the limits array because i am unsure how to position it dynamically based on where the next bar starts.
The second issue is the limit value in relation to the other values in the data array. It does not seem accurately positioned.
The third issue is with the line not drawing over the 3rd bar even though there are 3 values in the limits array. I tried changing the interpolate property but it's still an issue.
I am a beginner with D3 so apologies for all the questions :-)

I managed to figure it out by drawing lines over the bars at particular points and positioning based on the y and x axis scales
var yValueScale = chart.yAxis.scale(),
yValue = 0,
xValueScale = chart.xAxis.scale(),
g = d3.select("#chart svg .nvd3");
for(var i=0;i<limits.length;i++){
g.append("line")
.attr("class","limit-line")
.attr("x1", xValueScale(d3.time.format('%b-%d')(new Date(limits[i][0]))))
.attr("y1", yValueScale(limits[i][1]/ 1000000000))
.attr("x2", xValueScale(d3.time.format('%b-%d')(new Date(limits[i][0]))) + parseInt(d3.select("#chart svg .nv-bar").attr("width")))
.attr("y2", yValueScale(limits[i][1]/ 1000000000))
;
}
https://jsfiddle.net/s2vemzht/25/

Related

Adding a y axis horizonal line to a D3 scatter chart

I have created a scatter chart in D3 using images and now need to add a horizonal line to the chart based on the value of the "yave" field from my dataset which equals 5
I have added the following script that I know has worked in previous assignments but now I can't seem to align it to the y-axis:
//y-average-line
var yaveline = function(d) {
return data[0].yave;
}
var line = svg.append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", yaveline)
.attr("y2", yaveline)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("stroke-dasharray", "3,3");
A line is created but it appears near the top of the y-axis rather than at y = 5
Any ideas would be greatly appreciated. My fiddle is here
It appears at the top because the 5 is interpreted by the SVG as pixel coordinates, so it is at pixel y=5 (SVG counts from top to bottom). In your case, the 5 is not in pixel space, but in data space. To convert from the data space to the pixel space you have to use the yScale, the same way you do with the circles on the scatterplot:
var yaveline = function(d) {
return yScale(data[0].yave);
}
This will convert data[0].yave from data to pixel, that is, it will return the pixel that is associated with the data value.

D3js Zoom With Manually Drawn Circle

I am working on a d3 scatter plot where an area of the chart will be circled (a Youden Plot). Based on available samples, I have been able to add zoom to both my data points and my axis. However, I am unable to get the circle to zoom correctly.
I suspect that I need to set up some kind of scale (scaleSqrt, possibly), but I am struggling to find documentation on this that is written at a beginner level.
My current circle code is very straightforward
var circle = drawCircle();
function drawCircle() {
return svg
.append('g')
.attr('class', 'scatter-group')
.append('circle')
.attr("r", 75 )
.attr('cx', 200 + margin.left) //suspect this needs to be related to a scale
.attr('cy', 200 + margin.top) //suspect this needs to be related to
.attr('r', 75)//suspect this needs to be related to a scale
.attr('stroke', 'red')
.attr('stroke-width', 3)
.style('fill', 'none')
}
As is the zoomed function
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
//redraw data ppints
points.data(data)
.attr('cx', function(d) {return new_xScale(d.x)})
.attr('cy', function(d) {return new_yScale(d.y)});
//redraw circle
}
My work in progress is available in this fiddle . Can someone possible point me in the right direction?
I believe this will get you most of the way there. You need to update your circle attributes in the zoomed function along with the other elements:
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
//redraw data ppints
points.data(data)
.attr('cx', function(d) {return new_xScale(d.x)})
.attr('cy', function(d) {return new_yScale(d.y)});
// The new part:
// the transform
let trans = d3.event.transform
// the approximate domain value of the circle 'cx' for converting later
let cx_domain = xScale.invert(200 + margin.left)
// the approximate domain value of the circle 'cy' for converting later
let cy_domain = yScale.invert(200 + margin.top)
// the circle
let circ = d3.select('.scatter-group circle')
// the radius
let rad = 75
// reset the circle 'cx' and 'cy' according to the transform
circ
.attr('cx',function(d) { return new_xScale(cx_domain)})
.attr('cy',function(d) { return new_yScale(cy_domain)})
// reset the radius by the scaling factor
.attr('r', function(d) { return rad*trans.k })
}
See this fiddle
You'll notice the circle does not scale or move at quite the same rate as the scatter dots. This is possibly because of the use of the invert function, because the conversion from range to domain and back to range is imperfect. This issue is documented
For a valid value y in the range, continuous(continuous.invert(y)) approximately equals y; similarly, for a valid value x in the domain, continuous.invert(continuous(x)) approximately equals x. The scale and its inverse may not be exact due to the limitations of floating point precision.
Your original idea to assign dynamic values to cx, cy and r will likely compensate for this, because you can then avoid the inversion.

D3 - Vertical axis is not displaying correctly

I have a CSV file containing the French population by department. I can correctly display a map colored with population density but I encounter problems with the associated legend.
You can see the current result here: http://i.stack.imgur.com/Y26JT.jpg
After loading the CSV, here is my code to add the legend :
legend = svg.append('g')
.attr('transform', 'translate(525, 150)')
.attr('id', 'legend');
legend.selectAll('.colorbar')
.data(d3.range(9))
.enter().append('svg:rect')
.attr('y', function(d) { return d * 20 + 'px'; })
.attr('height', '20px')
.attr('width', '20px')
.attr('x', '0px')
.attr("class", function(d) { return "q" + d + "-9"; })
.attr('stroke', 'none');
legendScale = d3.scale.linear()
.domain([0, d3.max(csv, function(e) { return +e.POP; })])
.range([0, 9 * 20]);
legendAxis = d3.svg.axis()
.scale(legendScale)
.orient('right')
.tickSize(1);
legendLabels = svg.append('g')
.attr('transform', 'translate(550, 150)')
.attr('class', 'y axis')
.call(legendAxis);
Colors are obtain using ColorBrewer CSS (https://github.com/mbostock/d3/tree/master/lib/colorbrewer)
I have two problems:
The Hyphen '-' (a SVG line) is not displayed for each value of my axis
I cannot choose the number of values in my axis, I would like one at the beginning of each color block.
Thanks in advance for any help.
The reason your solution isn't showing the 'hyphen' (svg lines) is because you have the tickSize set very small. Try not setting it and it will default to 6 (according to the API docs - https://github.com/mbostock/d3/wiki/SVG-Axes).
To choose the number of values in your axis, you can add a call to ".ticks(N)", where N is the number of ticks you want. D3 will try to show that many ticks. You could also call ".tickValues([...])" and pass in the exact array of values to use for the ticks.
Here's a JSFiddle that corrects the issues in your example: http://jsfiddle.net/TRkGK/3/
And a sample of the part that fixes your issues:
var legendAxis = d3.svg.axis()
.scale(legendScale)
.orient('right')
.tickSize(6) // Tick size controls the width of the svg lines used as ticks
.ticks(9); // This tells it to 'try' to use 9 ticks
UPDATED:
You also want to make sure you're setting the CSS correctly. Here's what I use:
.y.axis line { stroke: #ccc; }
.y.axis path { display: none; }
In your example, when you add the larger tickSize, you are seeing the path in which the tick lines are defined. If you hide the path and give the lines a color, you'll see the ticks rather than the area in which the ticks are defined.
Hope this helps!
It looks like it could be a css issue, did you look at reblace's css in his fiddle?

D3: How to access an attribute of a previous item

I am using D3 to plot a rectangle for each object in an array, the height of the rectangle being dependant on the 'Size' property of the object. These rectangles are stacked on top of each other. I currently set the y position by summing the 'Size' of each subsequent rect that gets plotted - but this seems wrong - and I was wondering if there was a better way to do this, such as accessing the 'y' attribute of the previous item (and how?) or another way...
This is what the essence of my code looks like. There is a link to the fiddle below.
var cumY = 0;
var blocks1 = sampleSVG.selectAll("rect")
.data(fpp)
.enter()
.append("rect")
.sort(SortBySize)
.style("stroke", "gray")
.style("opacity", blockOpacity)
.style("fill", function (d) {return d.Colour})
.attr("width", 80)
.attr("height", function (d) {return d.Size})
.attr("x", 5)
.attr("y", function (d, i) {
var thisY = cumY;
cumY += d.Size;
// perhaps I could just return something like d.Size + previousItem.GetAttribute("y") ???
return thisY;
});
http://jsfiddle.net/ninjaPixel/bvER3/
This is tricky do! You're right that keeping track of the cumulative height 'seems wrong' - it works now but it isn't very idiomatic d3 and will get pretty messy once you start trying to do something more complicated.
I would try using d3's built in stack-layout which was created solve this problem. You might want to start working off of this example and posting an updated fiddle if you get stuck. Good luck!

D3.js ignores duplicate values

I'm exploring D3.js. I primarily used their tutorial to get what I have. But I have made some adjustments.
What I'm doing is counting the number of active & inactive items in a specific table. It then displays a graph with those values. Most everything works fines. I have 2 issues with it though:
It doesn't update automatically with my AJAX call when items are deleted. Only updates when items are added. But I'm not concerned about this for this post.
My primary issue: duplicate values aren't being treated as individual numbers. Instead it sees [10,10] and outputs it as a single bar in the graph as 10 (instead of 2 bars).
Looking at the D3 docs, it would seem the issue lies with .data. Documentation mentions that it joins data.
$(document).on("DOMSubtreeModified DOMNodeRemoved", ".newsfeed", function() {
var columns = ['created','deleted'];
var data = [numN, numD];
var y = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
var x = d3.scale.ordinal()
.domain(columns)
.rangeBands([0, 120]);
chart.selectAll("rect")
.data(data)
//.enter().append("rect")
.attr("x", x)
.attr("height", y)
.attr("width", x.rangeBand())
.attr("rx", 10)
.attr("ry", 10);
chart.selectAll("text")
.data(data)
//.enter().append("text")
.attr("y", y)
.attr("x", function(d) {
return x(d) + x.rangeBand() / 2;
}) //offset
.attr("dy", -10) // padding-right
.attr("dx", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
});
How can I make each value to display? If I pass in two different values, the chart displays as it should.
Your problem is at .attr("x", x). So the way you're doing it assigns the same x coordinate for both rects.
So try offsetting x coordinate.
.attr("x", function(d, i) { x + i * width_of_your_rect); })

Resources