在 D3 中制作不同颜色的线条补丁
Make patch of line different color in D3
我的可视化使用节点之间的线来表示它们之间的关系。线越宽,两个节点的连接越多。在这种情况下,我想将一小部分线条设置为较暗的色调,以便将更多信息附加到可视化中。离直线上的"mark"最近的那个节点就是主导节点。
是否有一些命令可以只更改部分行的颜色?或者创建一个与直线具有相同宽度和角度的矩形会更容易吗?
我附上了当前的可视化效果,我在其中创建了一些丑陋的小矩形来说明我正在尝试做什么。另外附上代码(可惜有点乱)
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node circle {
stroke: white;
stroke-width: 2px;
opacity: 1.0;
}
line {
stroke-width: 3.5px;
stroke-opacity: 1.0;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
data = {
nodes: [
{size: 200, fill: '#ffffff', line: '#8C8C8C', id: 'Me'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Bob'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Alice'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Tim Tim'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Mustafa'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Zeus'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'McHammer'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Thoooor'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Le Viking'}
],
links: [
{source: 0,target: 1, calls: 20, texts:0},
{source: 0,target: 2, calls: 5, texts:0},
{source: 0,target: 3, calls: 8, texts:0},
{source: 0,target: 4, calls: 3, texts:0},
{source: 0,target: 5, calls: 2, texts:0},
{source: 0,target: 6, calls: 3, texts:0},
{source: 0,target: 7, calls: 5, texts:0},
{source: 0,target: 8, calls: 2, texts:0}
]
}
var total_interactions = data.links.reduce(function(result, currentObject) {
return result + currentObject.calls;
}, 0);
console.log(total_interactions);
var mouseOverFunction = function(d) {
var circle = d3.select(this);
circle
.transition(500)
.attr("r", function(){ return 1.4 * node_radius(d)});
}
var mouseOutFunction = function() {
var circle = d3.select(this);
circle
.transition(500)
.attr("r", node_radius);
}
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
var line_diff = 0
function tick() {
callLink
.attr("x1", function(d) { return d.source.x-line_diff; })
.attr("y1", function(d) { return d.source.y-line_diff; })
.attr("x2", function(d) { return d.target.x-line_diff; })
.attr("y2", function(d) { return d.target.y-line_diff; });
textLink
.attr("x1", function(d) { return d.source.x+line_diff; })
.attr("y1", function(d) { return d.source.y+line_diff; })
.attr("x2", function(d) { return d.target.x+line_diff; })
.attr("y2", function(d) { return d.target.y+line_diff; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function node_radius(d) { return Math.pow(40.0 * d.size, 1/3); }
var width = 1000;
var height = 500;
var nodes = data.nodes
var links = data.links
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-3000) // -3000
.friction(0.6)
.gravity(0.6)
.size([width,height])
.start();
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = true;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var callLink = svg.selectAll(".call-line")
.data(links)
.enter().append("line");
var textLink = svg.selectAll(".text-line")
.data(links)
.enter().append("line");
var link = svg.selectAll("line");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node
.append("circle")
.attr("r", node_radius)
.style("fill", function(d) {return d.fill; })
.style("stroke", function(d) {return d.line; })
.on("mouseover", mouseOverFunction)
.on("mouseout", mouseOutFunction);
svg
.append("marker")
.attr("id", "arrowhead")
.attr("refX", 6 + 7)
.attr("refY", 2)
.attr("markerWidth", 6)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M 0,0 V 4 L6,2 Z");
// link
// .style("stroke-width", function stroke(d) {return d.calls/data.total_interactions*20; })
var line_width_factor = 40 // width for a line with all points
textLink
.style("stroke-width", function stroke(d) {return d.texts / total_interactions*line_width_factor; })
.style("stroke", "#70C05A")
callLink
.style("stroke-width", function stroke(d) {return d.calls / total_interactions*line_width_factor; })
.style("stroke", "#438DCA")
force
.on("tick",tick);
</script>
</body>
其实你can't apply CSS gradients to SVG elements。你必须使用 SVG 渐变。
要在图表中沿 link 创建 SVG 渐变,一般形式为:
defs.append("linearGradient")
.attr("id", gradient_id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", source.px)
.attr("y1", source.py)
.attr("x2", target.px)
.attr("y2", target.py)
.selectAll("stop")
.data([
{offset: "0%", color: "cyan"}, //color 1
{offset: "50%", color: "cyan"}, //start the gradient from haf distance
{offset: "100%", color: "darkBlue"} //to a darker color
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });
在你的例子中,source
和 target
的位置随着每个 force.tick()
而改变。因此,您需要为每个 tick
中的所有 link 计算并应用梯度。通过将此行添加到 tick
函数:
callLink.each(function(d){applyGradient(this, d)});
接下来,我们需要定义applyGradient(line, d)
函数:
function applyGradient(line, d){
var self = d3.select(line); //select the current link
// store the currently used gradient to delete it later.
var current_gradient = self.style("stroke")
current_gradient = current_gradient.substring(4, current_gradient.length-1);
// Generate a random id for the gradient calculated for this link
var new_gradient_id = "line-gradient" + getRandomInt();
// Determine the direction of the gradient. In your example, you need
// the darker hue to be near the bigger node.
var from = d.source.size < d.target.size? d.source: d.target;
var to = d.source.size < d.target.size? d.target: d.source;
// Add the gradient to the svg:defs
defs.append("linearGradient")
.attr("id", new_gradient_id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", from.px)
.attr("y1", from.py)
.attr("x2", to.px)
.attr("y2", to.py)
.selectAll("stop")
.data([
{offset: "0%", color: "cyan"},
{offset: "50%", color: "cyan"},
{offset: "100%", color: "darkBlue"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });
//apply the gradient
self.style("stroke", "url(#" + new_gradient_id + ")")
//Remove the old gradient from defs.
defs.select(current_gradient).remove();
}
这是一个working fiddle
我的可视化使用节点之间的线来表示它们之间的关系。线越宽,两个节点的连接越多。在这种情况下,我想将一小部分线条设置为较暗的色调,以便将更多信息附加到可视化中。离直线上的"mark"最近的那个节点就是主导节点。
是否有一些命令可以只更改部分行的颜色?或者创建一个与直线具有相同宽度和角度的矩形会更容易吗?
我附上了当前的可视化效果,我在其中创建了一些丑陋的小矩形来说明我正在尝试做什么。另外附上代码(可惜有点乱)
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node circle {
stroke: white;
stroke-width: 2px;
opacity: 1.0;
}
line {
stroke-width: 3.5px;
stroke-opacity: 1.0;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
data = {
nodes: [
{size: 200, fill: '#ffffff', line: '#8C8C8C', id: 'Me'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Bob'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Alice'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Tim Tim'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Mustafa'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Zeus'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'McHammer'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Thoooor'},
{size: 30, fill: '#D2D2D2', line: '#ffffff', id: 'Le Viking'}
],
links: [
{source: 0,target: 1, calls: 20, texts:0},
{source: 0,target: 2, calls: 5, texts:0},
{source: 0,target: 3, calls: 8, texts:0},
{source: 0,target: 4, calls: 3, texts:0},
{source: 0,target: 5, calls: 2, texts:0},
{source: 0,target: 6, calls: 3, texts:0},
{source: 0,target: 7, calls: 5, texts:0},
{source: 0,target: 8, calls: 2, texts:0}
]
}
var total_interactions = data.links.reduce(function(result, currentObject) {
return result + currentObject.calls;
}, 0);
console.log(total_interactions);
var mouseOverFunction = function(d) {
var circle = d3.select(this);
circle
.transition(500)
.attr("r", function(){ return 1.4 * node_radius(d)});
}
var mouseOutFunction = function() {
var circle = d3.select(this);
circle
.transition(500)
.attr("r", node_radius);
}
function isConnected(a, b) {
return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index;
}
function isConnectedAsSource(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function isConnectedAsTarget(a, b) {
return linkedByIndex[b.index + "," + a.index];
}
function isEqual(a, b) {
return a.index == b.index;
}
var line_diff = 0
function tick() {
callLink
.attr("x1", function(d) { return d.source.x-line_diff; })
.attr("y1", function(d) { return d.source.y-line_diff; })
.attr("x2", function(d) { return d.target.x-line_diff; })
.attr("y2", function(d) { return d.target.y-line_diff; });
textLink
.attr("x1", function(d) { return d.source.x+line_diff; })
.attr("y1", function(d) { return d.source.y+line_diff; })
.attr("x2", function(d) { return d.target.x+line_diff; })
.attr("y2", function(d) { return d.target.y+line_diff; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function node_radius(d) { return Math.pow(40.0 * d.size, 1/3); }
var width = 1000;
var height = 500;
var nodes = data.nodes
var links = data.links
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-3000) // -3000
.friction(0.6)
.gravity(0.6)
.size([width,height])
.start();
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = true;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var callLink = svg.selectAll(".call-line")
.data(links)
.enter().append("line");
var textLink = svg.selectAll(".text-line")
.data(links)
.enter().append("line");
var link = svg.selectAll("line");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node
.append("circle")
.attr("r", node_radius)
.style("fill", function(d) {return d.fill; })
.style("stroke", function(d) {return d.line; })
.on("mouseover", mouseOverFunction)
.on("mouseout", mouseOutFunction);
svg
.append("marker")
.attr("id", "arrowhead")
.attr("refX", 6 + 7)
.attr("refY", 2)
.attr("markerWidth", 6)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M 0,0 V 4 L6,2 Z");
// link
// .style("stroke-width", function stroke(d) {return d.calls/data.total_interactions*20; })
var line_width_factor = 40 // width for a line with all points
textLink
.style("stroke-width", function stroke(d) {return d.texts / total_interactions*line_width_factor; })
.style("stroke", "#70C05A")
callLink
.style("stroke-width", function stroke(d) {return d.calls / total_interactions*line_width_factor; })
.style("stroke", "#438DCA")
force
.on("tick",tick);
</script>
</body>
其实你can't apply CSS gradients to SVG elements。你必须使用 SVG 渐变。
要在图表中沿 link 创建 SVG 渐变,一般形式为:
defs.append("linearGradient")
.attr("id", gradient_id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", source.px)
.attr("y1", source.py)
.attr("x2", target.px)
.attr("y2", target.py)
.selectAll("stop")
.data([
{offset: "0%", color: "cyan"}, //color 1
{offset: "50%", color: "cyan"}, //start the gradient from haf distance
{offset: "100%", color: "darkBlue"} //to a darker color
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });
在你的例子中,source
和 target
的位置随着每个 force.tick()
而改变。因此,您需要为每个 tick
中的所有 link 计算并应用梯度。通过将此行添加到 tick
函数:
callLink.each(function(d){applyGradient(this, d)});
接下来,我们需要定义applyGradient(line, d)
函数:
function applyGradient(line, d){
var self = d3.select(line); //select the current link
// store the currently used gradient to delete it later.
var current_gradient = self.style("stroke")
current_gradient = current_gradient.substring(4, current_gradient.length-1);
// Generate a random id for the gradient calculated for this link
var new_gradient_id = "line-gradient" + getRandomInt();
// Determine the direction of the gradient. In your example, you need
// the darker hue to be near the bigger node.
var from = d.source.size < d.target.size? d.source: d.target;
var to = d.source.size < d.target.size? d.target: d.source;
// Add the gradient to the svg:defs
defs.append("linearGradient")
.attr("id", new_gradient_id)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", from.px)
.attr("y1", from.py)
.attr("x2", to.px)
.attr("y2", to.py)
.selectAll("stop")
.data([
{offset: "0%", color: "cyan"},
{offset: "50%", color: "cyan"},
{offset: "100%", color: "darkBlue"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });
//apply the gradient
self.style("stroke", "url(#" + new_gradient_id + ")")
//Remove the old gradient from defs.
defs.select(current_gradient).remove();
}
这是一个working fiddle