Draw ticks on negative part of the line chart d3.js - javascript

I want to place ticks for negative part of D3.js line chart. Somehow it only shows for positive part of the chart.
y = d3.scale.linear().domain([d3.min(data, function (d) { return d.TOTL_ACNT_BAL; }), d3.max(data, function (d) { return d.TOTL_ACNT_BAL; }) + 700]).range([height, 0]),
yAxisTicks = d3.svg.axis().scale(y)
.ticks(12)
.tickSize(width)
.tickFormat('')
.orient('right'),
Fiddle attached here :
http://jsfiddle.net/tpnc2zL5/

Related

nvd3 - Rescale Y axis and show Outlier Data points

I am creating a lineChart with NVD3 that is currently plotting my points correctly. The default behavior of the Y axis automatically scales the ticks on the Y axis. Then when a series is selected or deselected, the Y axis automatically resizes according to the present series.
There are two problems with this:
When the resizing happens, the Y axis flips, making the values decrease as you move up the Y axis.
More importantly, the automatic initial Y axis scale dramatically underestimates the Y axis values, causing many data points (outliers) to not be shown in the visual.
I have played around with Ydomain and forceY to set the min and max values of my data on the Y axis, but then I lose the functionality of the auto-scaling when selecting or deselecting a series.
I would like to have my visual preserve the default behavior of auto-scaling the Y axis when selecting and deselecting series, but also have it not cut the Y axis off early, thus showing all outlier data points.
Any help is much appreciated.
Current JS code:
d3.json('..\\static\\data\\hydrograph_data.json', function(data) { // open more dynamic file path
nv.addGraph( function() {
var getX = function(d) { return d.time_mili }
var getY = function(d) { return d.value }
var getMax = function(d) { return d.max }
var min = d3.min(data, function(d) { return d3.min(d.values, getY)})
var max = d3.max(data, function(d) { return d3.max(d.values, getMax)})
var chart = nv.models.lineChart()
.x( getX ) // this value is stored in miliseconds since epoch (converted in data_format.py with datetime)
.y( getY )
.color(d3.scale.category10().range())
.useInteractiveGuideline(true)
//.yDomain([min, max])
.margin({left: 90})
// .showControls(false)
;
chart.xAxis
.axisLabel(" Date ")
.ticks(5)
.tickFormat(function(d) {
return d3.time.format('%y-%d-%m')(new Date(d))
});
chart.yAxis
//.axisLabel('Discharge (cubic feet per second)')
.ticks(5)
.tickFormat(function(d) { return d3.format(",")(d) + " cfps"});
d3.select('#hydrograph svg')
.datum(data)
.call(chart);
//TODO: Figure out a good way to do this automatically
nv.utils.windowResize(chart.update);
return chart;
});
});
p.s.
I am working on a fiddle, but a sample of my data in the fiddle does not produce the same problems, so it is difficult to recreate without the entire data file present, which I cannot do in jsfiddle.
edit:
temporary solution was to create a dummy value point at the start of the series with value 0 as seen in NatasaPeic's answer to the issue.

show the bottom most tick of y axis

The bottom most tick in the Y axis is not visible in the line chart I've created. The axis creation code is:
var y = d3.scale.linear().range([height, 0]);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5)
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
The axis is ok, but I need to show the tick and text at the bottom most of the Y axis. What's going wrong here? Here is the JSFiddle.
Use nice() in the domain:
y.domain([
d3.min(chartData, function(n) {
return d3.min(n.values, function(d) {
return d.value;
});
}),
d3.max(chartData, function(n) {
return d3.max(n.values, function(d) {
return d.value;
});
})
]).nice();
Here is your Fiddle: https://jsfiddle.net/c8mjha3o/

What is causing d3js to stop drawing a line when x interval shortens?

