NVD3 not dealing with large values - javascript

I have a graph implemented in NVD3, and I'm having serious trouble with it. NVD3 seems unable to handle datasets which contain large values. The graph is located here: http://brad.kizer.com.ng/. The code for the graph is as so:
nv.addGraph(function() {
// Get maximum and minimum Y values
var minY = 2 >> 30,
maxY = 0;
data.forEach(function (d) {
d.values.forEach(function (s) {
minY = Math.min(minY, s.y);
maxY = Math.max(maxY, s.y);
});
});
var chart = nv.models.stackedArea()
.forceY([0, 20])
.height($(container).closest('.chart').height())
.width($(container).closest('.chart').width());
(Object.prototype.toString.call(data) === '[object Array]') || (data = [data]);
d3.select(container)
.attr('width', $(container).closest('.chart').width())
.attr('height', 250)
.datum(data)
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
I will appreciate any help so much, as this has kept me scratching my head for days.

Solved my problem. The issue was that the data provided for the Y-axis was strings, which made supposedly number addition become string concatenation:
"123" + "902" + "384" + "382" == "123902384382"; // (instead of 1791)
What I did was walk through the data and convert the strings to numbers.

Related

Creating newline in nvd3.js graph x-axis labels

so I've been trying to find a way to create new lines in the x-axis labels of my nvd3 graph but nothing seems to work so far. I have referred to these two questions: Newline in labels in d3 charts and nvd3 chart axis label but didn't find a sufficient answer because these are both relating to d3.js graphs. They both have answers relating to using tspan to create the new-line but doesn't seem to work for me no matter how much I play around with it. This is what I have now but it doesn't seem to be correct at all...any help would be appreciated!
nv.addGraph(function () {
var chart = nv.models.multiBarChart().stacked(false).showControls(false);
chart.x(function (d) { return d.x; });
chart.y(function (d) { return d.y; });
chart.yAxis
.axisLabel('Jobs')
//Too many bars and not enough room? Try staggering labels.
chart.staggerLabels(true);
chart.margin().left = 70;
//chart.showValues(true);
var tech_data = technicianReport();
console.log(tech_data[1] + " " + tech_data[2])
$("#date_range").text(tech_data[1] + " - " + tech_data[2]);
$("#tech_title").text("Technician-Advisor Actions");
d3.select('#tech_chart svg')
.datum(tech_data[0])
.transition().duration(500).call(chart);
var insertLinebreaks = function (d) {
var el = d3.select(this).text();
var words = d.description.split('\n');
el.text('');
for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(words[i]);
if (i > 0)
tspan.attr('x', 0).attr('dy', '15');
}
};
svg = d3.select("tech_chart svg");
svg.selectAll('g.x.axis g text').each(insertLinebreaks);
nv.utils.windowResize(function () { chart.update() });
return chart;
});
Use .wrapLabels parameter from the latest nvd3 source (1.8.1-dev) The parameter wraps long x axis labels. Compare the two screenshots
So you'll come up with
chart.wrapLabels(true);

D3.js Focus+Context via Brushing Barchart - Recalculate BarWidth on Brushing

I some issue with a barchart with Focus+Context via Brushing. It's works pretty well but my problem is :
I calculate the barwidth with the bins and the width of my graph when I process my data.
var data = d3.layout.histogram()
.bins(x.ticks(bins))
(values);
var numBins = data.length;
var barWidth = parseInt((width)/numBins) - 1;
To be coherent, on brushing the barwidth on the focus graph should increase (currently it's keep the same width). So I need to recalculate the barwidth. But i have no idea how can i do this...
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focusGraph.attr("x", function(d, i) { return x(d.x); });
focusGraph.attr("width", barWidth); // How can I calculate new barwidth?
focus.select(".x.axis").call(xAxis);
}
Here the code :
http://jsfiddle.net/qcLp6qu8/
I find a solution if can help somebody :)
http://jsfiddle.net/sx9myywh/
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
var b = x(data[1].x)-x(data[0].x);
var w = b-2;
focusGraph.attr("x", function(d, i) { return x(d.x); });
focusGraph.attr("width", w);
focus.select(".x.axis").call(xAxis);
}
Try this code,
focusGraph.attr("width", width/(x.ticks().length));
x.ticks() will give the array of ticks, .length will give the number of ticks. To calculate the bar width. Total width by number of ticks this will give the space between the ticks which has to be bar width.
See update fiddle
Hope this will work for you, If not ask me for more...
Added a little code for better handling.

How do I change XTick format in NVD3?

