d3v4 Timeline chart - upgrading from version 3 to 4 - d3.js

I have a timeline application - that was using the old d3. I've tried upgrading it and refactoring the codebase - but I've hit a snag with the scale change. This is good as a gantt chart.
//version 3
https://jsfiddle.net/5xsu76ck/1/
//version 4
https://jsfiddle.net/8cy719w0/2/
//scales
var x = d3.scaletime()
.domain([timeBegin, timeEnd])
.range([0, width]);
var x1 = d3.scaleLinear()
.range([0, width]);
var y1 = d3.scaleLinear()
.domain([0, laneLength])
.range([0, mainHeight]);
var y2 = d3.scaleLinear()
.domain([0, laneLength])
.range([0, miniHeight]);
var scaleFactor = (1/(timeEnd - timeBegin)) * (width);
current issue
https://jsfiddle.net/8cy719w0/2/ -- I've managed to get pass some issues - but now got a problem with the brush
//brush
var brush = d3.brushX()
.extent([
[0, 0],
[width, miniHeight]
])
.on("brush", display);

d3 version 4 uses d3.scaleTime, so your x variable should be
var x = d3.scaleTime()
.domain([timeBegin, timeEnd])
.range([0, width]);
https://github.com/d3/d3-scale#time-scales

Related

Is it possible to draw an equation based straight line in D3?

I am new to D3.js, pardon me if my understanding is wrong.
I have an equation for a straight line in a log-log plot, Log(Y)=Log(C) + Log(X), C is constant and user defined.
Is there a way to draw the straight line in D3 purely from the equation?
Thank you.
No this isn't possible exactly as you'd like in D3. D3 is less about mathmatical calculation & visualization compared to other tools (R, MatLab) and is more about binding data sets to DOM and handling animation between data sets.
That being said, if you calculate the X and Y values for the equation then you can plot those values easily. I've seen D3 used like this, with input boxes for C and then plotting across a range.
Following your comment here's an example:
const C = 1;
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 1000]); // pixels
const yScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 1000]);
const line = d3.line()
.x(d => xScale(d))
.y(d => yScale(Math.log(C) + Math.log(d)));
const values = [0, 50, 100];
d3.selectAll("path")
.datum(values)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("d", line);
Note that the key to pumping in the equation is defining how to generate the y value given the x in the line generator, covered by this line:
.y(d => yScale(Math.log(C) + Math.log(d)))

d3 axis date ticks on brush/zoom

I've been able to use the great tutorial for brush/zoom:
https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
I'm using the following scales and axis variables:
var x = d3.scaleTime().range([0, width]);
var x2 = d3.scaleTime().range([0, width]);
var xAxis = d3.axisBottom(x).ticks(d3.timeDay).tickFormat(d3.timeFormat("%x"));
var xAxis2 = d3.axisBottom(x2).ticks(10).tickFormat(d3.timeFormat("%x"));
Looks OK when zoomed in:
However, it gets extremely packed when zooming out:
How can I set the max number of tick marks, but limit the most granular tick to a single day?

D3 Beginner - having some issues with scale

I recently started working on D3 and found what feels like a good introductory tutorial by Mr Scott Murray at alignedleft.com. I'm currently trying to replicate his information on scale, but I'm running into a problem that I can't seem to solve. I've gone so far as to copy and paste his code and it isn't working.
I'm probably using a newer version of D3 than the tutorial is written on and I'm just missing something that changed in a version?
I'm currently using d3 version 4.3.0
The code I'm working with is
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88]
];
var w = 500;
var h = 200;
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([0, h]);
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[0]; })])
.range([0, w]);
var svg = d3.select("body").append("svg").attr("height", h).attr("width", w);
svg.selectAll("circle").data(dataset).enter().append("circle").attr("cx", function(d) {
return xScale(d[0]);
}).attr("cy", function(d){
return yScale(d[1]);
}).attr("r", 5);
Any guidance or reason for this not working would be appreciated
D3.js version four will break a fair amount of code from version three. The reason being the "great namespace flattening" of version 4.
The following methods relating to scales have changed:
d3.scale.linear ↦ d3.scaleLinear
d3.scale.sqrt ↦ d3.scaleSqrt
d3.scale.pow ↦ d3.scalePow
d3.scale.log ↦ d3.scaleLog
d3.scale.quantize ↦ d3.scaleQuantize
d3.scale.threshold ↦ d3.scaleThreshold
d3.scale.quantile ↦ d3.scaleQuantile
d3.scale.identity ↦ d3.scaleIdentity
d3.scale.ordinal ↦ d3.scaleOrdinal
d3.time.scale ↦ d3.scaleTime
d3.time.scale.utc ↦ d3.scaleUtc
So, your d3.scale.linear() should read d3.scaleLinear()

Logarithmic time scale

How to make a logarithmic datetime scale in D3?
a simple time scale is like this:
d3.time.scale()
.domain([new Date(2014, 0, 1), new Date()])
.range([0, 500])
and a simple log scale is like:
d3.scale.log()
.domain([new Date(2014, 0, 1), new Date()])
.rangeRound([0, 500])
.base(10)
Tried to chain their syntax in a various ways with no effect.
Chart will position users by last login date. Range will be about one year. If we space data linearly, most users will collide during last days/hours. With logarithm we can zoom last hours.
Solution could be by interactive zoom or several charts. But goal here is to make single static chart with nonlinear overview of year.
One alternative could be to convert datetime to "days from now", a number. It would work for data. But then I wouldn't know how to label axis ticks like "01-01-2014"...
Something like the below seems to fool d3js into thinking it has a real scale object. It should make a good starting point:
var xt = d3.scaleUtc()
.domain([start, now])
.range([1, width])
var xp = d3.scalePow()
.exponent(2)
.domain([1, width])
.range([0, width])
// Fool d3js into thinking that it is looking at a scale object.
function x_copy() {
var x = function(t) { return xp(xt(t)) }
x.domain = xt.domain
x.range = xp.range
x.copy = x_copy
x.tickFormat = xt.tickFormat
x.ticks = xt.ticks
return x
}
x = x_copy()
var xAxis = d3.axisBottom(x)
Create two scales and use one after the other. First use the time scale and than the log or pow scale.
var parseDate = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale()
.range([0,width]);
var xLog = d3.scale.pow().exponent(4)
.domain([1,width])
.range([0,width]);
than I'm using .forEach to get the linear points:
x.domain(d3.extent(data, function(d) { return parseDate(d.start); }));
data.forEach(function(d) {
d.start = x(parseDate(d.start));
});
when I'm drawing the objects I add the log scale:
.attr('cx', function (d) { return xLog(d.start)})

d3 mapping numbers with scale

If I set scales so points are in scale on the svg with:
d3.scale.linear().domain(xDomain).range([0, width]);
How can I get the unscaled number of the mouse x position?
Eg.
xPositions = [1,7,10]
7 is # x = ~300 on an svg of width 500
How would I map 300 to 7 based on arbitrary data?
You can use the scale.invert function for the inverse mapping:
var xScale = d3.scale.linear().domain(xDomain).range([0, width]);
var xDomainPos = [1, 7, 10];
var xRangePos = xDomainPos.map(function (d) { return xScale(d); });
var xNewDomainPos = xRangePos.map(function (d) { return xScale.invert(d); });

Resources