javascript - How to place marker head in the middle of the links -
i want place marker in middle of links instead of placing @ end done code.
although googled it, not able find solution 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 svg 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 initial nodes , links // - nodes known 'id', not index in array. // - reflexive edges indicated on node (as bold black rect). // - links source < target; edge directions set 'left' , 'right'. var nodes = [ { "id": "component", "description": "component containers", "type":"wiring" }, { "id": "form design , data design", "description": "in form design , data design can create form , data", "type": "wiring" }, { "id": "data , property ", "description": "all data has property , value associated it", "type":"wiring" }, { "id": "entity query", "description": "entity queries can used create entity relationship ", "type": "wiring" }, { "id": "entity query , entity data", "description": "entity data can used 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 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 link , 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 proper padding 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: function arg crucial here! nodes known id, not 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 ff drag_line .classed('hidden', true) .style('marker-end', ''); // check drag-to-self mouseup_node = d; if (mouseup_node === mousedown_node) { resetmousevars(); return; } // unenlarge target node d3.select(this).attr('transform', ''); // add link graph (update if exists) // nb: links strictly source < target; arrows separately specified 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 graph in motion force.start(); } function mousedown() { // prevent i-bar on drag //d3.event.preventdefault(); // because :active works in webkit? svg.classed('active', true); if (d3.event.ctrlkey || mousedown_node || mousedown_link) return; // insert new node @ 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 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); }); } // 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 both left , right selected_link.left = true; selected_link.right = true; } restart(); break; case 76: // l if (selected_link) { // set link direction left 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 right 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 mid point of links can use marker-mid
works pretty marker-start
, marker-end
except inserts marker element @ middle.
path.enter().append('svg:path') .style('marker-mid', function (d) { return 'url(#start-arrow)'; })
for demonstration purposes have used start-arrow
here, may of course adjusted liking.
however, marker drawn, if there vertex @ mid point. not true code, because drawing single straight line source target defining start , end points. on other hand, having straight line comes in handy, because easy calculate mid point , split straight line 2 segements, thereby inserting new vertex @ middle. there calculations going on in tick()
handler giving intermediate results assist in finding mid point:
// coordinates of mid point on line 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 @ following code snippet 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 svg 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 initial nodes , links // - nodes known 'id', not index in array. // - reflexive edges indicated on node (as bold black rect). // - links source < target; edge directions set 'left' , 'right'. var nodes = [ { "id": "component", "description": "component containers", "type":"wiring" }, { "id": "form design , data design", "description": "in form design , data design can create form , data", "type": "wiring" }, { "id": "data , property ", "description": "all data has property , value associated it", "type":"wiring" }, { "id": "entity query", "description": "entity queries can used create entity relationship ", "type": "wiring" }, { "id": "entity query , entity data", "description": "entity data can used 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 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 link , 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 proper padding 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: function arg crucial here! nodes known id, not 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 ff drag_line .classed('hidden', true) .style('marker-end', ''); // check drag-to-self mouseup_node = d; if (mouseup_node === mousedown_node) { resetmousevars(); return; } // unenlarge target node d3.select(this).attr('transform', ''); // add link graph (update if exists) // nb: links strictly source < target; arrows separately specified 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 graph in motion force.start(); } function mousedown() { // prevent i-bar on drag //d3.event.preventdefault(); // because :active works in webkit? svg.classed('active', true); if (d3.event.ctrlkey || mousedown_node || mousedown_link) return; // insert new node @ 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 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); }); } // 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 both left , right selected_link.left = true; selected_link.right = true; } restart(); break; case 76: // l if (selected_link) { // set link direction left 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 right 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>
Comments
Post a Comment