Reputation: 5923
My visualization uses lines between nodes to indicate their relationship. The wider the line, the more connected the two nodes are. In this context I would like to set a small section of the lines to a darker hue to attach more information to the visualization. Whichever node is closest to the "mark" on the line is the dominant node.
Is there some command to change the color for only part of the line? Or would it be easier to create a rectangle with the same width and angle as the line?
I've attached the current visualization, where I've created some ugly little rectangles to illustrate what I'm trying to do. Also, the code is attached (although it sadly is a bit messy).
<!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>
Upvotes: 0
Views: 1050
Reputation: 2970
In fact, you can't apply CSS gradients to SVG elements. You have to use SVG gradients.
To create an SVG gradient along a link in a graph, the general form would be:
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; });
In your case, the positions of source
and target
are changing with each force.tick()
. So, you need to calculate and apply gradients for all links in each tick
. By adding this line to the tick
function:
callLink.each(function(d){applyGradient(this, d)});
Next, we need to define applyGradient(line, d)
function:
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();
}
Here is a working fiddle
Upvotes: 1