将 d3 分层边缘捆绑可视化下载为 PNG 时显示深色填充链接
Dark filled Links being displayed when downloading d3 hierarchical edge bundling visualization as PNG
我创建了一个 D3 分层边缘捆绑 visualization.When 我将此可视化下载为 PNG,下载图像中的链接填充了深色,这在实际可视化中没有显示。
下载的图像是
显示的实际可视化是
如何在下载的图像中获得相同的可视化效果。
我当前的代码是:
<html>
<head>
<style>
.node {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.link {
stroke: steelblue;
stroke-opacity: .4;
fill: none;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var classes=[
{"name":"a","size":3938,"imports":["b","f"]},
{"name":"b","size":3938,"imports":[]},
{"name":"c","size":3938,"imports":["a"]},
{"name":"d","size":3938,"imports":[]},
{"name":"e","size":3938,"imports":[]},
{"name":"f","size":3938,"imports":[]}
];
var diameter = 700,
radius = diameter / 2,
innerRadius = radius - 120;
var cluster = d3.layout.cluster()
.size([360, innerRadius]);
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.85)
.radius(function(d) { return d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
var svg = d3.select("body").append("svg")
.attr("id","networkSvg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var nodes = cluster.nodes(packageHierarchy(classes)),
links = packageImports(nodes);
svg.selectAll(".link")
.data(bundle(links))
.enter().append("path")
.attr("class", "link")
.attr("d", line);
svg.selectAll(".node")
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.on("mouseover", function(d){
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
colorSource(d.name);
d.imports.forEach(function(name){
colorTarget(name);
colorOutgoingLink(d.name, name);
});
d.parent.children.forEach(function(data){
if(data.imports.indexOf(d.name)>-1)
{
colorTarget(data.name);
colorIncomingLink(data.name, d.name);
}
});
})
.on("mouseout",function(d)
{
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
})
.append("text")
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) { return d.key; });
// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name], i;
if (!node) {
node = map[name] = data || {name: name, children: []};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
}
// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({source: map[d.name], target: map[i]});
});
});
return imports;
}
function colorIncomingLink(src,tgt){
var link = d3.selectAll(".link")[0].filter(function(d){
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "green");
}
function colorOutgoingLink(src,tgt){
var link = d3.selectAll(".link")[0].filter(function(d){
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "red");
}
function colorTarget(name){
//iterate through all teh dom and get the DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d){
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "red");
}
function colorSource(name){
//iterate through all teh dom and get teh DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d){
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "green");
}
function download()
{
img = new Image(),
serializer = new XMLSerializer(),
svgStr = serializer.serializeToString(document.getElementById('networkSvg'));
img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);
// You could also use the actual string without base64 encoding it:
//img.src = "data:image/svg+xml;utf8," + svgStr;
var canvas = document.createElement("canvas");
var w=2000;
var h=2000;
canvas.width = w;
canvas.height = h;
canvas.getContext("2d").drawImage(img,0,0,w,h);
var imgURL = canvas.toDataURL("image/png");
var dlLink = document.createElement('a');
dlLink.download = "down";
dlLink.href = imgURL;
dlLink.dataset.downloadurl = ["image/png", dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
}
</script>
<button onclick="download()">Download</button>
</body>
</html>
正如 Cyril 所说,您可以通过 d3 设置所有样式,而不是使用外部样式 sheet。
这是工作代码片段。
var classes = [{
"name": "a",
"size": 3938,
"imports": ["b", "f"]
}, {
"name": "b",
"size": 3938,
"imports": []
}, {
"name": "c",
"size": 3938,
"imports": ["a"]
}, {
"name": "d",
"size": 3938,
"imports": []
}, {
"name": "e",
"size": 3938,
"imports": []
}, {
"name": "f",
"size": 3938,
"imports": []
}
];
var diameter = 700,
radius = diameter / 2,
innerRadius = radius - 120;
var cluster = d3.layout.cluster()
.size([360, innerRadius]);
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.85)
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
var svg = d3.select("body").append("svg")
.attr("id", "networkSvg")
.attr("width", diameter)
.attr("height", diameter)
.style("background-color","white")
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var nodes = cluster.nodes(packageHierarchy(classes)),
links = packageImports(nodes);
svg.selectAll(".link")
.data(bundle(links))
.enter().append("path")
.attr("class", "link")
.style("stroke", "steelblue")
.style("stroke-opacity", .4)
.style("fill", "none")
.attr("d", line);
svg.selectAll(".node")
.data(nodes.filter(function(n) {
return !n.children;
}))
.enter().append("g")
.attr("class", "node")
.style("font", "11px Helvetica Neue Helvetica, Arial, sans-serif")
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.on("mouseover", function(d) {
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
colorSource(d.name);
d.imports.forEach(function(name) {
colorTarget(name);
colorOutgoingLink(d.name, name);
});
d.parent.children.forEach(function(data) {
if (data.imports.indexOf(d.name) > -1) {
colorTarget(data.name);
colorIncomingLink(data.name, d.name);
}
});
})
.on("mouseout", function(d) {
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
})
.append("text")
.style("stroke","black")
.attr("dx", function(d) {
return d.x < 180 ? 8 : -8;
})
.attr("dy", ".31em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? null : "rotate(180)";
})
.text(function(d) {
return d.key;
});
// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name],
i;
if (!node) {
node = map[name] = data || {
name: name,
children: []
};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
}
// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({
source: map[d.name],
target: map[i]
});
});
});
return imports;
}
function colorIncomingLink(src, tgt) {
var link = d3.selectAll(".link")[0].filter(function(d) {
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "green");
}
function colorOutgoingLink(src, tgt) {
var link = d3.selectAll(".link")[0].filter(function(d) {
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "red");
}
function colorTarget(name) {
//iterate through all teh dom and get the DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d) {
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "red");
}
function colorSource(name) {
//iterate through all teh dom and get teh DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d) {
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "green");
}
function download() {
img = new Image(),
serializer = new XMLSerializer(),
svgStr = serializer.serializeToString(document.getElementById('networkSvg'));
img.src = 'data:image/svg+xml;base64,' + window.btoa(svgStr);
// You could also use the actual string without base64 encoding it:
//img.src = "data:image/svg+xml;utf8," + svgStr;
var canvas = document.createElement("canvas");
var w = 2000;
var h = 2000;
canvas.width = w;
canvas.height = h;
canvas.getContext("2d").drawImage(img, 0, 0, w, h);
var imgURL = canvas.toDataURL("image/png");
var dlLink = document.createElement('a');
dlLink.download = "down";
dlLink.href = imgURL;
dlLink.dataset.downloadurl = ["image/png", dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button onclick="download()">Download</button>
我创建了一个 D3 分层边缘捆绑 visualization.When 我将此可视化下载为 PNG,下载图像中的链接填充了深色,这在实际可视化中没有显示。
下载的图像是
显示的实际可视化是
如何在下载的图像中获得相同的可视化效果。 我当前的代码是:
<html>
<head>
<style>
.node {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.link {
stroke: steelblue;
stroke-opacity: .4;
fill: none;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var classes=[
{"name":"a","size":3938,"imports":["b","f"]},
{"name":"b","size":3938,"imports":[]},
{"name":"c","size":3938,"imports":["a"]},
{"name":"d","size":3938,"imports":[]},
{"name":"e","size":3938,"imports":[]},
{"name":"f","size":3938,"imports":[]}
];
var diameter = 700,
radius = diameter / 2,
innerRadius = radius - 120;
var cluster = d3.layout.cluster()
.size([360, innerRadius]);
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.85)
.radius(function(d) { return d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
var svg = d3.select("body").append("svg")
.attr("id","networkSvg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var nodes = cluster.nodes(packageHierarchy(classes)),
links = packageImports(nodes);
svg.selectAll(".link")
.data(bundle(links))
.enter().append("path")
.attr("class", "link")
.attr("d", line);
svg.selectAll(".node")
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.on("mouseover", function(d){
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
colorSource(d.name);
d.imports.forEach(function(name){
colorTarget(name);
colorOutgoingLink(d.name, name);
});
d.parent.children.forEach(function(data){
if(data.imports.indexOf(d.name)>-1)
{
colorTarget(data.name);
colorIncomingLink(data.name, d.name);
}
});
})
.on("mouseout",function(d)
{
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
})
.append("text")
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) { return d.key; });
// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name], i;
if (!node) {
node = map[name] = data || {name: name, children: []};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
}
// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({source: map[d.name], target: map[i]});
});
});
return imports;
}
function colorIncomingLink(src,tgt){
var link = d3.selectAll(".link")[0].filter(function(d){
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "green");
}
function colorOutgoingLink(src,tgt){
var link = d3.selectAll(".link")[0].filter(function(d){
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "red");
}
function colorTarget(name){
//iterate through all teh dom and get the DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d){
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "red");
}
function colorSource(name){
//iterate through all teh dom and get teh DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d){
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "green");
}
function download()
{
img = new Image(),
serializer = new XMLSerializer(),
svgStr = serializer.serializeToString(document.getElementById('networkSvg'));
img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);
// You could also use the actual string without base64 encoding it:
//img.src = "data:image/svg+xml;utf8," + svgStr;
var canvas = document.createElement("canvas");
var w=2000;
var h=2000;
canvas.width = w;
canvas.height = h;
canvas.getContext("2d").drawImage(img,0,0,w,h);
var imgURL = canvas.toDataURL("image/png");
var dlLink = document.createElement('a');
dlLink.download = "down";
dlLink.href = imgURL;
dlLink.dataset.downloadurl = ["image/png", dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
}
</script>
<button onclick="download()">Download</button>
</body>
</html>
正如 Cyril 所说,您可以通过 d3 设置所有样式,而不是使用外部样式 sheet。
这是工作代码片段。
var classes = [{
"name": "a",
"size": 3938,
"imports": ["b", "f"]
}, {
"name": "b",
"size": 3938,
"imports": []
}, {
"name": "c",
"size": 3938,
"imports": ["a"]
}, {
"name": "d",
"size": 3938,
"imports": []
}, {
"name": "e",
"size": 3938,
"imports": []
}, {
"name": "f",
"size": 3938,
"imports": []
}
];
var diameter = 700,
radius = diameter / 2,
innerRadius = radius - 120;
var cluster = d3.layout.cluster()
.size([360, innerRadius]);
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.85)
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
var svg = d3.select("body").append("svg")
.attr("id", "networkSvg")
.attr("width", diameter)
.attr("height", diameter)
.style("background-color","white")
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var nodes = cluster.nodes(packageHierarchy(classes)),
links = packageImports(nodes);
svg.selectAll(".link")
.data(bundle(links))
.enter().append("path")
.attr("class", "link")
.style("stroke", "steelblue")
.style("stroke-opacity", .4)
.style("fill", "none")
.attr("d", line);
svg.selectAll(".node")
.data(nodes.filter(function(n) {
return !n.children;
}))
.enter().append("g")
.attr("class", "node")
.style("font", "11px Helvetica Neue Helvetica, Arial, sans-serif")
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.on("mouseover", function(d) {
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
colorSource(d.name);
d.imports.forEach(function(name) {
colorTarget(name);
colorOutgoingLink(d.name, name);
});
d.parent.children.forEach(function(data) {
if (data.imports.indexOf(d.name) > -1) {
colorTarget(data.name);
colorIncomingLink(data.name, d.name);
}
});
})
.on("mouseout", function(d) {
d3.selectAll(".node").style("fill", "black");
d3.selectAll(".link").style("stroke", "steelblue");
})
.append("text")
.style("stroke","black")
.attr("dx", function(d) {
return d.x < 180 ? 8 : -8;
})
.attr("dy", ".31em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? null : "rotate(180)";
})
.text(function(d) {
return d.key;
});
// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name],
i;
if (!node) {
node = map[name] = data || {
name: name,
children: []
};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
}
// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({
source: map[d.name],
target: map[i]
});
});
});
return imports;
}
function colorIncomingLink(src, tgt) {
var link = d3.selectAll(".link")[0].filter(function(d) {
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "green");
}
function colorOutgoingLink(src, tgt) {
var link = d3.selectAll(".link")[0].filter(function(d) {
return (d3.select(d).data()[0][0].name == src && d3.select(d).data()[0][2].name == tgt);
});
d3.selectAll(link).style("stroke", "red");
}
function colorTarget(name) {
//iterate through all teh dom and get the DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d) {
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "red");
}
function colorSource(name) {
//iterate through all teh dom and get teh DOM which has the data
var node = d3.selectAll(".node")[0].filter(function(d) {
return d3.select(d).data()[0].name == name;
});
d3.selectAll(node).style("fill", "green");
}
function download() {
img = new Image(),
serializer = new XMLSerializer(),
svgStr = serializer.serializeToString(document.getElementById('networkSvg'));
img.src = 'data:image/svg+xml;base64,' + window.btoa(svgStr);
// You could also use the actual string without base64 encoding it:
//img.src = "data:image/svg+xml;utf8," + svgStr;
var canvas = document.createElement("canvas");
var w = 2000;
var h = 2000;
canvas.width = w;
canvas.height = h;
canvas.getContext("2d").drawImage(img, 0, 0, w, h);
var imgURL = canvas.toDataURL("image/png");
var dlLink = document.createElement('a');
dlLink.download = "down";
dlLink.href = imgURL;
dlLink.dataset.downloadurl = ["image/png", dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<button onclick="download()">Download</button>