简体   繁体   中英

Draw arrow inside a datamaps using d3.js

I am new to d3.js , i am trying to create a custom map svg .

With some reference i did a custom map which is shown below.

箭头指向页面左下角

The code snippet for the above output is here.

https://jsfiddle.net/9kbp4h6j/

 "use strict" var svg = d3.select("body").append("svg").append("g").attr("transform", "translate(100,50)") svg.append("svg:defs") .append("svg:marker") .attr("id", "arrow") .attr("refX", 2) .attr("refY", 6) .attr("markerWidth", 13) .attr("markerHeight", 13) .attr("orient", "auto") .append("svg:path") .attr("d", "M2,2 L2,11 L10,6 L2,2"); var line = d3.svg.line() .x(function (point) { return point.lx; }) .y(function (point) { return point.ly; }); function lineData(d) { // i'm assuming here that supplied datum // is a link between 'source' and 'target' var points = [{ lx: d.source.x, ly: d.source.y }, { lx: d.target.x, ly: d.target.y } ]; return line(points); } var path = svg.append("path") .data([{ source: { x: 0, y: 0 }, target: { x: 80, y: 80 } }]) .attr("class", "line") //.style("marker-end", "url(#arrow)") .attr("d", lineData); //var arrow = svg.append("svg:path") //.attr("d", "M2,2 L2,11 L10,6 L2,2"); console.log(d3.svg.symbol()) var arrow = svg.append("svg:path") .attr("d", d3.svg.symbol().type("triangle-down")(10, 1)); arrow.transition() .duration(2000) .ease("linear") .attrTween("transform", translateAlong(path.node())) //.each("end", transition); // Returns an attrTween for translating along the specified path element. function translateAlong(path) { var l = path.getTotalLength(); var ps = path.getPointAtLength(0); var pe = path.getPointAtLength(l); var angl = Math.atan2(pe.y - ps.y, pe.x - ps.x) * (180 / Math.PI) - 90; var rot_tran = "rotate(" + angl + ")"; return function (d, i, a) { console.log(d); return function (t) { var p = path.getPointAtLength(t * l); return "translate(" + px + "," + py + ") " + rot_tran; }; }; } var totalLength = path.node().getTotalLength(); path .attr("stroke-dasharray", totalLength + " " + totalLength) .attr("stroke-dashoffset", totalLength) .transition() .duration(2000) .ease("linear") .attr("stroke-dashoffset", 0); var bubble_map = new Datamap({ element: document.getElementById('canada'), scope: 'canada', geographyConfig: { popupOnHover: true, highlightOnHover: true, borderColor: '#444', borderWidth: 0.5, dataUrl: 'https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/canada.topo.json' //dataJson: topoJsonData }, fills: { 'MAJOR': '#306596', 'MEDIUM': '#0fa0fa', 'MINOR': '#bada55', defaultFill: '#dddddd' }, data: { 'JH': { fillKey: 'MINOR' }, 'MH': { fillKey: 'MINOR' } }, setProjection: function (element) { var projection = d3.geo.mercator() .center([-106.3468, 68.1304]) // always in [East Latitude, North Longitude] .scale(250) .translate([element.offsetWidth / 2, element.offsetHeight / 2]); var path = d3.geo.path().projection(projection); return { path: path, projection: projection }; } }); let bubbles = [{ centered: "MB", fillKey: "MAJOR", radius: 8, state: "Manitoba" }, { centered: "AB", fillKey: "MAJOR", radius: 8, state: "Alberta" }, { centered: "NT", fillKey: "MAJOR", radius: 8, state: "Northwest Territories" }, { centered: "NU", fillKey: "MEDIUM", radius: 8, state: "Nunavut" }, { centered: "BC ", fillKey: "MEDIUM", radius: 8, state: "British Columbia" }, { centered: "QC", fillKey: "MINOR", radius: 8, state: "Québec" }, { centered: "NB", fillKey: "MINOR", radius: 8, state: "New Brunswick" } ] // // ISO ID code for city or <state></state> setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely. bubble_map.bubbles(bubbles, { popupTemplate: function (geo, data) { return `<div class="hoverinfo">city: ${data.state}, Slums: ${data.radius}%</div>`; } }); }, 1000);
 .line { stroke: blue; stroke-width: 1.5px; fill: white; } circle { fill: red; } #marker { stroke: black; fill: black; }
 <!DOCTYPE html> <html> <meta charset="utf-8"> <body> <script src="http://d3js.org/d3.v3.min.js"></script> <script src="http://d3js.org/topojson.v1.min.js"></script> <script src="https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/datamaps.none.js"></script> <div id="canada" style="height: 600px; width: 900px;"></div> </body> </html>

I have a marker which is attached to the body , but the actual output what i need is,

  1. The arrow must be starting from the bubble shown in the image
  2. It should end on some random directions so that a popup template box can be added to describe the actual location.

So at last the actual output what i need should look somewhat like this.

箭头从每个位置指出

Any help appreciated.

Lines can be individually customized by including 2 fields in data: arrowDirectionAngle and arrowLineLength .

 "use strict" var line = d3.svg.line() .x(function (point) { return point.lx; }) .y(function (point) { return point.ly; }); function lineData(d) { // i'm assuming here that supplied datum // is a link between 'source' and 'target' var points = [{ lx: d.source.x, ly: d.source.y }, { lx: d.target.x, ly: d.target.y } ]; return line(points); } // Returns an attrTween for translating along the specified path element. function translateAlong(path) { var l = path.getTotalLength(); var ps = path.getPointAtLength(0); var pe = path.getPointAtLength(l); var angl = Math.atan2(pe.y - ps.y, pe.x - ps.x) * (180 / Math.PI) - 90; var rot_tran = "rotate(" + angl + ")"; return function (d, i, a) { //console.log(d); return function (t) { var p = path.getPointAtLength(t * l); return "translate(" + px + "," + py + ") " + rot_tran; }; }; } var bubble_map = new Datamap({ element: document.getElementById('canada'), scope: 'canada', geographyConfig: { popupOnHover: true, highlightOnHover: true, borderColor: '#444', borderWidth: 0.5, dataUrl: 'https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/canada.topo.json' //dataJson: topoJsonData }, fills: { 'MAJOR': '#306596', 'MEDIUM': '#0fa0fa', 'MINOR': '#bada55', defaultFill: '#dddddd' }, data: { 'JH': { fillKey: 'MINOR' }, 'MH': { fillKey: 'MINOR' } }, setProjection: function (element) { var projection = d3.geo.mercator() .center([-106.3468, 68.1304]) // always in [East Latitude, North Longitude] .scale(250) .translate([element.offsetWidth / 2, element.offsetHeight / 2]); var path = d3.geo.path().projection(projection); return { path: path, projection: projection }; } }); let bubbles = [{ centered: "MB", fillKey: "MAJOR", radius: 8, state: "Manitoba", arrowDirectionAngle: 90, arrowLineLength: 120 }, { centered: "AB", fillKey: "MAJOR", radius: 8, state: "Alberta", arrowDirectionAngle: 90, arrowLineLength: 100 }, { centered: "NT", fillKey: "MAJOR", radius: 8, state: "Northwest Territories", arrowDirectionAngle: 180, arrowLineLength: 130 }, { centered: "NU", fillKey: "MEDIUM", radius: 8, state: "Nunavut", arrowDirectionAngle: -25, arrowLineLength: 80 }, { centered: "BC ", fillKey: "MEDIUM", radius: 8, state: "British Columbia", arrowDirectionAngle: 125, arrowLineLength: 65 }, { centered: "QC", fillKey: "MINOR", radius: 8, state: "Québec", arrowDirectionAngle: -25, arrowLineLength: 70 }, { centered: "NB", fillKey: "MINOR", radius: 8, state: "New Brunswick", arrowDirectionAngle: 65, arrowLineLength: 50 } ] function renderArrows(targetElementId) { let svgRoot = d3.select("#" + targetElementId).select("svg"); svgRoot.append("svg:defs") .append("svg:marker") .attr("id", "arrow") .attr("refX", 2) .attr("refY", 6) .attr("markerWidth", 13) .attr("markerHeight", 13) .attr("orient", "auto") .append("svg:path") .attr("d", "M2,2 L2,11 L10,6 L2,2"); let linesGroup = svgRoot.append("g"); linesGroup.attr("class", "lines"); let bubbleElements = svgRoot.selectAll(".datamaps-bubble")[0]; bubbleElements.forEach(function (bubbleElement) { let xPosition = bubbleElement.cx.baseVal.value; let yPosition = bubbleElement.cy.baseVal.value; let datum = d3.select(bubbleElement).datum(); let degree = datum.arrowDirectionAngle; let radius = datum.arrowLineLength; let theta = degree * Math.PI / 180; let path = linesGroup.append("path") .data([{ source: { x: xPosition, y: yPosition }, target: { x: xPosition + radius * Math.cos(theta), y: yPosition + radius * Math.sin(theta) } }]) .style("stroke", "blue") .style("stroke-width", "1.5px") .style("fill", "white") //.style("marker-end", "url(#arrow)") .attr("d", lineData); let arrow = svgRoot.append("svg:path") .attr("d", d3.svg.symbol().type("triangle-down")(10, 1)); arrow.transition() .duration(2000) .ease("linear") .attrTween("transform", translateAlong(path.node())) var totalLength = path.node().getTotalLength(); path .attr("stroke-dasharray", totalLength + " " + totalLength) .attr("stroke-dashoffset", totalLength) .transition() .duration(2000) .ease("linear") .attr("stroke-dashoffset", 0); }); } // // ISO ID code for city or <state></state> setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely. bubble_map.bubbles(bubbles, { popupTemplate: function (geo, data) { return `<div class="hoverinfo">city: ${data.state}, Slums: ${data.radius}%</div>`; } }); renderArrows("canada"); }, 1000);
 .line { stroke: blue; stroke-width: 1.5px; fill: white; } circle { fill: red; } #marker { stroke: black; fill: black; }
 <!DOCTYPE html> <html> <meta charset="utf-8"> <body> <script src="http://d3js.org/d3.v3.min.js"></script> <script src="http://d3js.org/topojson.v1.min.js"></script> <script src="https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/datamaps.none.js"></script> <div id="canada" style="height: 600px; width: 900px;"></div> </body> </html>

Lines add separately using path line and arrow, and using mouse event you can manipulate the location of the path based on the center of the hovered bubble, also for the tooltip you can use d3.select() to get the div of thee tooltip and change its innerHTML attribute to display a message.

Here is a working solution:

 var svg = d3.select("body").append("svg").append("g").attr("transform", "translate(100,50)") svg.append("svg:defs") .append("svg:marker") .attr("id", "arrow") .attr("refX", 2) .attr("refY", 6) .attr("markerWidth", 13) .attr("markerHeight", 13) .attr("orient", "auto") .append("svg:path") .attr("d", "M2,2 L2,11 L10,6 L2,2"); var line = d3.svg.line() .x( function(point) { return point.lx; }) .y( function(point) { return point.ly; }); function lineData(d){ // i'm assuming here that supplied datum // is a link between 'source' and 'target' var points = [ {lx: d.source.x, ly: d.source.y}, {lx: d.target.x, ly: d.target.y} ]; return line(points); } // Returns an attrTween for translating along the specified path element. function translateAlong(path) { var l = path.getTotalLength(); var ps = path.getPointAtLength(0); var pe = path.getPointAtLength(l); var angl = Math.atan2(pe.y - ps.y, pe.x - ps.x) * (180 / Math.PI) - 90; var rot_tran = "rotate(" + angl + ")"; return function(d, i, a) { return function(t) { var p = path.getPointAtLength(t * l); if(t < 0.111) { return ''; } return "translate(" + px + "," + py + ") " + rot_tran; }; }; } var bubble_map = new Datamap({ element: document.getElementById('canada'), scope: 'canada', geographyConfig: { popupOnHover: true, highlightOnHover: true, borderColor: '#444', borderWidth: 0.5, dataUrl: 'https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/canada.topo.json' //dataJson: topoJsonData }, fills: { 'MAJOR': '#306596', 'MEDIUM': '#0fa0fa', 'MINOR': '#bada55', defaultFill: '#dddddd' }, data: { 'JH': { fillKey: 'MINOR' }, 'MH': { fillKey: 'MINOR' } }, setProjection: function (element) { var projection = d3.geo.mercator() .center([-106.3468, 68.1304]) // always in [East Latitude, North Longitude] .scale(250) .translate([element.offsetWidth / 2, element.offsetHeight / 2]); var path = d3.geo.path().projection(projection); return { path: path, projection: projection }; } }); let bubbles = [ { centered: "MB", fillKey: "MAJOR", radius: 8, state: "Manitoba" }, { centered: "AB", fillKey: "MAJOR", radius: 8, state: "Alberta" }, { centered: "NT", fillKey: "MAJOR", radius: 8, state: "Northwest Territories" }, { centered: "NU", fillKey: "MEDIUM", radius: 8, state: "Nunavut" }, { centered: "BC ", fillKey: "MEDIUM", radius: 8, state: "British Columbia" }, { centered: "QC", fillKey: "MINOR", radius: 8, state: "Québec" }, { centered: "NB", fillKey: "MINOR", radius: 8, state: "New Brunswick" } ]; // ISO ID code for city or <state></state> setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely. bubble_map.bubbles(bubbles, { popupTemplate: function (geo, data) { return ``; // return `<div class="hoverinfo">city: ${data.state}, Slums: ${data.radius}%</div>`; } }); const line_data = [{source: {x:0, y:0}, target: {x:100, y:100}}]; var path = d3.select('.datamap').append("path") .data(line_data) .attr("class", "line") .attr("d", lineData); var arrow = d3.select('.datamap').append("svg:path") .attr('d', null) .attr('class', 'tri'); }, 1000); setTimeout(() => { // only start drawing bubbles on the map when map has rendered completely. const svg = d3.select('.datamap'); var circles = d3.selectAll('circle'); var state_data = d3.selectAll('circle').data(); circles.on("mouseover", function(d, i) { if(!document.querySelectorAll(".active").length) { let x = circles[0][i].cx.baseVal.value; let y = circles[0][i].cy.baseVal.value+8; state_info = state_data[i]; const line_data = [{source: {x, y}, target: {x , y : y + 100}}]; if(i === 2) { line_data[0].source.x = line_data[0].source.x - 8; line_data[0].source.y = line_data[0].source.y - 8; line_data[0].target.y = line_data[0].source.y; line_data[0].target.x = line_data[0].source.x - 150; } if(i === 3) { line_data[0].source.x = line_data[0].source.x + 8; line_data[0].source.y = line_data[0].source.y - 8; line_data[0].target.y = line_data[0].source.y; line_data[0].target.x = line_data[0].source.x + 100; } if(i === 4) { line_data[0].source.x = line_data[0].source.x - 8; line_data[0].source.y = line_data[0].source.y - 8; line_data[0].target.y = line_data[0].source.y + 50; line_data[0].target.x = line_data[0].source.x / 2; } if(i === 5) { line_data[0].source.x = line_data[0].source.x + 8; line_data[0].source.y = line_data[0].source.y - 8; line_data[0].target.y = line_data[0].source.y; line_data[0].target.x = line_data[0].source.x + 100; } var path = d3.select('path.line'); path.data(line_data) .attr("class", "line") .attr("d", lineData); var arrow = d3.select('.tri'); arrow.attr("d", d3.svg.symbol().type("triangle-down")(10,1)); arrow.interrupt(); arrow.transition() .duration(2000) .ease("linear") .attr("class", "tri") .attrTween("transform", translateAlong(path.node())) .each("end", () => { d3.select('.datamaps-hoverover') .style('display','block') .style('left', (line_data[0].target.x + 10) + "px") .style('top', (line_data[0].target.y + 10) +"px") .html('<div class="hoverinfo"><strong> this is from the custom tooltip: city: ' + state_info.state + ', Slum: ' + state_info.radius + '%</strong></div>'); }); var totalLength = path.node().getTotalLength(); path.interrupt(); path .attr("stroke-dasharray", totalLength + " " + totalLength) .attr("stroke-dashoffset", totalLength) .transition() .duration(2000) .ease("linear") .attr("stroke-dashoffset", 0); } }); circles.on("mouseout", function(d, i) { d3.select('path.line').attr('d', null); d3.select('path.tri').attr('d', null); d3.select('.datamaps-hoverover') .style('display','none') .html(''); }) }, 1500);
 .line { stroke: blue !important; fill: blue } .tri { stroke: blue !important; fill: blue } circle { fill: red; } #marker { stroke: black; fill: black; }
 <!DOCTYPE html> <meta charset="utf-8"> <html> <head> <script src="https://d3js.org/d3.v3.min.js"></script> <script src="https://d3js.org/topojson.v1.min.js"></script> <script src="https://rawgit.com/Anujarya300/bubble_maps/master/data/geography-data/datamaps.none.js"></script> </head> <body> <div id="canada" style="height: 600px; width: 900px;"></div> </body> </html>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM