plotting tick text above the axis - d3.js

I am using d3 v4 for ploting the graph. And currently the tick text on the x-axis is coming below the axis. and I want that text on above the axis.
//Set the Xaxis scale Range
let x = scaleLinear().rangeRound([0, width]);
let x_axis = axisBottom(x);
x.domain(extent(graphData, function (d) {
return d.weeks;
}));
g.append("g").attr("transform", "translate(0," + height + ")").call(axisBottom(x).ticks(5)).attr("transform", "translate(0, 120)");
so can you help me how to put the tick text above the x-axis.

If you want the ticks on top of the axis, you should use axisTop, instead of axisBottom.
The names are pretty easy to understand and the API is very clear:
d3.axisTop(scale): In this orientation, ticks are drawn above the horizontal domain path.
d3.axisBottom(scale): In this orientation, ticks are drawn below
the horizontal domain path. (emphases mine)
Here is a demo, the first axis uses axisTop, and the second one, below, uses axisBottom:
var svg = d3.select("svg");
var x = d3.scaleLinear().range([20, 280]);
var xAxisTop = d3.axisTop(x)(svg.append("g").attr("transform", "translate(0,50)"))
var xAxisBottom = d3.axisBottom(x)(svg.append("g").attr("transform", "translate(0,100)"))
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Related

Unable to display x axis categories and make y axis start at 0

In this D3.js version 6.7 bar chart I am trying to align the x axis to show the categories and show the y axis to start at 0. Extending the height of the svg and changing the transforms does not appear to be working. How can I make the x axis categories appear under the bars and make the y axis start at 0? Thank you.
async function barChart() {
const dataset = await d3.csv("https://assets.codepen.io/417105/bodypart-injury-clean.csv");
console.log(dataset);
const width = 400;
const height = 400;
const margin = {top:20, right:30, bottom:30, left:40};
const canvas = d3.select("#viz")
.append("svg")
.attr("width", width)
.attr("height", height);
const wrapper = canvas.append("g").style("transform",`translate(${margin.left}px,${margin.top}px)`);
const xScale = d3.scaleBand()
.domain(["Arm","Eye","Head","Hand","Leg","Other"])
.range([0,width - margin.left])
.padding(0.2);
const yScale = d3.scaleLinear()
.domain(d3.extent(dataset, d => +d.Total))
.range([height,0]);
console.log(xScale("Leg"));
console.log(yScale(1700));
const barRect = wrapper.append("g")
.selectAll('rect')
.data(dataset)
.join('rect')
.attr('x', d => xScale(d.BodyRegion))
.attr('y', d => yScale(+d.Total))
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(+d.Total))
.attr('fill', 'teal');
const yAxis = d3.axisLeft().scale(yScale);
wrapper.append("g").call(yAxis);
const xAxis = d3.axisBottom().scale(xScale);
wrapper.append("g").attr('transform', `translate(0,${height-margin.bottom})`).call(xAxis);
}
barChart();
The Y scale
The scale's domain sets the extent of the scale in your data's units, the scale's range sets the scale's extent in scaled units (pixels here). The first value in the domain is mapped to the first value in the range.
Your domain is set to:
.domain(d3.extent(dataset, d => +d.Total))
d3.extent returns the minimum and maximum matching values, as your minimum value is not zero, your scale's domain does not start at 0. If you want to set the scale's domain's lower bounds to zero, you need to set that, like so:
.domain([0,d3.max(dataset,d=> +d.Total)])
.domain/.range take arrays, these arrays for a linear scale must have the same number of elements
But you also don't want your scale's range to be [height,0] because of margins:
.range([height-margin.bottom,margin.top])
You want the data to be scaled from between within the two margins, height-margin.bottom is the furthest down the page you want to plot data, and margin.top is the furthest to the top of the SVG you want to plot data.
Now your bars are a bit off, that's because you aren't accounting for the margin in the height attribute:
.attr('height', d => height - yScale(+d.Total))
you need:
.attr('height', d => height - margin.bottom - yScale(+d.Total))
Note, a common approach to avoid having to constantly reference the margin is to apply the margin to a parent g and have width height reflect the size of the plot area within that g (not the entire SVG).
The X Axis
Now that the y scale is configured, let's look at the x axis. All you need to do here is boost the bottom margin: the text is appended (you can inspect the page to see it is there, just not visible). Try margin.bottom = 50.

