d3.js building a grid of rectangles - d3.js

I'm trying to build a grid of rectangles in d3.js.
The grid is 7 rows (days in a week), and 24 columns (hours in a day).
The following code only draws (row:column):
day0:hour0,
day1:hour1,
day2:hour2,
day3:hour3,
day4:hour4,
day5:hour5,
day6:hour6,
day7:hour7
Question: Any ideas why the following code wouldn't work?
/**
* calendarWeekHour Setup a week-hour grid:
* 7 Rows (days), 24 Columns (hours)
* #param id div id tag starting with #
* #param width width of the grid in pixels
* #param height height of the grid in pixels
* #param square true/false if you want the height to
* match the (calculated first) width
*/
function calendarWeekHour(id, width, height, square)
{
var calData = randomData(width, height, square);
var grid = d3.select(id).append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "chart");
grid.selectAll("rect")
.data(calData)
.enter().append("svg:rect")
.attr("x", function(d, i) { return d[i].x; })
.attr("y", function(d, i) { return d[i].y; })
.attr("width", function(d, i) { return d[i].width; })
.attr("height", function(d, i) { return d[i].height; })
.on('mouseover', function() {
d3.select(this)
.style('fill', '#0F0');
})
.on('mouseout', function() {
d3.select(this)
.style('fill', '#FFF');
})
.on('click', function() {
console.log(d3.select(this));
})
.style("fill", '#FFF')
.style("stroke", '#555');
}
////////////////////////////////////////////////////////////////////////
/**
* randomData() returns an array: [
[{id:value, ...}],
[{id:value, ...}],
[...],...,
];
~ [
[hour1, hour2, hour3, ...],
[hour1, hour2, hour3, ...]
]
*/
function randomData(gridWidth, gridHeight, square)
{
var data = new Array();
var gridItemWidth = gridWidth / 24;
var gridItemHeight = (square) ? gridItemWidth : gridHeight / 7;
var startX = gridItemWidth / 2;
var startY = gridItemHeight / 2;
var stepX = gridItemWidth;
var stepY = gridItemHeight;
var xpos = startX;
var ypos = startY;
var newValue = 0;
var count = 0;
for (var index_a = 0; index_a < 7; index_a++)
{
data.push(new Array());
for (var index_b = 0; index_b < 24; index_b++)
{
newValue = Math.round(Math.random() * (100 - 1) + 1);
data[index_a].push({
time: index_b,
value: newValue,
width: gridItemWidth,
height: gridItemHeight,
x: xpos,
y: ypos,
count: count
});
xpos += stepX;
count += 1;
}
xpos = startX;
ypos += stepY;
}
return data;
}

The problem is that your databinding is only iterating through the first dimension of the array (0,1,2) and you are trying to use it to iterate through the second dimension (0,0)(0,1)(0,2) which is leading to the (0,0)(1,1)(2,2) behavior.
To get the results you want, just use a subselect. Start with your row definition:
var row = chart.selectAll(".row")
.data(data) // each row will be bound to the array at data[i]
.enter().append("div")
.attr("class", "row")
…
Then use the identity function (as the data property) to dereference
the cells for each row:
var cell = row.selectAll(".cell")
.data(function(d) { return d; }) // then iterate through data[i] for each cell
.enter().append("div")
.attr("class", "cell")
…
You can see a working example with full source at http://bl.ocks.org/2605010.

Related

how to put dynamic data text inside rectangle in d3

