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.
Related
Following the c3js documentation there is no option for Bubble chart. One workaround for that is to setup scatter plot and specify point radius, but all of the bubbles will be the same height.
point = {
r: function(d) {
var num = d.value;
return num
},
Adding the value of axis inside the r solve the problem, but now the problem is how to setup very high or very low values ? For e.g if there is 1 000 000 value the whole chart will be colored. Is there any simple workarounds for that ?
First of all, set r to return the square root of your chosen variable e.g. return sqrt(num), that way a circle representing a data point 100 times the size of another has 100, not 10,000, times the area (area=pi r2 and all that)
If the numbers are still too big use a linear scale to restrict them to a usable size:
rscale = d3.scale.linear().domain([1,1000]).range([0,10])
and then return rscale(sqrt(num))
If your problem is to represent large and small values on the same chart so small values don't disappear and large values don't exceed the chart size look at using a d3 log scale:
rscale = d3.scale.log().base(10).domain([1,1000]).range([0,10])
Of course on a log scale the areas aren't linearly proportionate any more so whether the sqrt step is necessary is debatable. If you don't just remember to adjust the domain to account for this - change it to domain([1,1000000])
if you don't know the size of your numbers beforehand it will be worthwhile looping through your dataset to pick out the min and max to plug into the domain value: domain([your_min, your_max]). my examples above all assume a max of one million.
Here's an example I forked on jsfiddle, numbers from a few hundred to over a hundred thousand are displayed using a log scale and all are visible but the differences are still obvious:
http://jsfiddle.net/m9gcno5n/
In d3, if you want to create an axis you might do something like this:
var xAxis = d3.svg.axis()
.scale(x)
where x is a scale function. I understand that the domain of x defines the start and ending values for the ticks. I'm having trouble understanding how the range of x changes the resulting axis. What does the domain map to in the context of an axis.
Think about what one must do to create a visual representation of any data set. You must convert each data point (e.g. 1 million dollars) into a point on the screen. If your data has a minimum value of $0 and maximum value of $1000000, you have a domain of 0 to 1000000. Now to represent your data on a computer screen you must convert each data point (e.g. $25) into a number of pixels. You could try a simple 1 to 1 linear conversion ($25 converts to 25 pixels on the screen), in which case your range would be the same as your domain = 0 to 1000000. But this would require a bloody big screen. More likely we have an idea of how large we want the graphic to appear on the screen, so we set our range accordingly (e.g. 0 to 600).
The d3 scale function converts each data point in your dataset into a corresponding value within your range. That enables it to be presented on the screen. The previous example is a simple conversion so the d3.scale() function is not doing much for you, but spend some time converting data points into a visual representation and you will quickly discover some situations where the scale function is doing a lot of work for you.
In the particular case of an axis, the scale function is doing exactly the same thing. It is doing the conversion (to pixels) for each 'tick' and placing them on the screen.
I only had 5 values[1,2,3,4,5] as my y - coordinates in the d3.js line plot. But, I end up getting more values [0.5,1,1.5,2,2.5,3,3.5,4,4.5,5] Is there a way to edit the d3.js file or the html file inorder to plot the values as per my requirement?
The tick marks created by a d3 axis can be controlled in two ways:
Using axis.tickValues(arrayOfValues) you can explicitly set the values that you want to show up on the axis. The ticks are positioned by passing each value to the associated scale, so the values should be within your scale's domain. This works for any type of scale, including ordinal scales, so long as the values you give are appropriate to that scale.
Alternately, using axis.ticks(parameters) you can modify the way the scale calculates tick marks. The types of parameters you can use depends on the type of scale you're using -- the values you specify will be passed directly to the scale's .ticks() method, so check the documentation for each scale type. (Parameters will be ignored for ordinal scales, which don't have a ticks() method.)
For linear scales, the scale.ticks() method accepts a number as a parameter; the scale then generates approximately that many ticks, evenly spaced within the domain with round number values. If you do not specify a tick count, the default is to create approximately 10 ticks, which is why you were getting ticks on 0.5 intervals when your domain was from 0 to 5.
So how do you get the behaviour you want (no decimal tick values)?
Using .tickValues(), you would create an array of unique Y-values to be your ticks:
var yValues = data.map(function(d){return d.y;});
//array of all y-values
yValues = d3.set(yValues).values();
//use a d3.set to eliminate duplicate values
yAxis.tickValues( yValues );
Be aware that this approach will use the specified y values even if they aren't evenly spaced. That can be useful (some data visualization books suggest using this approach as an easy way of annotating your graph), but some people may think your graph looks messy or broken.
Using .ticks(), you would figure out the extent of your Y domain, and set the number of ticks so that you do not have more tick marks then you have integers available on your domain:
var yDomain = yScale.domain();
yAxis.ticks( Math.min(10, (yDomain[1] - yDomain[0]) );
This will create the default (approximately 10) ticks for wide domains, but will create one tick per integer value when the difference between the max and min of your domain is less than 10. (Although the tick count is usually approximate, the scale will always prefer integer values if that matches the tick count specified.)
Yes you can also try
yAxis.ticks(5).tickFormat(D3.numberFormat(",d"));
It does the trick of eliminating the decimal numbers, does not effect number of ticks
Here is a good resource for the format of the numbers using D3.
Say I have a graph where the x-axis tick labels are very long strings, and so I want to alternate the tick padding (the vertical distance between the text and the x-axis) so that the tick labels don't overlap.
I know this can be achieved post-rendering by selecting the tick elements and applying a transform attribute. But I'd like to do:
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom")
.tickSize(0)
.tickPadding(function(i) {
// some logic here to determine the alternating-height strategy by index, e.g.
return i % 2 ? 20 : 30;
});
This doesn't work in d3 as-is -- the documentation (https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-tickPadding) says that tickPadding just takes the number of pixels. Is there a better way to do this? Monkey patch d3's axis.tickPadding function to take a function or a number, then apply the function when drawing the ticks?
For the source-code patch, see:
https://github.com/adonomay/d3/commit/bfdb36fa17806666775c6804b86eb10bea3b3393
An alternate fix is to wrap the text manually after rendering:
https://github.com/mbostock/d3/issues/1641
http://bl.ocks.org/mbostock/7555321
I also was hoping to do this. I was using week numbers and it got a little cramped. Basically the x axis looked like
111213141516171819
and I was hoping to stack them. I ended up doing this
$('.tick text').each(function(i){
var yval = this.getAttribute("y");
if( i % 2 == 0 )this.setAttribute("y",parseInt(yval)+10);
});
Once the graph was drawn. I made sure to include some extra margin (10) on the bottom as well. The result was
12 14 16 18
11 13 15 17 19
The only choice you have is to modify the source. D3 assumes throughout the axis implementation that tickPadding is a number, not a function -- see the source.
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);