d3.js realtime line chart with circle - javascript

I'm trying to make a d3 realtime line chart with circle at the data point.
However, circles are gathered on the left side and it is not given to the data point.
This method is fine for static data to show circles with line chart.
chart.append('circle')
.data(data)
.attr('class', 'ciecle')
.attr("cy", line.x())
.attr("cy", line.y())
.attr("r", 5)
.attr("fill", 'blue');
However, it does not work with dynamically increasing data.
I want to move the circles with realtime line chat.
The follow code was forked from this URL
http://bl.ocks.org/KevinGutowski/131809cc7bcd1d37e10ca37b89da9630
Would you please let me how to change the code?
<svg id="chart"></svg>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var data = [];
var width = 500;
var height = 500;
var globalX = 0;
var duration = 100;
var max = 500;
var step = 10;
var chart = d3.select('#chart')
.attr('width', width + 50)
.attr('height', height + 50);
var x = d3.scaleLinear().domain([0, 500]).range([0, 500]);
var y = d3.scaleLinear().domain([0, 500]).range([500, 0]);
// -----------------------------------
var line = d3.line()
.x(function(d){ return x(d.x); })
.y(function(d){ return y(d.y); });
var smoothLine = d3.line().curve(d3.curveCardinal)
.x(function(d){ return x(d.x); })
.y(function(d){ return y(d.y); });
// -----------------------------------
// Draw the axis
var xAxis = d3.axisBottom().scale(x);
var axisX = chart.append('g').attr('class', 'x axis')
.attr('transform', 'translate(0, 500)')
.call(xAxis);
var path = chart.append('path');
var circle = chart.append('circle');
// Main loop
function tick() {
// Generate new data
var point = {
x: globalX,
y: ((Math.random() * 450 + 50) >> 0)
};
data.push(point);
globalX += step;
// Draw new line
path.datum(data)
.attr('class', 'smoothline')
.attr('d', smoothLine);
// Append circles.  It should given to data point
chart.append('circle')
.data(data)
.attr('class', 'ciecle')
.attr("cy", line.x())
.attr("cy", line.y())
.attr("r", 5)
.attr("fill", 'blue');
// Shift the chart left
x.domain([globalX - (max - step), globalX]);
axisX.transition()
.duration(duration)
.ease(d3.easeLinear,.1)
.call(xAxis);
path.attr('transform', null)
.transition()
.duration(duration)
.ease(d3.easeLinear,.1)
.attr('transform', 'translate(' + x(globalX - max) + ')');
//move with line
circle.attr('transform', null)
.transition()
.duration(duration)
.ease(d3.easeLinear,.1)
.attr('transform', 'translate(' + x(globalX - max) + ')')
.on('end', tick);
// Remote old data (max 50 points)
if (data.length > 50) data.shift();
}
tick();
</script>

The coordinates of the path get repeatedly updated in the tick function (which repeatedly calls itself) using path.datum(data). You also need to update the locations of the circles on each tick using the adjusted (shifted) scale, which gets changed here:
x.domain([globalX - (max - step), globalX]);
To make the transitions smooth, you also need to update the transforms in each tick. You could update it for each circle and the path itself individually, but I just put both in a group (<g>) element and animate the whole group. Here's a working example:
http://bl.ocks.org/Sohalt/9715be30ba57e00f2275d49247fa7118/43a24a4dfa44738a58788d05230407294ab7a348

Related

Javascript d3 pie chart doesn't pull data from JSON file with list of dictionaries