Update method : Thanks in advance. I am creating rectangles based on the api response data. The rectangles will will be removed and re-created when the new data come back from api. I want to achieve same thing for adding text inside the rectangles, means as soon as I receive the fresh data text should be overrided or re-created based on the data.
/* update selection*/
var rectangles = vis.ganttSvgRef.selectAll("rect").data(chartData);
/*exit selection*/
rectangles.exit().remove();
/*enter selection*/
var innerRects = rectangles.enter().append("rect").merge(rectangles)
.attr("x", function (d) {
return vis.timeScale(parseTime(d.arrivalTime_data)) + sidePadding;
})
.attr("y", function (d, i) {
for (var j = 0; j < slotNumber.length; j++) {
if (d.slot == slotNumber[j]) {
return vis.yScale(d.slot);
}
}
})
.attr("width", function (d) {
return (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data)));
})
.attr("height", barHeight)
.attr("stroke", "none")
.attr("fill", function (d) {
for (var i = 0; i < vesselsNames.length; i++) {
return serviceColorSelector[d.serviceName_data]
}
})
how to add the text in the rectangles in middle, which should also get update based on the data receive. Thanks
Updated code as suggested by #Michael Rovinsky : This code works perfectly fine for appending the text inside the rect, but on few rect the text is overflowing outside the rect area. I don't want to show the text if it overflow from rect area or how can i hide the text if it overflow from rect area ?
var rectangles = vis.ganttSvgRef.selectAll("rect")
.data(chartData);
rectangles.exit().remove();
var innerRects = rectangles.enter().append("g");
let rectinst = innerRects.append("rect").merge(rectangles)
.attr("x", function (d) {
return vis.timeScale(parseTime(d.arrivalTime_data)) +
sidePadding;
})
.attr("y", function (d, i) {
for (var j = 0; j < slotNumber.length; j++) {
if (d.slot == slotNumber[j]) {
return vis.yScale(d.slot);
}
}
})
.attr("width", function (d) {
return (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data)));
})
.attr("height", barHeight)
.attr("stroke", "none")
.attr("fill", function (d) {
for (var i = 0; i < vesselsNames.length; i++) {
return serviceColorSelector[d.serviceName_data]
}
})
let text = vis.ganttSvgRef.selectAll(".rect-text")
.data(chartData);
text.exit().remove();
innerRects.append("text").merge(text)
.attr("class", 'rect-text')
.text(function (d) {
let rectWidth = (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data)));
console.log("rect width : ", rectWidth)
console.log("d.vesselName_data : ",
vis.timeScale(d.vesselName_data.length))
return d.vesselName_data;
})
.attr("x", function (d) {
return (vis.timeScale(parseTime(d.departureTime_data)) -
vis.timeScale(parseTime(d.arrivalTime_data))) / 2 +
vis.timeScale(parseTime(d.arrivalTime_data)) + sidePadding;
})
.attr("y", function (d, i) {
for (var j = 0; j < slotNumber.length; j++) {
if (d.slot == slotNumber[j]) {
return vis.yScale(d.slot) + (barHeight / 2);
}
}
})
.attr("font-size", barHeight / 2)
.attr("text-anchor", "middle")
.attr("text-height", barHeight)
.attr("fill", '#fff');
Append "g" instead of "rect" on enter():
const containers = rectangles.enter().append("g");
Append "rect" and "text" under "g":
containers.append("rect").attr('width', ...).attr('height', ...)...
containers.append("text").text('My Text Here')...
You can write your own text wrapping function d3 based on the rect dimensions and given padding.
Please check following link for customised text wrapping and overflow control in d3.js-
Code- text-wrapping-in-d3
function wrap(text) {
text.each(function() {
var text = d3.select(this);
var words = text.text().split(/\s+/).reverse();
var lineHeight = 20;
var width = parseFloat(text.attr('width'));
var y = parseFloat(text.attr('y'));
var x = text.attr('x');
var anchor = text.attr('text-anchor');
var tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('text-anchor', anchor);
var lineNumber = 0;
var line = [];
var word = words.pop();
while (word) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
lineNumber += 1;
line.pop();
tspan.text(line.join(' '));
line = [word];
tspan = text.append('tspan').attr('x', x).attr('y', y + lineNumber * lineHeight).attr('anchor', anchor).text(word);
}
word = words.pop();
}
});
}
function dotme(text) {
text.each(function() {
var text = d3.select(this);
var words = text.text().split(/\s+/);
var ellipsis = text.text('').append('tspan').attr('class', 'elip').text('...');
var width = parseFloat(text.attr('width')) - ellipsis.node().getComputedTextLength();
var numWords = words.length;
var tspan = text.insert('tspan', ':first-child').text(words.join(' '));
// Try the whole line
// While it's too long, and we have words left, keep removing words
while (tspan.node().getComputedTextLength() > width && words.length) {
words.pop();
tspan.text(words.join(' '));
}
if (words.length === numWords) {
ellipsis.remove();
}
});
}
d3.selectAll('.wrapme').call(wrap);
d3.selectAll('.dotme').call(dotme);

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>

