D3 how to use collision with text elements - javascript

I am making an application in which there are words displayed on screen. The users can drag them (this works).
I wanted to add an additional functionality though. I want the words to push each other away/prevent overlap when they are being dragged to each other.
I have tried to mess around with the code below, but I can't figure it out.
const simulation = d3
.forceSimulation()
.alphaTarget(0.8) // stay hot
.velocityDecay(0.1) // low friction
.force("x", d3.forceX().strength(0.05))
.force("y", d3.forceY().strength(0.05))
.force("collide", d3.forceCollide().strength(1).radius(15).iterations(10))
.force("charge", d3.forceManyBody().strength(0.4)) // Nodes are attracted one each other of value is > 0
.on("tick", function () {
wordcloudSvg
.selectAll(".keywords")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
});
});
simulation.nodes(this.keywords).alphaTarget(0.5).alphaDecay(0.05).restart();
I have messed with adding extra things like force many body, and changing the strength/radius. But nothing seems to work.
Here is my svg:
let wordcloudSvg = d3
.select(".wordcloud-svg .keywords")
.selectAll("text")
.data(this.keywords)
.enter()
.append("text")
.text(function (d) {
return d.title;
})
.attr("x", function (d) {
return d.position_x;
})
.attr("y", function (d) {
return d.position_y;
})
.attr("fill", function (d) {
return d.color;
})
.style("font-size", function (d) {
return d.font_size + "px";
})
.attr("data-id", function (d) {
return d.id;
})
.attr("class", "keyword")
.call(d3
.drag()
.on("start", function (event) {
// simulation.stop();
let current = d3.select(this);
deltaX = current.attr("x") - event.x;
deltaY = current.attr("y") - event.y;
window.Echo.private("meeting." + self.routeId).whisper("drag", {
id: current.attr("data-id"),
phase: "start",
delta: { deltaX, deltaY },
event: event,
title: current.text(),
});
})
.on("drag", function (event) {
let current = d3.select(this);
if (event.x >= 60 && event.x <= 1400) {
d3.select(this).attr("x", event.x + deltaX);
}
if (event.y >= 30 && event.y <= 545) {
d3.select(this).attr("y", event.y + deltaY);
}
window.Echo.private("meeting." + self.routeId).whisper("drag", {
id: current.attr("data-id"),
phase: "start",
delta: { deltaX, deltaY },
event: event,
title: current.text(),
});
})
.on("end", function (event) {
let current = d3.select(this);
window.Echo.private("meeting." + self.routeId).whisper("drag", {
id: current.attr("data-id"),
phase: "start",
delta: { deltaX, deltaY },
event: event,
title: current.text(),
});
self.keywords.filter((obj) => {
if (obj.title == current.text()) {
obj.position_x = event.x + deltaX;
obj.position_y = event.y + deltaY;
}
});
transcriptApi.updateKeywords(self.routeId, current);
const keywordElements = document.querySelectorAll(".keyword");
if (event.sourceEvent.clientX >= 43 && event.sourceEvent.clientX <= 140 && event.sourceEvent.clientY >= 20 && event.sourceEvent.clientY <= 170) {
keywordElements.forEach((el) => {
if (current.text() == el.textContent) {
self.keywords.filter((obj) => {
if (obj.title == el.textContent) {
el.remove();
transcriptApi.deleteKeyword(self.routeId, obj.id);
let index = self.keywords.indexOf(obj);
self.keywords.splice(index, 1);
window.Echo.private("meeting." + self.$route.params.id).whisper("context", { data: "nothing", id: el.getAttribute("data-id"), type: "delete" });
}
});
}
});
}
})
);d3
.drag()
.on("start", function (event) {
let current = d3.select(this);
if (event.x >= 60 && event.x <= 1400) {
d3.select(this).attr("x", event.x + deltaX);
}
if (event.y >= 30 && event.y <= 545) {
d3.select(this).attr("y", event.y + deltaY);
}
});
})
.on("drag", function (event) {
let current = d3.select(this);
if (event.x >= 60 && event.x <= 1400) {
d3.select(this).attr("x", event.x + deltaX);
}
if (event.y >= 30 && event.y <= 545) {
d3.select(this).attr("y", event.y + deltaY);
}
})
.on("end", function (event) {
let current = d3.select(this);
self.keywords.filter((obj) => {
if (obj.title == current.text()) {
obj.position_x = event.x + deltaX;
obj.position_y = event.y + deltaY;
}
});
})
);

Related

vanilla javascript to Reactjs component?

