Set zero baseline for D3.js graph - javascript

We have a graph that when all data is zero, shows the zero dollar value in the middle of the y-axis, and a few negative values below. It would be better to have the zero at the bottom, with dollar values up the side.
Here's the graph code:
nv.addGraph(function() {
var chart = nv.models.multiBarChart()
.transitionDuration(350)
.reduceXTicks(false)
.rotateLabels(0)
.showControls(false) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1)
.tooltipContent(function (key, y, e, graph) {
return '<p> Total Cost: $' + graph.value + '</p>';
})
;
chart.xAxis
.tickFormat(function(d) { return d3.time.format('%b, %y')(new Date(d)); });
chart.yAxis
.tickFormat(function(d) { return '$' + d3.format(',f')(d) });
d3.select('#hardware_costs_graph svg')
.datum(exampleData())
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
How can the baseline be changed so that no negative values show?

Thanks Lars. I did search for this answer for a while, and could not find it.
Here is the solution for anyone who needs it.
Added forceY to chart instantiation:
var chart = nv.models.multiBarChart()
.transitionDuration(350)
.reduceXTicks(false)
.rotateLabels(0)
.showControls(false) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1)
.tooltipContent(function (key, y, e, graph) {
return '<p> Total Cost: $' + graph.value + '</p>';
})
.forceY([0,5])
;

Related

d3 tooltip bar for multi line chart on mouseover on Y Axis (code supplied)

I'm trying to implement a tooltip on mouseover for a multi line chart.
I've followed the code from this example and tried to change it so that I see the X values of the lines for a given hovered Y value, but I'm not able to get it to work.
My attempt can be found below.
In my actual implementation I'm writing in Typescript and the functions 'getTotalLength()' and 'getPointAtLength()' are saying they don't exist on property Element.
Also if you can add a text box at on the line that has the hovered Y value that'd help me a lot!
https://codesandbox.io/s/modest-minsky-hvsms?fontsize=14&hidenavigation=1&theme=dark
Thanks
So after careful review there were several errors which I have corrected.
Your paths for the data lines were not assigned the class so you need to assign the class of dataLine to them when you append them like so:
svg
.selectAll(".dataLine")
.data(nestedData)
.enter()
.append("path")
.attr("fill", "none")
.attr("class", "dataLine")
.attr("stroke", d => itemMap(d.key).color)
.attr("stroke-width", d => itemMap(d.key).lineWeight)
.attr("d", d =>
d3
.line()
.x(d => x(d.xvalue))
.y(d => y(d.yvalue))(d.values)
);
As pointed out in the comment above, stop using arrow functions if you intend to use this. Once you do that, your d3.mouse(this) starts working.
The example you followed had the paths from left to right, while yours is from top to bottom. This required several changes in terms of coordinates to get the alignment of the mouseover line and the circles with the text values near them to align properly. The correct code is as follows:
.on("mousemove", function() {
//#ts-ignore
var mouse = d3.mouse(this);
d3.select(".mouse-line").attr("d", () => {
var d = "M" + plotWidth + "," + mouse[1];
d += " " + 0 + "," + mouse[1];
return d;
});
d3.selectAll(".mouse-per-line").attr("transform", function(d, i) {
var yDepth = y.invert(mouse[1]);
var bisect = d3.bisector(d => d.depth).right;
var idy = bisect(d.values, yDepth);
var beginning = 0;
var end = lines[i].getTotalLength();
var target = null;
while (true) {
target = Math.floor((beginning + end) / 2);
var pos = lines[i].getPointAtLength(target);
if (
(target === end || target === beginning) &&
pos.y !== mouse[1]
) {
break;
}
if (pos.y > mouse[1]) {
end = target;
} else if (pos.y < mouse[1]) {
beginning = target;
} else {
break;
}
}
d3.select(this)
.select("text")
.text(x.invert(pos.x).toFixed(2));
return "translate(" + pos.x + "," + mouse[1] + ")";
});
});
Fully working codesandbox here.

Offset Line stroke-weight d3.js

