Dynamic overiding of measure axes in dimple.js causing floating droplines - javascript

eI am creating charts in dimple.js using a dynamic data set. To do this I am using addMeasureAxis for both x and y.
My problem is that I want to change the range of these axes since having the axis cross at the origin often leave my points all in the corner of the graph. To solve this I try set the x/y axis minima to the lowest value of my data by axis.overrideMin.
This gives me a graph scaled better to my data but the axes minima are still not what I had set, instead are slightly lower. As such when I mouseover the drop-lines do not reach the axis, rather they stop at my overrideMin value. Am I overriding incorrectly or can I extend where the drop-lines go to.
$scope.svg = new dimple.newSvg("dimple-chart", 800, 600);
$scope.chart = new dimple.chart($scope.svg, $scope.chartData);
$scope.chart.data = $scope.chartData;
$scope.chart.setBounds(60, 30, 500, 330);
var x,y,dataSeries;
x = $scope.chart.addMeasureAxis("x", xStatProperty);
y = $scope.chart.addMeasureAxis("y", yStatProperty);
$scope.chartData = _.sortBy($scope.chartData, xStatProperty); \\Sorts data
x.overrideMin = $scope.chartData[0][xStatProperty]; \\Overides to min value
$scope.chartData = _.sortBy($scope.chartData, yStatProperty);
y.overrideMin = $scope.chartData[0][yStatProperty];
dataSeries = $scope.chart.addSeries("Team", dimple.plot.bubble);
$scope.chart.draw();

There is nothing wrong with the approach you are using to override, it sounds like you might be using an old version of the library. Updating to version 2.1 should fix this problem.
Edit:
Following your comment below I've done some more investigation and this is caused by axis value rounding. Here is an example of the problem:
http://jsbin.com/ricud/2/edit?js,output
In order to ensure that the axes finish on a labelled point, dimple will round the axes to the next ticked value, this probably shouldn't be the case for overridden axes so please create an issue in Git Hub for that.
It's tricky to work around in a flexible way because you need to know the granularity of the axis. The example above could be fixed by using a floor calculation:
http://jsbin.com/ricud/3/edit?js,output
But that wouldn't work if the axis looked like this:
http://jsbin.com/ricud/4/edit?js,output
The only workaround I can think of relies on some internal API knowledge so is pretty hacky but I think this will work for all cases:
chart.draw();
if (x.overrideMin > 0) {
x._origin = chart._xPixels();
}
if (y.overrideMin > 0) {
y._origin = chart._yPixels() + chart._heightPixels();
}
http://jsbin.com/ricud/7/edit?js,output

Related

Highcharts Y-Axis Limits

I am having problems controlling the Y-Axis range of a highcharts graph. It seems like highcharts likes nice round numbers. When my data passes or is close to certain thresholds, the Y-Axis range can expand a lot which effectively compresses all the plot points downward.
Here is a jsfiddle that illustrates the problem I am having:
https://jsfiddle.net/shannonwrege/z8h5eork
The relevant code for this post is this:
chart.yAxis[0].setExtremes(0, max, true, false);
Keep in mind that I don't know what the data will look like in advance, so I must dynamically modify the Y-Axis range. Right now I am using the setExtremes because of other suggestions I've read on stackoverflow.
The maximum y-value of the data in the first two charts is 99. You'll notice that the y-axis is set at 150 in the first chart where the range is automatically calculated and 100 in the second chart where I specifically set the extreme values. The look of the 2nd chart is what I want. So it seems like setExtremes(0,99,true,false) should do the trick, but it actually doesn't.
In the 3rd chart I changed the data so that the maximum y-value of the data is 101, and I called setExtremes(0,101,true,false). You'll note that the y-axis is now back to 150.
Ideally I want the scale of the graph to be capped on the maximum value to limit the about of extra white space. I want to see all of the data, but I don't necessarily care about the y-axis displaying a maximum band that is greater than the max data value. In this case, I would be happy with the y-axis displaying 100 on the axis and some points over but still visible.
Does anyone know how to make this work?
I ended up using the endOnTick parameter to solve this problem. Adding the following line to the yAxis configuration parameters did exactly what I wanted:
endOnTick: false,
Here's the updated Fiddle showing the results.
https://jsfiddle.net/shannonwrege/z8h5eork/3/
All of the charts look pretty good in my opinion (even the one where the yAxis range was auto calculated).
You will need to read the data and then round up to set the idealMax
var chart,
idealMax = 0; // init the max value
// Read the data to find the highest value
for (i=0;i < (options.series[0].data).length; i++ ){
if (options.series[0].data[i][1] > idealMax) {
idealMax = options.series[0].data[i][1];
}
}
// Round the max to the nearest 10
idealMax = Math.round(idealMax / 10) * 10;
options.yAxis.tickPixelInterval = idealMax/10;
Highcharts.chart('container1', options);
chart = $('#container1').highcharts();
chart.yAxis[0].setExtremes(0, idealMax, true, false);
Updated Fiddle

