D3 axis: set domain based on numbers but display them as strings - javascript

I have for dataset an array of objects like so
[
{
time: '20:07',
seconds: 7620,
value: 49,995
},
...
]
I'm currently creating X axis like so
this.x = d3.scaleLinear()
.domain(this.values.map(function(d) { return d.seconds}))
.range([ 0, width])
this.chart.append('g')
.call(d3.axisBottom(this.x))
what happens now is following
so this correct domain, 7620 is smallest seconds value and 7800 highest, but now instead of showing seconds I wish to display 'time' value. First label on x axis would be then '20:07' instead of 7620.
In a nutshell, create domain based on seconds but show time instead. How can I do that ?

You can transform the tick labels using tickFormat:
this.chart.append('g')
.call(d3.axisBottom(this.x).tickFormat(d3.timeFormat("%H:%M")))

this.x = d3.scaleBand()
.domain(array.map(value=>value.time))
.range([ 0, width])
for custom labels i prefer to use scaleBand()

Related

Always show negative values on Y axis using D3js

I have a dataset which is is something like
[{key: 'abc', series: [1000,2500,3000]}, {key: 'xyz', series: [-20, 0,0]}]
In this case when I plot my bar chart with d3.js, The 'y' label ticks ignores negative values since they are not in the range of other numbers (1000,2500,3000). Is there a way to force the negative number ticks to be shown. Or if my y tick range is [0,200,400,800] then it should be [-200, 0, 200,400,800].
You may hard code your y axis range using:
d3.scale.linear().range([-200,800]);

x axis on c3 chart shows only first and last value

I'm using c3 chart for js. My code is below:
c3.generate({
bindto:'#someChart',
data: {
columns: [
data
]
},
axis: {
x : {
type: 'categories',
categories:categories,
tick:{
count: 12
}
}
},
point:{
show:false
}
});
I have one problem. On x axis is showed only first and last value(12th).
It's a known bug in c3 where if the tick is due to occur at a fractional x value (i.e. the number of ticks means it should pop up say every 2.06666 categories) then it doesn't render --> https://github.com/c3js/c3/issues/1638
There's a fix that's offered there to run before you generate your chart -->
c3.chart.internal.fn.categoryName = function (i) {
var config = this.config, categoryIndex = Math.ceil(i);
return i < config.axis_x_categories.length ? config.axis_x_categories[categoryIndex] : i;
};
But I find while it now shows the right number of ticks, it often still doesn't line up the labels and ticks nicely to the data points (they're positioned partway in between).
On that point, it's better, if you can, to set the number of ticks (-1) to divide without a fraction into the number of data points (-1) you're wanting to show and they'll both avoid your initial issue and line up nicely.
e.g. (datapoints - 1)/(no.of.ticks - 1) == whole number

D3.js - How to map continuous domains to discrete ranges with scales?

I'm trying to make a scale thats relates a continuous domain with a discrete range. My attempt is this one:
var scale = d3.scale.linear()
.domain([0, 15.43])
.range([0, 1, 2, 3, 4]);
So the domain will be from 0 to 15.43 taking all possible float numbers. How can I tell the scale to take all numbers from 0 to 15.43?
You are looking for scale.quantize:
var q = d3.scale.quantize().domain([0, 1]).range(['a', 'b', 'c']);
console.log(q(0));
console.log(q(0.3));
console.log(q(0.35 ));
console.log(q(0.5 ));
console.log(q(1));
Of course, the range values can also be numeric.

D3 difference between ordinal and linear scales

