使用 d3.js 在数据图中绘制箭头
Draw arrow inside a datamaps using d3.js
我是 d3.js 的新手,我正在尝试创建自定义地图 svg。
根据一些参考,我做了一个自定义地图,如下所示。
上面输出的代码片段在这里。
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(" + p.x + "," + p.y + ") " + 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>
我有一个附加到 body 的标记,但我需要的实际输出是,
- The arrow must be starting from the bubble shown in the image
- It should end on some random directions so that a popup template box can be added to describe the actual location.
所以最后我需要的实际输出应该看起来像这样。
感谢任何帮助。
可以通过在数据中包含 2 个字段来单独自定义行:arrowDirectionAngle
和 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(" + p.x + "," + p.y + ") " + 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>
使用路径线和箭头分别添加线条,使用鼠标事件您可以根据悬停气泡的中心来操纵路径的位置,对于工具提示您也可以使用 d3.select()
来获取div
工具提示并更改其 innerHTML
属性以显示消息。
这是一个可行的解决方案:
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(" + p.x + "," + p.y + ") " + 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>
我是 d3.js 的新手,我正在尝试创建自定义地图 svg。
根据一些参考,我做了一个自定义地图,如下所示。
上面输出的代码片段在这里。
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(" + p.x + "," + p.y + ") " + 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>
我有一个附加到 body 的标记,但我需要的实际输出是,
- The arrow must be starting from the bubble shown in the image
- It should end on some random directions so that a popup template box can be added to describe the actual location.
所以最后我需要的实际输出应该看起来像这样。
感谢任何帮助。
可以通过在数据中包含 2 个字段来单独自定义行:arrowDirectionAngle
和 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(" + p.x + "," + p.y + ") " + 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>
使用路径线和箭头分别添加线条,使用鼠标事件您可以根据悬停气泡的中心来操纵路径的位置,对于工具提示您也可以使用 d3.select()
来获取div
工具提示并更改其 innerHTML
属性以显示消息。
这是一个可行的解决方案:
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(" + p.x + "," + p.y + ") " + 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>