HighCharts: How to draw a straight line in multiple axes like plotLines with fixed X-axis with different values

I'm using highcharts for my project. I've used multiple axes for drawing the chart. What I want is there should be a straight line on fixed x-axis even though with different values.
I want to have something like plotLines which will be straight and fixed on the x-axis but still shows different values I put it.
I would create separate series to achieve that. I think it's simpler solution than playing around with renderer. Demo: http://jsfiddle.net/6wo5mzo4/
(function(H) {
var seriesTypes = H.seriesTypes;
seriesTypes.fakeLine = Highcharts.extendClass(seriesTypes.line);
seriesTypes.fakeLine.prototype.translate = function() {
var s = this;
seriesTypes.line.prototype.translate.call(s);
H.each(s.points, function(point) {
point.plotY = s.chart.plotWidth / 2; // display in the middle - note: plotWidth, not plotHeight, because chart is inverted
});
}
})(Highcharts);
However, I still have doubts how useful such chart will be. I would also suggest to review the requirements.

Does d3.js offer a method to extend my dataset to the origin of my graph?

Take this scenario from a graph I'm working on at the moment:
The problem I'm having is in the bottom left. My dataset's first coordinate is defined at approximately (60,5), yet the domain I'm looking to cover extends right down to 0. Is there any way I can get d3 to extrapolate this data to my origin? I've browsed the API but nothing clearly stands out.
I'm well aware I could just .push a new object with coordinates (0,0) onto my dataset array, but I would prefer not to as I may need to do manipulation with my data later, making this an undesirable option.
Since you have not provided a fiddle i chose to put up a small fiddle to explain this:
My Data set is like this:
data = [{
xval: 10,
yval: 100
}, {
xval: 40,
yval: 90
}, {
xval: 50,
yval: 12
}, {
xval: 90,
yval: 70
}]
You can see the values of x and y value varies from 0 to 100.
So you will define the range like:
x.domain([0, 100]);//this will show x axis start from 0
y.domain([0,100]);//this will show y axis start from 0
example here:
as per your requirement you want the y axis to start from 10 so you do
x.domain([0, 100]);//this will show x axis start from 0
y.domain([10,100]);//this will show y axis start from 10
example here
Hope this solves your problem. ..:)
You can also adjust your data domain to the maximum and the minimum using the extent function that d3 provides.
var x_domain = d3.extent(data,function(d){return d.xval});
var y_domain = d3.extent(data,function(d){return d.yval});
x.domain(x_domain);
y.domain(y_domain);
That way the graph will always be adjusted to the data domain in both coordinates whatever data comes.
Watch this working.
Well, I found an answer to my own question here.
d3 will never extend a line beyond the final data point.
The solution is the following:
If your really must have the line start and end at the very end of your range, then you have two options:
Create a custom interpolation function; or,
Add an "end-value" data point when you pass your data array to d3.svg.line.
For me, it looks like I'm going to have to include a "start value" datapoint. Disappointing.

d3.v3 scatterplot with all circles the same radius