D3 Donut transition, d,i getting undefine

var arcMin = 75; // inner radius of the first arc
var arcWidth = 25; // width
var arcPad = 10; // padding between arcs
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
})
.startAngle(0 * (PI/180))
.endAngle(function(d, i) {
// console.log(d); <----getting undefine under attrTween Call
return 2*PI*d.value/100;
});
var path = g.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return d.data.color;
})
.transition()
.delay(function(d, i) {
return i * 800;
});
// .attrTween('d', function(d) {
// // This part make my chart disapear
// var i = d3.interpolate(d.startAngle, d.endAngle);
// return function(t) {
// d.endAngle = i(t);
// return arc(d);
// }
// // This part make my chart disapear
// });
arc(d) always return "M0,0Z"..
I found that the reason is when calling arc under arcTween, all d,i return undefine. How can i solve this.
Codes here: https://jsfiddle.net/m8oupfne/3/
Final product:
Couple things:
At first glance your attrTween function doesn't work because your arc function is dependent on both d,i and you only pass d to it.
But, fixing that doesn't make your chart transition nicely? Why? Because your arc function doesn't seem to make any sense. You use pie to calculate angles and then overwrite them in your arc function. And each call to the arc function calculates endAngle the same since it's based on d.value.
So, if you want a custom angle calculation, don't call pie at all, but pre-calculate your endAngle and don't do it in your arc function.
arc becomes:
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
});
Pre-calculate the data:
dataset.forEach(function(d,i){
d.endAngle = 2*PI*d.value/100;
d.startAngle = 0;
});
arcTween becomes:
.attrTween('d', function(d,i) {
var inter = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = inter(t);
return arc(d,i);
}
});
Running code:
(function(d3) {
'use strict';
var dataset = [
{ label: 'a', value: 88, color : '#898989'},
{ label: 'b', value: 56 , color : '#898989'},
{ label: 'c', value: 20 , color : '#FDD000'},
{ label: 'd', value: 46 , color : '#898989'},
];
var PI = Math.PI;
var arcMin = 75; // inner radius of the first arc
var arcWidth = 25; // width
var arcPad = 10; // padding between arcs
var arcBgColor = "#DCDDDD";
var width = 360;
var height = 360;
var radius = Math.min(width, height) / 2;
var donutWidth = 15; // NEW
var svg = d3.select('#canvas')
.append('svg')
.attr('width', width)
.attr('height', height);
var gBg = svg.append('g').attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var g = svg.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
});
var arcBg = d3.arc()
.innerRadius(function(d, i) {
return arcMin + i*(arcWidth) + arcPad;
})
.outerRadius(function(d, i) {
return arcMin + (i+1)*(arcWidth);
})
.startAngle(0 * (PI/180))
.endAngle(function(d, i) {
return 2*PI;
});
var pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
var pathBg = gBg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arcBg)
.attr('fill', arcBgColor );
dataset.forEach(function(d,i){
d.endAngle = 2*PI*d.value/100;
d.startAngle = 0;
});
var path = g.selectAll('path')
.data(dataset)
.enter()
.append('path')
.attr('fill', function(d, i) {
return d.color;
})
.transition()
.duration(800)
.delay(function(d, i) {
return i * 800;
})
.attrTween('d', function(d,i) {
var inter = d3.interpolate(d.startAngle, d.endAngle);
return function(t) {
d.endAngle = inter(t);
return arc(d,i);
}
});
})(window.d3);
<script src="https://cdn.jsdelivr.net/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/d3js/4.6.0/d3.min.js"></script>
<div id="canvas"></div>

