how to draw a grid incrementally in d3? - d3.js

Below is an HTML file that will draw a 10x10 grid of squares alternating light grey and dark grey. It fills a 2d array called the_grid with 0 or 1; then fills a 1d array called nodes with x, y, and color; then draws nodes with d3. They all appear. They look like this:
How do I instead have nodes drawn (i.e. appear) one at a time, in the order given by the nodes array (so I could draw different patterns, say vertical wipe, horizontal wipe, whatever)?
I tried fiddling with the transition function without success. It just draws the whole grid, and slides it into place. The squares don't visibly appear one by one.
The code:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script>
function draw_function() {
var vis = d3.select("#graph")
.append("svg")
.attr("width", 200).attr("height", 200);
// fill the_grid
var shape=[10,10];
var the_grid=[];
for (var idx = 0; idx < shape[0]; idx++) {
var row = [];
for (var jdx = 0; jdx < shape[1]; jdx++) {
var val = (idx+jdx)/2;
row.push(Math.floor(val)==val ? 1 : 0);
}
the_grid.push(row);
}
// fill nodes
var rectwidth = 10;
var nodes = [];
for (var idx = 0; idx < the_grid.length; idx++) {
for (var jdx = 0; jdx < the_grid[0].length; jdx++) {
var node = {x: idx * (rectwidth+1),
y: jdx * (rectwidth+1),
color: the_grid[idx][jdx] == 1 ? 'black' : 'lightgrey'};
nodes.push(node);
}
}
// draw nodes
vis.selectAll("rect.nodes")
.data(nodes)
.enter()
.append("svg:rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", rectwidth)
.attr("width", rectwidth)
.attr("fill", function(d) { return d.color; })
}
// function has to execute after dom is loaded
window.onload = draw_function
</script>
<style>rect { color: black; }</style>
</head>
<body><div id="graph"/></body>
</html>

To stagger the transitions of multiple elements entered at the same time from the same data array you can use transition.delay(), you can specify a constant (which will start all transitions simutaneously) or you can specify a function to base the delay on the datum of each element or its index:
selection.transition()
.delay(function(d,i) { return i * 100; })
.attr(...
Above and in the snippet below I've used the index:
function draw_function() {
var vis = d3.select("#graph")
.append("svg")
.attr("width", 200).attr("height", 200);
// fill the_grid
var shape=[10,10];
var the_grid=[];
for (var idx = 0; idx < shape[0]; idx++) {
var row = [];
for (var jdx = 0; jdx < shape[1]; jdx++) {
var val = (idx+jdx)/2;
row.push(Math.floor(val)==val ? 1 : 0);
}
the_grid.push(row);
}
// fill nodes
var rectwidth = 10;
var nodes = [];
for (var idx = 0; idx < the_grid.length; idx++) {
for (var jdx = 0; jdx < the_grid[0].length; jdx++) {
var node = {x: idx * (rectwidth+1),
y: jdx * (rectwidth+1),
color: the_grid[idx][jdx] == 1 ? 'black' : 'lightgrey'};
nodes.push(node);
}
}
// draw nodes
vis.selectAll("rect.nodes")
.data(nodes)
.enter()
.append("svg:rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", rectwidth)
.attr("width", rectwidth)
.attr("fill","white")
.transition()
.duration(1000)
.delay(function(d,i) { return i * 100; })
.attr("fill", function(d) { return d.color; })
}
// function has to execute after dom is loaded
window.onload = draw_function
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph"/>
And below I've used the datum to create a random transition order:
function draw_function() {
var vis = d3.select("#graph")
.append("svg")
.attr("width", 200).attr("height", 200);
// fill the_grid
var shape=[10,10];
var the_grid=[];
for (var idx = 0; idx < shape[0]; idx++) {
var row = [];
for (var jdx = 0; jdx < shape[1]; jdx++) {
var val = (idx+jdx)/2;
row.push(Math.floor(val)==val ? 1 : 0);
}
the_grid.push(row);
}
// fill nodes
var rectwidth = 10;
var nodes = [];
for (var idx = 0; idx < the_grid.length; idx++) {
for (var jdx = 0; jdx < the_grid[0].length; jdx++) {
var node = {
delay: Math.random()*2000,
x: idx * (rectwidth+1),
y: jdx * (rectwidth+1),
color: the_grid[idx][jdx] == 1 ? 'black' : 'lightgrey'};
nodes.push(node);
}
}
// draw nodes
vis.selectAll("rect.nodes")
.data(nodes)
.enter()
.append("svg:rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", rectwidth)
.attr("width", rectwidth)
.attr("fill","white")
.transition()
.duration(1000)
.delay(function(d,i) { return d.delay; })
.attr("fill", function(d) { return d.color; })
}
// function has to execute after dom is loaded
window.onload = draw_function
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="graph"/>

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);