Every example I have found shows all of the scatter plot points to be of random radii. Is it possible to have them all the same size? If I try to statically set the radius all of the circles will be very small (I'm assuming the default radius). However, if I use Math.random() as in most examples there are circles large and small. I want all the circles to be large. Is there a way to do that? Here's the code snippet forming the graph data using Math.random() (this works fine for some reason):
function scatterData(xData, yData)
{
var data = [];
for (var i = 0; i < seismoNames.length; i++)
{
data.push({
key: seismoNames[i],
values: []
});
var xVals=""+xData[i];
xVals=xVals.split(",");
var yVals=""+yData[i];
yVals=yVals.split(",");
for (var j = 0; j < xVals.length; j++)
{
data[i].values.push({
x: xVals[j],
y: yVals[j],
size: Math.random()
});
}
}
return data;
}
Math.random() spits out values between 0 and 1 such as 0.164259538891095 and 0.9842195005008699. I have tried putting these as static values in the 'size' attribute, but no matter what the circles are always really small. Is there something I'm missing?
Update: The NVD3 API has changed, and now uses pointSize, pointSizeDomain, etc. instead of just size. The rest of the logic for exploring the current API without complete documentation still applies.
For NVD3 charts, the idea is that all adjustments you make can be done by calling methods on the chart function itself (or its public components) before calling that function to draw the chart in a specific container element.
For example, in the example you linked too, the chart function was initialized like this:
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.color(d3.scale.category10().range());
chart.xAxis.tickFormat(d3.format('.02f'));
chart.yAxis.tickFormat(d3.format('.02f'));
The .showDistX() and .showDistY() turn on the tick-mark distribution in the axes; .color() sets the series of colours you want to use for the different categories. The next too lines access the default axis objects within the chart and set the number format to be a two-digit decimal. You can play around with these options by clicking on the scatterplot option from the "Live Code" page.
Unfortunately, the makers of the NVD3 charts don't have a complete documentation available yet describing all the other options you can set for each chart. However, you can use the javascript itself to let you find out what methods are available.
Inspecting a NVD3.js chart object to determine options
Open up a web page that loads the d3 and nvd3 library. The live code page on their website works fine. Then open up your developer's console command line (this will depend on your browser, search your help pages if you don't know how yet). Now, create a new nvd3 scatter chart function in memory:
var testChart = nv.models.scatterChart();
On my (Chrome) console, the console will then print out the entire contents of the function you just created. It is interesting, but very long and difficult to interpret at a glance. And most of the code is encapsulated so you can't change it easily. You want to know which properties you can change. So run this code in the next line of your console:
for (keyname in testChart){console.log(keyname + " (" + typeof(testChart[keyname]) + ")");}
The console should now print out neatly the names of all the methods and objects that you can access from that chart function. Some of these will have their own methods and objects you can access; discover what they are by running the same routine, but replacing the testChart with testChart.propertyName, like this:
for (keyname in testChart.xAxis){console.log(keyname + " (" + typeof(testChart.xAxis[keyname]) + ")");}
Back to your problem. The little routine I suggested above doesn't sort the property names in any order, but skimming through the list you should see three options that relate to size (which was the data variable that the examples were using to set radius)
size (function)
sizeDomain (function)
sizeRange (function)
Domain and range are terms used by D3 scales, so that gives me a hint about what they do. Since you don't want to scale the dots, let's start by looking at just the size property. If you type the following in the console:
testChart.size
It should print back the code for that function. It's not terribly informative for what we're interested in, but it does show me that NVD3 follows D3's getter/setter format: if you call .property(value) you set the property to that value, but if you call .property() without any parameters, it will return back the current value of that property.
So to find out what the size property is by default, call the size() method with no parameters:
testChart.size()
It should print out function (d) { return d.size || 1}, which tells us that the default value is a function that looks for a size property in the data, and if it doesn't exist returns the constant 1. More generally, it tells us that the value set by the size method determines how the chart gets the size value from the data. The default should give a constant size if your data has no d.size property, but for good measure you should call chart.size(1); in your initialization code to tell the chart function not to bother trying to determine size from the data and just use a constant value.
Going back to the live code scatterplot can test that out. Edit the code to add in the size call, like this:
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.color(d3.scale.category10().range())
.size(1);
chart.xAxis.tickFormat(d3.format('.02f'));
chart.yAxis.tickFormat(d3.format('.02f'));
Adding that extra call successfully sets all the dots to the same size -- but that size is definitely not 1 pixel, so clearly there is some scaling going on.
First guess for getting bigger dots would be to change chart.size(1) to chart.size(100). Nothing changes, however. The default scale is clearly calculating it's domain based on the data and then outputting to a standard range of sizes. This is why you couldn't get big circles by setting the size value of every data element to 0.99, even if that would create a big circle when some of the data was 0.01 and some was 0.99. Clearly, if you want to change the output size, you're going to have to set the .sizeRange() property on the chart, too.
Calling testChart.sizeRange() in the console to find out the default isn't very informative: the default value is null (nonexistent). So I just made a guess that, same as the D3 linear scale .range() function, the expected input is a two-element array consisting of the max and min values. Since we want a constant, the max and min will be the same. So in the live code I change:
.size(1);
to
.size(1).sizeRange([50,50]);
Now something's happening! But the dots are still pretty small: definitely not 50 pixels in radius, it looks closer to 50 square pixels in area. Having size computed based on the area makes sense when sizing from the data, but that means that to set a constant size you'll need to figure out the approximate area you want: values up to 200 look alright on the example, but the value you choose will depend on the size of your graph and how close your data points are to each other.
--ABR
P.S. I added the NVD3.js tag to your question; be sure to use it as your main tag in the future when asking questions about the NVD3 chart functions.
The radius is measured in pixels. If you set it to a value less than one, yes, you will have a very small circle. Most of the examples that use random numbers also use a scaling factor.
If you want all the circles to have a constant radius you don't need to set the value in the data, just set it when you add the radius attribute.
Not sure which tutorials you were looking at, but start here: https://github.com/mbostock/d3/wiki/Tutorials
The example "Three little circles" does a good step-by-step of the different things you can do with circles:
http://mbostock.github.io/d3/tutorial/circle.html