I have a .json file with data, and I'd like to make a d3 donut (pie) chart from it. I'm not especially fluent in javascript, and every example I can find either pulls from inline json data or the json file is structured differently than mine (mine is a list of dictionaries; theirs are often single dictionaries). I've been troubleshooting for a few days, and somehow can't land on anything that actually works. Any thoughts/tips?
The example at https://www.d3-graph-gallery.com/graph/donut_label.html uses inline json data to render a donut chart with labels. I've attempted to modify it that code by:
pulling json data from /data/all-facet-digitized.json
pull labels each dictionary's "facet" key ("true" and "false"), and values from each dictionary's "count" key (373977 and 55433).
change the color scale domain to match the facet keys ("true" and "false")
/data/all-facet-digitized.json looks like:
[
{
"count": "55433",
"facet": "true"
},
{
"count": "373977",
"facet": "false"
}
]
Code in the of my html file looks like:
<div id="chart"></div> <!-- div containing the donut chart -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var width = 450
height = 450
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one) minus margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'chart'
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Parse the Data
d3.json("/data/all-facet-digitized.json", function(data) {
// set the color scale
var color = d3.scaleOrdinal()
.domain(["true","false"])
.range(d3.schemeDark2);
// Compute the position of each group on the pie:
var pie = d3.pie()
.sort(null) // Do not sort group by size
.value(function(d) {return d.count; })
var data_ready = pie(d3.entries(data))
// The arc generator
var arc = d3.arc()
.innerRadius(radius * 0.5) // This is the size of the donut hole
.outerRadius(radius * 0.8)
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll('allSlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d){ return(color(d.facet)) })
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 0.7)
// Add the polylines between chart and labels:
svg
.selectAll('allPolylines')
.data(data_ready)
.enter()
.append('polyline')
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", 1)
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d); // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
})
// Add the polylines between chart and labels:
svg
.selectAll('allLabels')
.data(data_ready)
.enter()
.append('text')
.text( function(d) { console.log(d.facet) ; return d.facet} )
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
})
</script>
My result renders as an empty space:
<div id="chart">
<svg width="450" height="450">
<g transform="translate(225,225)"></g>
</svg>
</div>
The schemeDark2 doens't exist in d3 v4. I've replaced it with schemeCategory10:
var color = d3.scaleOrdinal()
.domain(["true","false"])
.range(d3.schemeCategory10);
Since you have an array of objects, you don't need d3.entries. That takes an object and converts it to an array where each key is an item of the array. But since you already have an array here, you can put it directly in pie():
// Compute the position of each group on the pie:
var pie = d3.pie()
.sort(null) // Do not sort group by size
.value(function(d) {return d.count; })
var data_ready = pie(data)
Now that you've got the data, you can access it on any of the functions: try putting console.log(data_ready) to see what's available. You'll see that the data is bound for each object as the .data property. pie() takes an array and puts it in a format that's convenient to make pie charts with.
Say we want to access the facet property: we would access that as item.data.facet. So in your functions, to access, you can do:
svg
.selectAll('allSlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d){ return(color(d.data.facet)) })
<head></head>
<div id="chart"></div> <!-- div containing the donut chart -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var width = 450
height = 450
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one) minus margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'chart'
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Parse the Data
var data = [
{
"count": "55433",
"facet": "true"
},
{
"count": "373977",
"facet": "false"
}
]
// set the color scale
var color = d3.scaleOrdinal()
.domain(["true","false"])
.range(d3.schemeCategory10);
// Compute the position of each group on the pie:
var pie = d3.pie()
.sort(null) // Do not sort group by size
.value(function(d) {return d.count; })
var data_ready = pie(data)
console.log('data_r', data_ready)
// The arc generator
var arc = d3.arc()
.innerRadius(radius * 0.5) // This is the size of the donut hole
.outerRadius(radius * 0.8)
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll('allSlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d){ return(color(d.data.facet)) })
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 0.7)
// Add the polylines between chart and labels:
svg
.selectAll('allPolylines')
.data(data_ready)
.enter()
.append('polyline')
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", 1)
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d); // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
})
// Add the polylines between chart and labels:
svg
.selectAll('allLabels')
.data(data_ready)
.enter()
.append('text')
.text( function(d) { return d.data.facet} )
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
</script>
Ok, the issues here is that you've completely missed how data_ready is structured after converting the JSON response. You might want to add console.log(data_ready) just after you set data_ready and inspect it in the console for better understanding of the following fixes.
First a color fix:
.attr('fill', function(d){ return(color(d.data.value.facet)) })
Then a data fix:
.value(function(d) {return d.value.count; })
And lastly a label fix:
.text( function(d) { console.log(d.data.key) ; return d.data.value.facet } )
Your script should look like this:
// set the dimensions and margins of the graph
var width = 450
height = 450
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'my_dataviz'
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.json("/data/all-facet-digitized.json", function(data) {
// set the color scale
var color = d3.scaleOrdinal()
.domain(["true","false"])
.range(d3.schemeDark2);
// Compute the position of each group on the pie:
var pie = d3.pie()
.sort(null) // Do not sort group by size
.value(function(d) {return d.value.count; })
var data_ready = pie(d3.entries(data))
// The arc generator
var arc = d3.arc()
.innerRadius(radius * 0.5) // This is the size of the donut hole
.outerRadius(radius * 0.8)
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll('allSlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d){ return(color(d.data.value.facet)) })
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 0.7)
// Add the polylines between chart and labels:
svg
.selectAll('allPolylines')
.data(data_ready)
.enter()
.append('polyline')
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", 1)
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d); // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
})
// Add the polylines between chart and labels:
svg
.selectAll('allLabels')
.data(data_ready)
.enter()
.append('text')
.text( function(d) { console.log(d.data.key) ; return d.data.value.facet } )
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
})