I have a little vanilla javascript code, and am hoping to convert it into a React component. I really dont know where to begin, and I have a few questions that seem be very simple.
First, how do i even get started converting this to React? Do i place all of these variables in the constructor, or in the render (before the return) Is all of this inline styling sufficient, without any external CSS?
This is the class (d3-based KMeans clustering visualization):
var flag = false;
var WIDTH = d3.select("#kmeans")[0][0].offsetWidth - 20;
var HEIGHT = Math.max(300, WIDTH * .7);
var svg = d3.select("#kmeans svg")
.attr('width', WIDTH)
.attr('height', HEIGHT)
.style('padding', '10px')
.style('background', '#223344')
.style('cursor', 'pointer')
.style('-webkit-user-select', 'none')
.style('-khtml-user-select', 'none')
.style('-moz-user-select', 'none')
.style('-ms-user-select', 'none')
.style('user-select', 'none')
.on('click', function() {
d3.event.preventDefault();
step();
});
d3.selectAll("#kmeans button")
.style('padding', '.5em .8em');
d3.selectAll("#kmeans label")
.style('display', 'inline-block')
.style('width', '15em');
var lineg = svg.append('g');
var dotg = svg.append('g');
var centerg = svg.append('g');
d3.select("#step")
.on('click', function() { step(); draw(); });
d3.select("#restart")
.on('click', function() { restart(); draw(); });
d3.select("#reset")
.on('click', function() { init(); draw(); });
var groups = [], dots = [];
function step() {
d3.select("#restart").attr("disabled", null);
if (flag) {
moveCenter();
draw();
} else {
updateGroups();
draw();
}
flag = !flag;
}
function init() {
d3.select("#restart").attr("disabled", "disabled");
//var N = parseInt(d3.select('#N')[0][0].value, 10);
//var K = parseInt(d3.select('#K')[0][0].value, 10);
var N = 700;
var K = 9;
groups = [];
for (var i = 0; i < K; i++) {
var g = {
dots: [],
color: 'hsl(' + (i * 360 / K) + ',100%,50%)',
center: {
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT
},
init: {
center: {}
}
};
g.init.center = {
x: g.center.x,
y: g.center.y
};
groups.push(g);
}
dots = [];
flag = false;
for (i = 0; i < N; i++) {
var dot ={
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT,
group: undefined
};
dot.init = {
x: dot.x,
y: dot.y,
group: dot.group
};
dots.push(dot);
}
}
function restart() {
flag = false;
d3.select("#restart").attr("disabled", "disabled");
groups.forEach(function(g) {
g.dots = [];
g.center.x = g.init.center.x;
g.center.y = g.init.center.y;
});
for (var i = 0; i < dots.length; i++) {
var dot = dots[i];
dots[i] = {
x: dot.init.x,
y: dot.init.y,
group: undefined,
init: dot.init
};
}
}
function draw() {
var circles = dotg.selectAll('circle')
.data(dots);
circles.enter()
.append('circle');
circles.exit().remove();
circles
.transition()
.duration(500)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('fill', function(d) { return d.group ? d.group.color : '#ffffff'; })
.attr('r', 5);
if (dots[0].group) {
var l = lineg.selectAll('line')
.data(dots);
var updateLine = function(lines) {
lines
.attr('x1', function(d) { return d.x; })
.attr('y1', function(d) { return d.y; })
.attr('x2', function(d) { return d.group.center.x; })
.attr('y2', function(d) { return d.group.center.y; })
.attr('stroke', function(d) { return d.group.color; });
};
updateLine(l.enter().append('line'));
updateLine(l.transition().duration(500));
l.exit().remove();
} else {
lineg.selectAll('line').remove();
}
var c = centerg.selectAll('path')
.data(groups);
var updateCenters = function(centers) {
centers
.attr('transform', function(d) { return "translate(" + d.center.x + "," + d.center.y + ") rotate(45)";})
.attr('fill', function(d,i) { return d.color; })
.attr('stroke', '#aabbcc');
};
c.exit().remove();
updateCenters(c.enter()
.append('path')
.attr('d', d3.svg.symbol().type('cross'))
.attr('stroke', '#aabbcc'));
updateCenters(c
.transition()
.duration(500));}
function moveCenter() {
groups.forEach(function(group, i) {
if (group.dots.length == 0) return;
// get center of gravity
var x = 0, y = 0;
group.dots.forEach(function(dot) {
x += dot.x;
y += dot.y;
});
group.center = {
x: x / group.dots.length,
y: y / group.dots.length
};
});
}
function updateGroups() {
groups.forEach(function(g) { g.dots = []; });
dots.forEach(function(dot) {
// find the nearest group
var min = Infinity;
var group;
groups.forEach(function(g) {
var d = Math.pow(g.center.x - dot.x, 2) + Math.pow(g.center.y - dot.y, 2);
if (d < min) {
min = d;
group = g;
}
});
// update group
group.dots.push(dot);
dot.group = group;
});
}
init(); draw();
Any help in understanding (or or course providing converted code) would be so helpful.
If you're beginning from scratch, set up a boiler plate.For e.g create-react-app
Install d3 library in this project
Create a component for this and import and render that component in your app.js
Make these variables as part of the state of the component and method inside the component as class methods

Get visible root node in zoomable sunburst