d3jsv4 timeline chart with vertical month axis during scrub

I am trying to build a d3js timeline chart with axis grid lines vertically indicating the month region.
https://www.cssscript.com/demo/simple-scrollable-timeline-chart-with-d3-js-d3-timeline/
^ something like this featured some code - which I think is v3
var xAxis = d3.svg.axis().scale(x).orient('bottom').tickSize(-height);
svg.append('g').attr('class', 'x axis').attr('transform', 'translate(0,' + height + ')').call(xAxis);
my current implementation has failed.
https://jsfiddle.net/g89kuoe1/4/
these are the v4 methods to create axis -- but it needs to be repeated on the chart to formulate the grid lines.
var xAxis = d3.axisBottom(xRange).tickFormat(function(d){ return d.x;});
var yAxis = d3.axisLeft(yRange);
or should a new clip path be made to contain these lines - and add them as lines - with x1 values that hit each month?
https://jsfiddle.net/c8gdfxob/
** SOLVED -- had to use minExtent and maxExtent -- and force the scale of the ticks to be a month
this creates non-moving vertical grid lines - but I am unsure how to adapt it to the codebase so they morph with the scrub
it maps the timeline months -- but doesn't flow with the morph..
var scale = d3
.scaleTime()
.range([0, w])
.domain([minExtent, maxExtent]);
// Gridline
var gridlines = d3.axisTop()
.tickFormat("")
.ticks(d3.timeMonth)
.tickSize(-mainHeight)
.scale(scale);
main.selectAll(".grid").remove()
main.append("g")
.attr("class", "grid")
.call(gridlines);

The axis label at x=0 does not show up

I am using D3 to draw a line chart. The value at x=0 does not show up.
The code for the axis is shown below.
const xScale = d3
.scaleTime()
.domain(d3.extent(data[0].series, d => d.time))
.range([xPadding, width - xPadding]);
const xAxis = d3
.axisBottom(xScale)
.ticks(4)
.tickSizeOuter(0)
.tickSizeInner(0)
.tickFormat(d3.timeFormat('%Y'));
I am not sure why it is not showing up the label at x=0, which is 2014. On checking the SVG, only three tick marks are displayed, but the one at x=0 is not in the SVG element.
CodePen for this: https://codepen.io/vijayst/pen/bLJYoK?editors=1111
I see different solutions which have their pros and cons. The third solution should be the cleanest and most generic.
Add the left tick manually:
Since d3 handles itself the location of x-axis ticks, one way of doing so would (if the data set is fixed) would be to manually add the missing tick:
svg
.append("g")
.append("text")
.text("2014-02-01") // can be retrieved from data instead of being harcoded
.style("font-size", 10)
.style("font-family", "sans-serif")
.attr("transform", "translate(0," + (height - yPadding + 10) + ")")
which looks great, but in this case you might have problems if for a given dataset, d3 chooses to display a tick close to the left edge of the axis. Both d3's tick and the label we've included could overlap.
Modify the x-scale to start before the first day of the year:
An other solution would be to increase the x-axis range on the left to make it start one month before the first point's date. To try this out, we can replace:
.domain(d3.extent(data[0].series, d => d.time))
with
.domain(d3.extent([new Date(2013, 12), new Date(2019, 1)]))
which allow d3 to legitimately include a "year-tick" for 2014 at the begin of the x-axis.
but in this case, the first point will have an offset with the begin of the x-axis range.
Push a specific tick to ticks auto-generated by d3:
An other solution: we can push a specific tick to the ticks auto-generated by d3. But this requires to modify the format of ticks to "%Y-%m".
In order to do this, replace:
.tickFormat(d3.timeFormat("%Y"));
with
.tickFormat(d3.timeFormat("%Y-%m"));
we can then push a new specific tick to the set of ticks generated by d3:
var ticks = xScale.ticks();
ticks.push(new Date(2014, 1, 1));
xAxis.tickValues(ticks);
and include some padding in the left and the right of the chart since now tick labels have a part displayed outside the graph:
const svg = d3
.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("padding-left", 15)
.style("padding-right", 15);