how to change data via key not index in a dropdown in d3

I have the following prototype code. it is a dropdown that contains key values used to change a pie chart and it works. But in my actual code I need to select by the key not the index of the data. seems like it should be easy, I am just missing it. So to restate: the code uses the index of the dropdown and I need it to use the actual key [the value of the dropdown] instead.
The code in question is where the change function is used.
// fake data
// data.tsv
//region fruit count
//East Apples 53245
//West Apples 28479
//South Apples 19697
//North Apples 24037
//Central Apples 40245
//East Oranges 200
//South Oranges 200
//Central Oranges 200
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.value(function(d) { return d.count; })
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 20);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path");
d3.tsv("data.tsv", type, function(error, data) {
var regionsByFruit = d3.nest()
.key(function(d) { return d.fruit; })
.entries(data)
.reverse();
var select = d3.select("form").append("div")
.append("select")
.on("change", function() {
change(regionsByFruit[d3.event.target.selectedIndex]); })
.selectAll("option")
.data(regionsByFruit)
.enter()
.append("option");
select.selectAll("option")
//.attr("type", "radio")
.attr("name", "fruit")
.attr("value", function(d) { return d.key; })
.filter(function(d, i) { return !i; });
select.append("span")
.text(function(d) { return d.key; });
function change(region) {
var data0 = path.data(),
data1 = pie(region.values);
path = path.data(data1, key);
path.enter().append("path")
.each(function(d, i) { this._current = findNeighborArc(i, data0, data1, key) || d; })
.attr("fill", function(d) { return color(d.data.region); })
.append("title")
.text(function(d) { return d.data.region; });
path.exit()
.datum(function(d, i) { return findNeighborArc(i, data1, data0, key) || d; })
.transition()
.duration(750)
.attrTween("d", arcTween)
.remove();
path.transition()
.duration(750)
.attrTween("d", arcTween);
}
});
function key(d) {
return d.data.region;
}
function type(d) {
d.count = +d.count;
return d;
}
function findNeighborArc(i, data0, data1, key) {
var d;
return (d = findPreceding(i, data0, data1, key)) ? {startAngle: d.endAngle, endAngle: d.endAngle}
: (d = findFollowing(i, data0, data1, key)) ? {startAngle: d.startAngle, endAngle: d.startAngle}
: null;
}
// Find the element in data0 that joins the highest preceding element in data1.
function findPreceding(i, data0, data1, key) {
var m = data0.length;
while (--i >= 0) {
var k = key(data1[i]);
for (var j = 0; j < m; ++j) {
if (key(data0[j]) === k) return data0[j];
}
}
}
// Find the element in data0 that joins the lowest following element in data1.
function findFollowing(i, data0, data1, key) {
var n = data1.length, m = data0.length;
while (++i < n) {
var k = key(data1[i]);
for (var j = 0; j < m; ++j) {
if (key(data0[j]) === k) return data0[j];
}
}
}
function arcTween(d) {
var i = d3.interpolate(this._current, d);
this._current = i(0);
return function(t) { return arc(i(t)); };
}
ody {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 10px sans-serif;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
input {
margin: 0 7px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<form></form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I could not run the code you provided but I managed to tweak it in order to achieve what I think you wanted. The approach I took was the following:
var select = d3.select("form").append("div")
.append("select")
.on('change', updatePie) // run this fn when select value changes
function updatePie(d, i) {
var selectedFruit = this.value; // get the value of the selected option which is your key
var newData = regionsByFruit.filter(function(value) { // filter data so you only get the data with the given key
return value.key === selectedFruit;
});
// Update data in elements
var updatee = svg.selectAll(".arc").data(pie(newData[0].values));
// Do exit, update and addition of nodes logic
}
Plunker: http://plnkr.co/edit/EruBcbvZ6QhBVZXKOFSf?p=preview

D3 force layout graph with nodes positioned in a grid

I have built a force-directed graph, however, in my case, I have also a grid with 80px*80px boxes. I'd like that each node in graph was positioned not only according to existing gravity and forces, but also in the middle of the closest grid box (without being fixed).
Is it possible to do this in d3js?
Moritz Stefaner came up with a way to do this
code: https://github.com/moritzstefaner/gridexperiments/
demo: http://moritzstefaner.github.io/gridexperiments/
EDIT:
as mentioned by #altocumulus, this didn't have a copy of the code. Normally I only copy the code from individual's sites as they're much more likely to disappear than something on github. Or I'll copy it when it is short (less than 50 loc?). Anyway, since the meat of the code can probably be pulled out, I've copied mortiz's index.html file below. the other referenced js files can be found elsewhere very easily. (just note that you should probably pull the versions of each library as of Dec 9, 2011)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<meta charset="utf-8">
<title>Forces and grids</title>
<script type="text/javascript" src="d3.min.js"></script>
<script type="text/javascript" src="d3.layout.min.js"></script>
<script type="text/javascript" src="d3.geom.min.js"></script>
<script type="text/javascript" src="underscore-min.js"></script>
<script src="jquery-1.7.2.min.js" charset="utf-8"></script>
<style type="text/css" media="screen">
.menu { position:absolute; top :20px; right:20px; }
</style>
</head>
<body>
<script type="text/javascript" charset="utf-8">
var w = 700, h = 700;
var vis = d3.select("body").append("svg:svg").attr("width", w).attr("height", h);
var background = vis.append("g");
var nodes = [];
var links = [];
var USE_GRID = true;
var GRID_SIZE = 60;
var GRID_TYPE = "HEXA";
// set up event handlers
$(document).ready(function(){
$("#USE_GRID").click(
function(){
USE_GRID = $(this).is(":checked");
$(this).blur();
force.start();
}
);
//$("#CELL_SIZE").rangeinput();
$("#CELL_SIZE").bind("change",
function(){
console.log($(this).attr("value"));
GRID_SIZE = $(this).attr("value");
grid.init();
force.start();
}
);
$("[name=GRID_TYPE]").click(
function(){
GRID_TYPE = $(this).attr("value");
grid.init();
force.start();
}
);
});
for(var i = 0; i < 30; i++) {
var node = {
label : "node " + i
};
nodes.push(node);
};
for(var i = 0; i < nodes.length; i++) {
for(var j = 0; j < i; j++) {
if(Math.random() > .99-Math.sqrt(i)*.02)
links.push({
source : i,
target : j,
weight :1
});
}
};
var force = d3.layout.force().size([w, h]).nodes(nodes).links(links).gravity(1).linkDistance(function(d){return (1-d.weight)*100}).charge(-3000).linkStrength(function(x) {
return x.weight * 5
});
force.start();
var link = vis.selectAll("line.link").data(links).enter().append("svg:line").attr("class", "link").style("stroke-width", 1.5).style("stroke", "#555").style("opacity", function(d){return d.weight*.7});
var node = vis.selectAll("g.node").data(force.nodes()).enter().append("svg:g").attr("class", "node");
node.append("svg:circle").attr("r", 6).style("fill", "#555").style("stroke", "#FFF").style("stroke-width", "4px");
node.call(force.drag);
var updateLink = function() {
this.attr("x1", function(d) {
return d.source.screenX;
}).attr("y1", function(d) {
return d.source.screenY;
}).attr("x2", function(d) {
return d.target.screenX;
}).attr("y2", function(d) {
return d.target.screenY;
});
}
var updateNode = function() {
this.attr("transform", function(d) {
if(USE_GRID) {
var gridpoint = grid.occupyNearest(d);
if(gridpoint) {
d.screenX = d.screenX || gridpoint.x;
d.screenY = d.screenY || gridpoint.y;
d.screenX += (gridpoint.x - d.screenX) * .2;
d.screenY += (gridpoint.y - d.screenY) * .2;
d.x += (gridpoint.x - d.x) * .05;
d.y += (gridpoint.y - d.y) * .05;
}
} else {
d.screenX = d.x;
d.screenY = d.y;
}
return "translate(" + d.screenX + "," + d.screenY + ")";
});
};
var grid = function(width, height) {
return {
cells : [],
init : function() {
this.cells = [];
for(var i = 0; i < width / GRID_SIZE; i++) {
for(var j = 0; j < height / GRID_SIZE; j++) {
// HACK: ^should be a better way to determine number of rows and cols
var cell;
switch (GRID_TYPE) {
case "PLAIN":
cell = {
x : i * GRID_SIZE,
y : j * GRID_SIZE
};
break;
case "SHIFT_ODD_ROWS":
cell = {
x : i * GRID_SIZE,
y : 1.5 * (j * GRID_SIZE + (i % 2) * GRID_SIZE * .5)
};
break;
case "HEXA":
cell = {
x : i * GRID_SIZE + (j % 2) * GRID_SIZE * .5,
y : j * GRID_SIZE * .85
};
break;
}
this.cells.push(cell);
};
};
},
sqdist : function(a, b) {
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
},
occupyNearest : function(p) {
var minDist = 1000000;
var d;
var candidate = null;
for(var i = 0; i < this.cells.length; i++) {
if(!this.cells[i].occupied && ( d = this.sqdist(p, this.cells[i])) < minDist) {
minDist = d;
candidate = this.cells[i];
}
}
if(candidate)
candidate.occupied = true;
return candidate;
}
}
}(w, h);
force.on("tick", function() {
vis.select("g.gridcanvas").remove();
if(USE_GRID) {
grid.init();
var gridCanvas = vis.append("svg:g").attr("class", "gridcanvas");
_.each(grid.cells, function(c) {
gridCanvas.append("svg:circle").attr("cx", c.x).attr("cy", c.y).attr("r", 2).style("fill", "#555").style("opacity", .3);
});
}
node.call(updateNode);
link.call(updateLink);
});
</script>
<div class="menu">
<div>
<input type="checkbox" id="USE_GRID" checked>use grid</input>
</div>
<div>
<input type="range" min="30" step="10" max="150" id="CELL_SIZE" value="60"></input>
</div>
<div>
<input type="radio" name="GRID_TYPE" value="PLAIN">plain</input>
<input type="radio" name="GRID_TYPE" value="SHIFT_ODD_ROWS">Shift odd rows</input>
<input type="radio" name="GRID_TYPE" value="HEXA" checked>Hexa</input>
</div>
</div>
</body>
You have to apply your custom forces in
force.on("tick", function() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
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; });
});
so there is no built-in way to do such a thing...
So in your case you have to find the close grid box centers and count the x and y values using the distance between the nodes and the box centers and some gravity equation.
In you case
node
.attr("cx", function(d) {
d.x += f(d).x;
return d.x;
})
.attr("cy", function(d) {
d.y += f(d).y;
return d.y;
});
where f(d) is the vector of your gravity force depends on the distance between the box centers and the actual node d. For example
var blackHole = function (d) {
var gc = {
x: 100,
y: 100
};
var k = 0.1;
var dx = gc.x - d.px;
var dy = gc.y - d.py;
return {
x: k * dx,
y: k * dy
};
};
It is pretty hard to find out an f(d) which really works by multiple gravity centers, so I suggest you to read about such force algorithms. I tried out some funny examples, but none of them works the way you want. ;-)
Now at least:
var grid = function (d) {
var fx = d.px % 100;
if (fx < 0)
fx += 100;
if (fx > 50)
fx -= 100;
var fy = d.py % 100;
if (fy < 0)
fy += 100;
if (fy > 50)
fy -= 100;
var k = -1;
return {
x: k * fx,
y: k * fy
};
};
This is a 100px dense grid with very simple forces... But I guess the result is not what you expected, nodes can overlap, because by force layout only nodes with common links repel each other, at least that is my experience (edit: that's because the negative charge)... I think is could be much easier to build a custom force layout using d3 quad...

how to draw rectangles for each column in a row in d3.js

I would like to draw 4 different rectangles for each attribute in json. So i need to draw 4 rectangles for each id. in the below example i would have 16 rectangles for id 1-4.
The width, height of the rectangle are hard coded for now. Also the x and y axis.
Currently it takes each row as a rectangle.?
I have json data like this:
[
[{ "checkins":10},{"builds":11},{"oss":1},{"appsec":10},{"id":1}],
[{ "checkins":1},{"builds":1},{"oss":21},{"appsec":10},{"id":2}],
[{ "checkins":11},{"builds":3},{"oss":11},{"appsec":10},{"id":3}],
[{ "checkins":21},{"builds":20},{"oss":3},{"appsec":30},{"id":4}]
]
I have written the code:
var x_axis = 1;
var y_axis = 45;
var xvar;
var yvar;
var x;
d3.json("GraphData.json", function(data)
{
var rectangle= svggraph.selectAll("rect").data(data).enter().append("rect");
var RectangleAttrb = rectangle
.attr("id", function (d,i) { return "id" + i ; })
.attr("x", function (d,i)
{
xvar=i+1;
if(i==0) return x_axis=0;
if ((i > 0) && (xvar%4==1))
{
x_axis = 0;
}
else
{
x_axis=x_axis+22;
}
//y=i+1;
return x_axis;
})
.attr("y", function (d,i)
{
Yvar=i+1;
if ((i > 0) && (Yvar%4==1))
{
y_axis = y_axis+ 30;
}
return y_axis;
})
.attr("width",function(d) { return 20; } )
.attr("height",function(d) { return 15; })
.style("stroke", function (d) { return "black";})
.style("fill", function(d) { console.log(d);return "white"; });
});
It's still creating only 4 rectangles
I found a workaround solution. It was creating an array and looping through the same to create the rectangles and assigning the x and y axis along with the height and width.
<html>
<script>
var x_axis = 1;
var y_axis = 45;
var x;
var rectangle,RectangleAttrb;
var rect;
var rectdata;
createtinderboxes();
function createtinderboxes()
{
//console.log(" the function is called ");
d3.json("GraphData.json", function(data)
{
rectdata = data;
//console.log(rectdata.length);
for(x=0;x<rectdata.length;x++)
{
console.log(rectdata[x]);
for (index=0;index<5; index++)
{
svggraph.append("rect")
.attr("x", assignxaxis(rectdata, index))
.attr("y", assignyaxis(rectdata,yvar))
.attr("width", 20)
.attr("height", 25)
.style("fill","white")
.style("stroke","black");
}
yvar = yvar+26;
}
});
}
function assignxaxis(rectdata,x)
{
console.log(rectdata[x]);
if (x==4) return;
if(x==0)
{
return x_axis=0
}
else
{
x_axis=x_axis+22;
}
return x_axis;
}
function assignyaxis(rectdata,y)
{
return y;
}
</script>
</html>

d3.js building a grid of rectangles

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.

Resources