This is a weird error; hard to explain so bear with me. I have some data that I query from a database, this data is normally 60 seconds part. However, if I insert a whole bunch of data that's not within 60 seconds (say 0-1 second apart); when this data is queried, d3js draws the lines just fine for the data that is 60 sec part, but when it encounters the new data that is not 60 seconds apart, it stops drawing the line; see below:
The red dot is a mouse over showing that there is indeed data; this occurs all along that "invisible" area (where the dot is). To the left where there is a clearly visible line; this data is separated by 60 seconds. If I slow down the data input back to 60 seconds the lines come back (but not for the area where it's 0-2 seconds apart).
Here is some of my source:
var xScale = d3.time.scale(); // time series
var yScale = d3.scale.linear(); // our float/int data points
var xAxis = d3.svg.axis();
var yAxis = d3.svg.axis();
var vline = d3.svg.line(); // our data will use this line
var varea = d3.svg.area(); // our data will fill this area
xScale
.domain(d3.extent(data, function(d) { return parseDate(d.x); }))
.range([0, width]);
yScale
.domain([0, d3.max(data, function(d) {
if (config.dtype == "%") {
return 100;
} else if (d.y >= 1) {
return d.y;
}
return 1;
})])
.range([height,0]);
xAxis
.scale(xScale)
.orient("bottom")
.ticks(12)
.innerTickSize(-height)
.outerTickSize(-height)
.tickPadding(3);
yAxis
.scale(yScale)
.orient("left")
.ticks(5)
.innerTickSize(-width)
.outerTickSize(-width)
.tickPadding(3)
.tickFormat(d3.format(",.2f"));
vline
.defined(function(d) { return d.y != null; })
.x(function(d) { return xScale(parseDate(d.x)); })
.y(function(d) { return yScale(d.y); });
varea
.defined(function(d) { return d.y != null; })
.x(function(d) { return xScale(parseDate(d.x)); })
.y0(height)
.y1(function(d) { return yScale(d.y); });
Would anyone have any idea why it's doing this?
You are using d3.line.defined which basically can make "hole" in your line. (See doc)
the generated path data will automatically be broken into multiple distinct subpaths, skipping undefined data.
See this example, looking exactly as the one you posted: http://bl.ocks.org/mbostock/3035090
Even if there is data where your mouse is, if it's a lone point surrounded by undefined values, the line / area won't draw, as it needs two consecutive data points to draw the line / area.

D3.js Line chart with relative min and max in Y axis

Consider the following VND3 line graph: http://jsfiddle.net/tramtom/hfv68yan/
The graph plots the line ok, however most of my data is the static over long periods, and the two series will always have one at the top of the graphic and other on the bottom axis.
How to create relative minimum and maximums for the Y axis in a way that lines for the series be almost in the middle or at least spaced so that the lower valued series does not lie almost entirely on the x-axis.
Need to add 100 units below the minimum and 100 units above the maximum so the lines don't be at the top or bottom of the graphic.
I tried setting a domain and range values like in here http://jsfiddle.net/tramtom/hfv68yan/1/ but have no effect
var yScale = d3.scale.linear()
.domain([
d3.min(data, function (d) {
return d.y;
}) - 100,
d3.max(data, function (d) {
return d.y;
}) + 100
])
.range([
d3.min(data, function (d) {
return d.y;
}),
d3.max(data, function (d) {
return d.y;
})
]);
You need to force the axis range by adding .forceY([0,500]) to the chart instantiation.
This SO answer might be helpful. That should at least point you in the right direction.

D3 brushing on grouped bar chart

I am trying to get brushing to work similar to this example, but with a grouped bar chart: http://bl.ocks.org/mbostock/1667367
I don't really have a good understanding of how brushing works (I haven't been able to find any good tutorials), so I'm a bit at a loss as to what is going wrong. I will try to include the relevant bits of code below. The chart is tracking the time to fix broken builds by day and then grouped by portfolio. So far the brush is created and the user can move and drag it, but the bars in the main chart are re-drawn oddly and the x axis is not updated at all. Any help you can give would be greatly appreciated. Thank you.
// x0 is the time scale on the X axis
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
// x1 is the portfolio scale on the X axis
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();
// Define the X axis
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.tickFormat(dateFormat)
.orient("bottom");
var mini_xAxis = d3.svg.axis()
.scale(mini_x0)
.tickFormat(dateFormat)
.orient("bottom");
After binding the data...
// define the axis domains
main_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
mini_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
main_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
mini_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
// Create brush for mini graph
var brush = d3.svg.brush()
.x(mini_x0)
.on("brush", brushed);
After adding the axis's, etc.
// Create the bars
var bar = main.selectAll(".bars")
.data(nested)
.enter().append("g")
.attr("class", function(d) { return d.key + "-group bar"; })
.attr("fill", function(d) { return color(d.key); } );
bar.selectAll("rect").append("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("class", function(d) { return d.portfolio; })
.attr("transform", function(d) { return "translate(" + main_x0(d.date) + ",0)"; })
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); })
.attr("y", function(d) { return main_y(d.buildFixTime); })
.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
Here is the brush function (trying several different options)...
function brushed() {
main_x1.domain(brush.empty() ? mini_x1.domain() : brush.extent());
//main.select("rect")
//.attr("x", function(d) { return d.values; })
//.attr("width", function(d) { return d.values; });
bar.select("rect")
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); });
//.attr("y", function(d) { console.log(d); return main_y(d.buildFixTime); })
//.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
main.select(".x.axis").call(main_xAxis);
}
The problem comes from trying to use the brush to set the x-scale domain, when your x-scale is an ordinal scale. In other words, the expected domain of your x-axis is a list of categories, not a max-min numerical extent. So the problem is right at the top of the brushing function:
function brushed() {
main_x0.domain(brush.empty() ? mini_x0.domain() : brush.extent());
The domain set by brush.extent() is an array of two numbers, which then completely throws off your ordinal scale.
According to the wiki, if one of the scales attached to a brush function is an ordinal scale, the values returned by brush.extent() are values in the output range, not in the input domain. Ordinal scales don't have an invert() method to convert range values into domain values.
So, you have a few options on how to proceed:
You could re-do the whole graph using a linear time scale for your main x-axes instead of an ordinal scale. But then you have to write your own function to figure out the width of each day on that axis instead of being able to use .rangeBand().
You can create your own "invert" function to figure out which categorical values (dates on the mini_x0.domain) are included in the range returned by brush.extent(). Then you would have to both reset the main_x0.domain to only include those dates on the axis, and filter out your rectangles to only draw those rectangles.
Or you can leave the domain of main_x0. be, and change the range instead. By making the range of the graph larger, you space out the bars greater. In combination with a clipping path to cut off bars outside the plotting area, this has the effect of only showing a certain subset of bars, which is what you want anyway.
But what should the new range be? The range returned by brush.extent() is the beginning and end positions of the brushing rectangle. If you used these values as the range on the main graph, your entire graph would be squished down to just that width. That's the opposite of what you want. What you want is for the area of the graph that originally filled that width to be stretched to fill the entire plotting area.
So, if your original x range is from [0,100], and the brush covers the area [20,60], then you need a new range that satisfies these conditions:
the 20% mark of the new range width is at 0;
the 60% mark of the new range width is at 100.
Therefore,
the total width of the new range is ( (100-0) / (60-20) )*(100-0) = 250;
the start of the new range is at (0 - (20/100)*250) = -50;
the end of the new range is at (-50) + 250 = 200.
Now you could do all the algebra for figuring out this conversion yourself. But this is really just another type of scaling equation, so why not create a new scale function to convert between the old range and the zoomed-in range.
Specifically, we need a linear scale, with its output range set to be the actual range of the plotting area. Then set the domain according to the range of the brushed area that we want to stretch to cover the plotting area. Finally, we figure out the range of the ordinal scale by using the linear scale to figure out how far off the screen the original max and min values of the range would be. And from there, we-can resize the other ordinal scale and reposition all the rectangles.
In code:
//Initialization:
var main_xZoom = d3.scale.linear()
.range([0, main_width - 275])
.domain([0, main_width - 275]);
//Brushing function:
function brushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(brush.empty() ?
originalRange:
brush.extent() );
main_x0.rangeRoundBands( [
main_xZoom(originalRange[0]),
main_xZoom(originalRange[1])
], 0.2);
main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);
bar.selectAll("rect")
.attr("transform", function (d) {
return "translate(" + main_x0(d.date) + ",0)";
})
.attr("width", function (d) {
return main_x1.rangeBand();
})
.attr("x", function (d) {
return main_x1(d.portfolio);
});
main.select("g.x.axis").call(main_xAxis);
}
Working fiddle based on your simplified code (Note: you still need to set a clipping rectangle on the main plot):
http://fiddle.jshell.net/CjaD3/1/

Categories

Resources