For the simplest use case, a bar chart with values ranging from -10 to 10, how does one go about coding this cleanly using the Protovis JavaScript charting library?
By cleanly I mean centering the axis, showing x and y axis labels, and representing the column values of the chart where negative values fall below the y axis and positive values exceed the y axis.
Here's a working example: http://jsfiddle.net/nrabinowitz/yk5By/3/
The important parts of this are as follows:
Make an x-axis scale going from your min value to your max value (in your case, it would be pv.Scale.linear(-10,10).range(0,w); in my example, I calculate min and max based on the data).
Base the width of the bar on the absolute distance of the datum from 0:
.width(function(d) { return Math.abs(x(d) - x(0)); })
Then adjust the .left() property based on whether the datum is positive or negative:
.left(function(d) { return d > 0 ? x(0) : x(0) - this.width(); });
Because we're using a simple x-axis scale, the adding axis labels is super-easy:
vis.add(pv.Label)
.data(x.ticks()) // you could also use pv.range(min, max, 1) here
.left(x);
Related
I need to display elements on my chart with equal interval between elements with dates ticks on X axis. Elements have different interval because they have scaled by date range. Is it possible to display some elements on chart with equal interval between them regardless of dates on X axis?
I have tried to use different scales like bandscale and linearscale. But they works incorrect when i am trying to pass my min and max dates to domain function.
For example this chart:
https://js.devexpress.com/Demos/WidgetsGallery/Demo/Charts/Overview/jQuery/Light/
I need the same equal distance between dots but have dates on my X axis instead of names.
Since it is more important to have the bars equally spaced than to have them spaced according to their time value, use d3.scaleBand() and pass to the domain the full array of dates that are represented in the dataset, e.g., .domain(data.map(d => d.date)).
I have a scatterplot that uses constraint relaxation to de-conflict the labels for the points that it graphs (Plunker here). The problem is that, when I relax the constraints, this causes collisions between the point labels and the x-axis labels. The axes are generated using d3.extent and d3.scale.linear.
I've tried to de-conflict the point labels and the x-axis by extending the length of the y-axis, but the closest I've come to achieving this is by changing the original value of 0 to 30 in the following stanza:
var yext = d3.extent(data, d => d[1]);
var sy = d3.scale.linear()
.domain(yext)
.range([height, 30]) // flip y-axis
.nice();
The result is less than ideal, leaving an awkward gap instead of an intersection between the x and y axes:
What I want to achieve is something like this:
(Except I want to achieve this through code, rather than Photoshop).
Can anyone demonstrate a solution? (Plunker here)
Why don't you add a padding in the domain? Like:
.domain([yext[0] * 0.95, yext[1] * 1.05])
//less here---------^-- more here----^
Here is the plunker with that solution: http://plnkr.co/edit/rKArjn7DwQa9g1X5CaNW?p=preview
I am learning data visualization with d3.js, The following scale behaves strangely with d3.svg.axis
var scaleFunc = d3.scale.pow()
.exponent(1.6)
.domain(d3.extent(arr, numFunc)) // numfunc returns float between 0 - 5
.range([ ( maxPlotHeight -axes.bufferBottom ), axes.bufferTop ])
Axis function :
d3.svg.axis()
.scale(scaleFunc)
.orient("left")
.ticks(5)
If the extent in scale starts from 0 it works but with only 4 ticks, and if the extent start from anything above 0 ( like 1.2, 1.4 ) it shows 6 ticks with values : .5, .0, .5, .0, .5, .0
somewhat working y-axis image
broken y-axis image
Please help me understand the problem
From the docs on axis.ticks([arguments…])
The arguments will later be passed to scale.ticks to generate tick
values
and for pow.ticks([count])
The specified count is only a hint; the scale may return more or fewer
values depending on the input domain.
This is why you are not getting the exact number of ticks as you might have expected when calling .ticks(5).
The series of tick values .5, .0,... is most likely caused since there is not enough space to the left to display the entire number. Therefore, you will only see the decimal separator and the fractional part, whereas the integer part in front of the dot is cut off. Try translating everything to the right to allow for a wider margin to be able to display the tick values.
We're attempting to display two series of data on the same chart. The first series (ie: number of quotations) can only contain positive integers (and 0). The second series (ie: sales value) can contain both positive and negative float values (in case you're wondering, the negative values result from the issuing of credit notes).
As indicated on the attached image, the problem we're having is that when the second series contains negative values, the 0-point "base line" of the two series of data is no longer shared.
We've tried setting the "min" options of the Y axes to 0, but then we lose out the insight on negative values.
We've also tried setting the min of the first series to be equal to the minimum extreme of the second series, but unfortunately this doesn't scale the columns very nicely (since the values of each Y axis are on completely different scales, ie: 10s vs 1000s).
How can we configure the chart so that the 0-point "base line" is shared? In other words, how would we make the blue columns start at the same base line as the green 0 point?
Update
Linger's answer is a step in the right direction. Setting the min and tickinterval for both axes does the trick. However the values need to be determined dynamically based on variable data, which is where I am now stuck.
Any tips on how these min and tickinterval values can be determined before I generate the chart?
I've done some thinking about it in the mean time. The values associated with the left axis (blue / quotations) are always positive and start from zero. Thus it is the right axis (green / sales) that dictates the number of ticks to show below the zero point. Now since highcharts automatically determines the best scale for both blue and green, all I need to do is find a way to set the left axis' minimum value like so (excuse the pseudo-code):
var factor = right_axis.min / right_axis.tickinterval;
left_axis.min = factor * left.tickinterval;
Note: I have seen the reference API has a setExtremes() method under Axis, but this would require me to first initialise the chart, and then go back and update its left axis. I am hoping to do this before drawing the chart. Perhaps I'm overlooking something obvious?
Thanks.
You can control what you are asking for with a combination of tickInterval and min on the yaxis as demonstrated in this jsfiddle mock up.
EDIT
In your code while reading in the XML you will have to keep track of what the lowest value is and also the highest value on the sales side. Then decide how you want to display those values if they meet a certain value. After that use if statements to set the values. Here is a couple of examples.
Low less than -50 and greater -90
With High less than 400
Primary Axis: min: -2, tickInterval: 1,
Secondary Axis: min: -100, tickInterval: 50
Example
Low less than -50 and greater -90
With High greater than 400
Primary Axis: min: -1, tickInterval: 1,
Secondary Axis: min: min: -100, tickInterval: 100,
Example
To figure out what the min on the Primary Axis should be you simply divide the min on the Secondary Axis by its tickInterval. Then the tickInterval for the Primary Axis would always be 1.
You can also calculate second axis tick positions based on the max series value related to this axis and the first axis ticks positions to get the same amount of ticks and 0 line shared.
Using axis.update() method update tick positions in the second axis using property tickPositions.
Example with a load event. Notice, that when you add points dynamically you have to make those calculations and update tick positions each time your first axis ticks will change:
chart: {
type: 'column',
events: {
load: function() {
var chart = this,
yAxis = this.yAxis,
max = yAxis[1].dataMax,
positiveTicksAmount = 0,
negativeTicksAmount = 0,
tickPositions = [],
tickInterval,
min,
i;
yAxis[0].tickPositions.forEach(function(tick) {
if (tick > 0) {
positiveTicksAmount++;
} else if (tick < 0) {
negativeTicksAmount++;
}
});
tickInterval = max / positiveTicksAmount;
for (i = negativeTicksAmount; i > 0; i--) {
tickPositions.push(-tickInterval * i);
}
for (i = 0; i <= positiveTicksAmount; i++) {
tickPositions.push(
tickInterval * i
);
}
yAxis[1].update({
tickPositions: tickPositions
});
}
}
},
Jsfiddle examples:
https://jsfiddle.net/wchmiel/84q0sxrt/
http://jsfiddle.net/wchmiel/nuj7fagh/
Api reference:
https://api.highcharts.com/class-reference/Highcharts.Axis#update
https://api.highcharts.com/highcharts/yAxis.tickPositions
I'm making a simple JavaScript graphing library using the canvas element. I really suck at math so I'm stuck with a simple issue.
If I have a number - for example 30000, and I want to plot it relatively to graph's height which is 400. How do I calculate the y value for that?
You would want to figure out your max for the graph. Say, in this case 50000. Then, take your height and divide it by the max (so 400/50000) to get a ratio multiplier. Any number you want to plot you multiply by that ratio and that should give you a number that fits on your space. Is that what you're asking for?
For that you need to first find out the maximum and minimum values that you need to plot. For example, in this list of (x,y) coordinates: [(1,3),(2,10),(3,0),(4,-10)], the max value is 10 and the min value is -10. This gives you a span of (max-min = 10-(-10) = ) 20.
Notice that you can now translate the set of y values into a number in the range [0,max-min] (i.e. [0,20] in this case). Here, a value of 0 will get plotted as a 0 in the graph and a value of 20 will get plotted as 400. Also, a value of 20/2 will be plotted as 400/2. Thus, a value of 20/x is plotted as 400/x. This means that any value can now be plotted as 400*value/20.
So, to translate a given value n to its corresponding y value on the graph, simply convert n to (n-min)*400/(max-min).