d3 : Y Axis intervals not getting set by ticks - javascript

I want to set Y Axis by 0-100 percent in the intervals of 0-25-50-75-100.
For that I am setting ticks to 4.
But I can see Y axis in the intervals of 0-20-40-60-80-100
How can I set Y axis in the intervals of 0-25-50-75-100 ??
I have below code -
const extent=[0,100];
const yScale = scaleLinear().domain(extent).range([height, 0]);
const x0Scale = scaleBand()
.domain(data.map((d) => d.month))
.range([0, width])
.padding(0.46);
const x1Scale = scaleBand()
// .domain(data.map((d) => d.type))
.rangeRound([0, x0Scale.bandwidth()])
.padding(0.22);
const xAix = axisBottom(x0Scale);
const yAix = axisLeft(yScale);
svg
.select(".x-axis")
.attr("transform", `translate(${xMargin}, ${height + yMargin})`)
.style("font-weight", "bold")
.style("font-size", "0.700rem")
.call(xAix);
svg
.select(".y-axis")
.attr("transform", `translate(${0 + xMargin}, ${yMargin} )`)
.style("font-weight", "bold")
.style("font-size", "0.725rem")
.call(yAix.ticks(4).tickSize(-width).tickFormat(function(d){return d + "%"}));

d3 takes priority for some of the tick functions , you can do force tick Values by tickValues(d3.range(minValue, maxValue, noOfSteps)); . In your case
yAix.tickSize(-width).tickValues(d3.range(0,100,4).tickFormat(function(d){return d + "%"})
or use tickValues(d3.range(d3.min(dataarray),d3.max(dataarray),noOfticks))

Use tickValues instead of ticks.
svg
.select(".y-axis")
.attr("transform", `translate(${0 + xMargin}, ${yMargin} )`)
.style("font-weight", "bold")
.style("font-size", "0.725rem")
.call(yAix
.ticksValues([0, 25, 50, 75, 100])
.tickSize(-width)
.tickFormat(function(d){return d + "%"}));
ticks is used as a hint only - d3 might display a few more or less ticks, depending on the scale and the available screen space.
tickValues allows you to select the exact values for which to display ticks.

Related

Grid lines in d3 js don't match the axes

I am trying to make a multi-line chart with d3.js in react. The plot looks fine and comes up well, but the gridlines are not aligned sometimes. It is very random, and sometimes some graphs have aligned gridlines, some don't.
This is how some of them look:
I have this code for my gridlines:
svg
.append('g')
.attr('class', 'grid')
.attr('transform', `translate(0,${height})`)
.call(
d3.axisBottom(xScale)
.tickSize(-height)
.tickFormat(() => ""),
);
svg
.append('g')
.attr('class', 'grid')
.call(
d3.axisLeft(yScale)
.tickSize(-width)
.tickFormat(() => ""),
);
I followed this example: https://betterprogramming.pub/react-d3-plotting-a-line-chart-with-tooltips-ed41a4c31f4f
Any help on how I can align those lines perfectly would be appreciated.
You may consider niceing your y-scale so that minima and maxima of your data sets are rounded down/ up such that the ticks are equally spaced.
In your tutorial this bit of code:
const yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, yMaxValue]);
Can become:
const yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, yMaxValue])
.nice(); // <--------------------------- here
Here's a basic example of using nice on an x-scale where the first example is 'not nice' and the second is 'nice'.
// note gaps of 10 between data points
// apart from first and last where gap is different
const data = [3, 4, 14, 24, 34, 44, 47];
// svg
const margin = 20;
const width = 360;
const height = 140;
const svg = d3.select("body")
.append("svg")
.attr("width", width + margin + margin)
.attr("height", height + margin + margin);
// scale without 'nice'
const xScale1 = d3.scaleLinear()
.range([0, width])
.domain(d3.extent(data));
// scale with nice
const xScale2 = d3.scaleLinear()
.range([0, width])
.domain(d3.extent(data))
.nice();
// plot axes with both scales for comparison
// not 'nice'
svg.append("g")
.attr("transform", `translate(${margin},${margin})`)
.call(d3.axisBottom(xScale1));
// 'nice'
svg.append("g")
.attr("transform", `translate(${margin},${margin + 50})`)
.call(d3.axisBottom(xScale2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

D3.js making a bar chart where x axis represents date and y axis represents times slept that day

I'm trying to make a chart of sleeping sessions that looks something like this:
https://i.stack.imgur.com/uNbEm.jpg
Right my chart seems to only be plotting one of my days/data points. I've tried a bunch of things but nothing so far seems to make it draw rectangles for the rest of the days in the dataset. Does anyone know why this is happening?
Here's what my messed up chart looks like:
https://i.stack.imgur.com/AkzHb.png
And here is the code that's drawing the chart:
// y domain is 24 hours
var yScale =
d3.scaleLinear()
.domain([0,23])
.range([chartArea.height, 0])
.nice();
var yAxis = svg
.append("g")
.classed("yAxis", true)
.attr(
'transform', 'translate(' + padding.left+','+padding.top+')'
);
var yAxisFn = d3
.axisLeft(yScale)
.ticks(24);
yAxisFn(yAxis);
// x domain is dates of data
var xScale = d3
.scaleBand()
.domain(sleepArr.map((d) => d.date))
.range([0, chartArea.width])
.padding(.2);
var xAxis = svg
.append("g")
.classed("xAxis", true)
.attr(
'transform', 'translate('+padding.left+','+(chartArea.height + padding.top)+')'
)
.call(d3.axisBottom(xScale));
var rectGrp = svg
.append("g")
.attr('transform', 'translate(' +padding.left+','+padding.top+')')
.selectAll("rect")
.data(sleepArr)
.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("height", (d) => chartArea.height - d.sessions[0].startTime.getHours())
.attr("x", (d) => xScale(d.date))
.attr("y", (d) => d.sessions[0].startTime.getHours())
.attr("fill", (d) => colors[getDayOfWeek(d.date)])
.append("title")
.text((d) => d.date);

How to set minimal step for y-axis in d3.js?

I have a bar chart where values can range from 0 to 5. The values can only be integers (0, 1, 2, 3, 4, 5).
However, the y-axis renders with smaller steps, for example 0, 0.5, 1, 1.5, 2 etc. I want to set the axis values to integers only, but playing with domain and range hasn't helped me at all.
I don't see an option to set something like minimalInterval = 1. How do I do this? I'm sure there's an option somewhere. Current code for the axes:
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
x.domain(data.map(function(d) { return d.day; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end");
There is nothing like steps for a D3 generated axis.
However, in your case, the solution is simple: you can use tickValues with d3.range(6) and a formatter for integers or, even simpler, you can use ticks.
According to the API,
Sets the arguments that will be passed to scale.ticks and scale.tickFormat when the axis is rendered, and returns the axis generator. The meaning of the arguments depends on the axis’ scale type: most commonly, the arguments are a suggested count for the number of ticks (or a time interval for time scales), and an optional format specifier to customize how the tick values are formatted.
So, in your case:
axis.ticks(5, "f");
Where 5 is the count and f is the specifier for fixed point notation.
Here is a demo (with an horizontal axis):
var svg = d3.select("svg");
var scale = d3.scaleLinear()
.domain([0, 5])
.range([20, 280]);
var axis = d3.axisBottom(scale)
.ticks(5, "f")
var gX = svg.append("g")
.attr("transform", "translate(0,50)")
.call(axis)
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Just for completeness, the same code without ticks:
var svg = d3.select("svg");
var scale = d3.scaleLinear()
.domain([0, 5])
.range([20, 280]);
var axis = d3.axisBottom(scale);
var gX = svg.append("g")
.attr("transform", "translate(0,50)")
.call(axis)
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

D3.js: Generating an X axis removes some of my point's labels, generating a Y axis removes them all

Edit: After Cyril correctly solved the problem I noticed that simply putting the functions that generate my axes underneath the functions used to generate the labels solves the problem.
I've almost finished reading the O'Reilly book's tutorials on D3.js and made the scatter graph on the penultimate page, but when adding the following code to generate my X axis more than half of my labels disappear:
// Define X Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
// Generate our axis
svg.append('g')
.call(xAxis);
The odd thing is that of the labels that don't disappear the 3 that stay are the bottom 3 pairs from my dataset ([85,21], [220,88], [750,150]):
var myData = [
[5, 20],
...,
...,
[85, 21],
[220, 88],
[750,150]
];
Here is an image of what's happening, prior to adding the axis at the top each of these points had red text labels:
Below is the rest of the code that generates my scatter graph, it follows the methods explained in the book almost exactly and I can't pinpoint where the error is coming from.
// =================
// = SCALED SCATTER GRAPH
// =================
var p = 30; // Padding
var w = 500 + p; // Width
var h = 500 + p; // Height
// SVG Canvas and point selector
var svg = d3.select('body')
.append('svg')
.attr('width',w)
.attr('height',h);
// Scales take an input value from the input domain and return
// a scaled value that corresponds to the output range
// X Scale
var xScale = d3.scale.linear()
.domain([0, d3.max(myData, function(d){
return d[0];
})])
.range([p, w - (p + p)]); // With padding. Doubled so labels aren't cut off
// Y Scale
var yScale = d3.scale.linear()
.domain([0, d3.max(myData, function(d){
return d[1];
})])
.range([h - p, p]); // With padding
// Radial scale
var rScale = d3.scale.linear()
.domain([0, d3.max(myData, function(d){ return d[1];})])
.range([2,5]);
// Define X Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
// Generate our axis
svg.append('g')
.call(xAxis);
// Plot scaled points
svg.selectAll('circle')
.data(myData)
.enter()
.append('circle')
.attr('cx', function(d){
return xScale(d[0]);
})
.attr('cy', function(d){
return yScale(d[1]);
})
.attr('r', function(d){
return rScale(d[1]);
});
// Plot all labels
svg.selectAll('text')
.data(myData)
.enter()
.append('text')
.text(function(d){
return d;
})
.attr('x', function(d){
return xScale(d[0]);
})
.attr('y', function(d){
return yScale(d[1]);
})
.style('fill', 'red')
.style('font-size',12);
js-fiddle: https://jsfiddle.net/z30cqeoo/
The problem is here:
svg.selectAll('text')
The x axis and y axis makes text element as ticks, so when the axis are present the above line will return array of ticks, thus it explains why it's not displaying when axis is added.
So the correct way would be to do something like this:
svg.selectAll('.text') //I am selecting those elements with class name text
svg.selectAll('.text')
.data(myData)
.enter()
.append('text')
.text(function(d){
console.log(d)
return d;
})
.attr('x', function(d){
return xScale(d[0]);
})
.attr('y', function(d){
return yScale(d[1]);
})
.attr('class',"text") //adding the class
.style('fill', 'red')
.style('font-size',12);
Full working code here.

d3 axis labeling

How do I add text labels to axes in d3?
For instance, I have a simple line graph with an x and y axis.
On my x-axis, I have ticks from 1 to 10. I want the word "days" to appear underneath it so people know the x axis is counting days.
Similarly, on the y-axis, I have the numbers 1-10 as ticks, and I want the words "sandwiches eaten" to appear sideways.
Is there a simple way to do this?
Axis labels aren't built-in to D3's axis component, but you can add labels yourself simply by adding an SVG text element. A good example of this is my recreation of Gapminder’s animated bubble chart, The Wealth & Health of Nations. The x-axis label looks like this:
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("income per capita, inflation-adjusted (dollars)");
And the y-axis label like this:
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("life expectancy (years)");
You can also use a stylesheet to style these labels as you like, either together (.label) or individually (.x.label, .y.label).
In the new D3js version (version 3 onwards), when you create a chart axis via d3.svg.axis() function you have access to two methods called tickValues and tickFormat which are built-in inside the function so that you can specifies which values you need the ticks for and in what format you want the text to appear:
var formatAxis = d3.format(" 0");
var axis = d3.svg.axis()
.scale(xScale)
.tickFormat(formatAxis)
.ticks(3)
.tickValues([100, 200, 300]) //specify an array here for values
.orient("bottom");
If you want the y-axis label in the middle of the y-axis like I did:
Rotate text 90 degrees with text-anchor middle
Translate the text by its midpoint
x position: to prevent overlap of y-axis tick labels (-50)
y position: to match the midpoint of the y-axis (chartHeight / 2)
Code sample:
var axisLabelX = -50;
var axisLabelY = chartHeight / 2;
chartArea
.append('g')
.attr('transform', 'translate(' + axisLabelX + ', ' + axisLabelY + ')')
.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'rotate(-90)')
.text('Y Axis Label')
;
This prevents rotating the whole coordinate system as mentioned by lubar above.
If you work in d3.v4, as suggested, you can use this instance offering everything you need.
You might just want to replace the X-axis data by your "days" but remember to parse string values correctly and not apply concatenate.
parseTime might as well do the trick for days scaling with a date format ?
d3.json("data.json", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.year = parseTime(d.year);
d.value = +d.value;
});
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([d3.min(data, function(d) { return d.value; }) / 1.005, d3.max(data, function(d) { return d.value; }) * 1.005]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(6).tickFormat(function(d) { return parseInt(d / 1000) + "k"; }))
.append("text")
.attr("class", "axis-title")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Population)");
fiddle with global css / js
D3 provides a pretty low-level set of components that you can use to assemble charts. You are given the building blocks, an axis component, data join, selection and SVG. It's your job to put them together to form a chart!
If you want a conventional chart, i.e. a pair of axes, axis labels, a chart title and a plot area, why not have a look at d3fc? it is an open source set of more high-level D3 components. It includes a cartesian chart component that might be what you need:
var chart = fc.chartSvgCartesian(
d3.scaleLinear(),
d3.scaleLinear()
)
.xLabel('Value')
.yLabel('Sine / Cosine')
.chartLabel('Sine and Cosine')
.yDomain(yExtent(data))
.xDomain(xExtent(data))
.plotArea(multi);
// render
d3.select('#sine')
.datum(data)
.call(chart);
You can see a more complete example here: https://d3fc.io/examples/simple/index.html
chart.xAxis.axisLabel('Label here');
or
xAxis: {
axisLabel: 'Label here'
},

Categories