I made a zoomable sunburst visualisation with labels (see in action, or check out the code). When clicking on an item, the innermost visible node has its label turned sideways. I'd like to fix the label just for this one node, but I haven't found a way to do this.
Is there a way to say "if (current node is the root of visible nodes)"? Any other ideas?
The full visualization:
Zoomed in after click. I'd like to make the 'Calm' node text horizontal:
While not perfect, this modified version of the code you were using adjusts the text of the currently selected node as it animates and makes it horizontal.
var width = 960,
height = 700,
radius = (Math.min(width, height) / 2) - 10;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear()
.range([0, 2 * Math.PI]);
var y = d3.scaleLinear()
.range([0, radius]);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var partition = d3.partition();
function startAngle(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); }
function endAngle(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); }
function innerRadius(d) { return Math.max(0, y(d.y0)); }
function outerRadius(d) { return Math.max(0, y(d.y1)); }
var arc = d3.arc()
.startAngle( function(d) { return startAngle(d); })
.endAngle( function(d) { return endAngle(d); })
.innerRadius(function(d) { return innerRadius(d); })
.outerRadius(function(d) { return outerRadius(d); })
var texttransform = function(d) {
var translation = y(d.y0);
var rotation = computeTextRotation(d);
if (rotation > 90 && rotation < 270) {
rotation = rotation + 180;
translation = -translation;
}
return (
"rotate(" + rotation + ")" +
"translate(" + translation + ",0)"
);
}
var transition = {};
function calcTransitionPercentage(){
var now = Date.now()-transition.clockNow;
if(!transition.delay || now > transition.delay){
return Math.min(1,(now-(transition.delay||0))/transition.duration);
}
return 0;
}
function computeTextRotation(d) {
if (d.depth === 0) {
return 0;
}
var current = x((d.x0 + d.x1)/2);
var angle = (current - Math.PI / 2) / Math.PI * 180;
if(transition.node === d){
angle -= 90 * calcTransitionPercentage();
}
return (angle > 90 || angle < 270) ? angle : 180 + angle ;
}
var textanchor = function(d) {
if (d.depth === 0) {
return "middle";
}
var rotation = computeTextRotation(d);
return (rotation > 90 && rotation < 270) ? "end" : "start";
}
var textdx = function(d) {
if (d.depth === 0) {
return 0;
}
var rotation = computeTextRotation(d);
return (rotation > 90 && rotation < 270) ? -6 : 6;
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
function calcFontSize(d) {
const xFactor = 12, yFactor = 7.5 ; // stub
if (d.depth === 0) {
return "30px";
}
// use inner arc len as text height delimiter
var innerArc = (endAngle(d) - startAngle(d)) * 2 * Math.PI * innerRadius(d);
var len = (d.y1-d.y0) * radius;
return Math.min(innerArc / yFactor, len / d.data.textlen * xFactor) + "px";
}
function click(d = { x0: 0, x1: 1, y0: 0, y1: 1 }) {
transition = {clockNow: Date.now(), duration: 750, node: d }
var trans = svg.transition().duration(750);
trans.selectAll("path")
.attrTween("d", function(n) { return function() { return arc(n); }; })
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]),
yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]);
return function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
};
});
trans.selectAll("text")
.attrTween("transform", function(n) { return function() { return texttransform(n); }; })
.attrTween("text-anchor", function(n) { return function() { return textanchor(n); }; })
.attrTween("dx", function(n) { return function() { return textdx(n); }; })
.styleTween("font-size", function(n) { return function() { return calcFontSize(n); }; });
trans.selectAll("text")
.delay(400)
.attrTween("opacity", function(n) { return function() {
if (d === n || n.ancestors().includes(d)) {
return 1;
} else {
return 0;
}
}; });
}
d3.text('https://raw.githubusercontent.com/manooh/NVSee/master/data/feelings_EN.txt', function(error, raw){
if (error) throw error;
// replace two-space indentation with pipes
raw = raw.replace(new RegExp(' ', 'g'), '|');
//read pipe-delimited data
var dsv = d3.dsvFormat('|');
var flatData = dsv.parse(raw);
var rData = currentNode = tree(flatData);
rData = d3.hierarchy(rData);
var nodes = partition(rData
.sum(function(d) { return 1; }) // each leaf gets a size of 1
.sort(function(a, b) { d3.ascending(a.name, b.name) }) // not working?
)
.descendants();
g = svg.selectAll("path")
.data(nodes)
.enter().append("g");
path = g.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
var c;
if (d.depth === 0) {
return "white";
} else if (d.depth === 1) {
c = color((d.children ? d : d.parent).data.name);
} else if (d.depth > 1) {
c = d3.color(d.parent.data.color).darker();
}
d.data.color = c;
return c;
})
.on("click", click)
.append("title")
.text(function(d) { return d.data.name });
text = g.append("text")
.style("fill", function(d) {
if (d.depth === 0) {
return "#CCC";
} else {
return "#FFF";
}})
.attr("class", "svglabel")
.attr("transform", texttransform)
.attr("text-anchor", textanchor)
.attr("dx", textdx)
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.data.name; })
.style("font-size", function(d) {
// hack. save text len as property to make accessible in transiton
d.data.textlen = this.getComputedTextLength();
return calcFontSize(d);
});
});
function tree(nodes) {
var curr, parent, root;
var lev = 1;
nodes.forEach(function(d) {
if (!root) {
// handle root (first node)
curr = {
name: d.d1,
children: []
};
root = curr;
parent = curr;
} else {
if (d['d' + (lev+1)]) {
// handle children
lev = lev+1;
parent = curr;
} else if (d['d' + (lev-1)]) {
// handle moving up the hierarchy
lev = lev-1;
parent = parent.parent;
} else if (!d['d' + lev]) {
// if it's neither child, nor moving up, nor a sibling, handle exception
throw "unhandled tree level";
}
curr = {
name: d['d' + lev],
children: []
};
curr.parent = parent;
parent.children.push(curr);
}
});
return root;
}
.svglabel {
font-family: sans-serif;
pointer-events: none;
}
body {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome and Opera */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>

Dragging & creating links not working with zoom

Guys I am following this example problem I am facing when I added zoom functionality this code not drag and creating edges between nodes. The problem which I am thinking could be 'g' appended with SVG for zooming.
// set up SVG for D3
var width = 960,
height = 500,
colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr('oncontextmenu', 'return false;')
.attr('width', width)
.attr('height', height)
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom",zoomed)).on("dblclick.zoom", null)
.append('svg:g');
zoomed() {
svg.attr("transform",
"translate(" + (<any>d3.event).translate + ")"
+ " scale(" + (<any>d3.event).scale + ")");
}
// set up initial nodes and links
// - nodes are known by 'id', not by index in array.
// - reflexive edges are indicated on the node (as a bold black circle).
// - links are always source < target; edge directions are set by 'left' and 'right'.
var nodes = [
{id: 0, reflexive: false},
{id: 1, reflexive: true },
{id: 2, reflexive: false}
],
lastNodeId = 2,
links = [
{source: nodes[0], target: nodes[1], left: false, right: true },
{source: nodes[1], target: nodes[2], left: false, right: true }
];
// init D3 force layout
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(150)
.charge(-500)
.on('tick', tick)
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
svg.append('svg:defs').append('svg:marker')
.attr('id', 'start-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M10,-5L0,0L10,5')
.attr('fill', '#000');
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr('class', 'link dragline hidden')
.attr('d', 'M0,0L0,0');
// handles to link and node element groups
var path = svg.append('svg:g').selectAll('path'),
circle = svg.append('svg:g').selectAll('g');
// mouse event vars
var selected_node = null,
selected_link = null,
mousedown_link = null,
mousedown_node = null,
mouseup_node = null;
function resetMouseVars() {
mousedown_node = null;
mouseup_node = null;
mousedown_link = null;
}
// update force layout (called automatically each iteration)
function tick() {
// draw directed edges with proper padding from node centers
path.attr('d', function(d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = d.left ? 17 : 12,
targetPadding = d.right ? 17 : 12,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY);
return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
});
circle.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
}
// update graph (called when needed)
function restart() {
// path (link) group
path = path.data(links);
// update existing links
path.classed('selected', function(d) { return d === selected_link; })
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; });
// add new links
path.enter().append('svg:path')
.attr('class', 'link')
.classed('selected', function(d) { return d === selected_link; })
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; })
.on('mousedown', function(d) {
if(d3.event.ctrlKey) return;
// select link
mousedown_link = d;
if(mousedown_link === selected_link) selected_link = null;
else selected_link = mousedown_link;
selected_node = null;
restart();
});
// remove old links
path.exit().remove();
// circle (node) group
// NB: the function arg is crucial here! nodes are known by id, not by index!
circle = circle.data(nodes, function(d) { return d.id; });
// update existing nodes (reflexive & selected visual states)
circle.selectAll('circle')
.style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.classed('reflexive', function(d) { return d.reflexive; });
// add new nodes
var g = circle.enter().append('svg:g');
g.append('svg:circle')
.attr('class', 'node')
.attr('r', 12)
.style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.style('stroke', function(d) { return d3.rgb(colors(d.id)).darker().toString(); })
.classed('reflexive', function(d) { return d.reflexive; })
.on('mouseover', function(d) {
if(!mousedown_node || d === mousedown_node) return;
// enlarge target node
d3.select(this).attr('transform', 'scale(1.1)');
})
.on('mouseout', function(d) {
if(!mousedown_node || d === mousedown_node) return;
// unenlarge target node
d3.select(this).attr('transform', '');
})
.on('mousedown', function(d) {
if(d3.event.ctrlKey) return;
// select node
mousedown_node = d;
if(mousedown_node === selected_node) selected_node = null;
else selected_node = mousedown_node;
selected_link = null;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y);
restart();
})
.on('mouseup', function(d) {
if(!mousedown_node) return;
// needed by FF
drag_line
.classed('hidden', true)
.style('marker-end', '');
// check for drag-to-self
mouseup_node = d;
if(mouseup_node === mousedown_node) { resetMouseVars(); return; }
// unenlarge target node
d3.select(this).attr('transform', '');
// add link to graph (update if exists)
// NB: links are strictly source < target; arrows separately specified by booleans
var source, target, direction;
if(mousedown_node.id < mouseup_node.id) {
source = mousedown_node;
target = mouseup_node;
direction = 'right';
} else {
source = mouseup_node;
target = mousedown_node;
direction = 'left';
}
var link;
link = links.filter(function(l) {
return (l.source === source && l.target === target);
})[0];
if(link) {
link[direction] = true;
} else {
link = {source: source, target: target, left: false, right: false};
link[direction] = true;
links.push(link);
}
// select new link
selected_link = link;
selected_node = null;
restart();
});
// show node IDs
g.append('svg:text')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id')
.text(function(d) { return d.id; });
// remove old nodes
circle.exit().remove();
// set the graph in motion
force.start();
}
function mousedown() {
// prevent I-bar on drag
//d3.event.preventDefault();
// because :active only works in WebKit?
svg.classed('active', true);
if(d3.event.ctrlKey || mousedown_node || mousedown_link) return;
// insert new node at point
var point = d3.mouse(this),
node = {id: ++lastNodeId, reflexive: false};
node.x = point[0];
node.y = point[1];
nodes.push(node);
restart();
}
function mousemove() {
if(!mousedown_node) return;
// update drag line
drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);
restart();
}
function mouseup() {
if(mousedown_node) {
// hide drag line
drag_line
.classed('hidden', true)
.style('marker-end', '');
}
// because :active only works in WebKit?
svg.classed('active', false);
// clear mouse event vars
resetMouseVars();
}
function spliceLinksForNode(node) {
var toSplice = links.filter(function(l) {
return (l.source === node || l.target === node);
});
toSplice.map(function(l) {
links.splice(links.indexOf(l), 1);
});
}
// only respond once per keydown
var lastKeyDown = -1;
function keydown() {
d3.event.preventDefault();
if(lastKeyDown !== -1) return;
lastKeyDown = d3.event.keyCode;
// ctrl
if(d3.event.keyCode === 17) {
circle.call(force.drag);
svg.classed('ctrl', true);
}
if(!selected_node && !selected_link) return;
switch(d3.event.keyCode) {
case 8: // backspace
case 46: // delete
if(selected_node) {
nodes.splice(nodes.indexOf(selected_node), 1);
spliceLinksForNode(selected_node);
} else if(selected_link) {
links.splice(links.indexOf(selected_link), 1);
}
selected_link = null;
selected_node = null;
restart();
break;
case 66: // B
if(selected_link) {
// set link direction to both left and right
selected_link.left = true;
selected_link.right = true;
}
restart();
break;
case 76: // L
if(selected_link) {
// set link direction to left only
selected_link.left = true;
selected_link.right = false;
}
restart();
break;
case 82: // R
if(selected_node) {
// toggle node reflexivity
selected_node.reflexive = !selected_node.reflexive;
} else if(selected_link) {
// set link direction to right only
selected_link.left = false;
selected_link.right = true;
}
restart();
break;
}
}
function keyup() {
lastKeyDown = -1;
// ctrl
if(d3.event.keyCode === 17) {
circle
.on('mousedown.drag', null)
.on('touchstart.drag', null);
svg.classed('ctrl', false);
}
}
// app starts here
svg.on('mousedown', mousedown)
.on('mousemove', mousemove)
.on('mouseup', mouseup);
d3.select(window)
.on('keydown', keydown)
.on('keyup', keyup);
restart();
this is added for zooming and paning.
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom",this.zoomed.bind(this))).on("dblclick.zoom", null)
.append('svg:g');

