Here is jsfiddle of D3 cluster force layout:
How to achieve 3D look of nodes similar to this picture: (don't pay attention on diagram itself, this is just illustration of "look" of circles)
Here is jsfiddle of the solution. It is based on SVG radial gradients.
For each node, a gradient is defined:
var grads = svg.append("defs").selectAll("radialGradient")
.data(nodes)
.enter()
.append("radialGradient")
.attr("gradientUnits", "objectBoundingBox")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", "100%")
.attr("id", function(d, i) { return "grad" + i; });
grads.append("stop")
.attr("offset", "0%")
.style("stop-color", "white");
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d) { return color(d.cluster); });
Then, instead of line:
.style("fill", function(d) { return color(d.cluster); })
this line is added in the code that creates circles:
.attr("fill", function(d, i) {
return "url(#grad" + i + ")";
})
This produces this effect:(animated gif that I used has some limitations for number of colors, so gradients are not smooth as in real example)
Create linear or radial gradient based on your requirement using different colors. Set fill attribute as gradient.
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#0c0")
.attr("stop-opacity", 1);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#c00")
.attr("stop-opacity", 1);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.style("fill", "url(#gradient)")
.call(force.drag);
Related
I have a simple bar chart using d3.js with a linear gradient on two rect objects. The gradient is there, but the gradient appears as an outline (two thin lines on the top and bottom of the rectangles). I want solid fill with the gradient, not outline. After a lot of research, I still don't have the answer.
Here is the d3 javascript:
var dataArray = [23, 13];
var names = [ "Category1", "Category2" ];
var widths = [ "50", "700" ];
var svg = d3.select("svg.d3svg")
.attr("height", "20%")
.attr("width", "100%")
var bar = svg.selectAll("g")
.data(dataArray)
.enter().append("g")
var gradient = svg
.append("linearGradient")
.attr("y1", "0%")
.attr("y2", "100%")
.attr("x1", "0%")
.attr("x2", "100%")
.attr("id", "gradient")
.attr("gradientUnits", "userSpaceOnUse")
gradient
.append("stop")
.attr('class', 'start')
.attr("offset", "0%")
.attr("stop-color", "red")
.attr("stop-opacity", 1);
gradient
.append("stop")
.attr('class', 'end')
.attr("offset", "100%")
.attr("stop-color", "green")
.attr("stop-opacity", 1);
var rect = bar.append('rect')
.attr("height", "7")
.attr("width", function(d, i) { return widths[i] })
.attr("y", function(d, i) { return (i * 40) + 30 })
.attr("x", "0")
.attr("stroke", "url(#gradient)")
var text = bar.append('text')
.attr("class", "text-svg")
.text (function(d, i) { return names[i] })
.attr("x", "0")
.attr("y", function(d, i) { return (i * 40) + 50 });
var text = bar.append('text')
.attr("class", "text-svg-caption")
.text ("Text for this goes here")
.attr("x", "135")
.attr("y", "120");
So my question is how do I get a solid fill in the rectangles with a linear gradient?
Thanks for any help.
The basic example of my question builds on this chart. The goal is to fill only half the circle with the group color.
This SO question explains how to make half circles.
Here's a snippet of the original code
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
Here`s adding a half circle
var grad = svg.append("defs").append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", "50%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", "50%").style("stop-color", "white");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
enter code here
How could I make this grad dependent on the d.group?
I tried
A get_grad() function and have it return the grad
A set_grad() function and have it set the fill attribute
However, I didn't manage to get either working. Who can help me?
If you want to have different elements with different gradients, you have to use the same data binding process to create the gradients themselves:
var defs = svg.append("defs")
.selectAll("foo")
.data(data)
.enter()
.append("linearGradient")
//etc...
Have in mind that IDs have to be unique. In the following demo I'm doing:
.attr("id", function(d) {
return "grad" + d
})
... to create unique IDs.
In the demo, this is the part that you probably are interested in:
defs.append("stop")
.attr("offset", "50%")
.style("stop-color", function(d) {
return colours(d)
})
As you can see, I'm applying the stop colours based on data.
Have a look at the demo (which is not a force directed chart, but simply a demo with elements using different gradients):
var svg = d3.select("svg");
var colours = d3.scaleOrdinal(d3.schemeCategory10);
var defs = svg.append("defs")
.selectAll("foo")
.data(d3.range(5))
.enter()
.append("linearGradient")
.attr("id", function(d) {
return "grad" + d
})
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "100%")
.attr("y2", "0%");
defs.append("stop")
.attr("offset", "50%")
.style("stop-color", function(d) {
return colours(d)
})
defs.append("stop")
.attr("offset", "50%")
.style("stop-color", "white");
var circles = svg.selectAll("foo")
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", function(d) {
return 25 + d * 62
})
.attr("r", 25)
.attr("stroke", "dimgray")
.attr("fill", function(d) {
return "url(#grad" + d + ")"
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
You can also play with the offsets:
var svg = d3.select("svg");
var colours = d3.scaleOrdinal(d3.schemeCategory10);
var defs = svg.append("defs")
.selectAll("foo")
.data(d3.range(5))
.enter()
.append("linearGradient")
.attr("id", function(d) {
return "grad" + d
})
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "100%")
.attr("y2", "0%");
defs.append("stop")
.attr("offset", function(d) {
return 20 + d * 15 + "%"
})
.style("stop-color", function(d) {
return colours(d)
})
defs.append("stop")
.attr("offset", function(d) {
return 20 + d * 15 + "%"
})
.style("stop-color", "white");
var circles = svg.selectAll("foo")
.data(d3.range(5))
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", function(d) {
return 25 + d * 62
})
.attr("r", 25)
.attr("stroke", "dimgray")
.attr("fill", function(d) {
return "url(#grad" + d + ")"
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
I am new to d3 javascript library. I am trying to draw line over a circle using d3. I am able to create circle but somehow line does not appear on circle. See sample code attached. Any help is highly appreciated.
diag_circles.data(circle_data)
.enter()
.append("circle")
.attr("cx", function (d) {
console.log("d.x", d.x);
return d.x
})
.attr("cy", function (d) {
return d.y
})
.attr("r", function (d) {
return d.r
})
.append('line')
.attr("x1", function(d){return d.x- d.r})
.attr("y1", function(d){return d.y})
.attr("x2", function (d) { return d.x+ d.r})
.attr("y2", function(d){return d.y})
.attr("stroke-width", 20)
.attr("stroke", "black");
https://jsfiddle.net/c58859xy/
In a nutshell: you cannot append a line element to a circle element.
When creating your SVG, you have to know which elements allow appended children and what children they can have.
Solution: You'll have to append the lines to the SVG:
var lines = svg.selectAll('line')
.data(circle_data)
.enter()
.append("line")
.attr("x1", function(d){return d.x- d.r})
.attr("y1", function(d){return d.y})
.attr("x2", function (d) { return d.x+ d.r})
.attr("y2", function(d){return d.y})
.attr("stroke-width", 20)
.attr("stroke", "black");
Here is the updated fiddle: https://jsfiddle.net/c58859xy/1/
I have this circual chart - https://jsfiddle.net/kyleopperman/aacgzxed/ - but i am struggling to get the gradient to start in the center of each segment and fill out to the edge. Can anyone help me with this?
var gradient = vis.append("defs")
.append("linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", "#61B5C3")
.attr("stop-opacity", 1);
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", "#5393AC")
.attr("stop-opacity", 1);
Okay I've managed to figure this one out using a radialGradient
var radialGradient = vis.append("defs")
.append("radialGradient")
.attr("id", "radialGradient")
.attr("cx", "0%")
.attr("cy", "0%")
.attr("r", "50%")
.attr("fx", "0%")
.attr("fy", "0%")
.attr("spreadMethod", "pad")
.attr("gradientUnits", "userSpaceOnUse");
Here is the working example: https://jsfiddle.net/kyleopperman/aacgzxed/1/
I have a simple force layout that calculates nodes and links when certain buttons are clicked. The first time the nodes are calculated and displayed everything is correctly positioned. However, when nodes are recalculated after another click, the position of the circles I have appended to the nodes are way off yet the text I added remains in the right place. Here's my JS:
//Compute Nodes and Links
var data = this.computePreviewNodes($(e.currentTarget).data("id"), $(e.currentTarget).data("type"));
var canvas = d3.select(".body").append("svg")
.attr("width", width)
.attr("height", screen.height/2)
.append("g");
canvas.append("text")
.text(compObj.name)
.attr("text-anchor", "middle")
.attr("font-size", "2em")
.attr("x", width/2)
.attr("y", 40);
var force = d3.layout.force()
.nodes(data.nodes)
.links(data.links)
.gravity(.05)
.distance(100)
.charge(-10)
.size([width, screen.height/2]);
var links = canvas.selectAll(".links")
.data(data.links)
.enter().append("line")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "blue");
var nodes = canvas.selectAll(".nodes")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "nodes")
.call(force.drag);
nodes.append("circle")
.attr("cx", function(d) {return d.x;})
.attr("cy", function(d) {return d.y;})
.attr("r", 10)
.attr("fill", "green");
nodes.append("text")
.text(function(d) {return d.name})
.attr("text-anchor", "right")
.attr("font-size", "1.8em")
.attr("y", 5);
force.on("tick", function(e) {
nodes
.attr("transform", function(d, i){
return "translate(" + d.x + ", " + d.y + ")";
});
links
.attr("x1", function(d) {return d.source.x;})
.attr("y1", function(d) {return d.source.y;})
.attr("x2", function(d) {return d.target.x;})
.attr("y2", function(d) {return d.target.y;})
})
force.start();
My computePreviewNodes() function simply comes up with what nodes need to be displayed based on which button is clicked. My thoughts are that maybe I'm not updating my node positions correctly after the second rendering of my nodes. Any ideas?
Here's my canvas at the first click:
And here it is when I click/calculate my nodes once again: