Show a tick every n value - javascript

How can I setup an axis that it shows not 5 or 10 or whatever number of ticks but rather show a tick each n units. In this case if a dataset looks like this:
[2,3,6,7,10,13,17,20]
The ticks will be on 0, 5, 10, 15, 20 if I configure it to show a tick for every 5th step.
How can it be achieved?
Here is a real example I am working on
I want on x axis show a tick after each 20 years, so it should be not 0, 10, 20, 30, ..., n, but 0, 20, 40, 60, 80, ..., n.
For y axis I need values to be 500k stepped, so it should be 0, 500k, 1m, 1.5m, ..., n.
Update
I have found a simple working solution. I call an axis via `.call(yAxis) as usual, then I find go through created ticks and check if their datum has a remainder when I divide their values by the step number I need. If there is no remainder, then I set opacity to 1, otherwise hide them by setting opacity to 0. Here is an example:
yAxisElement
.selectAll('.tick')
.style({
opacity: function (d, i) {
return d % 500000 ? 0 : 1;
}
})
It's not an ideal solution, because it can't add the ticks that are in the middle of existing ones by value, like on this image, if I want to use 0, 15, 30, 45 and so on, just show/hide those that are already there. In the case I need to add ticks for other values, I need to make a custom axis or use the solution suggested by Lars Kotthoff.

Related

How can I change x-axis position in dygraph?

I am new to dygraph and I have one issue: while creating dygraph using Javascript negative values of the y-axis are displayed above the x-axis 0 value.
Here is my code :
g6 = new Dygraph(document.getElementById('smooth-line'),
functionData,
{
labels: ['Year', 'First','Second'],
series: {
First: {
plotter: smoothPlotter,
color: '#26a69a ',
strokeWidth: 2
},
Second: {
plotter: smoothPlotter,
color: '#e57373 ',
strokeWidth: 2
}
},
legend: 'always',
gridLineColor: '#ddd',
//valueRange: [1.0, 30.0],
//yRangePad :[-20.0,20.0]
});
}
and the output of this code is:
Output of the code
As in the image x-axis is below to -ve values of y-axis so how to set position of x-axis at the 0 value of y-axis?
First a comment. Posting examples that are not self-contained takes a lot longer to troubleshoot.
This link: http://jsfiddle.net/yLytg398/1/ provides a reasonable approximation to your code that can be tested.
You actually had the solution already in your code, valueRange is what you need. You can even use valueRange: [0, null] to automatically calculate the upper bound.
Correction: I just realized that you actually wanted to have the x axis with labels moved into the middle of the graph, my solution does not address this, but for your example picture it still works, because valueRange can set the lower end of the y range to zero, so that the x axis is at y = 0.

Best d3 scale for mapping integers range

I want to construct a scale that maps a range of successive integers (indexes of characters in a string) to regular intervals in another range of integers (pixels, say 0-600). That is, I would like to assign characters to pixels and conversely as regularly as possible, the length of one not being necessarily a multiple of the other.
For instance, mapping [0,1,2,3] to 400 pixels, I would expect
0 -> 0-99
1 -> 100-199
2 -> 200-299
3 -> 300-399
and conversely
0-99 -> 0
100-199 -> 1
200-299 -> 2
300-399 -> 3
while for mapping 0-4000 to 400 pixels, I would expect
0-9 -> 0
10-19 -> 1
etc.
What is the best scale to use for this in d3 ?
On one hand I am afraid that discrete scales will not use the fact that the domain is equally separated and generate a huge switch statement if the number of elements is big. Since I will use the scale on every element to draw an image, I am worried about performance.
On the other hand, a linear scale such as
d3.scaleLinear()
.domain([0,399]) // 400 pixels
.rangeRound([0,3]) // 4 elements
gives me
0 0
66 0 // 1st interval has 66 pixels
67 1
199 1 // other intervals have 132 pixels
200 2
332 2
333 3 // last interval has 66 pixels
400 3
(fiddle)
so the interpolator returns unequal intervals (shorter at the ends).
Edit: not using d3, it is not hard to implement:
function coordinateFromSeqIndex(index, seqlength, canvasSize) {
return Math.floor(index * (canvasSize / seqlength));
}
function seqIndexFromCoordinate(px, seqlength, canvasSize) {
return Math.floor((seqlength/canvasSize) * px);
}
Too bad only if it does not come with d3 scales, since it would become much more readable.
The d3 Quantize Scale is the best option if you want to map onto an interval. The scale maps between discrete values and a continuous interval, though. I am not 100% clear on what you want to do, but let's look at how I could do a few of the things you mention with the quantize scale.
Mapping integers to intervals is straightforward, as long as you know that d3 uses half-open intervals [,) to break up the continuous domain.
var s1 = d3.scaleQuantize()
.domain([0,400])
.range([0,1,2,3]);
s1.invertExtent(0); // the array [0,100] represents the interval [0,100)
s1.invertExtent(1); // [100,200)
s1.invertExtent(2); // [200,300)
s1.invertExtent(3); // [300,400)
You could also enumerate the discrete values:
var interval = s.invertExtent(0);
d3.range(interval[0], interval[1]); // [0, 1, ... , 399]
These are nice values you've given though, and since you want to map integers to intervals of integers, we will need rounding when numbers aren't divisible. We can just use Math.round though.
var s2 = d3.scaleQuantize()
.domain([0,250])
.range([0,1,2,3]);
s2.invertExtent(0); // [0, 62.5)
s2.invertExtent(0).map(Math.round); // [0,63) ... have to still interpret as half open
There is no mapping from the interval itself to the integer, but the scale maps a point in an interval from the domain (which is continuous) to its value in the range.
[0, 99, 99.9999, 100, 199, 250, 399, 400].map(s1); // [0, 0, 0, 1, 1, 2, 3, 3]
I also suspect you switched the output of rangeRound from the linear scale with something else. I get
var srr = d3.scaleLinear()
.domain([0,3]) // 4 elements
.rangeRound([0,399]);
[0,1,2,3].map(srr); // [0, 133, 266, 399]
and
var srr2 = d3.scaleLinear()
.domain([0,4]) // 4 intervals created with 5 endpoints
.rangeRound([0,400]);
[0,1,2,3,4].map(srr2); // [0, 100, 200, 300, 400]
The output looks like a scale to us with a bar graph with 50% padding (then each position would be the midpoint of an interval that is 132 pixels). I am going to guess the cause is that rangeRound uses round to interpolate, rather than floor.
You could use a function designed for bar graphs also, if you want the width of the interval.
var sb = d3.scaleBand().padding(0).domain([0,1,2,3]).rangeRound([0,400]);
[0,1,2,3].map(sb); // [0, 100, 200, 300]
sb.bandwidth(); // 100
Not that any of this makes the code simpler.
Once I get to the functions you implement, it seems like the requirements are much simpler. There aren't any intervals involved really. The problem is that there isn't a one-to-one mapping. The best solution is either what you have done or to just use two linear scales with a custom interpolator (to find the floor, rather than rounding.
var interpolateFloor = function (a,b) {
return function (t) {
return Math.floor(a * (1 - t) + b * t);
};
}
var canvasSize = 400;
var sequenceLength = 4000;
var coordinateFromSequenceIndex = d3.scaleLinear()
.domain([0, sequenceLength])
.range([0, canvasSize])
.interpolate(interpolateFloor);
var seqIndexFromCoordinate = d3.scaleLinear()
.domain([0, canvasSize ])
.range([0, sequenceLength])
.interpolate(interpolateFloor);

google visualizations align 0 axis with two different y-axes

I'm creating a combochart with google's visualization library. I'm charting a store's traffic and revenue over the course of a day. I have set my draw options to
var options = {
seriesType: "bars",
series:{0:{targetAxisIndex:0},1:{targetAxisIndex:1}},
vAxes:{0:{title: "Revenue"},1:{title: "Traffic"}},
hAxis: {title: "Time", showTextEvery: 1},
};
which sets up the Revenue on a different Y-axis than the traffic. A sample of the data might look like this:
var data = [
// Time Revenue Traffic
['10:00-10:30', '132.57', '33'],
['10:30-11:00', '249.23', '42'],
['11:00-11:30', '376.84', '37'],
[... etc ..]
];
the problem I'm having is that Traffic values will always be positive whereas Revenue could be a negative number if there were returns. If that happens my Revenue axis will start at a negative value like -50 while Traffic starts at 0 and the horizontal baselines don't line up. I would like to have it so that even if Revenue has values less than 0 it's 0 axis will line up with the Traffic 0 axis.
Here's an example to show what's happening. See how the Traffic 0 axis is on the same level as the Revenue's -50 axis. I would like to know how to raise the Traffic baseline to the same level as the Revenue 0 axis.
I have a method that I am reasonably certain will always produce axis values with the same 0 point (I haven't proved that it can't produce axes with different 0 points, but I haven't encountered any).
To start off, get the range of the two date series (for our purposes, column 1 is "revenue" and column 2 is "traffic"):
var range1 = data.getColumnRange(1);
var range2 = data.getColumnRange(2);
For each series, get the max value of the series, or 1 if the max is less than or equal to 0. These values will be used as the upper bounds of the chart.
var maxValue1 = (range1.max <= 0) ? 1 : range1.max;
var maxValue2 = (range2.max <= 0) ? 1 : range2.max;
Then calculate a scalar value relating the two upper bounds:
var scalar = maxValue2 / maxValue1;
Now, calculate the lower bounds of the "revenue" series by taking the lower of range1.min and 0:
var minValue1 = Math.min(range1.min, 0);
then multiply that lower bound by the scalar value to get the lower bound of the "traffic" series:
var minValue2 = minValue1 * scalar;
Finally, set the vAxis minValue/maxValue options for each axis:
vAxes: {
0: {
maxValue: maxValue1,
minValue: minValue1,
title: 'Revenue'
},
1: {
maxValue: maxValue2,
minValue: minValue2,
title: 'Traffic'
}
}
The net result is that positive and negative proportions of each series are equal (maxValue1 / (maxValue1 - minValue1 == maxValue2 / (maxValue2 - minValue2 and minValue1 / (maxValue1 - minValue1 == minValue2 / (maxValue2 - minValue2), which means the chart axes should end up with the same positive and negative proportions, lining up the 0's on both sides.
Here's a jsfiddle with this working: http://jsfiddle.net/asgallant/hvJUC/. It should work for any data set, as long as the second data series has no negative values. I'm working on a version that will work with any data sets, but this should suffice for your use case.

Highcharts Drawing a line with chart.renderer.path

I'm looking at this formula:
http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
And here is how I draw it:
chart.renderer.path(['M', someNumber, 10, 'V', 1500, 0])
.attr({
'stroke-width': 2,
stroke: 'red',
id: 'vert'
})
.add();
And the line is drawn but it goes through the whole graph. I want it to be small. I think that to make it smaller i need to change the '10' but whatever value I put (even 10.000) the line's length remains the same.
In the path attribute:
path(['M', someNumber, 10, 'V', 1500, 0])
The 'M' means moveto, "someNumber", 10 are the x, y coordinates you are moving to (this does not draw just moves the "pen" to where you want to start the line). The 'V' means draw a vertical line, the 1500 is the y position to stop drawing it. I do not believe you need the 0 (the V attribute only takes one parameter).
If you want the length of the line to be smaller adjust the 1500 parameter.

How do I set a minimum upper bound for an axis in Highcharts?

I'm trying to set a minimum upper bound, specifically:
The Y axis should start at 0
The Y axis should go to at least 10, or higher (automatically scale)
The upper bound for the Y axis should never be less than 10.
Seems like something Highcharts does, but I can't seem to figure out how. Anybody have experience with this?
Highcharts doesn't seem to have an option for doing this at chart creation time. However, they do expose a couple methods to interrogate the extremes and change the extremes, getExtremes() and setExtremes(Number min, Number max, [Boolean redraw], [Mixed animation]) found in their documentation.
So, a possible solution (after chart creation):
if (chart.yAxis[0].getExtremes().dataMax < 10) {
chart.yAxis[0].setExtremes(0, 10);
}
yAxis[0] references the first y-axis, and I'm assuming that you only have one axis in this case. The doc explains how to access other axes.
This isn't ideal, because the chart has to redraw which isn't too noticeable, but it's still there. Hopefully, Highcharts could get this sort of functionality built in to the options.
A way to do this only using options (no events or functions) is:
yAxis: {
min: 0,
minRange: 10,
maxPadding: 0
}
Here minRange defines the minimum span of the axis. maxPadding defaults to 0.01 which would make the axis longer than 10, so we set it to zero instead.
This yields the same results as a setExtreme would give. See this JSFiddle demonstration.
Adding to Julian D's very good answer, the following avoids a potential re-positioning problem if your calculated max varies in number of digits to your desired upper bound.
In my case I had percentage data currently going into the 20's but I wanted a range of 0 to at least 100, with the possibility to go over 100 if required, so the following sets min & max in the code, then if dataMax turns out to be higher, reassigns max up to it's value. This means the graph positioning is always calculated with enough room for 3-digit values, rather than calculated for 2-digits then broken by squeezing "100" in, but allows up to "999" before there would next be a problem.
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
events: {
load: function(event) {
thisSetMax = this.yAxis[0].getExtremes().max;
thisDataMax = this.yAxis[0].getExtremes().dataMax;
if (thisDataMax > thisSetMax) {
this.yAxis[0].setExtremes(0, thisDataMax);
alert('Resizing Max from ' + thisSetMax + ' to ' + thisDataMax);
}
}
}
},
title: {
text: 'My Graph'
},
xAxis: {
categories: ['Jan 2013', 'Feb 2013', 'Mar 2013', 'Apr 2013', 'May 2013', 'Jun 2013']
},
yAxis: {
min: 0,
max: 100,
title: {
text: '% Due Tasks Done'
}
}
//Etc...
});
HigtCharts has a really good documentation of all methods with examples.
http://www.highcharts.com/ref/#yAxis--min
In your case I think you should the "min" and "max" properties of "yAxis".
min : Number
The minimum value of the axis. If null the min value is automatically calculated. If the startOnTick option is true, the min value might be rounded down. Defaults to null.
max : Number
The maximum value of the axis. If null, the max value is automatically calculated. If the endOnTick option is true, the max value might be rounded up. The actual maximum value is also influenced by chart.alignTicks. Defaults to null.
If you are creating your chart dynamically you should set
min=0
max=10 , if your all data values are less then 10
and
only min=0, if you have value greater then 10
Good luck.
Try setting the minimum value of the axis and the interval of the tick marks in axis units like so:
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container'
},
yAxis: {
min: 0,
max: 10,
tickInterval: 10
}
});
Also don't forget to set the max value.
Hope that helps.

Categories