d3jsv4 timeline chart with vertical month axis during scrub - d3.js

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);

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.

d3 js boxplot with log scale

I'm here referring to this reproducible example
http://bl.ocks.org/jensgrubert/7789216
but applied to the following dataset (csv)
"Q1","Q2","Q3","Q4"
0.43,30,0.42,0.3
19,2,15,14
41,46,28,100
8,1,0.45,0.05
0.71,0.68,5,0.4
21,14,7,23
0.63,0.11,0.47,0.22
10,15,0.87,0.4
16,16,18,14
0.01,0.72,0.31,0.28
Given that I want to have numbers with decimals I have been changing the original code to what follows:
var v1 = Math.round(x.Q1*100)/100,
v2 = Math.round(x.Q2*100)/100,
v3 = Math.round(x.Q3*100)/100,
v4 = Math.round(x.Q4*100)/100;
And given that I want to change the y-axis into a logarithm scale I've been changing the original code to what follows:
// the y-axis
var y = d3.scale.log()
.domain([0.001, 100])
.range([height + margin.top, 0 + margin.top]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickValues([0.001, 0.01, 0.1, 1, 10, 100])
Now my problem is that the box and wiskers seem not to be correctly aligned with the y scale: see for example the wrong placement of 1st quartile, median, 3rd quartile and so on...;
Is that probably due to the log transformation of data?
Do I need to transform the data itself as well before plotting them?
And eventually how to properly do all that?
I've also a second (apparently minor) issue: how to rotate the labels of the x-axis (QI, Q2, Q3, Q4)?
thank you
As for your minor issue in rotating the x-axis labels (QI, Q2, Q3, Q4) try this:
d3.selectAll('.x.axis .tick text')
.attr('transform', 'rotate(-90)')
.attr('text-anchor', 'end')
.attr('dx', '-1em')
.attr('dy', '-0.5em')

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);

plotting tick text above the axis

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>

Axis with either dynamic tickFormats or multipe axis with different timeScales

I'd like to either:
dynamically adjust the tickFormat of an D3 timeaxis depending on current zoom, like first showing years -> zoom in -> show months -> zoom in -> show days and so on up to seconds.
I've tried a bit. See this fiddle.
var w = 700,
h = 50,
xY = d3.time.scale().range([0, w]),
xAxisY = d3.svg.axis()
.scale(xY)
.orient("bottom")
.ticks(10)
.tickSize(10, 1)
var svgY = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g");
svgY.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 10 + ")");
svgY.append("svg:rect")
.attr("class", "pane")
.attr("width", w)
.attr("height", h)
.call(d3.behavior.zoom().on("zoom", zoom));
xY.domain([new Date(2000, 0, 1), new Date(2014, 0, 0)]);
xY.ticks(d3.time.minute, 1);
draw();
function draw() {
console.log("drawing");
svgY.select("g.x.axis").call(xAxisY);
}
function zoom() {
console.log("zooming");
d3.event.transform(xY); // TODO d3.behavior.zoom should support extents
draw();
}
It would be nice if the label format could be adjusted. If I zoom into minutely interval, th complete time shold be displayed ( 12:01 ) instead of sth like 12:00..............10......20......30....
or ( preferred, if possible ) :
add multiple x-axis with different formats below each other. In this case, the labels should disappear on overlap, eg:
-------2012--------------------------------------------------------------2013-----------------
--12----01----02----03----04----05----06----07----08----09----10----11----12----01----02------
Below the monthly axis there should appear a daily and so on. If I zoom into an detailled interval, the overlapping labels should disappear.
For this case I tried playing around with the above fiddle, simply adding futher axis / svgs but they react independently, eg: I have a yearly axis and a monthly axis. Zooming on the first also zooms the second and vice versa, but when I first change from first to second, both axis do a "jump". I think the second one moves back to its initial state on first moving.
Any ways to accomblish this?
In both cases, I'm surprised why the tick-lines have gone away?
In many examples the axis look like:
|2012 |2011 |2010
but the vertical lines are gone in the fiddled example!?

Resources