How can I get the D3.js axis ticks and positions as an array?

I usually place my axis ticks on the svg using this:
d3.svg.axis().scale(xScale(width)).ticks(4)
Is it possible to get these tick values and their svg coordinates so I can use a custom axis outside the svg using d3.svg.axis() ?
Yes, xScale.ticks(4) should give you the actual tick points as values, and you can pipe those back through your xScale to the the X position. You can also just pull the tick points back from the generated elements after you apply the axis to an actual element:
var svg = d3.select("svg");
var scale = d3.scale.linear()
.range([20, 280])
.domain([0, 100])
var axis = d3.svg.axis().scale(scale).orient("bottom").ticks(9);
// grab the "scale" used by the axis, call .ticks()
// passing the value we have for .ticks()
console.log("all the points", axis.scale().ticks(axis.ticks()[0]));
// note, we actually select 11 points not 9, "closest guess"
// paint the axis and then find its ticks
svg.call(axis).selectAll(".tick").each(function(data) {
var tick = d3.select(this);
// pull the transform data out of the tick
var transform = d3.transform(tick.attr("transform")).translate;
// passed in "data" is the value of the tick, transform[0] holds the X value
console.log("each tick", data, transform);
});
jsbin
In d3 v4 I ended up just parsing the rendered x values from the tick nodes
function parseX(transformText) {
let m = transformText.match(/translate\(([0-9\.]*)/);
let x = m[1];
if (x) {
return parseFloat(x);
}
}

How can I get a time axis onto an object, using D3?

I'm very new to d3 and trying to learn by building a visualization.
My goal right now is to make a circle and color the circle based on some temporal data. I've made the circle, and want to add a timescale to it. The circle I have created fine using d3.arc() on an svg element. I have also created a time scale (seen below). My question is, how can I "attach" this time scale to the circle? I want to be able to say that at xyz point in time, my data holds this value, so now color the circle based on a color scale.
Or...am I going about this wrong?
var time = d3.scale.ordinal()
.domain(d3.extent(data, function(d) {
return d.date;
}))
I think you may need to use a quantitative scale instead of ordinal.
https://github.com/mbostock/d3/wiki/Ordinal-Scales says -
Ordinal scales have a discrete domain, such as a set of names or categories
and in your code, you use the "extent" of the date property, which only gives you 2 values - the earliest and most recent date in your data. That is a discrete domain, but a very limited one, and wouldn't represent your data very well. The scale will only output at most 2 values.
var now = Date.now();
var then = now - 1000;
var colors = d3.scale.ordinal()
.domain([then, now])
.range(['#ff0000','#0000ff']);
colors(then); // red
colors(now); // blue
colors(now - 500); // red... expecting violet
change 'ordinal' to 'linear' and leave the rest as is.
var now = Date.now();
var then = now - 1000;
var colors = d3.scale.linear()
.domain([then, now])
.range(['#ff0000','#0000ff']);
colors(then); // red
colors(now); // blue
colors(now - 500); // violet
The tricky part (at least for me) was remembering that the output of d3.scale.linear() (the 'colors' variable above) is a function. It can be called just like any other function.
var fakeData = d3.range(then, now, 10);
var svg = d3.select('body')
.append('svg')
.attr({ height: 500, width: 500 });
var circle = svg.append('circle')
.attr({ r: 100, cx: 250, cy: 250 });
function changeTime(time){
circle.attr('fill', colors(time));
}

Resources