My D3 circle pack looks like this: (also accesible via jsfiddle)
However, I would like the diagram to look like this: (don't pay attention on labels, or circle pack placement, they are not essential for my case; I meant just co show "3d" looks of circles, and their coloring)
What would be the good way to achieve this?
After #Delapouite answer, I put together another jsfiddle:
The key code is:
var data2 = pack.nodes(data);
var grads = svg.append("defs").selectAll("radialGradient")
.data(data2)
.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", "navy");
and
var circles = vis.append("circle")
.attr("stroke", "black")
.attr("fill", function(d, i) {
return !d.children ? "url(#grad" + i + ")" : "beige";
})
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; });
You can fake the 3D effect of each ball by applying a soft radial gradient to the fill property of the circles :
https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Gradients
Related
I am working on a line chart based on the tutorial https://datawanderings.com/2019/11/01/tutorial-making-an-interactive-line-chart-in-d3-js-v-5/
For the code block below, I want to add points every three rows (every three days):
//---------------------------POINTS-----------------------------//
lines.selectAll("points")
.data(function(d) {return d.values})
.enter()
.append("circle")
.attr("cx", function(d) { return xScale(d.date); })
.attr("cy", function(d) { return yScale(d.measurement); })
.attr("r", 10)
.attr("class","point")
.style("opacity", 1);
I followed the link Add a Circle for Every nth Data Element d3.js, and tried to change it to
//---------------------------POINTS-----------------------------//
lines.selectAll("points")
.data(slices.filter(function(d,i) {return i%3 == 0}))
.enter()
.append("circle")
.attr("cx", function(d, i) { return xScale(d.date); })
.attr("cy", function(d, i) { return yScale(d.measurement); })
.attr("r", 10)
.attr("class","point")
.style("opacity", 1);
However, all the points appears to locate at the left top corner of the graph.
May I ask what I did wrong, and how can I modify my code to make the circles to locate on the lines as showing the circles every three days? Thank you!
I am using d3 for visualizing gene networks using a fixed force-directed layout.
The graph contains rectangular / elliptic / round rectangular shaped nodes with markers at the end of links between those nodes. So far (and as I understand) those markers are positioned by refX and refX and thus follow a radial shape around the end of the path which links two nodes. Is there any way that I can define a "path" or marker in such a manner that the marker moves along the shape of the node instead of around this node with a fixed distance relative to the end of the path?
To illustrate my problem:
var graph = {
"nodes": [{
"name": "from",
"fixed": true,
x: 100,
y: 100,
w: 60,
h: 20
}, {
"name": "to",
"fixed": true,
x: 250,
y: 250,
w: 60,
h: 20
}],
"links": [{
"source": 0,
"target": 1
}]
}
var width = 960,
height = 500;
var force = d3.layout.force()
.charge(-120)
.linkDistance(300)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
force.nodes(graph.nodes)
.links(graph.links)
.start();
var defs = svg.append("svg:defs");
var marker = defs.selectAll("marker");
marker = marker.data([{
"type": "arrow",
"d": "M0,-5L10,0L0,5L2,0",
"view": "0 -5 10 10",
"color": "#000000"
}])
.enter()
.append("svg:marker")
.attr("id", function(d) {
return d.type;
})
.attr("viewBox", function(d) {
return d.view;
})
.attr("refX", 30)
.attr("refY", 0)
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto");
marker.append("svg:path")
.attr("d", function(d) {
return d.d;
})
.style("fill", function(d) {
return d.color;
});
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", "5")
.attr("marker-end", "url(#arrow)");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("rect")
.attr("class", "node")
.attr("width", function(d) {
return d.w;
})
.attr("height", function(d) {
return d.h;
})
.style("fill", "blue")
.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x + d.source.w / 2;
})
.attr("y1", function(d) {
return d.source.y + d.source.h / 2;
})
.attr("x2", function(d) {
return d.target.x + d.target.w / 2;
})
.attr("y2", function(d) {
return d.target.y + d.target.h / 2;
})
node.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
JsFiddle example:
http://jsfiddle.net/millermaximilian/w3eq6ccc/
I am really thankful for any advice!
Max
There is no built in way. You can create the code to parameterize the location of the marker based on the mathematical definition of the shape. This is, fundamentally, what's going on when you set a marker to draw at X px from a node when that node is a circle, since mathematically it's just the radius. With a more complex shape, it's harder, though of course squares, rectangles and ellipses are still relatively easy to compute. With a complex svg:path shape, you could, I imagine, use some combination of path's built-in getPointAtLength and computing the angle from one node to another to do that, but I don't know of any implementations of any of the earlier examples, much less something like that.
I'm following this zoom example. In my case, I don't know how to redraw my data for my svg.
In the example, the svg is initialized like this
chartBody.append("svg:path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var line = d3.svg.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.value);
});
And is redrawn like this in function "zoomed"
svg.select(".line")
.attr("class", "line")
.attr("d", line);
While in my case
Init:
userSvg.selectAll("circle")
.data(userNodes.slice(1))
.enter().append("svg:circle")
.on("click", function(d){ console.log(d.ind); })
.on("mousemove", function(d){ brushOnUser(d.ind); })
.on("mouseout", function(){ brushOnUser(); })
.attr("r", function(d) { return d.radius; })
.attr("cx", function(d, i) { return userNodesScaleX(d.x); })
.attr("cy", function(d, i) { return userNodesScaleY(d.y); })
.style("fill", function(d, i) { return 'gray'; });
Redraw:
userSvg.selectAll("circle")
.attr("class", "circle");
Of course this redraw doesn't work.
So how do I redraw this?
In the redraw function, you need to set all the attributes that are changed. For a line, this is basically only the d attribute, as it contains all the information that determines how the line is drawn. For a circle, this would be the position of the circle and the radius. That is, your redraw function would look something like this.
userSvg.selectAll("circle")
.attr("r", function(d) { return d.radius; })
.attr("cx", function(d, i) { return userNodesScaleX(d.x); })
.attr("cy", function(d, i) { return userNodesScaleY(d.y); });
Depending on what you're changing, you may have to set a different set of attributes. That is, if you're not changing the radius, then there's no need to set that, but if you're changing the fill color, you would need to set that as well.
I have code here and I need to add text into the rect element on the right of all the hexagons and have it act like a paragraph inside of a div with a given height and width and have it wrap properly. Here is the jsfiddle. and the code that draws the rect is below:
http://jsfiddle.net/2nykc/
var infoRect = svgContainer.append("rect");
var infoRectAttr = infoRect.attr("x", function(){
return infoDivX;
})
.attr("y", function(){
return infoDivY;
})
.attr("width", function(){
return infoDivWidth;
})
.attr("height", function(){
return infoDivHeight;
})
.style("fill", "rgb(30,180,134)")
.style("stroke", "black")
.style("stroke-width", 3)
.attr("id", "infoRect");
I am trying to apply the colors from the color = d3.scale.category10(); var to the gradient for the circle svg, what am I doing wrong? All I am seeing is the first color of the color = d3.scale.category10();(which is blue) to 0% opacity gradient but that is all. If I take the gradient out then I see the range I want which is from 1-4? Thanks in advance!
var nodes = d3.range(300).map(function() { return {radius: Math.random() * 12 + 4}; }),
root = nodes[0],
color = d3.scale.category10();
root.radius = 0;
root.fixed = true;
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) { return i ? 0 : -4000; })
.nodes(nodes)
.size([width, height]);
force.start();
var svg = d3.select("body").append("svg:svg")
.attr("width", width)
.attr("height", height);
var gradient = svg.append("defs").append("radialGradient")
.attr("id", "gradient")
.attr("cx", "50%")
.attr("cy", "50%");
gradient.append("stop")
.attr("offset", "75%")
.style("stop-color", function(d, i) { return color(i % 4); })
.attr("stop-opacity", "1");
gradient.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) { return color(i % 4); })
.attr("stop-opacity", ".1");
svg.selectAll("circle")
.data(nodes.slice(1))
.enter().append("circle")
.attr("r", function(d) { return d.radius; })
.style("fill", "url(#gradient)");
Your stop elements don't have any data joined with them, so in your function (d, i), i will always be 0. If you just want the two stops, you could do something like this:
gradient.append("stop")
.attr("offset", "75%")
.style("stop-color", color(0))
.attr("stop-opacity", "1");
gradient.append("stop")
.attr("offset", "100%")
.style("stop-color", color(1))
.attr("stop-opacity", ".1");
If instead you're just trying to fade the edges of your circles, a gradient isn't what you want at all. Instead, you'll need to apply a solid color to each circle, then create a single opacity-only gradient inside a mask, and apply that mask to each circle. Something like this:
var defs = svg.append('defs');
var gradient = defs.append('radialGradient')
.attr('id', 'fadient');
gradient.append('stop')
.attr('offset', '75%')
.attr('stop-color', 'white')
.attr('stop-opacity', 1)
gradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', 'white')
.attr('stop-opacity', .1)
var mask = defs.append('mask')
.attr('id', 'mask')
.attr('maskContentUnits', 'objectBoundingBox')
.append('circle')
.attr('fill', 'url(#fadient)')
.attr('cx', .5)
.attr('cy', .5)
.attr('r', .5)
svg.selectAll("circle")
.data(nodes.slice(1))
.enter().append("circle")
.attr('cx', function (d, i) { return 20 * i })
.attr('cy', 50)
.attr("r", function(d) { return d.radius; })
.attr('mask', 'url(#mask)')
.attr("fill", function (d, i) { return color(i); });