Why marker-mid not working on line element in d3.js V4 [duplicate]

I want to place a marker in the middle of the links instead of placing it at the end as is done by my code.
Although I googled it, I am not able to find a solution for my code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../D3/d3.min.js"></script>
</head>
<body>
<style>
body {
background-color: #3a5795;
}
svg:not(.active):not(.ctrl) {
cursor: crosshair;
}
path.link {
fill: none;
stroke:floralwhite;
stroke-width: 4px;
cursor: default;
}
svg:not(.active):not(.ctrl) path.link {
cursor: pointer;
}
path.link.selected {
stroke-dasharray: 10,2;
}
path.link.dragline {
pointer-events: none;
}
path.link.hidden {
stroke-width: 0;
}
rect.node {
stroke-width: 1.5px;
cursor: pointer;
}
rect.node.reflexive {
stroke: #000 !important;
stroke-width: 2.5px;
}
text {
font: 12px sans-serif;
pointer-events: none;
}
text.id {
text-anchor: middle;
font-weight: bold;
}
</style>
<script type="text/javascript">
// set up SVG for D3
var width = 1400,
height = 800,
colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr('oncontextmenu', 'return false;')
.attr('width', width)
.attr('height', height);
// set up initial nodes and links
// - nodes are known by 'id', not by index in array.
// - reflexive edges are indicated on the node (as a bold black rect).
// - links are always source < target; edge directions are set by 'left' and 'right'.
var nodes = [
{
"id": "Component",
"description": "Component are the Containers",
"type":"wiring"
},
{
"id": "Form Design And Data Design",
"description": "In the Form Design and Data Design we can create form and data",
"type": "wiring"
},
{
"id": "Data and Property ",
"description": "All the Data has the Property and value Associated with It",
"type":"wiring"
},
{
"id": "Entity Query",
"description": "Entity Queries can be used to create Entity Relationship ",
"type": "wiring"
},
{
"id": "Entity Query and Entity Data",
"description": "Entity Data Can be used to create ",
"type": "wiring"
}
],
lastNodeId = 2,
links = [
];
// init D3 force layout
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(250)
.charge(-1000)
.gravity(0.05)
.on('tick', tick)
//define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
svg.append('svg:defs').append('svg:marker')
.attr('id', 'start-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 6)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M10,-5L0,0L10,5')
.attr('fill', '#000');
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr('class', 'link dragline hidden')
.attr('d', 'M0,0L0,0');
// handles to link and node element groups
var path = svg.append('svg:g').selectAll('path'),
rect = svg.append('svg:g').selectAll('g');
// mouse event vars
var selected_node = null,
selected_link = null,
mousedown_link = null,
mousedown_node = null,
mouseup_node = null;
function wrapText(text, width) {
text.each(function () {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
linenumber = 0,
lineHeight = 1.1, // ems
y = textEl.attr('y'),
dx = parseFloat(textEl.attr('dx') || 0),
dy = parseFloat(textEl.attr('dy') || 0),
tspan = textEl.text(null).append('tspan').attr('x', 0).attr('y', y).attr('dy', dy + 'em');
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(' '));
line = [word];
tspan = textEl.append('tspan').attr('x', 0).attr('y', y).attr('dx', dx).attr('dy', ++linenumber * lineHeight + dy + 'em').text(word);
}
}
});
}
function resetMouseVars() {
mousedown_node = null;
mouseup_node = null;
mousedown_link = null;
}
// update force layout (called automatically each iteration)
function tick() {
// draw directed edges with proper padding from node centers
path.attr('d', function (d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = d.left ? 17 : 12,
targetPadding = d.right ? 17 : 12,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY);
return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
});
rect.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
}
// update graph (called when needed)
function restart() {
// path (link) group
path = path.data(links);
// update existing links
path.classed('selected', function (d) { return d === selected_link; })
.style('marker-start', function (d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function (d) { return d.right ? 'url(#end-arrow)' : ''; });
// add new links
path.enter().append('svg:path')
.attr('class', 'link')
.classed('selected', function (d) { return d === selected_link; })
.style('marker-start', function (d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function (d) { return d.right ? 'url(#end-arrow)' : ''; })
.on('mousedown', function (d) {
if (d3.event.ctrlKey) return;
// select link
mousedown_link = d;
if (mousedown_link === selected_link) selected_link = null;
else selected_link = mousedown_link;
selected_node = null;
restart();
});
// remove old links
path.exit().remove();
// rect (node) group
// NB: the function arg is crucial here! nodes are known by id, not by index!
rect = rect.data(nodes, function (d) { return d.id; });
// update existing nodes (reflexive & selected visual states)
rect.selectAll('rect')
.style('fill', function (d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.classed('reflexive', function (d) { return d.reflexive; });
// add new nodes
var g = rect.enter().append('svg:g');
//g.append('svg:rect')
// .attr('class', 'node')
// .attr('r', 30)
g.append('svg:rect')
.attr('class', 'node')
.attr('width', 150)
.attr("height", 60)
.attr("rx", 30)
.attr("ry", 30)
.attr("x", -75)
.attr("y", -16.5)
.style('fill', function (d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.style('stroke', function (d) { return d3.rgb(colors(d.id)).darker().toString(); })
.classed('reflexive', function (d) { return d.reflexive; })
.on('mouseover', function (d) {
if (!mousedown_node || d === mousedown_node) return;
// enlarge target node
d3.select(this).attr('transform', 'scale(1.1)');
})
.on('mouseout', function (d) {
if (!mousedown_node || d === mousedown_node) return;
// unenlarge target node
d3.select(this).attr('transform', '');
})
.on('mousedown', function (d) {
if (d3.event.ctrlKey) return;
// select node
mousedown_node = d;
if (mousedown_node === selected_node) selected_node = null;
else selected_node = mousedown_node;
selected_link = null;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y);
restart();
})
.on('mouseup', function (d) {
if (!mousedown_node) return;
// needed by FF
drag_line
.classed('hidden', true)
.style('marker-end', '');
// check for drag-to-self
mouseup_node = d;
if (mouseup_node === mousedown_node) { resetMouseVars(); return; }
// unenlarge target node
d3.select(this).attr('transform', '');
// add link to graph (update if exists)
// NB: links are strictly source < target; arrows separately specified by booleans
var source, target, direction;
if (mousedown_node.id < mouseup_node.id) {
source = mousedown_node;
target = mouseup_node;
direction = 'right';
} else {
source = mouseup_node;
target = mousedown_node;
direction = 'left';
}
var link;
link = links.filter(function (l) {
return (l.source === source && l.target === target);
})[0];
if (link) {
link[direction] = true;
} else {
link = { source: source, target: target, left: false, right: false };
link[direction] = true;
links.push(link);
}
// select new link
selected_link = link;
selected_node = null;
restart();
});
// show node IDs
g.append('svg:text')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id')
.text(function (d) { return d.id; })
.call(wrapText, 100);
// remove old nodes
rect.exit().remove();
// set the graph in motion
force.start();
}
function mousedown() {
// prevent I-bar on drag
//d3.event.preventDefault();
// because :active only works in WebKit?
svg.classed('active', true);
if (d3.event.ctrlKey || mousedown_node || mousedown_link) return;
// insert new node at point
var point = d3.mouse(this),
node = { id: ++lastNodeId, reflexive: false };
node.x = point[0];
node.y = point[1];
nodes.push(node);
restart();
}
function mousemove() {
if (!mousedown_node) return;
// update drag line
drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);
restart();
}
function mouseup() {
if (mousedown_node) {
// hide drag line
drag_line
.classed('hidden', true)
.style('marker-end', '');
}
// because :active only works in WebKit?
svg.classed('active', false);
// clear mouse event vars
resetMouseVars();
}
function spliceLinksForNode(node) {
var toSplice = links.filter(function (l) {
return (l.source === node || l.target === node);
});
toSplice.map(function (l) {
links.splice(links.indexOf(l), 1);
});
}
// only respond once per keydown
var lastKeyDown = -1;
function keydown() {
//d3.event.preventDefault();
if (lastKeyDown !== -1) return;
lastKeyDown = d3.event.keyCode;
// ctrl
if (d3.event.keyCode === 17) {
rect.call(force.drag);
svg.classed('ctrl', true);
}
if (!selected_node && !selected_link) return;
switch (d3.event.keyCode) {
case 8: // backspace
case 46: // delete
if (selected_node) {
nodes.splice(nodes.indexOf(selected_node), 1);
spliceLinksForNode(selected_node);
} else if (selected_link) {
links.splice(links.indexOf(selected_link), 1);
}
selected_link = null;
selected_node = null;
restart();
break;
case 66: // B
if (selected_link) {
// set link direction to both left and right
selected_link.left = true;
selected_link.right = true;
}
restart();
break;
case 76: // L
if (selected_link) {
// set link direction to left only
selected_link.left = true;
selected_link.right = false;
}
restart();
break;
case 82: // R
if (selected_node) {
// toggle node reflexivity
selected_node.reflexive = !selected_node.reflexive;
} else if (selected_link) {
// set link direction to right only
selected_link.left = false;
selected_link.right = true;
}
restart();
break;
}
}
function keyup() {
lastKeyDown = -1;
// ctrl
if (d3.event.keyCode === 17) {
rect
.on('mousedown.drag', null)
.on('touchstart.drag', null);
svg.classed('ctrl', false);
}
}
// app starts here
svg.on('mousedown', mousedown)
.on('mousemove', mousemove)
.on('mouseup', mouseup);
d3.select(window)
.on('keydown', keydown)
.on('keyup', keyup);
restart();
</script>
</body>
</html>
To draw markers on the mid point of your links you can use marker-mid which works pretty much like marker-start and marker-end except that it inserts a marker element at the middle.
path.enter().append('svg:path')
.style('marker-mid', function (d) { return 'url(#start-arrow)'; })
For demonstration purposes I have just used the start-arrow here, which may of course be adjusted to your liking.
However, the marker will only be drawn, if there is a vertex at the mid point. This is not true for your code, because you are drawing a single straight line from source to target defining only start and end points. On the other hand, having a straight line comes in handy, because it is fairly easy to calculate the mid point and split up the straight line into two segements, thereby inserting a new vertex at the middle. There are already calculations going on in your tick() handler giving intermediate results to assist in finding the mid point:
// Coordinates of mid point on line to add new vertex.
midX = (targetX - sourceX) / 2 + sourceX;
midY = (targetY - sourceY) / 2 + sourceY;
// | v --- new vertex --- v |
return 'M' + sourceX + ',' + sourceY + 'L' + midX + ',' + midY + 'L' + targetX + ',' + targetY;
Have a look at the following code snippet for a working demo.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../D3/d3.min.js"></script>
</head>
<body>
<style>
body {
background-color: #3a5795;
}
svg:not(.active):not(.ctrl) {
cursor: crosshair;
}
path.link {
fill: none;
stroke:floralwhite;
stroke-width: 4px;
cursor: default;
}
svg:not(.active):not(.ctrl) path.link {
cursor: pointer;
}
path.link.selected {
stroke-dasharray: 10,2;
}
path.link.dragline {
pointer-events: none;
}
path.link.hidden {
stroke-width: 0;
}
rect.node {
stroke-width: 1.5px;
cursor: pointer;
}
rect.node.reflexive {
stroke: #000 !important;
stroke-width: 2.5px;
}
text {
font: 12px sans-serif;
pointer-events: none;
}
text.id {
text-anchor: middle;
font-weight: bold;
}
</style>
<script type="text/javascript">
// set up SVG for D3
var width = 1400,
height = 800,
colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr('oncontextmenu', 'return false;')
.attr('width', width)
.attr('height', height);
// set up initial nodes and links
// - nodes are known by 'id', not by index in array.
// - reflexive edges are indicated on the node (as a bold black rect).
// - links are always source < target; edge directions are set by 'left' and 'right'.
var nodes = [
{
"id": "Component",
"description": "Component are the Containers",
"type":"wiring"
},
{
"id": "Form Design And Data Design",
"description": "In the Form Design and Data Design we can create form and data",
"type": "wiring"
},
{
"id": "Data and Property ",
"description": "All the Data has the Property and value Associated with It",
"type":"wiring"
},
{
"id": "Entity Query",
"description": "Entity Queries can be used to create Entity Relationship ",
"type": "wiring"
},
{
"id": "Entity Query and Entity Data",
"description": "Entity Data Can be used to create ",
"type": "wiring"
}
],
lastNodeId = 2,
links = [
];
// init D3 force layout
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(250)
.charge(-1000)
.gravity(0.05)
.on('tick', tick)
//define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
svg.append('svg:defs').append('svg:marker')
.attr('id', 'start-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 6)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M10,-5L0,0L10,5')
.attr('fill', '#000');
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr('class', 'link dragline hidden')
.attr('d', 'M0,0L0,0');
// handles to link and node element groups
var path = svg.append('svg:g').selectAll('path'),
rect = svg.append('svg:g').selectAll('g');
// mouse event vars
var selected_node = null,
selected_link = null,
mousedown_link = null,
mousedown_node = null,
mouseup_node = null;
function wrapText(text, width) {
text.each(function () {
var textEl = d3.select(this),
words = textEl.text().split(/\s+/).reverse(),
word,
line = [],
linenumber = 0,
lineHeight = 1.1, // ems
y = textEl.attr('y'),
dx = parseFloat(textEl.attr('dx') || 0),
dy = parseFloat(textEl.attr('dy') || 0),
tspan = textEl.text(null).append('tspan').attr('x', 0).attr('y', y).attr('dy', dy + 'em');
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(' '));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(' '));
line = [word];
tspan = textEl.append('tspan').attr('x', 0).attr('y', y).attr('dx', dx).attr('dy', ++linenumber * lineHeight + dy + 'em').text(word);
}
}
});
}
function resetMouseVars() {
mousedown_node = null;
mouseup_node = null;
mousedown_link = null;
}
// update force layout (called automatically each iteration)
function tick() {
console.log(path);
// draw directed edges with proper padding from node centers
path.attr('d', function (d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = d.left ? 17 : 12,
targetPadding = d.right ? 17 : 12,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY),
midX = (targetX - sourceX) / 2 + sourceX,
midY = (targetY - sourceY) / 2 + sourceY;
return 'M' + sourceX + ',' + sourceY + 'L' + midX + ',' + midY + 'L' + targetX + ',' + targetY;
});
rect.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
}
// update graph (called when needed)
function restart() {
// path (link) group
path = path.data(links);
// update existing links
path.classed('selected', function (d) { return d === selected_link; })
.style('marker-start', function (d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-mid', function (d) { return 'url(#start-arrow)'; })
.style('marker-end', function (d) { return d.right ? 'url(#end-arrow)' : ''; });
// add new links
path.enter().append('svg:path')
.attr('class', 'link')
.classed('selected', function (d) { return d === selected_link; })
.style('marker-start', function (d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-mid', function (d) { return 'url(#start-arrow)'; })
.style('marker-end', function (d) { return d.right ? 'url(#end-arrow)' : ''; })
.on('mousedown', function (d) {
if (d3.event.ctrlKey) return;
// select link
mousedown_link = d;
if (mousedown_link === selected_link) selected_link = null;
else selected_link = mousedown_link;
selected_node = null;
restart();
});
// remove old links
path.exit().remove();
// rect (node) group
// NB: the function arg is crucial here! nodes are known by id, not by index!
rect = rect.data(nodes, function (d) { return d.id; });
// update existing nodes (reflexive & selected visual states)
rect.selectAll('rect')
.style('fill', function (d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.classed('reflexive', function (d) { return d.reflexive; });
// add new nodes
var g = rect.enter().append('svg:g');
//g.append('svg:rect')
// .attr('class', 'node')
// .attr('r', 30)
g.append('svg:rect')
.attr('class', 'node')
.attr('width', 150)
.attr("height", 60)
.attr("rx", 30)
.attr("ry", 30)
.attr("x", -75)
.attr("y", -16.5)
.style('fill', function (d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.style('stroke', function (d) { return d3.rgb(colors(d.id)).darker().toString(); })
.classed('reflexive', function (d) { return d.reflexive; })
.on('mouseover', function (d) {
if (!mousedown_node || d === mousedown_node) return;
// enlarge target node
d3.select(this).attr('transform', 'scale(1.1)');
})
.on('mouseout', function (d) {
if (!mousedown_node || d === mousedown_node) return;
// unenlarge target node
d3.select(this).attr('transform', '');
})
.on('mousedown', function (d) {
if (d3.event.ctrlKey) return;
// select node
mousedown_node = d;
if (mousedown_node === selected_node) selected_node = null;
else selected_node = mousedown_node;
selected_link = null;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y);
restart();
})
.on('mouseup', function (d) {
if (!mousedown_node) return;
// needed by FF
drag_line
.classed('hidden', true)
.style('marker-end', '');
// check for drag-to-self
mouseup_node = d;
if (mouseup_node === mousedown_node) { resetMouseVars(); return; }
// unenlarge target node
d3.select(this).attr('transform', '');
// add link to graph (update if exists)
// NB: links are strictly source < target; arrows separately specified by booleans
var source, target, direction;
if (mousedown_node.id < mouseup_node.id) {
source = mousedown_node;
target = mouseup_node;
direction = 'right';
} else {
source = mouseup_node;
target = mousedown_node;
direction = 'left';
}
var link;
link = links.filter(function (l) {
return (l.source === source && l.target === target);
})[0];
if (link) {
link[direction] = true;
} else {
link = { source: source, target: target, left: false, right: false };
link[direction] = true;
links.push(link);
}
// select new link
selected_link = link;
selected_node = null;
restart();
});
// show node IDs
g.append('svg:text')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id')
.text(function (d) { return d.id; })
.call(wrapText, 100);
// remove old nodes
rect.exit().remove();
// set the graph in motion
force.start();
}
function mousedown() {
// prevent I-bar on drag
//d3.event.preventDefault();
// because :active only works in WebKit?
svg.classed('active', true);
if (d3.event.ctrlKey || mousedown_node || mousedown_link) return;
// insert new node at point
var point = d3.mouse(this),
node = { id: ++lastNodeId, reflexive: false };
node.x = point[0];
node.y = point[1];
nodes.push(node);
restart();
}
function mousemove() {
if (!mousedown_node) return;
// update drag line
drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);
restart();
}
function mouseup() {
if (mousedown_node) {
// hide drag line
drag_line
.classed('hidden', true)
.style('marker-end', '');
}
// because :active only works in WebKit?
svg.classed('active', false);
// clear mouse event vars
resetMouseVars();
}
function spliceLinksForNode(node) {
var toSplice = links.filter(function (l) {
return (l.source === node || l.target === node);
});
toSplice.map(function (l) {
links.splice(links.indexOf(l), 1);
});
}
// only respond once per keydown
var lastKeyDown = -1;
function keydown() {
//d3.event.preventDefault();
if (lastKeyDown !== -1) return;
lastKeyDown = d3.event.keyCode;
// ctrl
if (d3.event.keyCode === 17) {
rect.call(force.drag);
svg.classed('ctrl', true);
}
if (!selected_node && !selected_link) return;
switch (d3.event.keyCode) {
case 8: // backspace
case 46: // delete
if (selected_node) {
nodes.splice(nodes.indexOf(selected_node), 1);
spliceLinksForNode(selected_node);
} else if (selected_link) {
links.splice(links.indexOf(selected_link), 1);
}
selected_link = null;
selected_node = null;
restart();
break;
case 66: // B
if (selected_link) {
// set link direction to both left and right
selected_link.left = true;
selected_link.right = true;
}
restart();
break;
case 76: // L
if (selected_link) {
// set link direction to left only
selected_link.left = true;
selected_link.right = false;
}
restart();
break;
case 82: // R
if (selected_node) {
// toggle node reflexivity
selected_node.reflexive = !selected_node.reflexive;
} else if (selected_link) {
// set link direction to right only
selected_link.left = false;
selected_link.right = true;
}
restart();
break;
}
}
function keyup() {
lastKeyDown = -1;
// ctrl
if (d3.event.keyCode === 17) {
rect
.on('mousedown.drag', null)
.on('touchstart.drag', null);
svg.classed('ctrl', false);
}
}
// app starts here
svg.on('mousedown', mousedown)
.on('mousemove', mousemove)
.on('mouseup', mouseup);
d3.select(window)
.on('keydown', keydown)
.on('keyup', keyup);
restart();
</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