How to assign different color to each node on a sunburst?

I am creating a sunburst for big data. To make it more readable, I need to assign different color for each node (ideally different shades of the same color for every subtree).
I've already tried with :
d3.scaleSequential()
d3.scale.ordinal()
d3.scale.category20c()
I think it can work but I am not sure where to put it exactly. For the moment it works only with one color for every subtree.
var width = 500;
var height = 500;
var radius = Math.min(width, height) / 2;
var color = d3.scaleSequential().domain([1,10]).interpolator(d3.interpolateViridis);
var g = d3.select('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
var partition = d3.partition() //.layout
.size([2 * Math.PI, radius]);
d3.json("file:///c:\\Users\\c1972519\\Desktop\\Stage\\tests_diagrams\\figure_4.8_ex3\\data2.json", function(error, nodeData){
if (error) throw error;
var root = d3.hierarchy(nodeData)
.sum(function(d){
return d.size;
});
partition(root);
var arc = d3.arc()
.startAngle(function(d) { return d.x0; })
.endAngle(function(d) { return d.x1; })
.innerRadius(function(d) { return d.y0; })
.outerRadius(function(d) { return d.y1; });
var arcs = g.selectAll('g')
.data(root.descendants())
.enter()
.append('g')
.attr("class", "node")
.append('path')
.attr("display", function (d) { return d.depth ? null : "none"; })
.attr("d", arc)
.style('stroke', '#fff')
.style("fill", function(d){return color(d)});
}
So I would like to have different shade on every subtree to make it more readable.
Anyone have an idea?
can you try with scaleLinear.
var x = d3.scaleLinear([10, 130], [0, 960]);
or
var color = d3.scaleLinear([10, 100], ["brown", "steelblue"]);
Example:
https://bl.ocks.org/starcalibre/6cccfa843ed254aa0a0d
Documentation:
https://github.com/d3/d3-scale/blob/master/README.md#scaleLinear
Linear Scales
d3.scaleLinear([[domain, ]range]) <>
Constructs a new continuous scale with the specified domain and range, the default interpolator and clamping disabled. If either domain or range are not specified, each defaults to [0, 1]. Linear scales are a good default choice for continuous quantitative data because they preserve proportional differences. Each range value y can be expressed as a function of the domain value x: y = mx + b.

D3 animated scatter plot not displaying

I am working on a d3.js Dashboard (so only one dashboard and many layouts). I wanted to display a scatterplot animated, with the dots moving regarding some filter I am applying.
But in order, I have to display something first which is not happening.
Here are my program, which is a scatterplot.js file in an MVC format.
function scatterplot(DOMElement){
var scatObj = {}; // main object
scatObj.setTooltipText = function(f){
tooltipText = f;
//updateInteractions();
return scatObj;
}
scatObj.setMouseclickCallback = function(f){
mouseclickCallback = f;
//updateInteractions();
return scatObj;
}
// function loading data and rendering it in scatter chart
// parameters:
// - dataset in following format:
// [{"key": year, "value": money}, {...}, ...]
// returns:
// - scatObj for chaining
scatObj.loadAndRender = function(data){
dataset = data;
render();
return scatObj;
}
// ---- PRIVATE VARIABLES
// sizing vars
var dataset = []; // Initialize empty array
var numDataPoints = dataset.length;
for(var i=0; i<numDataPoints; i++) {
var newNumber1 = d.value.x; // New random integer
var newNumber2 = d.value.y; // New random integer
dataset.push([newNumber1, newNumber2]); // Add new number to array
}
// Setup settings for graphic
var canvas_width = 500;
var canvas_height = 300;
var padding = 30; // for chart edges
// Create scale functions
var xScale = d3.scaleLinear() // xScale is width of graphic
.domain([0, d3.max(dataset, function(d) {
return d[0]; // input domain
})])
.range([padding, canvas_width - padding * 2]); // output range
var yScale = d3.scaleLinear() // yScale is height of graphic
.domain([0, d3.max(dataset, function(d) {
return d[1]; // input domain
})])
.range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
// scales
// Define X axis
var xAxis = d3.axisBottom()
.scale(xScale)
// Define Y axis
var yAxis = d3.axisLeft()
.scale(yScale)
var svg = d3.select("DOMElement") // This is where we put our vis
.append("svg")
.attr("width", canvas_width)
.attr("height", canvas_height);
var tooltip = d3.select(DOMElement).append("div")
.classed("tooltip", true);
// interaction settings
var tooltipText = function(d, i){return "tooltip over element "+i;}
var mouseoverCallback = function(d, i){ };
var mouseoutCallback = function(d, i){ };
var mouseclickCallback = function(d, i){ console.log(d,i)};
var keySelected = null;
// ---- PRIVATE FUNCTIONS
function render(){
GUP_scat();
}
function GUP_scat(){
// GUP
// Create Circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle") // Add circle svg
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
.attr("cy", function(d) { // Circle's Y
return yScale(d[1]);
})
.attr("r", 2); // radius
// Add to X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (canvas_height - padding) +")")
.call(xAxis);
// Add to Y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding +",0)")
.call(yAxis);
// On click, update with new data
d3.select(DOMElement)
.on("click", function() {
keySelected = (keySelected == d.key) ? null : d.key;
var numValues = dataset.length; // Get original dataset's length
dataset = []; // Initialize empty array
for(var i=0; i<numValues; i++) {
var newNumber1 = d.value.x; // Random int for x
var newNumber2 = d.value.y; // Random int for y
dataset.push([newNumber1, newNumber2]); // Add new numbers to array
}
// Update scale domains
xScale.domain([0, d3.max(dataset, function(d) {
return d[0]; })]);
yScale.domain([0, d3.max(dataset, function(d) {
return d[1]; })]);
// Update circles
svg.selectAll("circle")
.data(dataset) // Update with new data
.transition() // Transition from old to new
.duration(1000) // Length of animation
.each("start", function() { // Start animation
d3.select(this) // 'this' means the current element
.attr("fill", "red") // Change color
.attr("r", 5); // Change size
})
.delay(function(d, i) {
return i / dataset.length * 500; // Dynamic delay (i.e. each item delays a little longer)
})
//.ease("linear") // Transition easing - default 'variable' (i.e. has acceleration), also: 'circle', 'elastic', 'bounce', 'linear'
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
.attr("cy", function(d) {
return yScale(d[1]); // Circle's Y
})
.each("end", function() { // End animation
d3.select(this) // 'this' means the current element
.transition()
.duration(500)
.attr("fill", "black") // Change color
.attr("r", 2); // Change radius
});
// Update X Axis
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
// Update Y Axis
svg.select(".y.axis")
.transition()
.duration(100)
.call(yAxis);
mouseclickCallback(d, i);
});}
return scatObj; // returning the main object
}
I am calling it like this in my main.js file :
dash.append('div').attr('id', 'scat1').classed("scatterplot", true)
.append("h3").text("Per Scateub");
var scat1 = scatterplot("div#scat1")
.setTooltipText(function(d, i){
return "<b>"+d.data.key +" : "+d3.format(",.2f")(d.value)+"</b> /10 ";
})
.loadAndRender(dataManager.getFteOverall());
scat1.setMouseclickCallback(function(d, i){
dataManager.setNameFilter(d.key);
redraw();
})
and here is my nest :
dmObj.getFteOverall= function(){
var nestedData = d3.nest()
.key(function(d){ return d.naAsses;})
.rollup(function (v) { return {
x: d3.sum(v,function(d){
return (d.four*4,d.three*3,d.two*2,d.one)/100 }),
y: d3.sum(v, function(d){
return (d.fte)
}
)
};
})
.sortKeys(d3.ascending)
.entries(filteredData());
return nestedData;
}
I know it can seem a bit a stupid question, but I have been struggling with it, and I have already been stuck for a while.
I hope I am clear and you will be able to help me (just in case I am working with d3 V4).
Thanks to advance guys.

concentric emanating circles d3

I have an array of equally spaced values which I am using to draw concentric circles. I want to use an emanating effect, in essence, remove the outermost circle once its value exceeds the maximum value and add a new one at the center to compensate. I am unsure about manipulation on data set to remove and add new circle.
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("body").append("svg");
var w = window.innerWidth;
var h = window.innerHeight;
var separation = Math.min(50, w/12);
var n=Math.floor((w/2)/separation);
var ellipse=new Array(n);
for(var i=1;i<=n;i++){
ellipse[i-1]=(i*separation);
}
svg.attr("width", w).attr("height", h);
var g = svg.append("g");
var e=g.selectAll("ellipse")
.data(ellipse)
.enter()
.append("ellipse")
.attr("cx", w/2)
.attr("cy", h/2)
.attr("rx",0)
.attr("ry",0)
.transition()
.duration(5000)
.attr("rx", function(d,i){return ellipse[i];})
.attr("ry", function(d,i){return ellipse[i]/5;});
loop();
function loop(){
e.transition()
.attr("rx", function(d,i){
return ellipse[i]+separation;
})
.attr("ry", function(d,i){
return (ellipse[i]+separation)/5;
})
.on("end",loop());
}
</script>
You could approach it with a remove().exit() and enter().append() selection for each ring - but essentially you always have the same number of rings on the screen. Why not just recycle the same elements? When the size hits a threshold, reset it to zero, or some other starting value?
Something along the lines of:
var scale = d3.scaleLinear()
.range(["orange","steelblue","purple"])
.domain([0,60]);
var data = [0,10,20,30,40,50,60];
var width = 200;
var height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r",function(d) { return d; })
.attr("transform","translate(80,80)")
.attr("fill","none")
.style("stroke-width","4")
.style("stroke",function(d) { return scale(d) });
function transition() {
// Update data, max d is 60:
data = data.map(function(d) { return d == 60 ? 0 : d + 10});
var i = 0;
// Grow circles
circles
.data(data)
.filter(function(d) { return d > 0 })
.transition()
.ease(d3.easeLinear)
.attr("r", function(d) { return d; })
.style("stroke", function(d) { return scale(d) })
.style("opacity",function(d) { return d == 60 ? 0 : 1 })
.duration(1000)
.on("end",function(){if(++i==circles.size()-1) { transition(); } });
// Reset circles where r == 0
circles
.filter(function(d) { return d == 0 })
.attr("r", 0)
.style("opacity",1)
.style("stroke",function(d) { return scale(d); });
}
transition();
<script src="https://d3js.org/d3.v4.min.js"></script>
Note that .on("end",... triggers on each element's transition end - this is why I count to see if all elements are done transitioning before running the transition function again.

Syntax for axis transition in svg/d3

I've an incredibly basic syntax question. I've been learning d3, SVG, and Javascript mostly by editing someone else's code, which is challenging.
The goal is to update a y axis after updating the data and the scale, which is based on the data. I want the axis--ticks and labels and all--to transition with the domain of the data. The axis isn't getting updated. The problem might be related to scope, or I'm referencing the wrong SVG element. (There are actually several plots getting updated simultaneously, but I'm just focusing on the axis of one of them here.)
function makeYaxis (chart, scale, nticks, label, width, height, xmf, visName)
{
var yAxis = d3.svg.axis()
.scale(scale)
.orient("left")
.ticks(nticks);
chart.append("svg:g")
.attr("class","y axis")
.append("g")
.attr("transform","translate(60,1)") // fix magic #
.call(yAxis);
var xMove = xmf.yylMarginFactor * width - 1;
var yMove = (((1 - xmf.xxbMarginFactor) * height +
xmf.xxtMarginFactor * height) / 2);
chart.append("svg:text")
.attr("class", visName + "xLabel")
.attr("x", 0)
.attr("y", 0)
.attr("dy", "-2.8em")
.text(label)
.attr("transform", "rotate(-90) translate(-" + yMove + "," + xMove + ")");
}
function makeYscale (plotMargin, thedata, xmf)
{
var dataMin = d3.min(thedata[0]);
var dataMax = d3.max(thedata[0]);
var yyMargin = d3.max([plotMargin * (dataMax - dataMin),0.05]);
var yy = d3.scale.linear()
.domain([dataMin - yyMargin, yyMargin + dataMax])
.range([(1 - xmf.xxbMarginFactor) * height, xmf.xxtMarginFactor * height]);
return yy;
}
// Set up this visualization
var chart = d3.select("#vis1")
.append("svg:svg")
.attr("class", "vis1chart")
.attr("width", width)
.attr("height", height);
var yy = makeYscale(plotMargin, thetas, xmf);
makeYaxis(chart, yy, nYTicks, "parameter", width, height, xmf, "vis1");
var points = chart.selectAll("circle.vis1points")
.data(thetas)
.enter().append("svg:circle")
.attr("class", "vis1points")
.attr("cx", function (d, i) { return xx(i); })
.attr("cy", function (d, i) { return yy(d); })
.attr("r", 3);
points.transition()
.attr("cx", function (d, i) { return xx(i); })
.attr("cy", yy)
.duration(dur);
vis1move();
function vis1move ()
{
function movePoints ()
{
var tmp = chart.selectAll(".vis1points").data(thetas[0], function (d, i) { return i; } );
var opa1 = points.style("stroke-opacity");
var opa2 = points.style("fill-opacity");
tmp
.enter().insert("svg:circle")
.attr("class", "vis1points")
.attr("cx", function (d, i) { return xx(i); })
.attr("cy", yy)
.attr("r", 3)
.style("stroke-opacity", opa1)
.style("fill-opacity", opa2);
tmp.transition()
.duration(10)
.attr("cx", function (d, i) { return xx(i); })
tmp.exit()
.transition().duration(10).attr("r",0).remove();
};
// (Code for updating theta, the data, goes here)
// Update axes for this vis
yy = makeYscale(plotMargin, thetas, xmf);
var transition = chart.transition().duration(10);
var yAxis = d3.svg.axis()
.scale(yy)
.orient("left")
.ticks(4);
transition.select("y axis").call(yAxis);
movePoints(); // Previously had been before axis update (fixed)
}
Sorry I can't get this self-contained.
Is transition.select.("y axis").call(yAxis) correct? Is there anything glaringly off here?
Edit: To keep things simple, I now have
// Update axes for this vis
yy = makeYscale(plotMargin, thetas, xmf);
var yAxis = d3.svg.axis().scale(yy);
chart.select("y axis").transition().duration(10).call(yAxis);
I'm wondering if something in the way I created the axis in the first place (in makeYaxis()) prevents me from selecting it properly. The yy function is returning the right values under the hood, and the data points are getting plotted and rescaled properly. It's just that the axis is "stuck."
Following meetamit's suggestions and the example here, I have stumbled on what appears to be a solution.
First, in function makeYaxis(), I removed the line .append("g"). I also updated the class name to yaxis. The function now reads
function makeYaxis (chart, scale, nticks, label, width, height, xmf, visName)
{
var yAxis = d3.svg.axis()
.scale(scale)
.orient("left")
.ticks(nticks);
chart.append("svg:g")
.attr("class","yaxis") // note new class name
// .append("g")
.attr("transform","translate(60,1)")
.call(yAxis);
// everything else as before
}
I then added an extra period in my call to select(".yaxis"):
chart.select(".yaxis").transition().duration(10).call(yAxis);
I would be very grateful if anyone could explain to me exactly why this solution appears to work.

Categories