I'm using d3.js to plot a highway network over a map SVG. I'd like to be able to vary the stroke-weight of the line to illustrate demand based on a value.
Highway links are define as one way, so for example a two way road would have two overlapping line elements (with separate id's). I can use stroke-weight to edit the thickness of the line based on a variable (as below), but on a two way road, the larger of the two stroke weights will always cover the smaller rendering it invisible.
Is there an easy way to offset a line by half its stroke-weight to the left hand side of the direction the line is drawn? (direction denoted by x1,y1 x2,y2)
d3.csv("links.csv", function (error, data) {
d3.select("#lines").selectAll("line")
.data(data)
.enter()
.append("line")
.each(function (d) {
d.p1 = projection([d.lng1, d.lat1]);
d.p2 = projection([d.lng2, d.lat2]);
})
.attr("x1", function (d) { return d.p1[0]; })
.attr("y1", function (d) { return d.p1[1]; })
.attr("x2", function (d) { return d.p2[0]; })
.attr("y2", function (d) { return d.p2[1]; })
.on('mouseover', tip_link.show)
.on('mouseout', tip_link.hide)
.style("stroke", "black")
.style("stroke-width", lineweight)
});
One option would be to just create new start/end points when drawing your lines and use those:
var offset = function(start,destination,distance) {
// find angle of line
var dx = destination[0] - start[0];
var dy = destination[1] - start[1];
var angle = Math.atan2(dy,dx);
// offset them:
var newStart = [
start[0] + Math.sin(angle-Math.PI)*distance,
start[1] + Math.cos(angle)*distance
];
var newDestination = [
destination[0] + Math.sin(angle-Math.PI)*distance,
destination[1] + Math.cos(angle)*distance
];
// return the new start/end points
return [newStart,newDestination]
}
This function takes two points and offsets them by a particular amount given the angle between the two points. Negative values shift to the other side, swapping the start and destination points will shift to the other side.
In action, this looks like, with the original line in black:
var offset = function(start,destination,distance) {
// find angle of line
var dx = destination[0] - start[0];
var dy = destination[1] - start[1];
var angle = Math.atan2(dy,dx);
// offset them:
var newStart = [
start[0] + Math.sin(angle-Math.PI)*distance,
start[1] + Math.cos(angle)*distance
];
var newDestination = [
destination[0] + Math.sin(angle-Math.PI)*distance,
destination[1] + Math.cos(angle)*distance
];
// return the new start/end points
return [newStart,newDestination]
}
var line = [
[10,10],
[200,100]
];
var svg = d3.select("svg");
// To avoid repetition:
function draw(selection) {
selection.attr("x1",function(d) { return d[0][0]; })
.attr("x2",function(d) { return d[1][0]; })
.attr("y1",function(d) { return d[0][1]; })
.attr("y2",function(d) { return d[1][1]; })
}
svg.append("line")
.datum(line)
.call(draw)
.attr("stroke","black")
.attr("stroke-width",1)
svg.append("line")
.datum(offset(...line,6))
.call(draw)
.attr("stroke","orange")
.attr("stroke-width",10)
svg.append("line")
.datum(offset(...line,-4))
.call(draw)
.attr("stroke","steelblue")
.attr("stroke-width",5)
<svg width="500" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
You will need to adapt this to your data structure, and it requires twice as many lines as before, because you aren't using stroke width, your using lines. This is advantageous if you wanted to use canvas.

Rotate Pie Label in dc.js Pie Chart

Let's say I have the following code in dc.js to create a pie chart:
var chart = dc.pieChart("#test");
d3.csv("morley.csv", function(error, experiments) {
var ndx = crossfilter(experiments),
runDimension = ndx.dimension(function(d) {return "run-"+d.Run;})
speedSumGroup = runDimension.group().reduceSum(function(d) {return d.Speed * d.Run;});
chart
.width(768)
.height(480)
.slicesCap(4)
.innerRadius(100)
.dimension(runDimension)
.group(speedSumGroup)
.legend(dc.legend())
// workaround for #703: not enough data is accessible through .label() to display percentages
.on('pretransition', function(chart) {
chart.selectAll('text.pie-slice').text(function(d) {
return d.data.key + ' ' + dc.utils.printSingleValue((d.endAngle - d.startAngle) / (2*Math.PI) * 100) + '%';
})
});
chart.render();
});
What I want to do is rotate the label, but when I do so, all of the labels translate to the center of the pie.
chart.renderlet(function (chart) {
chart.selectAll('text.pie-slice')
.attr('transform', 'rotate(315)');
});
Is there a way to rotate the labels without changing their position on the graph?
The problem is that you're replacing the transform attribute for these elements, which is currently used to "translate" the labels in position.
Since it's hard to dig into the calculations used here, I think the best approach is to pull the existing transform attribute and modify it, like this:
chart.on('renderlet', function (chart) {
chart.selectAll('text.pie-slice')
.attr('transform', function(d) {
var translate = d3.select(this).attr('transform');
var ang = ((d.startAngle + d.endAngle) / 2 * 180 / Math.PI)%360;
if(ang<180) ang -= 90; else ang += 90;
return translate + ' rotate(' + ang + ')';
});
});
For my own entertainment, I've also rotated the labels using the start and end angles of the pie slices.
It's unfortunate you can't do this as a pretransition event and avoid the "jump". It will just get overwritten by the animations. Doing this properly would require some changes to dc.js - file an issue if you're interested.
addition to Gordon's answer, for all pieCharts to have this feature; i changed labelPosition function in dc.js.
return 'translate(' + centroid + ')';
to
var ang = ((d.startAngle + d.endAngle) / 2 * 180 / Math.PI)%360;
if(ang<180) ang -= 90; else ang += 90;
return 'translate(' + centroid + ') rotate(' + ang +')';

Rotate labels in column chart d3.JS

I want to rotate the labels in yellow color.
image for the labels
i have gone through this link
rotate x axis text in d3
but in this link the values being passed to the translate function are static values.
<text transform="translate(200,100)rotate(180)">Hello!</text>
i want to pass dynamic values returned by a function.
so in this code the x and y are taking values from functions so i want to pass these values to the translate attribute but getting error in the console
d3.min.js:1 Error: Invalid value for attribute transform="translate(\"function(d){ return xScale(d.country) + xScale.rangeBand()/2; }\",\"function(d){ return yScale(d.populationValue)+ 12; }\")rotate(-90)"
.attr({
"x": function(d){ return xScale(d.country) + xScale.rangeBand()/2; },
"y": function(d){ return yScale(d.populationValue)+ 12; },
"text-anchor": 'middle',
"fill": 'yellow',
"transform": 'translate("function(d){ return xScale(d.country) + xScale.rangeBand()/2; }","function(d){ return yScale(d.populationValue)+ 12; }")rotate(-90)'
expected output
You have to return all the translate string using the function:
"transform": function(d){
return "translate(" + xScale(d.country) + xScale.rangeBand()/2
+ "," + yScale(d.populationValue) + 12 + ")rotate(-90)"
};
PS: after you do this, I bet that the result will not be what you expect... but that will be another problem, for another SO question.

NVD3.js yAxis and tooltip different percision

I'm using NVD3.js to display a multi-line chart.
I would like the yAxis to display to 2 decimal numbers
edited answer
var chart;
nv.addGraph(function () {
chart = nv.models.lineChart()
.options({
margin: { left: 140, bottom: 50 },
x: function (d, i) {
return i;
},
showXAxis: true,
showYAxis: true,
transitionDuration: 250,
tooltips: true,
tooltipContent: function (key, x, y, e, graph) {
return '<h3>' + key + '</h3>' +
'<p>' + e.point.y + ' at ' + x + '</p>'
}
});
// chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the parent chart, so need to chain separately
chart.xAxis
.axisLabel("Maturity")
.tickFormat(function(d) { return pivotedData[0].values[d].x; });
chart.yAxis
.axisLabel('Model Spread').tickFormat(d3.format(',.2f'));
d3.select('#chart1 svg')
.datum(pivotedData)
.call(chart);
//TODO: Figure out a good way to do this automatically
nv.utils.windowResize(chart.update);
chart.dispatch.on('stateChange', function (e) { nv.log('New State:', JSON.stringify(e)); });
return chart;
});
But in the tool tip on the lines i would like to display to 12 decimals places.
Is this possible?
You can set the function that is called to format the contents of a tooltip through .tooltipContent of the chart object. The default function is defined as follows.
tooltip = function(key, x, y, e, graph) {
return '<h3>' + key + '</h3>' +
'<p>' + y + ' at ' + x + '</p>'
}
You can format the value of y in there to your liking. Note that y is defined as
yAxis.tickFormat()(lines.y()(e.point, e.pointIndex))
This means that the tick formatting for the axis will affect it, so in order to achieve a higher precision there, you need to get the value directly -- lines can be accessed through chart.lines.
In the nvd3 source they have lines like so (Line Chart):
interactiveLayer.dispatch.on('elementMousemove', function(e) {
...
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
interactiveLayer.tooltip
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
...
.valueFormatter(function(d,i) {
return yAxis.tickFormat()(d);
})
.data(
{
value: xValue,
series: allData
}
)();
...
}
So if you want to override something like tooltip.contentGenerator you are stuck with the data it gives you. I think a good way to do this would be to have a setter for tooltip x formatter or something then it will use that over the axis formatter. Or simply sending over the raw data along with the value and series.
My usecase is that I am showing a timescale of 7 days on the graph I only tick the start of each day. However on the tooltip I want to give a more detailed view of that data.
Here is my pull request specific to the guidelines tooltip
https://github.com/novus/nvd3/pull/444
Calling: chart.tooltipXAxisFormatter(d3.time.format(toolTipFormat)); will set it.

Categories