Limiting domain when zooming or panning in D3.js

I have implemented a simple D3.js line chart that can be zoomed and panned. It is based on Stephen Bannasch's excellent example here.
The domain of my data is [0, n] in the x dimension.
How can I limit zooming and panning to this domain using the built-in zoom behavior (i.e. using mousewheel events)?
I want to prevent users from panning past 0 on the lower end or n on the upper end, for example they should never be able to see negative values on the x-axis, and want to limit zooming to the same window.
The examples that I found based on Jason Davies work using extent( [...],[...],[...] ) seem to no longer work in version 2.9.1. Unfortunately, the zoom behavior is currently one of the few features not documented in the otherwise outstanding API documentation.
Any pointers are welcome.
PS. I have posted the same question on the D3.js mailing list but did not get a response: https://groups.google.com/d/topic/d3-js/w6LrHLF2CYc/discussion. Apologies for the cross-posting.
Sadly, the solution posted by Bill did only half the trick: While it does indeed inhibit the panning, it causes the graph to distort if zoom is applied. It is then usually impossible to return to a properly proportioned and positioned graph.
In the following version the proportions of the axes are maintained, even if scrolling to the borders.
As soon as the scaling hits 100%, the scales' domains are reset to their original position. This guarantees a correct positioning, even if the intermediate steps return illegal parameters for the axes.
While not perfect, I hope this script can help somebody until d3 (re)implements this feature.
# x and y are the scales
# xAxis and yAxis are the axes
# graph is the graph you want attach the zoom to
x0 = x.copy()
y0 = y.copy()
successfulTranslate = [0, 0]
zoomer = d3.behavior.zoom()
.scaleExtent([1,2])
onZoom = ->
ev = d3.event # contains: .translate[x,y], .scale
if ev.scale == 1.0
x.domain x0.domain()
y.domain y0.domain()
successfulTranslate = [0, 0]
else
xTrans = x0.range().map( (xVal) -> (xVal-ev.translate[0]) / ev.scale ).map(x0.invert)
yTrans = y0.range().map( (yVal) -> (yVal-ev.translate[1]) / ev.scale ).map(y0.invert)
xTransOk = xTrans[0] >= x0.domain()[0] and xTrans[1] <= x0.domain()[1]
yTransOk = yTrans[0] >= y0.domain()[0] and yTrans[1] <= y0.domain()[1]
if xTransOk
x.domain xTrans
successfulTranslate[0] = ev.translate[0]
if yTransOk
y.domain yTrans
successfulTranslate[1] = ev.translate[1]
zoomer.translate successfulTranslate
graph.select('g.x.axis').call(xAxis)
graph.select('g.y.axis').call(yAxis)
drawBars()
zoomer.on('zoom', onZoom)
# ...
graph.call(zoomer)
You just need to limit the domain on redraw. The following code will prevent the graph from being zoomed out past it's initial domains (as used in http://bl.ocks.org/1182434).
SimpleGraph.prototype.redraw = function() {
var self = this;
return function() {
self.x.domain([Math.max(self.x.domain()[0], self.options.xmin), Math.min(self.x.domain()[1], self.options.xmax)]);
self.y.domain([Math.max(self.y.domain()[0], self.options.ymin), Math.min(self.y.domain()[1], self.options.ymax)]);
....

Categories

Resources