A clone of JezzBall.
The goal is to fill at least 75% of the room. Click on any corner button or use mouse wheel to change the orientation of the wall builder. For more detailed instructions, see JezzBall Walkthrough/FAQ page on IGN.
Enjoy ! :)
/.project | |
/.settings |
A clone of JezzBall.
The goal is to fill at least 75% of the room. Click on any corner button or use mouse wheel to change the orientation of the wall builder. For more detailed instructions, see JezzBall Walkthrough/FAQ page on IGN.
Enjoy ! :)
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>D3 JezzBall</title> | |
<link rel="icon" href="/favicon.png"> | |
<style> | |
@import url("http://bl.ocks.org/style.css?20120730"); | |
</style> | |
<header> | |
<a href="/ericcitaire">ericcitaire</a>’s block <a href="https://gist.github.com/ericcitaire/5408146">#5408146</a> | |
</header> | |
<h1>D3 JezzBall</h1> | |
<p><aside style="margin-top:-43px;">April 29, 2013</aside> | |
<iframe src="index.html" marginwidth="0" marginheight="0" scrolling="no"></iframe> | |
<p><aside><a style="position:relative;top:6px;" href="index.html" target="_blank">Open in a new window.</a></aside> | |
<footer> | |
<aside>April 29, 2013</aside> | |
<a href="/ericcitaire">ericcitaire</a>’s block <a href="https://gist.github.com/ericcitaire/5408146">#5408146</a> | |
</footer> |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>D3 JezzBall</title> | |
<style> | |
body { | |
padding: 0px; | |
margin: 0px; | |
} | |
rect { | |
fill: none; | |
pointer-events: all; | |
} | |
.smallTextStroke { | |
font-family: sans-serif; | |
font-size: 12px; | |
fill: none; | |
stroke: black; | |
stroke-width: 1px; | |
} | |
.smallText { | |
font-family: sans-serif; | |
font-size: 12px; | |
fill: white; | |
} | |
.bigTextStroke { | |
font-family: sans-serif; | |
font-size: 24px; | |
font-weight: bold; | |
fill: none; | |
stroke: black; | |
stroke-width: 4px; | |
} | |
.bigText { | |
font-family: sans-serif; | |
font-size: 24px; | |
font-weight: bold; | |
fill: white; | |
} | |
.cell { | |
} | |
.cell.wall { | |
fill: #000; | |
stroke: #000; | |
} | |
.cell.air { | |
fill: #ddd; | |
stroke: #999; | |
} | |
.cell.blue { | |
stroke: blue; | |
stroke-opacity: .8; | |
} | |
.cell.red { | |
stroke: red; | |
stroke-opacity: .8; | |
} | |
.ball { | |
fill: #f00; | |
fill-opacity: 1; | |
stroke: #333; | |
stroke-opacity: .5; | |
} | |
.builder.head { | |
stroke: #333; | |
stroke-opacity: .5; | |
} | |
.builder.blue { | |
fill: blue; | |
} | |
.builder.red { | |
fill: red; | |
} | |
.builder.tail { | |
fill-opacity: .4; | |
} | |
.switch { | |
position: absolute; | |
top: 0px; | |
left: 0px; | |
padding: 0px; | |
width: 20px; | |
height: 20px; | |
} | |
#nextLevelButton, #playAgainButton { | |
position: absolute; | |
width: 100px; | |
visibility: hidden; | |
} | |
</style> | |
<body> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<form> | |
<input id="switchOrientationButton1" class="switch" type="button" value="V" /> | |
<input id="switchOrientationButton2" class="switch" type="button" value="V" /> | |
<input id="switchOrientationButton3" class="switch" type="button" value="V" /> | |
<input id="switchOrientationButton4" class="switch" type="button" value="V" /> | |
<input id="nextLevelButton" type="button" value="Next level" /> | |
<input id="playAgainButton" type="button" value="Play again" /> | |
</form> | |
<script type="text/javascript" src="jezzball.js"></script> |
d3.selection.prototype.size = function() { | |
var n = 0; | |
this.each(function() { ++n; }); | |
return n; | |
}; | |
var level = 1; | |
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { | |
if (key == "level") { | |
level = +value; | |
} | |
}); | |
var w = 960, | |
h = 500, | |
sz = 20, | |
r = sz / 2, | |
sr = r * r, | |
ssz = sz * sz, | |
v = 3, | |
n = level + 1, | |
t = 5000; | |
var rows = Math.ceil(h / sz); | |
var cols = Math.ceil(w / sz); | |
var s = false; | |
d3.selectAll(".switch") | |
.on("click", function(e) { s = !s; d3.selectAll(".switch").attr("value", s ? "H" : "V"); }); | |
d3.select("#switchOrientationButton2") | |
.style("left", (w - 20) + "px"); | |
d3.select("#switchOrientationButton3") | |
.style("top", (h - 20) + "px"); | |
d3.select("#switchOrientationButton4") | |
.style("top", (h - 20) + "px") | |
.style("left", (w - 20) + "px"); | |
d3.select("#nextLevelButton") | |
.style("top", Math.round(h / 2 + 20) + "px") | |
.style("left", Math.round(w / 2 - 50) + "px") | |
.on("click", function(e) { window.location.href = "?level=" + (level + 1); }); | |
d3.select("#playAgainButton") | |
.style("top", Math.round(h / 2 + 20) + "px") | |
.style("left", Math.round(w / 2 - 50) + "px") | |
.on("click", function(e) { window.location.href = "?level=1"; }); | |
var cells = d3.range(0, rows * cols).map(function (d) { | |
var col = d % cols; | |
var row = (d - col) / cols; | |
return { | |
r: row, | |
c: col, | |
x: col * sz + r, | |
y: row * sz + r | |
}; | |
}); | |
var balls = d3.range(0, n).map(function (d) { | |
var bx = (w - sz * 4) * Math.random() + sz * 2; | |
var by = (h - sz * 4) * Math.random() + sz * 2; | |
var ball = { | |
x: bx, | |
y: by, | |
px: bx + v * (Math.random() > .5 ? 1 : -1), | |
py: by + v * (Math.random() > .5 ? 1 : -1), | |
id: d, | |
isMoving: true | |
}; | |
return ball; | |
}); | |
var svg = d3.select("body").append("svg") | |
.attr("width", w) | |
.attr("height", h); | |
var mousewheel = function(e) { | |
s = !s; | |
d3.selectAll(".switch").attr("value", s ? "H" : "V"); | |
var p = d3.mouse(this); | |
var c1 = ballCell({x: p[0], y: p[1]}); | |
previewLocation(c1, p); | |
d3.event.preventDefault(); | |
}; | |
svg.on("mousewheel.zoom", mousewheel) | |
.on("DOMMouseScroll.zoom", mousewheel); | |
var rectx = function(d) { return d.x - r; }; | |
var recty = function(d) { return d.y - r; }; | |
var tailx = function(d) { return d.dx > 0 ? d.sx - r : rectx(d) - d.dx * sz; }; | |
var taily = function(d) { return d.dy > 0 ? d.sy - r : recty(d) - d.dy * sz; }; | |
var tailw = function(d) { return d.dx == 0 ? sz : d.sz = (d.x - d.sx) * d.dx; }; | |
var tailh = function(d) { return d.dy == 0 ? sz : d.sz = (d.y - d.sy) * d.dy; }; | |
var ballCell = function(b) { | |
var row = (b.y - b.y % sz) / sz; | |
var col = (b.x - b.x % sz) / sz; | |
return cells[row * cols + col]; | |
}; | |
var topCell = function(c) { return cells[Math.max(0, c.r - 1) * cols + c.c]; }; | |
var leftCell = function(c) { return cells[c.r * cols + Math.max(0, c.c - 1)]; }; | |
var bottomCell = function(c) { return cells[Math.min(rows - 1, c.r + 1) * cols + c.c]; }; | |
var rightCell = function(c) { return cells[c.r * cols + Math.min(cols - 1, c.c + 1)]; }; | |
var topLeftCell = function(c) { return cells[Math.max(0, c.r - 1) * cols + Math.max(0, c.c - 1)]; }; | |
var bottomLeftCell = function(c) { return cells[Math.min(rows - 1, c.r + 1) * cols + Math.max(0, c.c - 1)]; }; | |
var bottomRightCell = function(c) { return cells[Math.min(rows - 1, c.r + 1) * cols + Math.min(cols - 1, c.c + 1)]; }; | |
var topRightCell = function(c) { return cells[Math.max(0, c.r - 1) * cols + Math.min(cols - 1, c.c + 1)]; }; | |
var cell = svg.selectAll(".cell") | |
.data(cells) | |
.enter().append("rect") | |
.attr("class", function(d) { return "cell " + ((d.isWall = d.c == 0 || d.c == cols - 1 || d.r == 0 || d.r == rows - 1) ? "wall" : "air"); }) | |
.attr("x", rectx) | |
.attr("y", recty) | |
.attr("width", sz) | |
.attr("height", sz) | |
.each(function(d) { | |
d.elnt = d3.select(this); | |
}); | |
function previewLocation(c1, p) { | |
if (blue) blue.classed("blue", false); | |
if (red) red.classed("red", false); | |
var c2, d; | |
if (s) { | |
d = p[0] - c1.x; | |
c2 = d > 0 ? rightCell(c1) : leftCell(c1); | |
} else { | |
d = p[1] - c1.y; | |
c2 = d > 0 ? bottomCell(c1) : topCell(c1); | |
} | |
if (c1.isWall || c2.isWall) { | |
blue = null; | |
red = null; | |
} else if (d > 0) { | |
blue = c1.elnt; | |
red = c2.elnt; | |
} else { | |
blue = c2.elnt; | |
red = c1.elnt; | |
} | |
if (blue) blue.classed("blue", function(d) { return !d.isWall; }); | |
if (red) red.classed("red", function(d) { return !d.isWall; }); | |
} | |
var blue, red; | |
svg.selectAll(".air") | |
.on("mouseover", function(c1) { | |
var p = d3.mouse(this); | |
previewLocation(c1, p); | |
}).on("mouseout", function() { | |
if (blue) blue.classed("blue", false); | |
if (red) red.classed("red", false); | |
}).on("click", function() { | |
if (percentageCleared < 75 && lives >= 0 && timeLeft > 0) { | |
var dx = s ? 1 : 0; | |
var dy = s ? 0 : 1; | |
if (blue) blue.each(function(d) { startWall(d, "blue", -dx, -dy); }); | |
if (red) red.each(function(d) { startWall(d, "red", dx, dy); }); | |
} | |
}); | |
var areaCleared = 0; | |
var totalArea = svg.selectAll(".air").size(); | |
var percentageCleared = 0; | |
svg.append("text") | |
.attr("x", w / 2 - 50) | |
.attr("y", h - 5) | |
.attr("class", "smallText") | |
.attr("id", "areaFilledText") | |
.text("Area cleared: 0%"); | |
var lives = n; | |
svg.append("text") | |
.attr("x", 50) | |
.attr("y", 15) | |
.attr("class", "smallText") | |
.attr("id", "livesText") | |
.text("Lives: " + lives); | |
var gameStartedAt = new Date().getTime(); | |
var timeLeft = t; | |
svg.append("text") | |
.attr("x", w - 130) | |
.attr("y", 15) | |
.attr("class", "smallText") | |
.attr("id", "timeLeftText") | |
.text("Time left: " + timeLeft); | |
var force = d3.layout.force() | |
.gravity(0) | |
.charge(0) | |
.friction(1) | |
.size([w, h]); | |
balls.forEach(function (b) { | |
svg.append("svg:circle") | |
.data([b]) | |
.attr("class", "ball") | |
.attr("cx", function (d) { | |
return d.x; | |
}) | |
.attr("cy", function (d) { | |
return d.y; | |
}) | |
.attr("r", r); | |
force.nodes().push(b); | |
}); | |
force.on("tick", function () { | |
var ball = svg.selectAll(".ball"); | |
ball.attr("cx", function (d) { return d.x; }) | |
.attr("cy", function (d) { return d.y; }) | |
.each(function(b) { | |
detectCollisions(b); | |
var cc = ballCell(b); | |
var tc = topCell(cc); | |
var lc = leftCell(cc); | |
var bc = bottomCell(cc); | |
var rc = rightCell(cc); | |
if (cc.isWall || (tc.isWall && bc.isWall) || (lc.isWall && rc.isWall)) { | |
cc.elnt | |
.classed("air", true) | |
.classed("wall", false); | |
b.px = b.x = cc.x; | |
b.py = b.y = cc.y; | |
cc.isWall = b.isMoving = false; | |
} | |
}); | |
var head = svg.selectAll(".head"); | |
head.attr("x", function(d) { d.x += d.dx * (v * .4); return rectx(d); }) | |
.attr("y", function(d) { d.y += d.dy * (v * .4); return recty(d); }) | |
.each(function(d) { | |
svg.select("." + d.cl + ".tail") | |
.attr("x", tailx) | |
.attr("y", taily) | |
.attr("width", tailw) | |
.attr("height", tailh); | |
}); | |
var air = svg.selectAll(".air"); | |
var tail = svg.selectAll(".tail"); | |
var wallWasBuilt = false; | |
head.filter(function(h) { | |
var hc = ballCell(h); | |
if (h.dy < 0) { | |
var tc = topCell(hc); | |
return tc.isWall && h.y - tc.y < sz; | |
} | |
if (h.dx < 0) { | |
var lc = leftCell(hc); | |
return lc.isWall && h.x - lc.x < sz; | |
} | |
if (h.dy > 0) { | |
var bc = bottomCell(hc); | |
return bc.isWall && bc.y - h.y < sz; | |
} | |
if (h.dx > 0) { | |
var rc = rightCell(hc); | |
return rc.isWall && rc.x - h.x < sz; | |
} | |
}).each(function(h) { | |
air.filter(function(a) { | |
return h.dx == 0 ? h.x == a.x && Math.min(h.sy, h.y) <= a.y && a.y <= Math.max(h.sy, h.y) : h.y == a.y && Math.min(h.sx, h.x) <= a.x && a.x <= Math.max(h.sx, h.x); | |
}) | |
.classed("newWall", true) | |
.classed("air", false) | |
.each(function(d) { if (!d.isWall) { ++areaCleared; d.isWall = true; } }); | |
tail.filter("." + h.cl) | |
.remove(); | |
wallWasBuilt = true; | |
}).remove(); | |
if (wallWasBuilt) { | |
fillEmptyRooms(); | |
} | |
var timePlayed = Math.floor(((new Date().getTime()) - gameStartedAt) / 100); | |
timeLeft = timePlayed > t ? 0 : t - timePlayed; | |
svg.select("#timeLeftText") | |
.text("Time left: " + timeLeft); | |
if (percentageCleared < 75 && lives >= 0 && timeLeft > 0) { | |
force.resume(); | |
} else { | |
force.stop(); | |
var text, textWidth; | |
if (percentageCleared >= 75 && lives >= 0 && timeLeft > 0) { | |
text = "Level complete !"; | |
textWidth = 188; | |
d3.select("#nextLevelButton") | |
.style("visibility", "visible"); | |
} else { | |
text = "Game over !"; | |
textWidth = 138; | |
d3.select("#playAgainButton") | |
.style("visibility", "visible"); | |
} | |
svg.append("text") | |
.attr("x", w / 2 - textWidth / 2) | |
.attr("y", h / 2) | |
.attr("class", "bigTextStroke") | |
.text(text); | |
svg.append("text") | |
.attr("x", w / 2 - textWidth / 2) | |
.attr("y", h / 2) | |
.attr("class", "bigText") | |
.text(text); | |
} | |
}); | |
force.start(); | |
function detectCollisions(b) { | |
var dx = b.x - b.px > 0 ? 1 : -1; | |
var dy = b.y - b.py > 0 ? 1 : -1; | |
var d, sd; | |
// tail collision | |
svg.selectAll(".tail").filter(function(t) { | |
var w = tailw(t); | |
var h = tailh(t); | |
var x0 = tailx(t); | |
var x1 = x0 + w; | |
var y0 = taily(t); | |
var y1 = y0 + h; | |
return x0 - r < b.x && b.x < x1 - r && y0 - r < b.y && b.y < y1 + r; | |
}) | |
.each(function(t) { | |
--lives; | |
svg.select("#livesText") | |
.text("Lives: " + (lives < 0 ? 0 : lives)); | |
svg.selectAll(".head." + t.cl).remove(); | |
}) | |
.remove(); | |
// wall borders collision | |
var cc = ballCell(b); | |
var tc = topCell(cc); | |
if (tc.isWall && dy < 0 && (d = b.y - tc.y) <= sz) { | |
bounceY(b, sz, d, dy); | |
} | |
var lc = leftCell(cc); | |
if (lc.isWall && dx < 0 && (d = b.x - lc.x) <= sz) { | |
bounceX(b, sz, d, dx); | |
} | |
var bc = bottomCell(cc); | |
if (bc.isWall && dy > 0 && (d = bc.y - b.y) <= sz) { | |
bounceY(b, sz, d, dy); | |
} | |
var rc = rightCell(cc); | |
if (rc.isWall && dx > 0 && (d = rc.x - b.x) <= sz) { | |
bounceX(b, sz, d, dx); | |
} | |
svg.selectAll(".head").each(function(h) { | |
if (h.y - r <= b.y && b.y <= h.y + r) { | |
if (dx < 0 && (d = b.x - h.x) <= sz && d > 0) { | |
bounceX(b, sz, d, dx); | |
} | |
if (dx > 0 && (d = h.x - b.x) <= sz && d > 0) { | |
bounceX(b, sz, d, dx); | |
} | |
} | |
if (h.x - r <= b.x && b.x <= h.x + r) { | |
if (dy < 0 && (d = b.y - h.y) <= sz && d > 0) { | |
bounceY(b, sz, d, dy); | |
} | |
if (dy > 0 && (d = h.y - b.y) <= sz && d > 0) { | |
bounceY(b, sz, d, dy); | |
} | |
} | |
}); | |
// wall corners collision | |
var tlc = topLeftCell(cc); | |
if (!tc.isWall && !lc.isWall && tlc.isWall && (sd = cornerSquareDistance(b.x, b.y, tlc.x + r, tlc.y + r)) <= sr) { | |
d = Math.sqrt(sd); | |
if (dx < 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy < 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
var blc = bottomLeftCell(cc); | |
if (!bc.isWall && !lc.isWall && blc.isWall && (sd = cornerSquareDistance(b.x, b.y, blc.x + r, blc.y - r)) <= sr) { | |
d = Math.sqrt(sd); | |
if (dx < 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy > 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
var brc = bottomRightCell(cc); | |
if (!bc.isWall && !rc.isWall && brc.isWall && (sd = cornerSquareDistance(b.x, b.y, brc.x - r, brc.y - r)) <= sr) { | |
d = Math.sqrt(sd); | |
if (dx > 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy > 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
var trc = topRightCell(cc); | |
if (!tc.isWall && !rc.isWall && trc.isWall && (sd = cornerSquareDistance(b.x, b.y, trc.x - r, trc.y + r)) <= sr) { | |
d = Math.sqrt(sd); | |
if (dx > 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy < 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
svg.selectAll(".head").each(function(h) { | |
if ((sd = cornerSquareDistance(b.x, b.y, h.x + r, h.y + r)) <= sr && sd > 0) { | |
d = Math.sqrt(sd); | |
if (dx < 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy < 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
if ((sd = cornerSquareDistance(b.x, b.y, h.x + r, h.y - r)) <= sr && sd > 0) { | |
d = Math.sqrt(sd); | |
if (dx < 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy > 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
if ((sd = cornerSquareDistance(b.x, b.y, h.x - r, h.y - r)) <= sr && sd > 0) { | |
d = Math.sqrt(sd); | |
if (dx > 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy > 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
if ((sd = cornerSquareDistance(b.x, b.y, h.x - r, h.y + r)) <= sr && sd > 0) { | |
d = Math.sqrt(sd); | |
if (dx > 0) { | |
bounceX(b, r, d, dx); | |
} | |
if (dy < 0) { | |
bounceY(b, r, d, dy); | |
} | |
} | |
}); | |
// ball collision | |
svg.selectAll(".ball").each(function(b2) { | |
if (b.id == b2.id) { | |
return; | |
} | |
var sd = cornerSquareDistance(b.x, b.y, b2.x, b2.y); | |
if (sd <= ssz) { | |
var dx2 = b2.x - b2.px > 0 ? 1 : -1; | |
var dy2 = b2.y - b2.py > 0 ? 1 : -1; | |
var d = Math.sqrt(sd); | |
if (b.isMoving && (b2.x - b.x) * dx > r / 2) { | |
bounceX(b, sz, d, dx); | |
} | |
if (b2.isMoving && (b.x - b2.x) * dx2 > r / 2) { | |
bounceX(b2, sz, d, dx2); | |
} | |
if (b.isMoving && (b2.y - b.y) * dy > r / 2) { | |
bounceY(b, sz, d, dy); | |
} | |
if (b2.isMoving && (b.y - b2.y) * dy2 > r / 2) { | |
bounceY(b2, sz, d, dy2); | |
} | |
} | |
}); | |
} | |
function bounceX(b, m, d, dx) { | |
if (b.isMoving) { | |
b.x -= (m - d) * dx; | |
b.px = b.x + dx * v; | |
} | |
} | |
function bounceY(b, m, d, dy) { | |
if (b.isMoving) { | |
b.y -= (m - d) * dy; | |
b.py = b.y + dy * v; | |
} | |
} | |
function cornerSquareDistance(x0, y0, x1, y1) { | |
var w = x1 - x0; | |
var h = y1 - y0; | |
return (w * w + h * h); | |
} | |
function fillEmptyRooms() { | |
var air = d3.selectAll(".air"); | |
air | |
.each(function (d) { | |
d.visited = false; | |
}); | |
svg.selectAll(".ball") | |
.each(function (b) { | |
var cc = ballCell(b); | |
cc.visited = true; | |
visit(cc); | |
}); | |
air | |
.classed("newWall", function(d) { return !d.visited; }) | |
.classed("air", function(d) { return d.visited; }) | |
.each(function(d) { if (!d.visited && !d.isWall) { ++areaCleared; d.isWall = true; } }); | |
percentageCleared = Math.floor((areaCleared * 100) / totalArea); | |
svg.select("#areaFilledText") | |
.text("Area cleared: " + percentageCleared + "%"); | |
svg.selectAll(".newWall") | |
.on("mouseover", null) | |
.on("mouseout", null) | |
.on("click", null) | |
.classed("wall", true) | |
.classed("blue", false) | |
.classed("red", false); | |
} | |
function visit(c) { | |
var tc = topCell(c); | |
if (!tc.isWall && !tc.visited) { | |
tc.visited = true; | |
visit(tc); | |
} | |
var lc = leftCell(c); | |
if (!lc.isWall && !lc.visited) { | |
lc.visited = true; | |
visit(lc); | |
} | |
var bc = bottomCell(c); | |
if (!bc.isWall && !bc.visited) { | |
bc.visited = true; | |
visit(bc); | |
} | |
var rc = rightCell(c); | |
if (!rc.isWall && !rc.visited) { | |
rc.visited = true; | |
visit(rc); | |
} | |
} | |
function startWall(cell, cl, dx, dy) { | |
var wallHead = { | |
sx: cell.x, | |
sy: cell.y, | |
x: cell.x, | |
y: cell.y, | |
dx: dx, | |
dy: dy, | |
cl: cl | |
}; | |
if (svg.selectAll("." + cl + ".head").empty()) { | |
svg.selectAll("." + cl + ".head") | |
.data([wallHead]).enter().append("rect") | |
.classed("builder", true) | |
.classed(cl, true) | |
.classed("head", true) | |
.attr("x", rectx) | |
.attr("y", recty) | |
.attr("width", sz) | |
.attr("height", sz); | |
var tail = svg.selectAll("." + cl + ".tail") | |
.data([wallHead]); | |
tail.enter().append("rect") | |
.classed("builder", true) | |
.classed(cl, true) | |
.classed("tail", true) | |
.attr("x", tailx) | |
.attr("y", taily) | |
.attr("width", tailw) | |
.attr("height", tailh); | |
tail.exit().remove(); | |
} | |
} |