d3 (v4): giving elements the appearance of momentum? - javascript

Suppose you want to have an html page where the entire background is an svg, such that there is some animation going on constantly. In this silly example, I have made a smiley face which randomly moves about. While some people might find brownian motion appealing to the eye, it would be nicer if the svg element could move with the appearance of momentum (both direction and rotation).
One might quickly realize that having an object move along a path would solve this issue and it would... for the first pass of the object. However if the element were to be bounded by the screen, then how could one get the transition to adjust for the deflection?
In short the question is as follows:
Using d3.js v4 how can I make an svg element (such as #Mr_Smiley in the demo below) appear to float* across the html page?
*let float mean smoothly move with constant velocity along a vector or arc within the svg space with recoil upon hitting borders and correct deflection
var mr_s = d3.select("svg").append("g").attr("id", "Mr_Smiley")
mr_s.append("circle").attr("cx", 30).attr("cy", 30).attr("r", 30).style("fill","yellow").style("stroke", "black")
mr_s.append("circle").attr("cx", 20).attr("cy", 20).attr("r", 5).style("fill","black")
mr_s.append("circle").attr("cx", 40).attr("cy", 20).attr("r", 5).style("fill","black")
mr_s.append("path").attr("d", "M20 40 A 10 10 0 0 0 40 40").style("fill","black")
mr_s.datum({"x": 30, "y": 30, "r": 1})
mr_s.attr("transform", function(d) {"translate(" + d.x + ", " + d.y + ") rotate(" + d.r + ")"})
dur = 100
step = 10
// Lets have Mr. S go for a trip
d3.select("#Mr_Smiley")
.transition()
.duration(dur)
.on("start", function repeat() {
d3.active(this)
.attr("transform",
function(d)
{
// update y
if (Math.random() >= .5) {
d.y += step
} else {
d.y -= step
// basic bounds
if (d.y < 0) {
d.y = 0
}
}
// update x
if (Math.random() >= .5) {
d.x += step
} else {
d.x -= step
if (d.x < 0) {
d.x = 0
}
}
// update r
if (Math.random() >= .5) {
d.r += step
} else {
d.r -= step
}
return "translate(" + d.x + ", " + d.y + ") rotate(" + d.r + ")"
})
.transition()
.attr("transform",
function(d)
{
// update y
if (Math.random() >= .5) {
d.y += step
} else {
d.y -= step
}
// update x
if (Math.random() >= .5) {
d.x += step
} else {
d.x -= step
}
// update r
if (Math.random() >= .5) {
d.r += step
} else {
d.r -= step
}
return "translate(" + d.x + ", " + d.y + ") rotate(" + d.r + ")"
})
.transition()
.on("start", repeat)
})
mr_s.on("mouseover", mouseover)
mr_s.on("mouseout", mouseout)
function mouseover(d, i) {
var svg = d3.select("svg")
svg.append("text").attr("id", "mouseover_text").text("god help me, this is so unsmooth").attr("x", d.x).attr("y", d.y)
}
function mouseout(d, i) {
d3.select("#mouseover_text").remove()
}
html, body {
width: 100%;
height: 100%;
}
svg {
position: absolute;
width: 100%;
height: 100%;
background-color: light-blue;
border: 1px black solid;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
<h1>
Mr. Smiley goes on a trip
</h1>

Here's the simplest Mr S. bounce around a room I can code using d3 conventions:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var w = 250,
h = 250,
r = 30;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
.style('border', '1px solid steelblue');
var ix = Math.random() * ((Math.random() > 0.5) ? 5 : -5),
iy = Math.random() * ((Math.random() > 0.5) ? 5 : -5),
x = w / 2,
y = h / 2;
var mr_s = svg.append("g")
.attr("id", "Mr_Smiley")
mr_s.append("circle")
.attr("r", r)
.style("fill", "yellow")
.style("stroke", "black");
mr_s.append("circle")
.attr("cx", -10)
.attr("cy", -10)
.attr("r", 5)
.style("fill", "black");
mr_s.append("circle")
.attr("cx", 10)
.attr("cy", -10)
.attr("r", 5)
.style("fill", "black");
mr_s.append("path")
.attr("d", "M-10 10 A 10 10 0 0 0 10 10")
.style("fill", "black");
mr_s.attr('transform', 'translate(' + x + ',' + y + ')');
d3.interval(tick, 20);
function tick() {
x += ix;
y += iy;
if (x > (w - r) || x < r) {
ix = -ix;
}
if (y > (h - r) || y < r) {
iy = -iy;
}
mr_s.attr('transform', 'translate(' + x + ',' + y + ')');
}
</script>
</body>
</html>

Related

d3 drag blurring/jittery handle

This is on d3 v4.
I'm trying to create an expandable rectangle area, with bounds (sort of a constrained d3-brush). I add a handle which shows up on mouseover.
var rectHeight = 80, rectWidth = 100, maxWidth = 200;
var svg = d3.select("svg");
var brect = svg.append("g")
.attr("id", "brect");
brect.append("rect")
.attr("id", "dataRect")
.attr("width", rectWidth)
.attr("height", rectHeight)
.attr("fill", "green");
var handleResizeGroup = brect.append("g")
.attr("id", "handleResizeGroup")
.attr("transform", `translate(${rectWidth})`)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded));
function dragStarted() {
d3.select(this.previousSibling).classed("active", true);
}
function dragEnded() {
d3.select(this.previousSibling).classed("active", false);
}
function dragged(d) {
var h = d3.select(this);
var r = d3.select(this.previousSibling);
var currWidth = r.attr("width");
var t = (d3.event.x >= 0 && d3.event.x <= maxWidth) ? d3.event.x : currWidth;
r.attr("width", t);
h.attr("transform", `translate(${t})`)
}
handleResizeGroup.append("path")
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.attr("stroke", "grey")
.attr("cursor", "ew-resize")
.attr("d", resizePath);
handleResizeGroup.append("rect")
.attr("id", "resizeRect")
.attr("width", "8")
.attr("fill-opacity", 0)
.attr("cursor", "ew-resize")
.attr("height", rectHeight)
//.attr("pointer-events", "all")
.on("mouseover", function(){
d3.select(this.previousSibling)
.attr("stroke-opacity", "100%");
})
.on("mouseout", function() {
d3.select(this.previousSibling)
.attr("stroke-opacity", "0");
});
function resizePath(d) {
var e = 1,
x = e ? 1 : -1,
y = rectHeight / 3;
return "M" + (.5 * x) + "," + y
+ "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
+ "V" + (2 * y - 6)
+ "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
+ "Z"
+ "M" + (2.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8)
+ "M" + (4.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8);
}
rect.active {
stroke-width: 1;
stroke: rgb(0,0,0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width=1200 height=500></svg>
I'm noticing 2 issues
When I drag the handle, I see a jitter in the handle itself (presumably because handle is shown only on mouseover?)
If I drag the mouse too fast - say to the left, the rectangle does not catch-up
Can someone help to understand what's going on, and how to fix these?
Thank you!
For your first issue, add .attr('pointer-events', 'none') to the greenrect`. the handle is jittering because your cursor moves slightly faster than the handle does- so you're constantly mousing-out of and then mousing-in to the handle as you move and it catches up.
I don't really see what you're doing with resizePath. Is that adding the grey stroke around the rect? Why not just add/remove a stroke? Your second issue may be due to constantly resizing that path.

D3: Circle Points don't update when changing slider

I've started trying to learn D3 as merely a hobby and I've wanted to create a type of animation that displays the 'method of exhaustion' to approximate the area of a circle. The behavior I wish to obtain is shown in this animation.
I have been able to draw the circle, the points around the circle as well as the triangles. Now I am working on a function called 'update' which upon listening to a slider event on the HTML dynamically changes the value for 'n' and use that to recalculate the coordinates and display them on screen.
I have managed to be able to change the fill of the circles but aside from that the update function doesn't update the coordinates of the circles as well as add new circles. I would really appreciate if someone could help me get this working as well as provide some insight as to why my approach is failing.
var w = 500;
var h = 500;
var n = 17;
var r = h / 2 - 20;
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
//id++;
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red');
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update() {
console.log('Hey man!');
points(); // Re-generate points
console.log('coords[1].x = ' + coords[1].x);
console.log('coords[1].y = ' + coords[1].y);
//Make Selection
approx_pts
.selectAll('circle')
.data(coords, function(d) {
return d.id;
});
approx_pts
.attr('fill', 'blue')
.attr('r', r / 25);
approx_pts
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'green');
};
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<input id="slider" type="range" min="3" max="100" step="1" value="3">
You are missing a proper "enter", "update" and "exit" selections:
var updateSelection = svg.selectAll('.circle_points')
.data(coords, function(d) {
return d.id;
});
var exitSelection = updateSelection.exit().remove();
updateSelection.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'blue')
.attr('r', r / 25);
var enterSelection = updateSelection.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'green')
.attr("class", "circle_points");
Here is your code with those changes:
var w = 500;
var h = 500;
var n = 17;
var r = h / 2 - 20;
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
//id++;
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red')
.attr("class", "circle_points");
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update() {
points(); // Re-generate points
//Make Selection
var updateSelection = svg.selectAll('.circle_points')
.data(coords, function(d) {
return d.id;
});
var exitSelection = updateSelection.exit().remove();
updateSelection.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'blue')
.attr('r', r / 25);
var enterSelection = updateSelection.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'green')
.attr("class", "circle_points");
};
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<input id="slider" type="range" min="3" max="100" step="1" value="3">
You need to remove the 'circle_points' and then .exit().remove() and then .enter()
The Update Pattern is needed here.
var w = 500;
var h = 250;
var n = 4;
var r = h / 2 - 201
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red');
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update(n) {
points(); // Re-generate points
d3.selectAll("circle").remove();
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords);
approx_pts.exit().remove();
approx_pts.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', 6)
.attr('fill', 'red');
d3.selectAll("polygon").remove();
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords)
approx_tri.exit().remove();
approx_tri.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<input id="slider" type="range" min="3" max="100" step="4" value="17">
</body>
var w = 500;
var h = 500;
var n = 17;
var r = h / 2 - 20;
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
//id++;
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red');
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update(n) {
//console.log('Hey man!');
points(); // Re-generate points
//console.log('coords[1].x = ' + coords[1].x);
//console.log('coords[1].y = ' + coords[1].y);
//Make Selection
approx_pts
.selectAll('circle')
.data(coords, function(d) {
return d.id;
});
approx_pts
.attr('fill', 'blue')
.attr('r', r / 25);
approx_pts
.attr('cx', function(d) {
return (d.x)+n;
})
.attr('cy', function(d) {
return d.y+n;
})
.attr('r', r / 50)
.attr('fill', 'green');
};
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<input id="slider" type="range" min="3" max="100" step="1" value="3">
remember to update the value when you drag it

If then else HTML/CSV file

I am currently building an organisation chart for my company using an html code (pasted below) build by the genius Mr Bostock. The code is calling a .csv file (flare.csv) where I am organizing my data (3 different columns: Employee Name/Employee span of control (5 ranges)/Employee Salary (6 ranges)).
I want to know if it is possible to change the color of the dots/circles on the diagram depending on the two parameters Span of control and Salary (Ranges).
Maybe a diagram for Span of control and another one for Salary.
Let s say if the Span of control of that employee is between 1 and 3 (Flare.csv file) then his circle on the chart will be yellow.
Thank you in advance for your answers.
Cheers,
Alex
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.node--internal text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="900"></svg>
<script src="d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + (height / 2 + 20) + ")");
var stratify = d3.stratify()
.parentId(function(d) { return d.id.substring(0, d.id.lastIndexOf(".")); });
var cluster = d3.cluster()
.size([360, width / 2 - 120]);
d3.csv("flare.csv", function(error, data) {
if (error) throw error;
var root = stratify(data)
.sort(function(a, b) { return a.height - b.height || a.id.localeCompare(b.id); });
cluster(root);
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
});
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + project(d.x, d.y) + ")"; });
node.append("circle")
.attr("r", 2.5);
node.append("text")
.attr("dy", "0.31em")
.attr("x", function(d) { return d.x < 180 === !d.children ? 6 : -6; })
.style("text-anchor", function(d) { return d.x < 180 === !d.children ? "start" : "end"; })
.attr("transform", function(d) { return "rotate(" + (d.x < 180 ? d.x - 90 : d.x + 90) + ")"; })
.text(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1); });
});
function project(x, y) {
var angle = (x - 90) / 180 * Math.PI, radius = y;
return [radius * Math.cos(angle), radius * Math.sin(angle)];
}
</script>

Specify startpoint to interpolate a circle on an arc when clicked on by the user

How do I provide the starting point of an arc for moving a circle along the path of the arc. I have a world map with several arcs displayed on it. I wish to interpolate the movement of a circle on the arc that has been selected by the user using the .on('click') event. I wish to know how I can identify the startPoint of the arc in question.
Specifically, I am not able to understand what parameters to provide in .attr("transform", "translate(" + startPoint + ")") attribute of the circle to enable the circle to start from the starting position of the arc.
At present, it passes the entire path and I receive the following error
d3.v3.min.js:1 Error: attribute transform: Expected number, "translate(M1051.5549785289…".
Although, surprisingly, the circle marker appears on the screen and interpolates along the first arc that has been drawn. However, I wish to change this interpolation to an arc that has been clicked by the user. In other words, how do I feed a new startPoint to the circle marker every time the user clicks on a different arc and to have a subsequent interpolation of the same.
var path3 = arcGroup.selectAll(".arc"),
startPoint = pathStartPoint(path3)
var marker = arcGroup.append("circle")
marker.attr("r", 7)
.attr("transform", "translate(" + startPoint + ")")
transition();
function pathStartPoint(path) {
var d = path.attr("d")
console.log(path)
dsplitted = d.split(" ");
return dsplitted[0].split(",");
}
function transition() {
marker.transition()
.duration(7500)
.attrTween("transform", translateAlong(path3.node()))
.each("end", transition);// infinite loop
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";//Move marker
}
}
}
As #altocumulus states in the their comment, getPointAtLength doesn't need a starting point. It takes as an argument a distance from 0 to path length where 0 is the starting point. Here's a quick example, click on any path below:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var w = 400,
h = 400;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
var data = []
for (var i = 0; i < 5; i++) {
data.push({
x0: Math.random() * w,
y0: Math.random() * h,
x1: Math.random() * w,
y1: Math.random() * h
});
}
var marker = svg.append("circle")
.attr("r", 20)
.style("fill", "steelblue")
.style("opacity", 0);
svg.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", function(d) {
var dx = d.x1 - d.x0,
dy = d.y1 - d.y0,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.x0 + "," + d.y0 + "A" + dr + "," + dr +
" 0 0,1 " + d.x1 + "," + d.y1;
})
.style("stroke", function(d, i) {
return d3.schemeCategory10[i];
})
.style("stroke-width", "10px")
.style("fill", "none")
.on("click", function(d){
marker
.style("opacity", 1)
.transition()
.duration(1000)
.attrTween("transform", translateAlong(this))
.on("end", function(d) {
marker.style("opacity", 0);
});
});
function translateAlong(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")"; //Move marker
}
}
}
</script>
</body>
</html>

Chess moves in D3

Drawing a chess board with D3 is discussed in this question:
How to draw a chess board in D3?
Also, there is an incredible D3 chess board plugin by #jbkunst:
d3-chessboard plugin
However, I would like to animate chess moves, like this:
(but a lot smoother; with configurable duration etc.)
Do you have any advice how to do it, D3 style?
I would be happy with animation of just one move for now. I will build up the more general solution later.
Here's a quick implementation using chained transitions to make the pieces step across the board. I've tried to account for two different types of movement, "line" where the pieces move in a straight line (ie bishop, castle) and "step" where they move step-wise (ie knight). I based it off your work in the previous question.
// piece is the text element to move
// position is an object like { x: 4, y: 6 } of the board position to move to
// type is "step" or "line"
function movePiece(piece, position, type) {
var p = d3.select(piece),
d = p.datum();
(function repeat() {
if (type === "step"){
if (position.y === d.y) {
if (position.x === d.x) {
return;
} else if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
} else {
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
} else {
if (position.x === d.x &&
position.y === d.y) {
return;
}
else {
if (position.x != d.x){
if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
}
if (position.y != d.y){
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
}
}
p = p.transition()
.transition()
.attr("x", d.x * fieldSize)
.attr("y", d.y * fieldSize)
.each("end", repeat);
})();
}
Note, I didn't attempt to code whether it's a legal move.
Full example:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<script>
var pieces = {
NONE: {
name: "None",
code: " "
},
WHITE_KING: {
name: "White King",
code: "\u2654"
},
WHITE_QUEEN: {
name: "White Queen",
code: "\u2655"
},
WHITE_ROOK: {
name: "White Rook",
code: "\u2656"
},
WHITE_BISHOP: {
name: "White Bishop",
code: "\u2657"
},
WHITE_KNIGHT: {
name: "White Knight",
code: "\u2658"
},
WHITE_POWN: {
name: "White Pown",
code: "\u2659"
},
BLACK_KING: {
name: "Black King",
code: "\u265A"
},
BLACK_QUEEN: {
name: "Black Queen",
code: "\u265B"
},
BLACK_ROOK: {
name: "Black Rook",
code: "\u265C"
},
BLACK_BISHOP: {
name: "Black Bishop",
code: "\u265D"
},
BLACK_KNIGHT: {
name: "Black Knight",
code: "\u265E"
},
BLACK_POWN: {
name: "Black Pown",
code: "\u265F"
},
};
var board = [],
boardDimension = 8,
fieldSize = 40;
for (var i = 0; i < boardDimension * boardDimension; i++) {
board.push({
x: i % boardDimension,
y: Math.floor(i / boardDimension),
piece: pieces.NONE
});
};
board[0].piece = pieces.BLACK_ROOK
board[1].piece = pieces.BLACK_KNIGHT
board[2].piece = pieces.BLACK_BISHOP
board[3].piece = pieces.BLACK_QUEEN
board[4].piece = pieces.BLACK_KING
board[5].piece = pieces.BLACK_BISHOP
board[6].piece = pieces.BLACK_KNIGHT
board[7].piece = pieces.BLACK_ROOK
board[8].piece = pieces.BLACK_POWN
board[9].piece = pieces.BLACK_POWN
board[10].piece = pieces.BLACK_POWN
board[11].piece = pieces.BLACK_POWN
board[12].piece = pieces.BLACK_POWN
board[13].piece = pieces.BLACK_POWN
board[14].piece = pieces.BLACK_POWN
board[15].piece = pieces.BLACK_POWN
board[6 * 8 + 0].piece = pieces.WHITE_POWN
board[6 * 8 + 1].piece = pieces.WHITE_POWN
board[6 * 8 + 2].piece = pieces.WHITE_POWN
board[6 * 8 + 3].piece = pieces.WHITE_POWN
board[6 * 8 + 4].piece = pieces.WHITE_POWN
board[6 * 8 + 5].piece = pieces.WHITE_POWN
board[6 * 8 + 6].piece = pieces.WHITE_POWN
board[6 * 8 + 7].piece = pieces.WHITE_POWN
board[7 * 8 + 0].piece = pieces.WHITE_ROOK
board[7 * 8 + 1].piece = pieces.WHITE_KNIGHT
board[7 * 8 + 2].piece = pieces.WHITE_BISHOP
board[7 * 8 + 3].piece = pieces.WHITE_QUEEN
board[7 * 8 + 4].piece = pieces.WHITE_KING
board[7 * 8 + 5].piece = pieces.WHITE_BISHOP
board[7 * 8 + 6].piece = pieces.WHITE_KNIGHT
board[7 * 8 + 7].piece = pieces.WHITE_ROOK
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
svg.selectAll("rect")
.data(board)
.enter()
.append("rect")
.style("class", "fields")
.style("class", "rects")
.attr("x", function(d) {
return d.x * fieldSize;
})
.attr("y", function(d) {
return d.y * fieldSize;
})
.attr("width", fieldSize + "px")
.attr("height", fieldSize + "px")
.style("fill", function(d) {
if (((d.x % 2 == 0) && (d.y % 2 == 0)) ||
((d.x % 2 == 1) && (d.y % 2 == 1)))
return "beige";
else
return "tan";
});
var pieces = svg.selectAll("text")
.data(board)
.enter().append("text")
.attr("x", function(d) {
d.piece.x = d.x;
return d.x * fieldSize;
})
.attr("y", function(d) {
d.piece.y = d.y;
return d.y * fieldSize;
})
.style("font-size", "40")
.attr("text-anchor", "middle")
.attr("dy", "35px")
.attr("dx", "20px")
.text(function(d) {
return d.piece.code;
})
pieces
.append("title")
.text(function(d) {
return d.piece.name;
});
movePiece(pieces[0][6], {
x: 5,
y: 2
}, "step");
movePiece(pieces[0][58], {
x: 5,
y: 4
}, "line");
function movePiece(piece, position, type) {
var p = d3.select(piece),
d = p.datum();
(function repeat() {
if (type === "step"){
if (position.y === d.y) {
if (position.x === d.x) {
return;
} else if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
} else {
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
} else {
if (position.x === d.x &&
position.y === d.y) {
return;
}
else {
if (position.x != d.x){
if (position.x > d.x) {
d.x += 1;
} else {
d.x -= 1;
}
}
if (position.y != d.y){
if (position.y > d.y) {
d.y += 1;
} else {
d.y -= 1;
}
}
}
}
p = p.transition()
.transition()
.attr("x", d.x * fieldSize)
.attr("y", d.y * fieldSize)
.each("end", repeat);
})();
}
</script>
</body>
</html>
If you know the start position and the stop position, you can set a transition() for whatever process is moving the pieces. That will make it tween between the states in an animated fashion. This will be linear, though, so it will look nice for anyone who moves along the grid in a straight line, less good if you don't (e.g. a knight). For a knight, I would first transition along one axis, and then along the other.
Well, after some research, I found this: http://blog.visual.ly/creating-animations-and-transitions-with-d3-js/ which pretty much gives the same answer as nucleon: when you want to change attributes of an element (like position) in d3 and you do something like d3.select(selector).attr(attribute,value) and you have to use d3.select(selector).transition().attr(attribute,value)
However, the way the chessboard is drawn by the plugin, for example, you have g elements that represent squares, containing a rect and a text. the rect is the color of the square, while the text is the piece. If you change the transform of the text outside the range of the g element it disappears. Either way it feels a wrong model for moving pieces.
Supposing you are drawing your board and then, independently, draw your pieces, you might use the example in the link above to move the pieces where you want. Take care with the knight, you should move it with two transitions, though, and probably you should think a bit about the capturing animation.
That's all I got.

Categories