I would like to change the XTick format of the small chart to also be a date. I have the following which is lifted from this example:
function chart(div)
{
var testdata = loadData();
nv.addGraph(function() {
var chart = nv.models.lineWithFocusChart();
chart.xAxis.tickFormat(function(d) {
var dx = testdata[0].values[d] && testdata[0].values[d].x || 0;
return d3.time.format('%x')(new Date(dx))
});
chart.yAxis
.tickFormat(d3.format(',.2f'));
chart.y2Axis
.tickFormat(d3.format(',.2f'));
d3.select(div + ' svg')
.datum(loadData())
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
}
But only the large chart's format changes to date.
There are two ways of fixing the ticks for the second x-axis.
The first way is by setting the tickFormat explicitly for the second axis as well. This design follows the traditional paradigm of setting one attribute at a time.
var xFormat = function(d) {
var dx = testdata[0].values[d] && testdata[0].values[d].x || 0;
return d3.time.format('%x')(new Date(dx));
};
chart.xAxis.tickFormat(xFormat);
chart.xAxis2.tickFormat(xFormat);
The second way, which would avoid code duplication, is setting both the axis together using a special function exposed by the chart API:
// This would set both the axis simultaneously
chart.xTickFormat(xFormat);
This function is present in the code (introduced here) for exactly this purpose. However, as the API is not very stable, it might be removed later.

Display only whole values along y axis in d3.js when range is 0-1 or 0-2

I am using d3js to display a realtime representation of the views of a website. For this I use a stack layout and I update my dataset by JSON at the moment.
When there is only 1 or 2 views being displayed on the y axis, which is dynamic related to the amount of views in the graph, the axis labels are: 1 => 0, 0.2, 0.4, 0.6, 0.8, 1, the axis labels are: 2 => 0, 0.5, 1, 1.5, 2 This makes no sense for my dataset since it displays views of a page, and you can't have half a view.
I have a linear scale in d3js I base my y axis on
var y_inverted = d3.scale.linear().domain([0, 1]).rangeRound([0, height]);
According to the documentation of rangeRound() I should only get whole values out of this scale. For drawing my axis I use:
var y_axis = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(y_inverted.axis = d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(5));
Because it is a realtime application I update this every second by calling:
function update(){
y_inverted.domain([yStackMax, 0]);
y_axis.transition()
.duration(interval)
.ease("linear")
.call(y_inverted.axis);
}
yStackMax is calculated from a stacklayout, as far as I know the data used for the y values only contain integers.
var yStackMax = d3.max(layers, function(layer) {
return d3.max(layer, function(d) {
return d.y0 + d.y;
});
});
I have tried several things to get a proper value for my y axis.
d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(5).tickFormat(d3.format(",.0f"))
Got me the closest sofar, but it still displays 0, 0, 0, 1, 1, 1
Basically what I want is to only have 1 tick when yStackMax is 1, 2 ticks when it's 2, but it should also work if yStackMax is 12 or 1,000,000
Short answer: You can dynamically set the number of ticks. Set it to 1 to display only two tick labels:
var maxTicks = 5, minTicks = 1;
if (yStackMax < maxTicks) {
y_axis.ticks(minTicks)
}
else {
y_axis.ticks(maxTicks)
}
Long Answer (going a bit off topic):
While playing with your example I came up with a rather "complete solution" to all your formatting problems. Feel free to use it :)
var svg = d3.select("#svg")
var width = svg.attr("width")
var height = svg.attr("height")
var yStackMax = 100000
var interval = 500
var maxTicks = 5
var minTicks = 1
var y_inverted = d3.scale.linear().domain([0, 1]).rangeRound([0, height])
var defaultFormat = d3.format(",.0f")
var format = defaultFormat
var y_axis = d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(minTicks)
.tickFormat(doFormat)
var y_axis_root;
var decimals = 0;
function countDecimals(v){
var test = v, count = 0;
while(test > 10) {
test /= 10
count++;
}
return count;
}
function doFormat(d,i){
return format(d,i)
}
function init(){
y_axis_root = svg.append("g")
.attr("class", "y axis")
// I modified your example to move the axis to a visible part of the screen
.attr("transform", "translate(150,0)")
.call(y_axis)
}
// custom formatting functions:
function toTerra(d) { return (Math.round(d/10000000000)/100) + "T" }
function toGiga(d) { return (Math.round(d/10000000)/100) + "G" }
function toMega(d) { return (Math.round(d/10000)/100) + "M" }
function toKilo(d) { return (Math.round(d/10)/100) + "k" }
// the factor is just for testing and not needed if based on real world data
function update(factor){
factor = (factor) || 0.1;
yStackMax*=factor
decimals = countDecimals(yStackMax)
console.log("yStackMax decimals:",decimals, factor)
if (yStackMax < maxTicks) {
format = defaultFormat
y_axis.ticks(minTicks)
}
else {
y_axis.ticks(maxTicks)
if (decimals < 3 ) format = defaultFormat
else if(decimals < 6 ) format = toKilo
else if(decimals < 9 ) format = toMega
else if(decimals < 12) format = toGiga
else format = toTerra
}
y_inverted.domain([yStackMax, 0]);
y_axis_root.transition()
.duration(interval)
.ease("linear")
.call(y_axis);
}
init()
setTimeout(update, 200)
setTimeout(update, 400)
setTimeout(update, 600)
You can try it together with this html snippet:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.js"></script>
</head>
<body>
<div><svg id="svg" width="200" height="300"></svg></div>
<script src="axis.js"></script>
<button id="button1" onclick="update(10)">+</button>
<button id="button2" onclick="update(0.1)">-</button>
</body>
</html>
I know it is a bit off topic but I usually like to provide running examples/solutions. Regard the additional formatting stuff as a bonus to the actual problem.
If you ask for a certain number of ticks (via axis.ticks() ) then d3 will try to give you that many ticks - but will try to use pretty values. It has nothing to do with your data.
Your solutions are to use tickFormat, as you did, to round all the values to integer values, only ask for one tick as Juve answered, or explicitly set the tick values using axis.tickValues([...]) which would be pretty easy used in conjunction with d3.range
rangeRound will not help in this case because it relates to the output range of the scale, which in this case is the pixel offset to plot at: between 0 and height.
Going off of Superboggly's answer, this is what worked for me. First I got the max (largest) number from the y domain using y.domain().slice(-1)[0] and then I built an array of tick values from that using d3.range()...
var y_max = y.domain().slice(-1)[0]
var yAxis = d3.svg.axis()
.scale(y)
.tickValues(d3.range(y_max+1))
.tickFormat(d3.format(",.0f"))
Or just let the ticks as they are and "hide" decimal numbers
d3.svg.axis()
.scale(y_inverted)
.orient("left")
.ticks(5).tickFormat(function(d) {
if (d % 1 == 0) {
return d3.format('.f')(d)
} else {
return ""
}
});
Here is the code:
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));