Making different size of `radius` to create `arc` not working

I am creating pie chart using d3.js. I would like to create 3 pies with single svg element with animation.
This is working fine for me. But do creating different I am reducing the radius each time using a loop. But the radius not getting changed.
How to solve this?
my code (sample) :
var array1 = [
0,200
]
window.onload = function () {
var width = 660,
height = 200,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = null;
var pie = d3.layout.pie()
.value(function(d) {
return d; })
.sort(null);
function tweenPie(finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
for( var i = 0; i < 3; i++) {
arc = d3.svg.arc()
.innerRadius(radius - (5*i)) //each time size differs
.outerRadius(radius - (6)*i); //each time size differs
svg1.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(array1).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(5000)
.attrTween('d', tweenPie)
}
}
Live Demo
There is a single arc variable that is being used in the tweenPie method and in the for loop. Each time through the for loop, the arc variable is set to a new value. The tweenPie method is called for each pie chart after the for loop exits. As a result, all the pie charts are using the same tweenPie method which is using the arc created in the last for loop.
For each pie chart, you need to create a separate tweenPie method with its own arc. For example...
var array1 = [ 0, 200 ]
window.onload = function () {
var width = 660,
height = 200,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = null;
var pie = d3.layout.pie()
.value(function(d) {
return d; })
.sort(null);
function getTweenPie(arc) {
return function (finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
}
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
for( var i = 0; i < 3; i++) {
arc = d3.svg.arc()
.innerRadius(radius - (5*i)) //each time size differs
.outerRadius(radius - (6)*i); //each time size differs
svg1.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(array1).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(5000)
.attrTween('d', getTweenPie(arc))
}
}

In D3js, how do I cumulative add on every nth bound data value?

I have this code which outputs a straight line composed of a bunch of line segments:
var width = 400;
var height = 100;
var data = [1,1,1,1,1,1,1,1,1,1];
var lineSegments = data.length + 1;
d3.select("body").append("svg")
.attr({
width: width,
height: height
})
.selectAll("line")
.data(data)
.enter()
.append("line")
.attr({
x1: function (d, i) { return i * (width / lineSegments); },
x2: function (d, i) { return (i + 1) * (width / lineSegments); },
y1: function (d, i) { return height / 2; },
y2: function (d, i) { return height / 2; },
stroke: "black",
"stroke-width": 2
});
I want every 3d segment to be offset in the y direction by, say 10px, and I need the offset to be cumlative, i.e. the 3d element should have offset 10px, the 6th element should be offset 20px, etc.
This should result in a line like this:
How should I modify the code to get it to work? Is there a special d3 way of doing this?
You can use d3's selection.each() to target every 3d segment. This method has some useful variables for performing various functions.
line.each(function(d, i) {
console.log(i); // prints the current index
console.log(d); // prints the data at the current index
console.log(this); // prints the current DOM element
}
In your specific case, the i variable is useful to you. You can use this inside of an if statement to change the attributes of every 3rd element. Your revised code might be...
var width = 400;
var height = 100;
var data = [1,1,1,1,1,1,1,1,1,1];
var lineSegments = data.length + 1;
var offset = 5;
d3.select("body").append("svg")
.attr({
width: width,
height: height
})
.selectAll("line")
.data(data)
.enter()
.append("line")
.attr({
x1: function (d, i) { return i * (width / lineSegments); },
x2: function (d, i) { return (i + 1) * (width / lineSegments); },
y1: function (d, i) { return height / 2; },
y2: function (d, i) { return height / 2; },
stroke: "black",
"stroke-width": 2
})
.each(function(d,i) {
if (i !== 0 && i % 3 === 0) {
d3.select(this).attr('transform', 'translate(0,' + offset + ')');
offset += 5;
}
})
Here is a working codepen.

Resources