var xScale = d3.scale.ordinal().domain([0, d3.max(data)]).rangeRoundBands([0, w], .1);
var yScale = d3.scale.linear().domain([0, data.length]).range([h, 0]);
I'm confused about when to use ordinal or linear scale in D3.
Below is what I've discovered from the API doc, still bit lost... if anyone can help, it would be much appreciated.
ordinal(x)
Given a value x in the input domain, returns the corresponding value in the output range.
If the range was specified explicitly (as by range, but not rangeBands, rangeRoundBands or rangePoints), and the given value x is not in the scale’s domain, then x is implicitly added to the domain; subsequent invocations of the scale given the same value x will return the same value y from the range.
d3.scale.linear()
Constructs a new linear scale with the default domain [0,1] and the default range [0,1]. Thus, the default linear scale is equivalent to the identity function for numbers; for example linear(0.5) returns 0.5.
As for Ordinal Scales:
Ordinal scales have a discrete domain, such as a set of names or categories.
An ordinal scale's values must be coercible to a string, and the stringified version of the domain value uniquely identifies the corresponding range value.
So, as an example, a domain of an ordinal scale may contain names, like so:
var ordinalScale = d3.scale.ordinal()
.domain(['Alice', 'Bob'])
.range([0, 100]);
ordinalScale('Alice'); // 0
ordinalScale('Bob'); // 100
Notice how all values are strings. They cannot be interpolated. What is between 'Alice' and 'Bob'? I don't know. Neither does D3.
Now, as for Quantitative Scales (e.g. Linear Scales):
Quantitative scales have a continuous domain, such as the set of real numbers, or dates.
As an example, you can construct the following scale:
var linearScale = d3.scale.linear()
.domain([0, 10])
.range([0, 100]);
linearScale(0); // 0
linearScale(5); // 50
linearScale(10); // 100
Notice how D3 is able to interpolate 5 even if we haven't specified it explicitly in the domain.
Take a look at this jsfiddle to see the above code in action.
In D3.js scales transform a number from the domain to the range. For a linear scale the domain will be a continuous variable, with an unlimited range of values, which can be then transformed to a continuous range. For ordinal scales there will be a discrete domain, for example months of the year where there are limited range of possible values that may be ordered but aren't continuous. The API docs on Github can probably explain the difference better than I have
OK, we can start learning it with using both with the same data to see differences(I'm using d3 v4), imagine we have the data below with using ordinal and linear scales:
const data = [1, 2, 3, 4, 5];
const scaleLinear = d3.scaleLinear()
.domain([0, Math.max(...data)]).range([1, 100]);
const scaleOrdinal = d3.scaleOrdinal()
.domain(data).range(['one', 'two', 'three', 'four', 'five']);
Now we start calling them to see the result:
scaleLinear(1); //20
scaleOrdinal(1); //one
scaleLinear(2); //40
scaleOrdinal(2); //two
scaleLinear(5); //100
scaleOrdinal(5); //five
Look at the functions and the results we get, as you see in the ordinal one we map the data to our range, while in the linear one we stretch to the range, so in these cases for example scaleLinear(1) will return 20... our domain max is 100 and 100 divided by 5 is equal 20, so scaleLinear(1) is 20 and scaleLinear(2) is 40...
But as you see, scaleOrdinal(1) is map to the array in the range, so it's equal to one and scaleOrdinal(2) it's equal to two...
So that's how you can use these scales, scaleLinear is useful for many things including present the scale on page, but scaleOrdinal more useful for getting the data in order, that's how it's explained in the documentation:
# d3.scaleLinear() <>
Constructs a new continuous scale with the unit domain [0, 1], the
unit range [0, 1], the default interpolator and clamping disabled.
Linear scales are a good default choice for continuous quantitative
data because they preserve proportional differences. Each range value
y can be expressed as a function of the domain value x: y = mx + b.
d3.scaleOrdinal([range]) <>
Constructs a new ordinal scale with an empty domain and the specified
range. If a range is not specified, it defaults to the empty array; an
ordinal scale always returns undefined until a non-empty range is
defined.
Also this is a good example from d3 in depth using both ordinal and linear scales at the same time:
var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
var linearScale = d3.scaleLinear()
.domain([0, 11])
.range([0, 600]);
var ordinalScale = d3.scaleOrdinal()
.domain(myData)
.range(['black', '#ccc', '#ccc']);
d3.select('#wrapper')
.selectAll('text')
.data(myData)
.enter()
.append('text')
.attr('x', function(d, i) {
return linearScale(i);
})
.text(function(d) {
return d;
})
.style('fill', function(d) {
return ordinalScale(d);
});
body {
font-family: "Helvetica Neue", Helvetica, sans-serif;
font-size: 14px;
color: #333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<svg width="800" height="60">
<g id="wrapper" transform="translate(100, 40)">
</g>
</svg>

Show a tick every n value

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.

Categories