Adding labels to a pv.Layout.Stack (streamgraph) in Protovis

I'm working with the Protovis library to do a streamgraph of data. I want to label the different layers with the "words" array. I can't seem to get the words to line up how I'd like. I want them to be inserted where the graph is the largest for that particular layer, similar to this site:
http://mbostock.github.com/protovis/ex/jobs.html
var words = [
"tasty","delicious","yum","scrumpious","dry"];
var data = [
[23,52,6,3,16,35,24,12,35,119,2,5,65,33,81,61,55,122,3,19,2,5,65,33,81,61,55,122,3,19,54,72,85,119,23,52,6,3,16,35],
[43,2,46,78,46,25,54,72,85,119,23,52,6,3,16,35,24,12,35,119,23,52,6,3,16,35,24,12,35,119,2,5,65,33,81,61,55,122,3,19],
[2,5,65,33,81,61,55,122,3,19,54,72,85,119,23,52,6,3,16,35,2,5,65,33,81,1,5,12,95,14,12,8,84,115,15,27,6,31,6,35],
[2,5,6,3,1,6,5,12,32,191,142,22,75,139,27,32,26,13,161,35,21,52,64,35,21,61,55,123,5,142,54,58,8,11,53,2,64,3,16,35],
[2,5,65,33,81,61,55,122,3,19,54,72,85,119,23,52,6,3,16,35,2,5,65,33,81,61,55,123,5,142,54,58,8,11,53,2,64,3,16,35]];
var w = 800,
h = 300,
x = pv.Scale.linear(0, 40).range(0, w),
y = pv.Scale.linear(0, 600).range(0, h);
var vis = new pv.Panel()
.canvas('streamgraph')
.width(w)
.height(h);
vis.add(pv.Layout.Stack)
.layers(data)
.order("inside-out")
.offset("wiggle")
.x(x.by(pv.index))
.y(y)
.layer.add(pv.Area)
.fillStyle(pv.ramp("#aad", "#556").by(Math.random))
.strokeStyle(function () { this.fillStyle().alpha(.5) });
vis.render();
Try this:
vis.add(pv.Layout.Stack)
.layers(data)
.order("inside-out")
.offset("wiggle")
.x(x.by(pv.index))
.y(y)
.layer.add(pv.Area)
.fillStyle(pv.ramp("#aad", "#556").by(Math.random))
.strokeStyle(function () { this.fillStyle().alpha(.5) })
// this is new code:
.anchor("center").add(pv.Label)
.def("max", function(d) {return pv.max.index(d)})
.visible(function() {return this.index == this.max() })
.text(function(d, p) {return words[this.parent.index]});
Basically this adds a whole bunch of labels to your areas, But then only makes them visible at the index where the value is the maximum, by defining a function max on the series. I adapted this code from the code in the link you sent.

Categories

Resources