D3.js : Updating data on chart not working - d3.js
Continuing to try and master the enter-update-exit pattern...
I've got a relatively simple reusable d3.js chart, and I want to be able to update the chart between two data sets. I'm getting close, but the chart is not updating properly.
You can see a fiddle here: http://jsfiddle.net/rolfsf/vba6n4sh/2/
Where did I mess up the enter-update-exit pattern?
The chart code looks like this:
function relativeSizeChart() {
var width = 1200,
margin = 0,
padding = 16,
r = d3.scale.linear(),
onTotalMouseOver = null,
onTotalClick = null,
onClusterMouseOver = null,
onClusterClick = null,
val = function(d){return d;};
totalFormat = function(d){return d;};
clusterFormat = function(d){return d;};
clusterFormat2 = function(d){return d;};
function chart(selection) {
selection.each(function(data) {
//console.log(data);
var clusterCount = data.Clusters.length,
totalColWidth = 0.3*width,
colWidth = (width - totalColWidth)/clusterCount,
height = colWidth + 2*padding,
maxRadius = (colWidth - 10)/2;
var svg = d3.select(this).selectAll("svg")
.data([data]);
var svgEnter = svg
.enter().append("svg")
.attr('class', function(d){
if( onTotalMouseOver !== null || onTotalClick !== null ||onClusterMouseOver !== null || onClusterClick !== null){
return 'clickable';
}else{
return 'static';
}
})
.attr("width", width)
.attr("height", height);
var background, clusterLines;
background = svgEnter.append("g")
.attr('class', 'background');
var headers = svgEnter.append("g")
.attr('class', 'headers')
.selectAll("text.header")
.data(data.Headers, function(d){return d;});
var total = svgEnter.append("g")
.attr('class', 'total');
var cluster = svgEnter.selectAll('g.cluster')
.data(data.Clusters,function(d){ return d;});
var clusterEnter = cluster
.enter().append("g")
.attr('class', 'cluster')
.attr('transform', function (d, i) {
return 'translate(' + (totalColWidth + i*colWidth) + ',0)';
});
var clusters = svg.selectAll('g.cluster');
r = d3.scale.linear()
.domain([0, d3.max(data.Clusters, function(d){return d[1];})])
.range([40, maxRadius]);
background .append("rect")
.attr("class", "chart-bg")
.attr('x', 0)
.attr('y', padding)
.attr('height', (height-padding))
.attr('width', width)
.attr('class', 'chart-bg');
background .append("g")
.attr('class', 'cluster-lines');
background .append("line")
.attr("class", "centerline")
.attr('x1', (totalColWidth - padding))
.attr('x2', width - (colWidth/2))
.attr('y1', (height+padding)/2)
.attr('y2', (height+padding)/2);
clusterLines = background.select('g.cluster-lines')
.selectAll("line")
.data(data.Clusters,function(d){ return d;})
.enter().append('line')
.attr('class', 'cluster-line');
headers .enter().append('text')
.attr('class', 'header');
total .append("rect")
.attr("class", "total-cluster")
.attr('x', padding)
.attr('y', 0.2*(height+(4*padding)))
.attr('height', 0.5*(height))
.attr('width', totalColWidth-(2*padding))
.attr('rx', 4)
.attr('ry', 4)
.on('mouseover', onTotalMouseOver)
.on('click', onTotalClick);
total .append("text")
.attr("class", "total-name")
.attr('x', totalColWidth/2 )
.attr('y', function(d, i) { return ((height+padding)/2) + (padding + 10); });
total .append("text")
.attr("class", "total-value")
.attr('x', totalColWidth/2 )
.attr('y', function(d, i) { return ((height+padding)/2); })
.text(totalFormat(0));
clusterEnter.append('circle')
.attr('class', 'bubble')
.attr('cx', function(d, i) { return colWidth/2; })
.attr('cy', function(d, i) { return (height+padding)/2;})
.attr("r", "50")
.on('mouseover', function(d, i, j) {
if (onClusterMouseOver != null) onClusterMouseOver(d, i, j);
})
.on('mouseout', function() { /*do something*/ })
.on('click', function(d, i){
onClusterClick(this, d, i);
});
clusterEnter.append('text')
.attr('class', 'cluster-value')
.attr('x', function(d, i) { return colWidth/2; })
.attr('y', function(d, i) { return ((height+padding)/2); })
.text(clusterFormat(0));
clusterEnter.append('text')
.attr('class', 'cluster-value-2')
.attr('x', function(d, i) { return colWidth/2; })
.attr('y', function(d, i) { return ((height+padding)/2) + (padding + 10); })
.text(clusterFormat2(0));
//update attributes
clusterLines.attr('x1', function(d, i) { return totalColWidth + i*colWidth })
.attr('x2', function(d, i) { return totalColWidth + i*colWidth })
.attr('y1', function(d, i) { return padding })
.attr('y2', function(d, i) { return (height); });
headers .attr('x', function(d, i) {
if(i === 0){
return (totalColWidth/2);
}else{
return (totalColWidth + (i*colWidth) - (colWidth/2))
}
})
.attr('y', 12);
//clean up old
svg .exit().remove();
cluster .exit().selectAll('circle.bubble')
.style("opacity", 1)
.style("fill", "#DDD")
.style("stroke", "#DDD")
.transition()
.duration(500)
.style("opacity", 0);
cluster .exit().remove();
headers .exit().remove();
function update(data) {
//update with data
svg .selectAll('text.total-value')
.transition()
.delay(400)
.duration(1000)
.tween( 'text', function(d, i) {
var currentValue = +this.textContent.replace(/\D/g,'');
var interpolator = d3.interpolateRound( currentValue, d.Total[1] );
return function( t ) {
this.textContent = totalFormat(interpolator(t));
};
});
svg .selectAll('text.total-name')
.text(val(data.Total[0]));
svg .selectAll('circle')
.attr('class', function(d, i) {
if(d[1] === 0){ return 'bubble empty';}
else {return 'bubble';}
})
.transition()
.duration(1000)
.delay(function(d, i) { return 500 + (i * 100); })
.ease('elastic')
.attr("r", function (d, i) { return r(d[1]); });
svg .selectAll('text.cluster-value')
.transition()
.delay(function(d, i) { return 500 + (i * 100); })
.duration(1000)
.tween( 'text', function(d, i) {
var currentValue = +this.textContent.replace(/\D/g,'');
var interpolator = d3.interpolateRound( currentValue, d[1] );
return function( t ) {
this.textContent = clusterFormat(interpolator(t));
};
});
svg .selectAll('text.cluster-value-2')
.transition()
.delay(function(d, i) { return 500 + (i * 100); })
.duration(1000)
.tween( 'text', function(d, i) {
var currentValue = +this.textContent.replace(/\D/g,'');
var interpolator = d3.interpolateRound( currentValue, d[0] );
return function( t ) {
this.textContent = clusterFormat2(interpolator(t));
};
});
headers .text(function(d, i){return d});
}
update(data);
});
}
chart.totalFormat = function(_) {
if (!arguments.length) return totalFormat;
totalFormat = _;
return chart;
};
chart.clusterFormat = function(_) {
if (!arguments.length) return clusterFormat;
clusterFormat = _;
return chart;
};
chart.clusterFormat2 = function(_) {
if (!arguments.length) return clusterFormat2;
clusterFormat2 = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.onTotalClick = function(_) {
if (!arguments.length) return onTotalClick;
onTotalClick = _;
return chart;
};
chart.onTotalMouseOver = function(_) {
if (!arguments.length) return onTotalMouseOver;
onTotalMouseOver = _;
return chart;
};
chart.onClusterClick = function(_) {
if (!arguments.length) return onClusterClick;
onClusterClick = _;
return chart;
};
chart.onClusterMouseOver = function(_) {
if (!arguments.length) return onClusterMouseOver;
onClusterMouseOver = _;
return chart;
};
return chart;
}
my sample data looks like this
var data = {
"data1": {
Headers: ["Total", "Col 1A", "Col 2A", "Col 3A", "Col 4A"],
Total: ["Total # of Widgets", 1200],
Clusters: [
[100, 1200],
[67, 800],
[42, 500],
[17, 198]
]
},
"data2": {
Headers: ["Total", "Col 1B", "Col 2B", "Col 3B", "Col 4B"],
Total: ["Total # of Widgets", 1200],
Clusters: [
[20, 245],
[31, 371],
[32, 386],
[12, 145]
]
}
}
Thanks!!
There are a couple of issues with your enter/update/exit pattern:
background = svgEnter.append("g")
.attr('class', 'background');
var headers = svgEnter.append("g")
.attr('class', 'headers')
.selectAll("text.header")
.data(data.Headers, function(d){return d;});
var total = svgEnter.append("g")
.attr('class', 'total');
This code is referencing svgEnter, which is OK the first time through, as svgEnter has a non-empty selection (it contains the svg you create a little earlier).
On subsequent calls to this function, svgEnter will contain an empty selection, as the svg element already exists. So, I have modified this part of your code to:
svgEnter.append('g')
.attr('class', 'background');
var background = svg.selectAll('g.background');
svgEnter.append('g')
.attr('class', 'headers')
var headers = svg.selectAll('g.headers').selectAll('text.header')
.data(data.Headers, function(d) { return d; });
svgEnter.append('g')
.attr('class', 'total');
var total = svg.selectAll('g.total');
This will create the g elements if we also have to create the svg element. It will then create the variables similar to your existing code using selections from the svg element.
I think that they're the only changes I made, the rest of your code works as expected.
An updated fiddle is at http://jsfiddle.net/vba6n4sh/9/
Related
drag grouped elements move not continuous
I try move a grouped element but after click and drag, the elements jumped away. demo() function demo() { var tooltip = d3.select('body') .append('div') .attr('id','tooltip') .style('position','absolute') .style('opacity',0) .style('background','lightsteelblue') var svg = d3.select("body") .append("svg") .attr("width", 300) .attr("height", 200) .style("background", "#ececec") add_grid(svg); var data = [ { text: "O", x: 50, y: 50 }, ]; var g = svg.append('g') var fontsize = 20; var box = g.selectAll(".box") .data(data) .join('g') .attr('class','box') .attr("pointer-events", "all") box.call( d3.drag() .on("start",function(event,d) { d3.select(this).raise().classed("active", true); d3.select('#tooltip') .transition().duration(100) .style('opacity', 1) }) .on("drag",function(event,d) { d.x = event.x d.y = event.y d3.select(this).attr('transform',`translate(${d.x},${d.y})`) var desc = "(" + d.x.toFixed(1) +"," + d.y.toFixed(1) + ")" d3.select('#tooltip') .style('left', (event.x+2) + 'px') .style('top', (event.y-2) + 'px') .text(desc) }) .on("end", function dragEnd(event,d) { d3.select(this).classed("active", false); d3.select('#tooltip').style('opacity', 0)} )) .on('mouseover', function(event,d) { }) .on('mouseout', function(event,d) { }) .on('mousemove', function(event,d) { }) .on("mousedown", function(){ }) .on("mouseup", function(){ }); var txt = box.append("text") .attr("text-anchor", "middle") .attr("dominant-baseline",'text-before-edge')//'central')//text-bottom .attr("font-size", fontsize) .attr("x", (d) => d.x) .attr("y", (d) => d.y) var tspan = txt.selectAll(".tspan") .data((d) => d.text.split("\n")) .join("tspan") .attr("class", "tspan") .attr("x", function (d) { let x = +d3.select(this.parentNode).attr("x"); return x; }) .attr("y", function (d,i) { let y = +d3.select(this.parentNode).attr("y"); return y + i*fontsize * .9; }) .text((d) => d); box.each((d,i,n) => { var bbox = d3.select(n[i]).node().getBBox() var padding = 2 bbox.x -= padding bbox.y -= padding bbox.width += 2*padding bbox.height += 2*padding d.bbox = bbox }) .attr('transform',d => `translate(${0},${-d.bbox.height/2})`) .append('rect') .attr('x',d => d.bbox.x) .attr('y',d => d.bbox.y) .attr('width', d => d.bbox.width) .attr('height',d => d.bbox.height) .attr('stroke','red') .attr('fill','none') add_dots(svg,data) function add_dots(svg,data) { svg.selectAll('.dots') .data(data) .join('circle') .attr('class','dots') .attr('r',2) .attr('cx',d => d.x) .attr('cy',d => d.y) .attr('fill','red') } function add_grid(svg) { var w = +svg.attr("width"); var step = 10; var mygrid = function (d) { return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`; }; var grid = []; for (var i = 0; i < w; i += step) { grid.push(i); } svg .append("g") .selectAll(null) .data(grid) .enter() .append("path") .attr("d", (d) => mygrid(d)) .attr("fill", "none") .attr("stroke", "green") .attr("stroke-width", 0.5); } } <script src="https://unpkg.com/d3#7.0.4/dist/d3.min.js"></script>
Having trouble with D3v4 update pattern with 3 levels of nested data
I have data nested into 3 levels, which I need to dynamically update. The kicker is that the elements for the mid-level need to actually display on TOP of the elements for the low-level due to some hover behavior I need, so I'm having trouble with what the enter/update/exit/merge pattern should look like. (There don't need to be any elements displayed for the high-level). The code I have right now updates the data successfully but is not rendering the rectangles at all, instead giving me an error, Uncaught TypeError: this.setAttribute is not a function. How do I fix this problem, please? Here's what it should look like before updating: And here's what it should look like after updating: Here's a CodePen with the code Below ``` let width = 0.9 * window.innerWidth, height = 0.9 * window.innerHeight, colors = ['darkviolet', 'steelblue', 'coral', 'Turquoise', 'firebrick', 'mediumslateblue', 'palevioletred', 'green', 'aqua']; let data1 = [{"group":"A","segment":"1","item":"1"}, {"group":"A","segment":"1","item":"2"}, {"group":"A","segment":"1","item":"3"}, {"group":"A","segment":"2","item":"4"}, {"group":"A","segment":"2","item":"5"}, {"group":"A","segment":"2","item":"6"}, {"group":"A","segment":"3","item":"7"}, {"group":"A","segment":"3","item":"8"}, {"group":"A","segment":"3","item":"9"}, {"group":"B","segment":"4","item":"1"}, {"group":"B","segment":"4","item":"2"}, {"group":"B","segment":"4","item":"3"}, {"group":"B","segment":"5","item":"4"}, {"group":"B","segment":"5","item":"5"}, {"group":"B","segment":"5","item":"6"}, {"group":"B","segment":"6","item":"7"}, {"group":"B","segment":"6","item":"8"}, {"group":"B","segment":"6","item":"9"}, {"group":"C","segment":"7","item":"1"}, {"group":"C","segment":"7","item":"2"}, {"group":"C","segment":"7","item":"3"}, {"group":"C","segment":"8","item":"4"}, {"group":"C","segment":"8","item":"5"}, {"group":"C","segment":"8","item":"6"}, {"group":"C","segment":"9","item":"7"}, {"group":"C","segment":"9","item":"8"}, {"group":"C","segment":"9","item":"9"}], data2 = [{"group":"A","segment":"1","item":"1"}, {"group":"A","segment":"8","item":"2"}, {"group":"A","segment":"9","item":"3"}, {"group":"A","segment":"2","item":"4"}, {"group":"A","segment":"2","item":"5"}, {"group":"A","segment":"2","item":"6"}, {"group":"A","segment":"5","item":"7"}, {"group":"A","segment":"3","item":"8"}, {"group":"A","segment":"3","item":"9"}, {"group":"B","segment":"4","item":"1"}, {"group":"B","segment":"4","item":"2"}, {"group":"B","segment":"7","item":"3"}, {"group":"B","segment":"5","item":"4"}, {"group":"B","segment":"5","item":"5"}, {"group":"B","segment":"5","item":"6"}, {"group":"B","segment":"5","item":"7"}, {"group":"B","segment":"6","item":"8"}, {"group":"B","segment":"6","item":"9"}, {"group":"C","segment":"7","item":"1"}, {"group":"C","segment":"7","item":"2"}, {"group":"C","segment":"3","item":"3"}, {"group":"C","segment":"8","item":"4"}, {"group":"C","segment":"8","item":"5"}, {"group":"C","segment":"8","item":"6"}, {"group":"C","segment":"9","item":"7"}, {"group":"C","segment":"6","item":"8"}, {"group":"C","segment":"1","item":"9"}]; let button = d3.select('body') .append('button') .attr('type', 'button') .style('display', 'block') .text('Update') .on('click', function() { update(data2) }); var svg = d3.select('body').append('svg') .attr('width', width) .attr('height', height) .append('g'); let color = d3.scaleOrdinal().range(colors); update(data1); function getxy(data) { let grouped = Array.from(d3.group(data, d=> d.group, d=> d.segment), ([key, value]) => ({key, value})); grouped.forEach(function(s) { s.value = Array.from(s.value, ([key, value]) => ({key, value})); s.value.forEach(function(d) { d.start = d3.min(d.value, function(t) { t.segment = +t.segment; t.item = +t.item; return +t.item }); d.end = d3.max(d.value, function(t) { return t.item }); d.key = +d.key; d.group = s.key; }) }) let x1 = d3.scaleBand() .domain([1, 2, 3, 4, 5, 6, 7, 8, 9]) .range([width*0.05, width]) .padding(0.0); let y1 = d3.scaleBand() .domain(['A', 'B', 'C']) .range([10, height]) .padding(0.1); return [x1, y1, grouped]; } function update(data) { let xy = getxy(data); let x = xy[0], y = xy[1], groupedData = xy[2]; let barsAll = svg .selectAll('.bars') .data(groupedData); barsAll.exit().remove(); let barsEnter = barsAll .enter() .append('g') .attr('class', 'bars'); barsEnter = barsEnter.merge(barsAll); let segmentsAll = barsEnter .selectAll('.segments') .data(function(d) { return d.value }); segmentsAll.exit().remove(); let segmentsEnter = segmentsAll.enter(); let bitsAll = segmentsEnter .selectAll('.bits') .data(function(d) { return d.value }); bitsAll.exit().remove(); let bitsEnter = bitsAll .enter() .append('circle') .attr('class', 'bits') .attr('r', width*0.05) .attr('stroke', 'none'); bitsEnter = bitsEnter.merge(bitsAll); bitsEnter .attr('cx', function(d) { return x(d.item) }) .attr('cy', function(d) { return y(d.group) + y.bandwidth()/2 }) .attr('fill', function(d) { return color(d.segment) }); segmentsEnter.append('rect') .attr('stroke', 'black') .attr('class', 'segments') .style('fill-opacity', 0.2); segmentsEnter = segmentsEnter.merge(segmentsAll); segmentsEnter .attr('fill', function(d) { return color(d.key) }) .attr('height', y.bandwidth()*0.75) .attr('x', function(d) { return x(d.start) - width*0.05 }) .attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 }) .attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 }); } ``` <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://d3js.org/d3-array.v2.min.js"></script>
Well, by going around the “merge” step on the mid-level segments and low-level bits (but not on the top-level bars), I was able to hack a fix, finally. Working pen Still open to help from others because I feel like I should really get the hang of the whole flow - update, enter, exit, merge - at some point. let width = 0.9 * window.innerWidth, height = 0.9 * window.innerHeight, colors = ['darkviolet', 'steelblue', 'coral', 'Turquoise', 'firebrick', 'mediumslateblue', 'palevioletred', 'green', 'aqua']; let data1 = [{"group":"A","segment":"1","item":"1"}, {"group":"A","segment":"1","item":"2"}, {"group":"A","segment":"1","item":"3"}, {"group":"A","segment":"2","item":"4"}, {"group":"A","segment":"2","item":"5"}, {"group":"A","segment":"2","item":"6"}, {"group":"A","segment":"3","item":"7"}, {"group":"A","segment":"3","item":"8"}, {"group":"A","segment":"3","item":"9"}, {"group":"B","segment":"4","item":"1"}, {"group":"B","segment":"4","item":"2"}, {"group":"B","segment":"4","item":"3"}, {"group":"B","segment":"5","item":"4"}, {"group":"B","segment":"5","item":"5"}, {"group":"B","segment":"5","item":"6"}, {"group":"B","segment":"6","item":"7"}, {"group":"B","segment":"6","item":"8"}, {"group":"B","segment":"6","item":"9"}, {"group":"C","segment":"7","item":"1"}, {"group":"C","segment":"7","item":"2"}, {"group":"C","segment":"7","item":"3"}, {"group":"C","segment":"8","item":"4"}, {"group":"C","segment":"8","item":"5"}, {"group":"C","segment":"8","item":"6"}, {"group":"C","segment":"9","item":"7"}, {"group":"C","segment":"9","item":"8"}, {"group":"C","segment":"9","item":"9"}], data2 = [{"group":"A","segment":"1","item":"1"}, {"group":"A","segment":"8","item":"2"}, {"group":"A","segment":"9","item":"3"}, {"group":"A","segment":"2","item":"4"}, {"group":"A","segment":"2","item":"5"}, {"group":"A","segment":"2","item":"6"}, {"group":"A","segment":"5","item":"7"}, {"group":"A","segment":"3","item":"8"}, {"group":"A","segment":"3","item":"9"}, {"group":"B","segment":"4","item":"1"}, {"group":"B","segment":"4","item":"2"}, {"group":"B","segment":"7","item":"3"}, {"group":"B","segment":"5","item":"4"}, {"group":"B","segment":"5","item":"5"}, {"group":"B","segment":"5","item":"6"}, {"group":"B","segment":"5","item":"7"}, {"group":"B","segment":"6","item":"8"}, {"group":"B","segment":"6","item":"9"}, {"group":"C","segment":"7","item":"1"}, {"group":"C","segment":"7","item":"2"}, {"group":"C","segment":"3","item":"3"}, {"group":"C","segment":"8","item":"4"}, {"group":"C","segment":"8","item":"5"}, {"group":"C","segment":"8","item":"6"}, {"group":"C","segment":"9","item":"7"}, {"group":"C","segment":"6","item":"8"}, {"group":"C","segment":"1","item":"9"}]; let button = d3.select('body') .append('button') .attr('type', 'button') .style('display', 'block') .text('Update') .on('click', function() { update(data2) }); var svg = d3.select('body').append('svg') .attr('width', width) .attr('height', height) .append('g'); let color = d3.scaleOrdinal().range(colors); function getxy(data) { let grouped = Array.from(d3.group(data, d=> d.group, d=> d.segment), ([key, value]) => ({key, value})); grouped.forEach(function(s) { s.value = Array.from(s.value, ([key, value]) => ({key, value})); s.value.forEach(function(d) { d.start = d3.min(d.value, function(t) { t.segment = +t.segment; t.item = +t.item; return +t.item }); d.end = d3.max(d.value, function(t) { return t.item }); d.key = +d.key; d.group = s.key; }) }) let x1 = d3.scaleBand() .domain([1, 2, 3, 4, 5, 6, 7, 8, 9]) .range([width*0.05, width]) .padding(0.0); let y1 = d3.scaleBand() .domain(['A', 'B', 'C']) .range([10, height]) .padding(0.1); return [x1, y1, grouped]; } function update(data) { let xy = getxy(data); let x = xy[0], y = xy[1], groupedData = xy[2]; // update let barsAll = svg .selectAll('.bars') .data(groupedData); // exit barsAll.exit().remove(); // enter let barsEnter = barsAll .enter(); barsEnter = barsEnter.merge(barsAll).append('g'); barsEnter.selectAll('.segments').remove(); d3.selectAll('.segments').remove(); let segmentsAll = barsEnter .selectAll('.segments') .data(function(d) { return d.value }); segmentsAll.exit().remove(); let segmentsEnter = segmentsAll .enter(); let bitsAll = segmentsEnter .selectAll('.bits') .data(function(d) { return d.value }); bitsAll.exit().remove(); bitsAll .enter() .append('circle') .attr('class', 'bits') .attr('r', width*0.05) .attr('stroke', 'none') .attr('cx', function(d) { return x(d.item) }) .attr('cy', function(d) { return y(d.group) + y.bandwidth()/2 }) .attr('fill', function(d) { return color(d.segment) }); // bitsEnter = bitsEnter.merge(bitsAll); bitsAll .attr('cx', function(d) { return x(d.item) }) .attr('cy', function(d) { return y(d.group) + y.bandwidth()/2 }) .attr('fill', function(d) { return color(d.segment) }); segmentsEnter .append('rect') .attr('class', 'segments') .attr('stroke', 'black') .style('fill-opacity', 0.2) .attr('fill', function(d) { return color(d.key) }) .attr('height', function() { return y.bandwidth()*0.75 }) .attr('x', function(d) { return x(d.start) - width*0.05 }) .attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 }) .attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 }); segmentsAll .attr('fill', function(d) { return color(d.key) }) .attr('height', function() { return y.bandwidth()*0.75 }) .attr('x', function(d) { return x(d.start) - width*0.05 }) .attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 }) .attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 }); //segmentsAll = segmentsEnter.merge(segmentsAll); // segmentsEnter // .attr('fill', function(d) { return color(d.key) }) // .attr('height', function() { return y.bandwidth()*0.75 }) // .attr('x', function(d) { return x(d.start) - width*0.05 }) // .attr('y', function(d) { return y(d.group) + y.bandwidth()*0.125 }) // .attr('width', function(d) { return x(d.end) - x(d.start) + width*0.1 }); } update(data1); <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script> <script src="https://d3js.org/d3-array.v2.min.js"></script>
Removing segments from a d3 animated pie
I have prototype code I am working with here: jsfiddle The example shows how to add a segment when new data is added but not how to remove it again when the data changes [back]. I am fairly new to d3 and don't quite get the exit() function yet... if you reverse the initial and second dataset you will see that the grapes segment is never removed. Thanks in advance! any help would be great! The update code: (my final chart needs to update on a timer when data changes) var arcs = arc_grp.selectAll("path") .data(donut(data)); arcs.enter() .append("path") .attr("stroke", "white") .attr("stroke-width", 0.8) .attr("fill", function(d, i) { return color(i); }) .attr("d", arc) .each(function(d) { return this.current = d; }); var sliceLabel = label_group.selectAll("text") .data(donut(data)); sliceLabel.enter() .append("text") .attr("class", "arcLabel") .attr("transform", function(d) { return "translate(" + (arc.centroid(d)) + ")"; }) .attr("text-anchor", "middle") .style("fill-opacity", function(d) { if (d.value === 0) { return 1e-6; } else { return 1; } }) .text(function(d) { return d.data.label; }); };
To remove elements without data, you have to use exit(), which... Returns the exit selection: existing DOM elements in the selection for which no new datum was found. So, inside your updateChart function, you need an exit selection for both paths and texts: var newArcs = arcs.data(donut(dataset)); newArcs.exit().remove(); var newSlices = sliceLabel.data(donut(dataset)); newSlices.exit().remove(); Here is your updated code: // Setup all the constants var duration = 500; var width = 500 var height = 300 var radius = Math.floor(Math.min(width / 2, height / 2) * 0.9); var colors = ["#d62728", "#ff9900", "#004963", "#3497D3"]; // Test Data var d2 = [{ label: 'apples', value: 20 }, { label: 'oranges', value: 50 }, { label: 'pears', value: 100 }]; var d1 = [{ label: 'apples', value: 100 }, { label: 'oranges', value: 20 }, { label: 'pears', value: 20 }, { label: 'grapes', value: 20 }]; // Set the initial data var data = d1 var updateChart = function(dataset) { var newArcs = arcs.data(donut(dataset)); newArcs.exit().remove(); newArcs.enter() .append("path") .attr("stroke", "white") .attr("stroke-width", 0.8) .attr("fill", function(d, i) { return color(i); }) .attr("d", arc); newArcs.transition() .duration(duration) .attrTween("d", arcTween); var newSlices = sliceLabel.data(donut(dataset)); newSlices.exit().remove(); newSlices.transition() .duration(duration) .attr("transform", function(d) { return "translate(" + (arc.centroid(d)) + ")"; }) .style("fill-opacity", function(d) { if (d.value === 0) { return 1e-6; } else { return 1; } }); sliceLabel.data(donut(dataset)).enter() .append("text") .attr("class", "arcLabel") .attr("transform", function(d) { return "translate(" + (arc.centroid(d)) + ")"; }) .attr("text-anchor", "middle") .style("fill-opacity", function(d) { if (d.value === 0) { return 1e-6; } else { return 1; } }) .text(function(d) { return d.data.label; }); }; var color = d3.scale.category20(); var donut = d3.layout.pie() .sort(null) .value(function(d) { return d.value; }); var arc = d3.svg.arc() .innerRadius(radius * .4) .outerRadius(radius); var svg = d3.select("body") .append("svg") .attr("width", width) .attr("height", height); var arc_grp = svg.append("g") .attr("class", "arcGrp") .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"); var label_group = svg.append("g") .attr("class", "lblGroup") .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"); var arcs = arc_grp.selectAll("path") .data(donut(data)); arcs.enter() .append("path") .attr("stroke", "white") .attr("stroke-width", 0.8) .attr("fill", function(d, i) { return color(i); }) .attr("d", arc) .each(function(d) { return this.current = d; }); var sliceLabel = label_group.selectAll("text") .data(donut(data)); sliceLabel.enter() .append("text") .attr("class", "arcLabel") .attr("transform", function(d) { return "translate(" + (arc.centroid(d)) + ")"; }) .attr("text-anchor", "middle") .style("fill-opacity", function(d) { if (d.value === 0) { return 1e-6; } else { return 1; } }) .text(function(d) { return d.data.label; }); // Update the data setInterval(function(model) { return updateChart(d2); }, 2000); // Tween Function var arcTween = function(a) { var i = d3.interpolate(this.current, a); this.current = i(0); return function(t) { return arc(i(t)); }; }; .arcLabel { font: 10px sans-serif; fill: #fff; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Double-click on a node to fade out all but its immediate neighbours. Double-click to bring them back again
I am using the below code for the iamges and mouseover text. And now i have added the code for double click on a node to fade out all but it is not working and also link color is not changing. Can anyone help on this var width = 960, height = 500 var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); var force = d3.layout.force() .gravity(0.1) .charge(-120) .linkDistance(30) .size([width, height]); var voronoi = d3.geom.voronoi() .x(function(d) { return d.x; }) .y(function(d) { return d.y; }) .clipExtent([[0, 0], [width, height]]); d3.json("miserables1.json", function(error, json) { if (error) throw error; force .nodes(json.nodes) .links(json.links) .start(); var link = svg.selectAll(".link") .data(json.links) .enter().append("line") .attr("class", "link", "fill:red; stroke:red;"); var node = svg.selectAll(".node") .data(json.nodes) .enter().append("g") .attr("class", "node") .call(force.drag); node.append("svg:image") .attr("xlink:href", function(d) { return d.imagen }) .on('dblclick', connectedNodes); var circle = node.append("circle") .attr("r", 4.5); var label = node.append("text") .attr("dy", ".35em") .text(function(d) { return d.name; }); var cell = node.append("path") .attr("class", "cell"); force.on("tick", function() { cell .data(voronoi(json.nodes)) .attr("d", function(d) { return d.length ? "M" + d.join("L") : null; }); link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); circle .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); label .attr("x", function(d) { return d.x + 8; }) .attr("y", function(d) { return d.y; }); }); }); var toggle = 0; var linkedByIndex = {}; for (i = 0; i < graph.nodes.length; i++) { linkedByIndex[i + "," + i] = 1; }; graph.links.forEach(function (d) { linkedByIndex[d.source.index + "," + d.target.index] = 1; }); function neighboring(a, b) { return linkedByIndex[a.index + "," + b.index]; } function connectedNodes() { if (toggle == 0) { //Reduce the opacity of all but the neighbouring nodes d = d3.select(this).node().__data__; node.style("opacity", function (o) { return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1; }); link.style("opacity", function (o) { return d.index==o.source.index | d.index==o.target.index ? 1 : 0.1; }); toggle = 1; } else { node.style("opacity", 1); link.style("opacity", 1); toggle = 0; } }
d3 bar char labels transitions
Im having a bar charts with labels. After data refresh labels are not shown correctly. Not sure what Im doing wrong with newToolTip part. Labels are not removed and stay on canvas after update data. Thanks for help. //function for button click event function getValue(myDataArray) { document.getElementById('choosenButton').innerHTML = 'chosen data: ' + myDataArray; document.getElementById('choosenButton2').innerHTML = 'chosen data: ' + myDataArray; //Scales pre chart1 set up for various dataarrays var x = d3.scaleBand().domain(d3.range(0, eval(myDataArray).length)) .range([0, svgWidth]) .paddingInner(0.05); var y = d3.scaleLinear() .domain([0,d3.max(eval(myDataArray), function(d) { return (+d.balance)})]) .range([0, svgHeight]); // add the x Axis var xAxis = d3.scaleBand() .domain(eval(myDataArray).map(function(d) { return d.name})) .range([0, svgWidth]) .paddingInner(0.05); //add y axis var yAxis = d3.scaleLinear() .domain([0, d3.max(eval(myDataArray), function(d) { return (+d.balance)})]) .range([svgHeight, 0]); var bars = myCanvas1.selectAll('rect').data(eval(myDataArray)); var newToolTip = myCanvas1.selectAll('g').select('.tooltip').data(eval(myDataArray)); //exit data bars.exit() .transition() .duration(duration1) .attr('height', svgHeight - function(d) { return (+d.balance)}) .remove(); //enter new data bars.enter() .append('rect') .style('fill', 'steelblue') .on('mouseover',mouseover) .on('mouseout', mouseout) .attr('x', function(d, i) { return x(i); }) .attr('width', x.bandwidth()) .attr('y', function(d) { return (svgHeight - y(+d.balance));}) .attr('height', function(d) { return y(+d.balance); }) .merge(bars) .transition() .duration(duration1) //update .attr('x', function(d, i) { return x(i); }) .attr('width', x.bandwidth()) .attr('y', function(d) { return (svgHeight - y(+d.balance)); } ) .attr('height', function(d) { return y(+d.balance); }); newToolTip.exit() .transition() .duration(duration1) .remove(); newToolTip.enter() .append('text') .attr('class', 'tooltip') .style('fill', 'red') .attr('x', function(d, i) { return x(i); }) .attr('y', function(d) { return (svgHeight - y(+d.balance) - 20); } ) .text(function(d) { return +d.balance; }); newToolTip.attr('x', function(d, i) { return x(i); }) .style('fill', 'green') .attr('y', function(d) { return (svgHeight - y(+d.balance) - 20); } ) .text(function(d) { return +d.balance; }); myCanvas1.selectAll('g.yaxis') .transition() .duration(duration1) .call(d3.axisLeft(yAxis)); myCanvas1.selectAll('g.xaxis') .transition() .duration(duration1) .call(d3.axisBottom(xAxis)) .selectAll('text') .attr('dx', '-2.2em') .attr('dy', '-.15em') .attr('transform', 'rotate(-65)'); }; function mouseover() { d3.select(this).attr('opacity', .5); }; function mouseout() { d3.select(this).attr('opacity', 1); };
Here is working code: var labelsGroup = myCanvas1.append("g") var labels = labelsGroup .selectAll(".tooltip") .data(dataArray1) .enter() .append('text') .attr('class', 'tooltip') .attr('x', function(d, i) { return x(i); }) .attr('y', function(d) { return (svgHeight - y(+d.balance) - 20); } ) .text(function(d) { return +d.balance; }); and after click I upgrade chart with: var newToolTip = labelsGroup.selectAll('.tooltip').data(eval(myDataArray)); newToolTip.exit() .transition() .duration(duration1) .remove(); newToolTip.enter() .append("text") .attr('class', 'tooltip') .style('fill', 'red') .attr('x', function(d, i) { return x(i); }) .attr('y', function(d) { return (svgHeight - y(+d.balance) - 20); } ) .text(function(d) { return +d.balance; }) .style('opacity', 1); newToolTip.attr('x', function(d, i) { return x(i); }) .style('fill', 'green') .attr('y', function(d) { return (svgHeight - y(+d.balance) - 20); } ) .text(function(d) { return +d.balance; });