I'm simply trying to create a y-axis labelled from January to December, but I can't understand why my code creates: January, March, March, April.... December. Can anyone explain this to me please?
Thanks!
const w = 1078;
const h = 666;
const padding = 70;
const svg = d3
.select("#svg-container")
.append("svg")
.attr("width", w)
.attr("height", h);
const months = Array(12)
.fill(0)
.map((v, i) => new Date().setMonth(i));
const yScale = d3
.scaleTime()
.domain([months[11], months[0]])
.range([h - padding, padding]);
const yAxis = d3
.axisLeft(yScale)
.tickValues(months)
.tickFormat(d3.timeFormat("%B"));
svg
.append("g")
.attr("transform", "translate(" + padding + ", -20)")
.attr("id", "y-axis")
.call(yAxis);
body {
background: gray;
}
svg {
background: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="svg-container"></div>
That happens because setMonth() also sets the day you are currently in. Today is day 30 and February 2021 has only 28 days, therefore the non-existing February 30 2021 is in fact March 2 2021. You can see this if you print the dates in a different format:
To fix this, you can use the full Date function new Date(2021, i, 1)
const w = 1078;
const h = 666;
const padding = 70;
const svg = d3
.select("#svg-container")
.append("svg")
.attr("width", w)
.attr("height", h);
const months = Array(12)
.fill(0)
.map((v, i) => new Date(2021, i, 1));
const yScale = d3
.scaleTime()
.domain([months[11], months[0]])
.range([h - padding, padding]);
const yAxis = d3
.axisLeft(yScale)
.tickValues(months)
.tickFormat(d3.timeFormat("%B %d"));
svg
.append("g")
.attr("transform", "translate(" + padding + ", -20)")
.attr("id", "y-axis")
.call(yAxis);
body {
background: gray;
}
svg {
background: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="svg-container"></div>
Good for you for posting this on the 30th, otherwise there would be a hidden bug waiting to appear!
Additional resources
MBostock's month axis example: https://bl.ocks.org/mbostock/1849162
D3 Scaletime documentation: https://observablehq.com/#d3/d3-scaletime
Related
I'm working with D3 to create a line-graph. This graph is available here jsfiddle.
I'm trying to draw lines manually to represent certain data-point-values. I've tried to add comments to most of the lines in the code, so hopefully you can follow along.
My problem is that I cannot seem to draw negative numbers in a good way, if i do, then the graph-data-lines are misaligned. So my question is: How can i scale my graph so that I can show both negative and positive numbers? In this case, the graph should go from 2 to -2 based on the max/min values i've set.
currently. I'm scaling my graph like this
//
// Setup y scale
//
var y = d3.scale.linear()
.domain([0, max])
.range([height, 0]);
//
// Setup x scale
//
var x = d3.time.scale()
.domain(d3.extent(data, dateFn))
.range([0, width]);
In my mind, doing .domain([-2,max]) would be sufficient, but that seems to make things worse.
Also, my lines do not seem to match what the data-lines are saying. In the jsfiddle, the green line is set at 1. But the data-lines whose value are 1, are not on that green line.
So, this is pretty much a scale question i guess.
Visual (picasso-esc) representation of what the graph should look like if it worked.
As you want your y domain to be [-2, 2] as opposed to be driven by the data, you can remove a lot of setup and helper functions from your drawGraph function.
After drawing your graph, you can simply loop through the yLines array, and draw a line for each with the specified color, at the specified val according to your yScale.
Update: EDITED: As you will be supplied the values of nominal, upperTolerance, lowerTolerance, innerUpperTolerance, innerLowerTolerance from your endpoint (and they don't need to be calculated from the data on the client side), just feed those values into your data-driven yScale to draw the coloured lines.
Below I have just used the values 1, 1.8, -1.8, but you will receive values that will be more meaningfully tied to your data.
// Setup
const yLines = [{
val: 1,
color: 'green'
},
{
val: 1.8,
color: 'yellow'
},
{
val: -1.8,
color: 'red'
}
]
const margin = {
top: 10,
right: 80,
bottom: 60,
left: 20
};
const strokeWidth = 3;
const pointRadius = 4;
const svgWidth = 600;
const svgHeight = 600;
const width = svgWidth - margin.left - margin.right;
const height = svgHeight - margin.top - margin.bottom;
const stroke = '#2990ea'; // blue
const areaFill = 'rgba(41,144,234,0.1)'; // lighter blue
const format = d3.time.format("%b %e %Y");
const valueFn = function(d) {
return d.value
};
const dateFn = function(d) {
return format.parse(d.name)
};
// select the div and append svg to it
const graph = d3.select('#chart').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('overflow', 'visible');
const transformGroup = graph.append('g')
.attr('tranform', `translate(${margin.left}, ${margin.right})`)
// Make a group for yLines
const extraLines = transformGroup.append('g')
.attr('class', 'extra-lines')
// Generate some dummy data
const getData = function() {
let JSONData = [];
for (var i = 0; i < 30; i++) {
JSONData.push({
"name": moment().add(i, 'days').format('MMM D YYYY'),
"value": Math.floor(Math.random() * (Math.floor(Math.random() * 20))) - 10
})
}
return JSONData.slice()
}
const drawGraph = function(data) {
console.log(data)
// Setup y scale
const y = d3.scale.linear()
.domain(d3.extent(data.map((d) => d.value)))
.range([height, 0]);
// Setup y axis
const yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10)
.tickSize(0, 0, 0)
// append group & call yAxis
transformGroup.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + ",0)")
.call(yAxis);
// Draw extra coloured lines from yLines array
extraLines.selectAll('.extra-line')
.data(yLines)
.enter()
.append('line')
.attr('class', 'extra-line')
.attr('x1', margin.left)
.attr('x2', svgWidth - margin.right)
.attr('stroke', d => d.color)
.attr('y1', d => y(+d.val))
.attr('y2', d => y(+d.val))
.attr('stroke-width', strokeWidth)
.attr('opacity', 0.5)
// Setup x scale
const x = d3.time.scale()
.domain(d3.extent(data, dateFn))
.range([0, width])
// function for filling area under chart
const area = d3.svg.area()
.x(d => x(format.parse(d.name)))
.y0(height)
.y1(d => y(d.value))
// function for drawing line
const line = d3.svg.line()
.x(d => x(format.parse(d.name)))
.y(d => y(d.value))
const lineStart = d3.svg.line()
.x(d => x(format.parse(d.name)))
.y(d => y(0))
// make the line
transformGroup.append('path')
.attr('stroke', stroke)
.attr('stroke-width', strokeWidth)
.attr('fill', 'none')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
.attr('d', lineStart(data))
.attr('d', line(data))
// fill area under the graph
transformGroup.append("path")
.datum(data)
.attr("class", "area")
.attr('fill', areaFill)
.attr('transform', `translate(${margin.left}, ${margin.top})`)
.attr('d', lineStart(data))
.attr("d", area)
}
drawGraph(getData())
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="chart" style="margin: 0 auto;"></div>
I had perfectly adequate ticks in my earlier statically sized plot using d3.js v4; once I made it resizable, the ticks and values disappeared from the y axis.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test Plot Viewer</title>
<script src="js/lib/d3.v4.min.js"></script>
<script src="js/lib/jquery.min.js"></script>
<style>
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
#chart {
position: fixed;
left: 55px;
right: 15px;
top: 10px;
bottom: 55px;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
var chartDiv = document.getElementById("chart");
var svg = d3.select(chartDiv).append("svg");
// parse the date time
var parseTime = d3.timeParse("%m/%d %H:%M");
function render() {
$("svg").empty();
// Extract the width and height that was computed by CSS.
var width = chartDiv.clientWidth;
var height = chartDiv.clientHeight;
// Use the extracted size to set the size of an SVG element.
svg
.attr("width", width)
.attr("height", height);
var margin = {top: 10, right: 15, bottom: 55, left: 55};
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var line = d3.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.solar); });
// Get the data
d3.csv("data_fred.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.time = parseTime(d.time);
d.solar = +d.solar;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.solar; })]);
// Add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", line);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%m/%d %H:%M ")));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y))
.ticks(10);
});
}
render();
// Redraw based on the new size whenever the browser window is resized
window.addEventListener("resize", render);
</script>
</body>
</html>
The submitter function wants more details, but I have none...
blah
blah
blah
blah
characters added to pad non-code content.
The ticks are now gone on the y axis. I've added the .tick attribute to the y axis, but no joy.
How do I get my y axis ticks back on this responsive version of the chart? TIA
Posted later: Anyone? My non-responsive version of the code is drawing correctly; "responsifying" it makes the y-axis ticks and units disappear. I've tried almost every permutation of command ordering and placement, but no luck.
Whats happening here is your Y axis ticks are getting hidden because they're not in the viewport. What you need to do is put all the elements in your svg in a <g> wrapper and translate it by left and top margins.
Here's a fiddle
var chartDiv = document.getElementById("chart");
var svg = d3.select(chartDiv).append("svg");
var g = svg.append('g');
function render() {
$('svg').empty();
// Extract the width and height that was computed by CSS.
var width = $('#chart').width();
var height = $('#chart').height();
// Use the extracted size to set the size of an SVG element.
svg
.attr("width", width)
.attr("height", height);
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 50,
left: 40
};
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
// parse the date time
var parseTime = d3.timeParse("%m/%d %H:%M");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
.x(function(d) {
return x(d.time);
})
.y(function(d) {
return y(d.solar);
});
// Get the data
var data = [{
'time': '11/30 04:55',
'solar': -1.1
}, {
'time': '11/30 05:00',
'solar': -1.1
}, {
'time': '11/30 05:05',
'solar': -1.5
}, {
'time': '11/30 05:10',
'solar': -2
}, {
'time': '11/30 05:15',
'solar': 1
}]
// format the data
data.forEach(function(d) {
d.time = parseTime(d.time);
d.solar = +d.solar;
});
console.log(data)
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.time;
}));
var yExtent = d3.extent(data, function(d) {
return d.solar;
})
y.domain(yExtent);
g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Add the valueline path.
g.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%m/%d %H:%M ")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)");
// Add the Y Axis
g.append("g")
.call(d3.axisLeft(y));
}
// d3.select("svg").remove();
// svg.remove();
// d3.selectAll("g > *").remove()
// d3.selectAll("chartDiv.path.line").remove();
// d3.select("path.line").remove();
render();
// Redraw based on the new size whenever the browser window is resized.
window.addEventListener("resize", render);
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="chart"></div>
Happy coding :)
Got it - the axis ticks were disappearing off the left edge of the window - fixed that with a transform/translate:
// Add the Y Axis
svg.append("g")
.attr("transform", "translate(40 ,10)")
.call(d3.axisLeft(y));
...with a similar translation of the x axis and path to match.
Also, the axis scale now appeared with an extent of 0 to 1.0, as it wasn't being passed out of the file read loop since it was an asynchronous operation. Bringing the svg.append's into the data read loop restored my "normal" units to the axis.
I'm implementing a chart using d3 that has a sliding x axis. Demo
I noticed that the amount of ticks (i.e. the amount of axis labels) keeps growing, meaning that the labels that slide out of the chart are not removed from the DOM.
Why are the old labels stay in the DOM, and how could I fix that?
const timeWindow = 10000;
const transitionDuration = 3000;
const xScaleDomain = (now = new Date()) =>
[now - timeWindow, now];
const totalWidth = 500;
const totalHeight = 200;
const margin = {
top: 30,
right: 50,
bottom: 30,
left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;
const svg = d3.select('.chart')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
svg
.append('rect')
.attr('width', width)
.attr('height', height);
// Add x axis
const xScale = d3.scaleTime()
.domain(xScaleDomain(new Date() - transitionDuration))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
const xAxisSelection = svg
.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// Animate
const animate = () => {
console.log(d3.selectAll('.tick').size()); // DOM keeps growing!!!
xScale.domain(xScaleDomain());
xAxisSelection
.transition()
.duration(transitionDuration)
.ease(d3.easeLinear)
.call(xAxis)
.on('end', animate);
};
animate();
svg {
margin: 30px;
background-color: #ccc;
}
rect {
fill: #fff;
outline: 1px dashed #ddd;
}
<script src="https://unpkg.com/d3#4.4.1/build/d3.js"></script>
<div class="chart"></div>
Analysis
The axis component will actually try to remove the ticks, which are no longer visible. Examining the source code brings up the line:
tickExit.remove();
Debugging to this line shows, that the exit selection is correctly calculated, i.e. all exiting nodes are contained in tickExit. But the nodes will not be removed as expected, because you have an active transition running on them. The documentation has it:
# transition.remove() <>
For each selected element, removes the element when the transition ends, as long as the element has no other active or pending transitions. If the element has other active or pending transitions, does nothing.
Workaround
One—admittely hacky—workaround could make use of the way D3 fades the ticks, which are no longer visible. This is not very nice, though, because it relies on the inner workings of D3 and might break in the future, should this behavior be altered.
Because selection.remove() is not that faint hearted, it can be used to take care of the removal instead of using transition.remove(). Personally, I would use something along the following lines in your animate() function:
d3.selectAll(".tick")
.filter(function() {
return +d3.select(this).attr("opacity") === 1e-6;
})
.remove();
Because the axis component will eventually fade all non-visible ticks to an opacity of 1e-6 this can be used to discard those elements. Note, however, that the tick count will at first come up to some value other than the starting value, because the transition to the final opacity will take some time to complete. But, the excess tick count is small and can safely be ignored.
Have a look at the following working demo. In this example, the tick count will increase from the initial 10 to 19 and subsequently stay at this value.
const timeWindow = 10000;
const transitionDuration = 3000;
const xScaleDomain = (now = new Date()) =>
[now - timeWindow, now];
const totalWidth = 500;
const totalHeight = 200;
const margin = {
top: 30,
right: 50,
bottom: 30,
left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;
const svg = d3.select('.chart')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
svg
.append('rect')
.attr('width', width)
.attr('height', height);
// Add x axis
const xScale = d3.scaleTime()
.domain(xScaleDomain(new Date() - transitionDuration))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
const xAxisSelection = svg
.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// Animate
const animate = () => {
console.log(d3.selectAll('.tick').size()); // DOM keeps growing!!!
d3.selectAll(".tick")
.filter(function() {
return +d3.select(this).attr("opacity") === 1e-6;
})
.remove();
xScale.domain(xScaleDomain());
xAxisSelection
.transition()
.duration(transitionDuration)
.ease(d3.easeLinear)
.call(xAxis)
.on('end', animate);
};
animate();
svg {
margin: 30px;
background-color: #ccc;
}
rect {
fill: #fff;
outline: 1px dashed #ddd;
}
<script src="https://d3js.org/d3.v4.js"></script>
<div class="chart"></div>
Anything below is my take on the comments to issue #23 "Axis labels are not removed from the DOM" opened by OP for the d3-axis module, which contains some really good points.
The comment by Mike Bostock provides a more in-depth look at the concurring transitions on the same element, which will eventually prevent the removal of the ticks:
The problem is that when the end event for the parent G element is dispatched, the axis has not yet removed the old ticks. The ticks are removed by transition.remove, which listens for the end event on the tick elements. The end event for the G element is dispatched prior to the end event for the tick elements, so you are starting a new transition that interrupts the old one before the axis has a chance to remove the old ticks.
The real gem whatsoever is to be found in the comment by #curran, who suggested to use setTimeout(animate). This is brilliant and, as far as I know, the only non-intrusive, non-hacky solution to this problem! By pushing the animate function to the end of the event loop, this will defer the creation of the next transition until after the actual transition has had the chance to clean up after itself.
And, to wrap up this theoretical discussion, the probably best conclusion to your actual problem seems to be Mike Bostock's:
If you want a real-time axis, you probably don’t want transitions. Instead, use d3.timer and redraw the axis with every tick.
I'm implementing a chart using d3 that has a sliding x axis. Demo
When axis labels approach the edges, they fade out/in.
However, the labels animate into the left and right margins of the svg (the gray area):
How could I avoid the labels to be rendered on the svg margins?
const timeWindow = 10000;
const transitionDuration = 3000;
const xScaleDomain = (now = new Date()) =>
[now - timeWindow, now];
const totalWidth = 500;
const totalHeight = 200;
const margin = {
top: 30,
right: 50,
bottom: 30,
left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;
const svg = d3.select('.chart')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
svg
.append('rect')
.attr('width', width)
.attr('height', height);
// Add x axis
const xScale = d3.scaleTime()
.domain(xScaleDomain(new Date() - transitionDuration))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
const xAxisSelection = svg
.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// Animate
const animate = () => {
xScale.domain(xScaleDomain());
xAxisSelection
.transition()
.duration(transitionDuration)
.ease(d3.easeLinear)
.call(xAxis)
.on('end', animate);
};
animate();
svg {
margin: 30px;
background-color: #ccc;
}
rect {
fill: #fff;
outline: 1px dashed #ddd;
}
<script src="https://unpkg.com/d3#4.4.1/build/d3.js"></script>
<div class="chart"></div>
You can clip-path it:
svg.append('defs')
.append('clipPath')
.attr('id','myClip')
.append('rect')
.attr('width', width)
.attr('height', totalHeight);
...
const xAxisSelection = svg
.append('g')
.attr('clip-path', 'url(#myClip)')
...
Full Code:
const timeWindow = 10000;
const transitionDuration = 3000;
const xScaleDomain = (now = new Date()) =>
[now - timeWindow, now];
const totalWidth = 500;
const totalHeight = 200;
const margin = {
top: 30,
right: 50,
bottom: 30,
left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;
const svg = d3.select('.chart')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
svg.append('defs')
.append('clipPath')
.attr('id','myClip')
.append('rect')
.attr('width', width)
.attr('height', totalHeight);
// Add x axis
const xScale = d3.scaleTime()
.domain(xScaleDomain(new Date() - transitionDuration))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
const xAxisSelection = svg
.append('g')
.attr('clip-path', 'url(#myClip)')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// Animate
const animate = () => {
xScale.domain(xScaleDomain());
xAxisSelection
.transition()
.duration(transitionDuration)
.ease(d3.easeLinear)
.call(xAxis)
.on('end', animate);
};
animate();
svg {
margin: 30px;
background-color: #ccc;
}
rect {
fill: #fff;
outline: 1px dashed #ddd;
}
<script src="https://unpkg.com/d3#4.4.1/build/d3.js"></script>
<div class="chart"></div>
My first time to post question here. I am converting my version 3 of d3 path line transition code to version 4, and I am having a hard time.
First of all, I saw Mike's example (posted about two days agao) of smooth line transition with non-time x axis for version 4, so I did the similar thing to his example of version 3 with time x axis. The path line moves smoothly, but the x axis doesn't. Also, for my work, I cannot trigger the transition from where he did in this example, so I cannot use the variable "this" in the tick function. Here is the code:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
<style>
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<svg width="960" height="500"></svg>
<script>
(function() {
var n = 243,
duration = 750,
now = new Date(Date.now() - duration),
count = 0,
data = d3.range(n).map(function() { return 0; });
random = d3.randomNormal(0, .2),
data = d3.range(n).map(random);
var margin = {top: 6, right: 0, bottom: 20, left: 40},
width = 960 - margin.right,
height = 120 - margin.top - margin.bottom;
var x = d3.scaleTime()
.domain([now - (n - 2) * duration, now - duration])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var line = d3.line()
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.axisBottom().scale(x));
var timeline = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.transition()
.duration(500)
.ease(d3.easeLinear)
.on("start", tick);
var transition = d3.select({}).transition()
.duration(750)
.ease(d3.easeLinear);
function tick() {
data.push(random());
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
y.domain([0, d3.max(data)]);
// redraw the line
svg.select(".line")
.attr("d", line)
.attr("transform", null);
// slide the x-axis left
axis.call(x.axis);
// slide the line left
d3.active(this)
.attr("transform", "translate(" + x(now - (n - 1) * duration) + ")")
.transition().on("start", tick);
// pop the old data point off the front
data.shift();
}
})()
</script>
</body>
at the tick function, there is a "this", from debugging, I found out it's a path, so I tried to replace it with d3.active(d3.selectAll("path")), or d3.active(d3.selectAll(".line")), neither works. I also tried to assign a variable timeline to the path, so that I tried d3.active(timeline). It doesn't work either.
I am at my wits' end on this issue. I posted on d3 google group, nobody answered. I hope somebody here can give me some suggestions.
Thanks
Diana
The transition to v4 is indeed not easy. Had the same issue as you. Try the following code:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
<style>
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<svg width="960" height="500"></svg>
<script>
(function() {
var n = 243,
duration = 750,
now = new Date(Date.now() - duration),
count = 0,
data = d3.range(n).map(function() {
return 0;
});
random = d3.randomNormal(0, .2),
data = d3.range(n).map(random);
var margin = {top: 6, right: 0, bottom: 20, left: 40},
width = 960 - margin.right,
height = 120 - margin.top - margin.bottom;
var x = d3.scaleTime()
.domain([now - (n - 2) * duration, now - duration])
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var line = d3.line()
.x(function(d, i) {
return x(now - (n - 1 - i) * duration);
})
.y(function(d, i) {
return y(d);
});
var svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.axisBottom().scale(x));
var timeline = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.transition()
.duration(500)
.ease(d3.easeLinear);
var transition = d3.select({}).transition()
.duration(750)
.ease(d3.easeLinear);
(function tick() {
transition = transition.each(function() {
data.push(random());
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
y.domain([0, d3.max(data)]);
// redraw the line
svg.select(".line")
.attr("d", line)
.attr("transform", null);
// slide the x-axis left
axis.call(d3.axisBottom(x));
// slide the line left
//d3.active(this).attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
// pop the old data point off the front
data.shift();
}).transition().on("start", tick);
})();
})()
</script>
</body>
The trick is to chain transitions and using transition().on rather than transition().each. To update the axis you need to call the d3.axisBottom(x) in the same way that you instantiate the axis.