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.
Related
I want my X-axis to be at the bottom of the graph but when why data is set to zero then it comes to the center of Y-axis? How do I prevent it from going to the center??
let x = d3.scaleBand().rangeRound([0, width]).padding(1),
y = d3.scaleLinear().range([height, 0])
x.domain(data.map(function (d) { return d.timescale; }));
y.domain([0, d3.max(trends, function (c) {
return d3.max(c.values, function (v) {
return v.total;
});
})]);
The problem happens because the maximum extent of y.domain is undefined. As a fallback, the vertical axis gets centered.
To fix this problem, make sure that a value is set, the example below make the y scale to go between at least domains 0 and 1.
Once this is fixed, the x axis should be correctly positioned at the bottom of the chart area.
let maxExtent = Math.max(1, d3.max(trends, function (c) {
return d3.max(c.values, function (v) {
return v.total;
});
}))
y.domain([0, maxExtent]);
I have an data which show the performance of user and competency which is y and x axis respectively. Where its scaled from 1 to 10. I want to create an intersection point using d3js but as a beginner im having no idea where to start.
To create a dot at that point, you simply need to draw a new circle on the point of intersection. Here is a JSFiddle of a VERY simple graph with comments to make it easier to understand. The dots are drawn from data. The important piece for you is
.append("svg:circle")
.attr("cy", function(d) {
return y(d.y);
})
.attr("cx", function(d, i) {
return x(d.x);
})
.attr("r", 10)
which creates an svg circle, places it on the data point's x and y coordinates, and gives it a radius of 10. A more simplified version which might be easier to use for your application (because I don't know how your data is stored) is to feed in the data manually
.append("svg:circle")
.attr("cy", x(5))
.attr("cx", y(6))
.attr("r", 10)
This will create a single data point at 5,6 with a radius of 10. Note that this is using the scaling functions x() and y() to convert the data point 5,6 to pixel values
var x = d3.scale.linear()
.domain([0, 10])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 10])
.range([height, 0]);
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.
I'm trying to prevent clipping at the top most part of my chart by increasing the domain on the yAxis like so:
mainHeight = 640;
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]));
The idea is to get the max data for the yAxis and increase it by 1000.
The highest total is 14348 so with 1000 added on it creates 15348
However the top of the chart is still being clipped off and my axis hasn't increased to prevent the clipping. Even if I increase the number by 9999999999 it still doesn't happen.
The line is generated with:
var totalLine = d3.svg.line()
.interpolate('monotone')
.x(function (d) {
return xScale(d.date);
})
.y(function (d) {
return yScale(d.total);
});
Try throwing a .nice() at the end.
yScale = d3.scale.linear()
.range([mainHeight, 0])
.domain(d3.extent([0, d3.max(data, function (d) {
return (d.total)+1000;
})]))
.nice();
This will try to make the axis end on nice round numbers. You can read more here: https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md#linear_nice
Hope this helps.
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/