d3.js - table partially visible when insert to group element - d3.js

I want to insert a ta
console.clear()
var parsed = d3.csvParse(get_csv_text())
var g = d3.select('body')
.append('svg')
.append('g')
csv_table(g,parsed)
function csv_table(g,parsed) {
var caption = "demo table"
var xoff = 50
var yoff = 50
var width = 400
var height = 800
var headers = [Object.keys(parsed[0])]
var table = g.append("foreignObject")
.attr("width", width)
.attr("height", height)
.attr('x',xoff)
.attr('y',yoff)
.append("xhtml:table")
.style('font-weight','bold')
var thead = table.append("thead")
var tbody = table.append("tbody")
var tfoot = table.append("tfoot")
thead.selectAll("tr")
.data(headers).enter()
.append("tr")
.selectAll("th")
.data(d => d).enter()
.append("th")
.text(d => d)
.style('border-bottom','1px solid black')
tbody.selectAll("tr")
.data(parsed).enter()
.append("tr")
.selectAll("td")
.data(d => {
var elem = Object.entries(d).map(d => d[1])
return elem
}).enter()
.append("td")
.text(d => d)
thead.selectAll('tr')
.style('color','black')
.style('text-align','left')
g.selectAll('table')
.style('border-top','2px solid black')
.style('border-bottom','1.5px solid black')
tbody.selectAll('tr')
.style('text-align','left')
tbody.selectAll('tr:nth-child(even)')
.style('background-color','#eee')
table.append("caption")
.text(caption)
}
function get_csv_text() {
var data =`name,x,y
aaaaaaaaaaaa,8712.0,10631.0
b,9951.0,11806.0
c,12698.0,14797.0
d,13246.0,15391.0
e,14959.0,17237.0
f,15445.0,17842.0
g,26727.0,30004.0
h,8712.0,10631.0
i,9951.0,11806.0
j,12698.0,14797.0
k,13246.0,15391.0
l,14959.0,17237.0
m,15445.0,17842.0
n,26727.0,30004.0
`
return data
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
How to make the element always fit the table?

The svg element is too small. You can move the height and width attributes up, and then use .attr("width", width).attr("height", height) after the .append('svg').

Related

d3.js - join data update behavior wired

This demo try to update data when click the graph. it use the join method but looks like data and axis have been overlapped after click. the previous drawing not been cleared!
console.clear()
click_to_update()
function click_to_update() {
var svg = d3.select('body').append('svg')
.attr('width',600)
.attr('height',400)
.style('border','5px solid red')
var frame = svg.append('g').attr('class','frame')
frame.append('g').attr('class','xaxis')
frame.append('g').attr('class','yaxis')
d3.select('svg').on('click', function(){
var data = fetch_data()
refresh_graph(data)
})
var data = fetch_data()
refresh_graph(data)
function refresh_graph(data){
var svg = d3.select('svg')
var colors = d3.scaleOrdinal(d3.schemeSet3)
var margin = {left: 40,top: 10, right:10,bottom: 60},
width = +svg.attr('width') - margin.left - margin.right,
height = +svg.attr('height') - margin.top - margin.bottom;
var g = d3.select('.frame')
.attr('transform',`translate(${margin.left},${margin.top})`)
var xrange = data.map(function(d,i) { return i; })
var x = d3.scalePoint()
.domain(xrange)
.range([0, width]);
var ymax = d3.max(data,d => d.value)
var y = d3.scaleLinear()
.domain([0,ymax])
.range([height, 0]);
var drawpath = function(d,i) {
var ax = x(i)
var ay = y(d.value)
var bx = x(i)
var by = y(0)
var path = ['M',ax,ay,'L',bx,by]
return path.join(' ')
}
var g1 = g.selectAll('path')
.data(data)
.join('path')
.attr('stroke','gray')
.attr('stroke-width',1)
.style('fill', (d, i) => colors(i))
.transition()
.attr('d', drawpath)
g.selectAll('circle')
.data(data)
.join('circle')
.attr('fill','red')
.attr('cx',(d,i) => x(i))
.attr('cy',(d,i) => y(d.value))
.attr('r',5)
var xaxis = d3.select('.xaxis')
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var yaxis = d3.select('.yaxis')
.attr('transform','translate(-5,0)')
.call(d3.axisLeft(y));
}
function fetch_data(){
var num = parseInt(Math.random()*20) + 5
var data = d3.range(num).map(d => {
return {value:Math.random()}
})
return data
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>

How do I get rid of tiny lines between canvas rects

I am very new to D3 and as you can see in the image above there are tiny lines/gaps between each rectangle that I would love to get rid of, this is drawn on a canvas element with each rectangle starting where the last one ends using D3.js following this tutorial almost exactly minus adding the gaps between each square.
I've tried
this.canvas.imageSmoothingQuality = 'low';
draw() {
const canvas = d3
.select(this.chartContainer.nativeElement)
.append('canvas')
.attr('width', this.width)
.attr('height', this.height)
.attr(
'transform',
'translate(' + this.margin.left + ',' + this.margin.top + ')'
);
this.canvas = canvas.node().getContext('2d');
this.clearCanvas();
this.canvas.imageSmoothingQuality = 'low';
const elements = this.shadowContainer.selectAll('custom.rect');
const _this = this;
elements.each(function(d, i) {
const node = d3.select(this);
// Here you retrieve the colour from the individual in-memory node and set the fillStyle for the canvas paint
_this.canvas.fillStyle = node.attr('color');
// Here you retrieve the position of the node and apply it to the fillRect context function which will fill and paint the square.
_this.canvas.fillRect(
Number(node.attr('x')),
Number(node.attr('y')),
Number(node.attr('width')),
Number(node.attr('height'))
);
});
}
private dataBind(value) {
const customBase = document.createElement('custom');
this.shadowContainer = d3.select(customBase);
const {
viewModes: {
heatMap: {
data,
chartOptions: { engagementStatus, xAxis, yAxis }
}
}
} = value;
const x = this.d3
.scaleBand()
.range([0, this.width])
.domain(xAxis.categories);
this.shadowContainer
.append('g')
.style('font-size', 11)
.attr('class', 'x-axis')
.call(this.d3.axisTop(x).tickSize(0))
.select('.domain')
.remove();
this.shadowContainer
.selectAll('.x-axis text')
.style('text-anchor', 'start')
.attr('transform', function(d) {
return `translate(8, -8)rotate(-90)`;
});
const y = this.d3
.scaleBand()
.domain(d3.reverse(yAxis.categories))
.range([this.height, 0]);
const color = this.d3
.scaleLinear()
.domain([-2, -1, 0, 1])
// #ts-ignore
.range(['#5b717d', '#ffb957', '#ee6b56', '#40a050']);
const join = this.shadowContainer
.selectAll('custom.rect')
.data(data, function(d) {
return `${d.Date}:${d.Member}`;
});
const enterSelection = join
.enter()
.append('custom')
.attr('class', 'rect')
.attr('x', d =>
this.getCorrectDatePosition(
d.Date,
x,
xAxis.categories[0].split('/').length
)
)
.attr('y', function(d) {
return y(d.Member);
})
.attr('width', 24)
.attr('height', 24);
join
.merge(enterSelection)
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.attr('color', function(d) {
return color(d.score);
});
const exitSelection = join
.exit()
.transition()
.attr('width', 0)
.attr('height', 0)
.remove();
}
This is likely an issue stemming from your scales. It can occur with either SVG or canvas and occurs when dealing with coordinates that require plotting at fractions of a pixel.
Here's a demonstration with SVG:
var data = d3.range(20);
var x = d3.scaleBand()
.range([10,250])
.domain(data)
var svg = d3.select("body")
.append("svg")
.attr("width", 500);
var rect = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d=>x(d) )
.attr("y", 50)
.attr("width", x.bandwidth())
.attr("height",100)
.attr("fill","crimson")
svg.transition()
.attrTween("tween", function() {
var i = d3.interpolate(250,480)
return function(t) {
x.range([50,i(t)])
rect.attr("x",d=>x(d))
.attr("width", x.bandwidth());
return "";
}
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
And one with Canvas:
var data = d3.range(20);
var x = d3.scaleBand()
.range([10,250])
.domain(data)
var canvas = d3.select("body")
.append("canvas")
.attr("width", 500);
var rect = d3.create("div").selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d=>x(d) )
.attr("y", 50)
.attr("width", x.bandwidth())
.attr("height",100)
.attr("fill","crimson")
canvas.transition()
.attrTween("tween", function() {
var i = d3.interpolate(250,480)
var context = canvas.node().getContext("2d");
return function(t) {
x.range([50,i(t)])
context.fillStyle = "#fff";
context.fillRect(0,0,550,300);
rect.attr("x",d=>x(d))
.attr("width", x.bandwidth())
.each(function() {
var node = d3.select(this);
context.fillStyle = "crimson"
context.fillRect(
+node.attr("x"),
+node.attr("y"),
+node.attr("width"),
+node.attr("height"))
})
return "";
}
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
The solution is to be a bit more involved in setting the scale's domain and range. Start with the desired bandwidth, a whole number in pixels, and set the range so that the difference between the minimum and maximum values is equal to the number of values in the domain * the bandwidth.
So instead of:
const x = this.d3
.scaleBand()
.range([0, this.width])
.domain(xAxis.categories);
You'd have:
const length = 10; // length of a box side
const x = this.d3
.scaleBand()
.domain(xAxis.categories)
.range([0,xAxis.categories * length])
You could also calculate length above dynamically, say by using: Math.floor(width/xAxis.categories)
Using the above approach and a slightly contrived example to accommodate the transition, we remove the aliasing/moire pattern. Because we use only full pixels, the transition jumps as each bar increases in width by a full pixel at the same time, as space becomes available in the range:
var data = d3.range(20);
var length = 30;
var x = d3.scaleBand()
.range([10,data.length*length])
.domain(data)
var canvas = d3.select("body")
.append("canvas")
.attr("width", 500);
var rect = d3.create("div").selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d=>x(d) )
.attr("y", 50)
.attr("width", x.bandwidth())
.attr("height",100)
.attr("fill","crimson")
canvas.transition()
.attrTween("tween", function() {
var i = d3.interpolate(250,480)
var context = canvas.node().getContext("2d");
return function(t) {
length = Math.floor(i(t)/data.length)
x.range([10,length*data.length+10])
context.fillStyle = "#fff";
context.fillRect(0,0,550,300);
rect.attr("x",d=>x(d))
.attr("width", x.bandwidth())
.each(function(d,i) {
var node = d3.select(this);
context.fillStyle = d3.schemeCategory10[i%10];
context.fillRect(
+node.attr("x"),
+node.attr("y"),
+node.attr("width"),
+node.attr("height"))
})
return "";
}
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

d3js enter, update, exit confusion: need assistance please

I genuinely hate to ask this question, particularly as I know it has been asked dozens of times - and I've read through the posts. But my problem remains - I simply do not understand how this mechanism works. I am new to d3js, and am using v3.x in meteor; I've gone through tutorials and have gotten something working, but can't get it to update with new data. Again, my apologies for rehashing this, but none of the other posts I've read has resolved the issue.
Here is a code fragment, I've stripped out all the stuff that shouldn't make a difference to focus on the core functionality:
var w = 800;
var h = 800;
var intensity = 25;
var margin = {
top: 75,
right: 100,
bottom: 75,
left: 60
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// get csv data, x & y coords, etc...
createHeatmap = function(csv, x, y) {
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
// set some values
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 4;
// set the scales for both axes
...
// set up the axes
...
// define colorScale
...
// create heatmap
svg.selectAll('g')
.data(data)
.enter()
.append('g')
.selectAll('rect')
.data(function(d, i, j) {
return d;
})
.enter() // start drawing rects
.append('rect')
.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
// append axes, scales, labels, etc.
}
// create heatmap
createHeatmap(csv, x, y);
My problem is that I do not understand why the chart doesn't update the heatmap when I pass new data into createHeatmap().
I stepped through it in the debugger and everything works as I would expect during the initial creation of the heatmap, which renders correctly. When I send new data is when the mystery starts. The debugger shows, deep within d3js itself (not in my code) that the enter() has an array full od null values instead of the data I am passing in. The data exists up until that point. So, as d3js processes the null data it naturally returns an empty object so no update occurs.
Obviously I am not doing the update correctly but am clueless about what I need to do to correct it.
Any advise is greatly appreciated.
Thx!
Update:
Andrew, thanks for the response. I tried your first suggestion, modifying your example to fit my data, but it doesn't update with new data.
My changes:
var w = 800;
var h = 800;
var intensity = 25;
var margin = {
top: 75,
right: 100,
bottom: 75,
left: 60
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// get csv data, x & y coords, etc...
createHeatmap = function(csv, x, y) {
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
// set some values
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 4;
// set the scales for both axes
...
// set up the axes
...
// define colorScale
...
// append group of svg elements bound to data
var rows = svg.selectAll('g')
.data(data);
// enter new rows where needed
rows.enter().append('g');
// select all rects
var rects = rows.selectAll('rect')
.data(function(d, i, j) {
return d;
});
// enter new rects:
rects.enter().append('rect')
.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
Added snippet:
var csv = "'3, 6, 0, 8'\n'1, 9, 0, 4'\n'3, 0, 1, 8'\n'4, 0, 2, 7";
csv = csv.replace(/'/g,'');
var button = d3.select('button')
.on('click', function() {
createHeatmap(update());
});
var w = 120;
var h = 120;
var intensity = 10;
var margin = {
top: 25,
right: 25,
bottom: 25,
left: 25
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
createHeatmap(csv);
function createHeatmap(csv) {
console.log(csv);
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 30;
// define a colorScale with domain and color range
var colorScale = d3.scale.linear()
.domain([0,0.5,1])
.range(['red', 'green', 'blue']);
// append group of svg elements bound to data
var rows = svg.selectAll('g')
.data(data);
// enter new rows where needed
rows.enter().append('g');
// select all rects
var rects = rows.selectAll('rect')
.data(function(d, i, j) {
return d;
});
// enter new rects:
rects.enter().append('rect')
.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
}
function update() {
var data = "'0, 1, 9, 5'\n'4, 0, 7, 2'\n'6, 3, 0, 8'\n'5, 3, 7, 0";
data = data.replace(/'/g,'');
return data;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button>Update</button>
<div id="heatmap"></div>
The issue is in your method chaining.
On first run, things should run as expected:
// create heatmap
svg.selectAll('g') // 1. select all g elements
.data(data) // 2. assign data
.enter() // 3. enter and append a g for each item in the data array
.append('g') // that doesn't have a corresponding element in the DOM (or more accurately, the selection)
.selectAll('rect') // 4. For each newly entered g, select child rectangles
.data(function(d, i, j) { // 5. assign data to child selection.
return d;
})
.enter() // 6. Enter and append a rect for each item in the child g's data array
.append("rect") // that doesn't have a corresponding element in the DOM.
.... // 7. Style
On that first run, we select all the gs, there are none, so the enter selection will have an element for each item in the data array: we are entering everything. Same as with the child rectangles: there are no child rectangles existing when you make the selection, so you enter everything in the child data array.
On the second run, with svg.selectAll("g"), you select all the gs you created the first time around - there is no need to enter anything if the data array has the same number of items. You don't want to append anything: enter().append() the second time (not that you are appending more elements with .append() in any event).
Essentially on the second pass you are modifying an empty selection.
Instead you want to update. While the enter selection is empty on the second pass, the update selection has all the existing gs.
There are a few methods to do this, one is to break your chaining:
This is a version 3 solution:
var rows = svg.selectAll("g")
.data(data);
// enter new rows where needed
rows.enter().append("g")
// Select all rects
var rects = rows.selectAll("rect")
.data(function(d) { return d; })
// Enter new rects:
rects.enter().append("rect")
// Update rects (all rects, not just the newly entered):
rects.attr()...
The below snippet uses this pattern, it enters new rects and gs as needed. And then updates all the rects and gs afterwards. This takes advantage of a magic in d3v3, where the update selection and the enter selection are merged internally, this is not the case in d3v4,v5, which I'll show below.
var button = d3.select("button")
.on("click", function() {
update(random());
})
var svg = d3.select("div")
.append("svg");
var color = d3.scale.linear()
.domain([0,0.5,1])
.range(["red","orange","yellow"])
update(random());
function update(data) {
var rows = svg.selectAll("g")
.data(data);
// enter new rows where needed
rows.enter()
.append("g")
.attr("transform", function(d,i) {
return "translate("+[0,i*22]+")";
})
// Select all rects:
var rects = rows.selectAll("rect")
.data(function(d) { return d; })
// Enter new rects:
rects.enter().append("rect")
// Update rects:
rects.attr("fill", function(d) {
return color(d);
})
.attr("x", function(d,i) { return i*22; })
.attr("width", 20)
.attr("height", 20);
console.log("entered rows:" + rows.enter().size());
console.log("entered rects:" + rects.enter().size());
}
function random() {
return d3.range(5).map(function() {
return d3.range(5).map(function() {
return Math.random();
})
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button>Update</button>
<div></div>
v4/v5:
For v4/v5, which I suggest upgrading to, the pattern is a bit different as you have to explicitly merge the enter and update selections:
var button = d3.select("button")
.on("click", function() {
update(random());
})
var svg = d3.select("div")
.append("svg");
var color = d3.scaleLinear()
.domain([0,0.5,1])
.range(["red","orange","yellow"])
update(random());
function update(data) {
var rows = svg.selectAll("g")
.data(data);
// enter new rows where needed
rows = rows.enter()
.append("g")
.merge(rows) // merge with existing rows
.attr("transform", function(d,i) {
return "translate("+[0,i*22]+")";
})
// Select all rects:
var rects = rows.selectAll("rect")
.data(function(d) { return d; })
// Enter new rects:
rects = rects.enter().append("rect")
.merge(rects);
// Update rects:
rects.attr("fill", function(d) {
return color(d);
})
.attr("x", function(d,i) { return i*22; })
.attr("width", 20)
.attr("height", 20);
}
function random() {
return d3.range(5).map(function() {
return d3.range(5).map(function() {
return Math.random();
})
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button>Update</button>
<div></div>
Update
Your snippet almost incorporates the changes, but you still need to break up the second selection, that of the rectangles, so that you enter new rectangles and then update all of them:
var csv = "'3, 6, 0, 8'\n'1, 9, 0, 4'\n'3, 0, 1, 8'\n'4, 0, 2, 7";
csv = csv.replace(/'/g,'');
var button = d3.select('button')
.on('click', function() {
createHeatmap(update());
});
var w = 120;
var h = 120;
var intensity = 10;
var margin = {
top: 25,
right: 25,
bottom: 25,
left: 25
};
var svg = d3.select('#heatmap')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
createHeatmap(csv);
function createHeatmap(csv) {
console.log(csv);
var data = d3.csv.parseRows(csv).map(function(row) {
return row.map(function(d) {
return +d;
});
});
var min = 0;
var max = d3.max(data, function(d, i) {
return i + 1;
});
var rectSize = 30;
// define a colorScale with domain and color range
var colorScale = d3.scale.linear()
.domain([0,0.5,1])
.range(['red', 'green', 'blue']);
// append group of svg elements bound to data
var rows = svg.selectAll('g')
.data(data);
// enter new rows where needed
rows.enter().append('g');
// select all rects
var rects = rows.selectAll('rect')
.data(function(d, i, j) {
return d;
});
// enter new rects:
rects.enter().append('rect');
// CHANGES HERE:
// Broke chain so that update actions aren't carried out on the enter selection:
rects.attr('x', function(d, i, j) {
return (i * rectSize);
})
.attr('y', function(d, i, j) {
return (j * rectSize);
})
.attr('width', w / max)
.attr('height', h / max)
.style('fill', function(d, i, j) {
return colorScale(d * intensity);
});
}
function update() {
var data = "'0, 1, 9, 5'\n'4, 0, 7, 2'\n'6, 3, 0, 8'\n'5, 3, 7, 0";
data = data.replace(/'/g,'');
return data;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button>Update</button>
<div id="heatmap"></div>

Error: <path> attribute d: Expected number, "….35254915624212LNaN,NaNZ"

I am trying to build pie-chart using D3js. I am getting an error while running the code and pie chart is not coming properly.
This is the code:
var svg = d3.select('#pie_chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var total=0;
for(var a=0;a<data.length;a++){
total=total+parseInt(data[a].count);
}
var pie_data=[];
for( var a=0;a<data.length;a++){
pie_data[a]=(data[a].count/total)*100;
}
var arc = d3.arc().outerRadius(150);
var pie = d3.pie()
.value(function(d,i) {
return pie_data[i];
}).sort(null);
var path = svg.selectAll('path')
.data(pie(data))
.enter().append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return data[i].color;
});
Set innerRadius property of d3 arc.
var arc = d3.arc().outerRadius(150).innerRadius(0);
var width = 500,
height = 400;
var data = [{
count: 10,
color: 'black'
}, {
count: 20,
color: 'green'
}, {
count: 30,
color: 'blue'
}];
var svg = d3.select('#pie_chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var total = 0;
data.forEach(function(d) {
total += d.count;
});
var pie_data = [];
for (var a = 0; a < data.length; a++) {
pie_data[a] = (data[a].count / total) * 100;
}
var arc = d3.arc().outerRadius(150).innerRadius(0);
var pie = d3.pie()
.sort(null)
.value(function(d) {
return d;
});
var path = svg.selectAll('path')
.data(pie(pie_data))
.enter().append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return data[i].color;
});
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="pie_chart"></div>

d3.js not updating graph with new data

I am trying to update d3.js based graph with some random data after each 3 seconds, but it only renders the data first time. Here is the code
function drawGraph(data) {
var areaWidth = 200;
var areaHeight = 200;
var gdpData = data;
var leftMargin = 10;
var rightMargin = 10;
var maxGDP = Math.max.apply(Math, gdpData);
var myScale = d3.scaleLinear()
.domain([0, maxGDP])
.range([0, areaWidth - leftMargin - rightMargin]);
var spacing = areaHeight/(gdpData.length+3);
var fda = d3.select('#fun-drawing-area');
var recs = fda.selectAll('rect')
.data(gdpData);
recs.exit().remove();
recs.enter()
.append('rect')
.attr('y', function(d, i) {
return spacing * (i+1);
})
.attr('x', '10')
.attr('width', function(d, i) {
return myScale(d);
})
.attr('height', '20')
.style({
fill: 'steelblue',
stroke: 'black',
'stroke-width': 1
});
}
setInterval(function() {
var arr = [];
for (var i=0, t=5; i<t; i++) {
arr.push(Math.round(Math.random() * t))
}
console.log(arr);
drawGraph(arr);
}, 3000);
here is the plunkr https://plnkr.co/edit/OfFABbrZ6Teet6atLQD0?p=preview
In the new D3 4.x, you have to merge the selections:
recs.enter()
.append('rect')
.merge(recs)
.attr('y', function(d, i) {
return spacing * (i+1);
})
.attr('x', '10')
.attr('width', function(d, i) {
return myScale(d);
})
.attr('height', '20')
.style({
fill: 'steelblue',
stroke: 'black',
'stroke-width': 1
});
Here's your plunker: https://plnkr.co/edit/kaFCLcUKNqSiJkpOCHvS?p=preview
PS: I changed setTimeout to setInterval.

Resources