I am new to d3.js and not sure which d3 functionality to use.
I need to place a set of elements concentrically about an origin (in a circle).
svg.selectAll('circle').each(function() {
d3.select(this)
.attr('cx', r * Math.cos(theta))
.attr('cy', r * Math.sin(theta));
theta += thetaInc;
});
So instead of doing something tedious like the above code, what is the d3 short way of doing this?
The d3 way to do this would be to pass in the data and calculate the positions based on the index of the datum, i.e. something like
var theta = 2 * Math.PI / array.length;
svg.selectAll('circle').data(array).enter()
.append("circle")
.attr('cx', function(d, i) { return(r * Math.cos(i * theta)); })
.attr('cy', function(d, i) { return(r * Math.sin(i * theta)); });
Related
So in short I have a spiral timeline and currently map single dates on to it. This works fine. However I know want to map date ranges on this spiral. So currently the data has two columns with dates in: 'vstart' and 'vend' and these are the two data points I'd like to connect.
I know how to get the angles of these two columns based upon their x and y position when mapped onto the spiral. But I'm unsure how to draw a radial line between these two points.
This is the code currently used to draw the initial spiral:
var spiral = d3.radialLine()
.curve(d3.curveCardinal)
.angle(theta)
.radius(radius);
var path = svg.append("path")
.datum(points)
.attr("id", "spiral")
.attr("d", spiral)
.style("fill", "none")
.style("stroke", "grey")
.style("stroke", ("6, 5"))
.style("opacity",0.5);
so radius is defined as:
var radius = d3.scaleLinear()
.domain([start, end])
.range([40, r]);
and angle:
var theta = function(r) {
return numSpirals * Math.PI * r;
};
console.log(theta);
points:
var points = d3.range(start, end + 0.001, (end - start) / 779)
which produces an array that looks like this:
This successfully produces a spiral, and now I want to map some individual radial lines on this same path but related to dates.
So for example for 'vstart':
var linePerS = timeScale(d.vstart)
angleOnLineS = path.node().getPointAtLength(linePerS);
d.linePerS = linePerS;
d.x = angleOnLineS.x;
d.y = angleOnLineS.y;
d.a = (Math.atan2(angleOnLineS.y, angleOnLineS.x) * 360 / Math.PI) - 90;
And then the same but configured to 'vend'
So I can get the angle of the dates as mapped on the spiral but then how do I draw a radial line between the angle of vstart and vend?
I was wondering if I had to do something like this:
var arcUn = function(d,i) { [
[{"x": d.vstart, "y": d.vstart},
{"x": d.vend, "y": d.vend}]
]}
In order to have two points to connect. I don't want it to draw a continuous path but treat each line as a new path and loop through drawing a path between vstart and vend and repeating.
So I imagine if I was using one column and drawing a continuous line the code might look like this?
var arcs = d3.radialLine()
.curve(d3.curveCardinal)
.angle(function(d,i){var linePerS = timeScale(d.vstart)
angleOnLineS = path.node().getPointAtLength(linePerS);
d.linePer = linePer;
d.x = angleOnLineS.x;
d.y = angleOnLineS.y;
d.a = (Math.atan2(angleOnLine.y, angleOnLine.x) * 360 / Math.PI) - 90;
return d.a;
})
.radius(radius);
Radius here is taken from the radius that generates the original radius for the spiral.
Then:
svg.append("path")
.datum(spiralData)
.attr("id", "arcs")
.attr("d", arcs)
.style("fill", "none")
.style("stroke", "blue")
.style("stroke", ("6, 5"))
.style("opacity",0.5);
Though when I try this I get an error: "Error: attribute d: Expected number, "MNaN,NaNCNaN,NaN,…"."
So any help here is appreciated...
I'm trying to make a radial clock, but instead of clock arms, I need to have dots at the end of every path indicating time (blue). Thanks for the help!
Edit: like this: https://puu.sh/sH03Y/c59281fb5e.png
Line drawing part:
var clockLine = linesLayer.selectAll('.clock-line')
.data(fields);
clockLine.enter().append('line')
.attr('class', function (d) { return 'line clock-line ' + d.id; })
.attr('x1', 0).attr('y1', 0);
Full fiddle: http://jsfiddle.net/zh3owyr3/
Instead of a <line>, append a <circle>:
var clockCircle = linesLayer.selectAll('.clock-line')
.data(fields);
clockCircle.enter().append('circle')
.attr('class', function(d) {
return 'circle clock-circle ' + d.id;
})
.attr("r", 6)
.attr("fill", "teal");
And change its position in the tick function:
clockCircle.attr('cx', function(d) {
return d.index * radius * Math.cos(d.value * 2 * Math.PI - Math.PI / 2);
})
.attr('cy', function(d) {
return d.index * radius * Math.sin(d.value * 2 * Math.PI - Math.PI / 2);
});
Here is your updated fiddle: http://jsfiddle.net/mjru5ta8/
PS: you'll have to change your viewbox to avoid the seconds' circle being cropped.
I'm using D3.js's built-in arc function to generate SVG <path>s for my data.
.attr("d", function(element, index) {
var arc = d3.arc()
.innerRadius(iR)
.outerRadius(iR + 10)
.startAngle(element[1])
.endAngle(element[2])
.cornerRadius(isRounded ? cR : 0);
return arc();
});
This works perfectly, but I'd like to round one side (both corners) of certain arcs. When a corner radius is supplied with .cornerRadius(), however, it rounds all four corners.
I know there are various ways to selectively round the corners of rectangles, but I'm hoping there's some generic way to do this for arcs.
I also saw this question about rounding only some corners of an arc, but it has no answer (and D3 v4 has come out since it was posted).
Even with the v4 API, still no straight-forward way to do this. Looking at the source code, the cornerRadius becomes a fixed value for the calculation of the whole arc (all 4 corners). Easiest fix is to just append two arcs for every data point with the 2nd arc just filling in the corners.
Example, say we have this nicely rounded arcs:
var myArcs = [
[0, 45],
[180, 300]
];
var vis = d3.select('body')
.append('svg')
.attr('width', 400)
.attr('height', 400);
var arc = d3.arc()
.innerRadius(80)
.outerRadius(150)
var someArcs = vis.selectAll('path')
.data(myArcs)
.enter();
someArcs
.append("path")
.attr("transform", "translate(200,200)")
.attr("d", function(d) {
arc.startAngle(d[0] * (Math.PI / 180))
.endAngle(d[1] * (Math.PI / 180))
.cornerRadius(20);
return arc();
})
.attr("fill", function(d, i) {
return d3.schemeCategory10[i];
});
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
My fix would look like:
var myArcs = [
[0, 45],
[180, 300]
];
var vis = d3.select('body')
.append('svg')
.attr('width', 400)
.attr('height', 400);
var arc = d3.arc()
.innerRadius(80)
.outerRadius(150)
var someArcs = vis.selectAll('path')
.data(myArcs)
.enter();
someArcs
.append("path")
.attr("transform", "translate(200,200)")
.attr("d", function(d) {
arc.startAngle(d[0] * (Math.PI / 180))
.endAngle(d[1] * (Math.PI / 180))
.cornerRadius(20);
return arc();
})
.attr("fill", function(d, i) {
return d3.schemeCategory10[i];
});
someArcs
.append("path")
.attr("transform", "translate(200,200)")
.attr("d", function(d) {
arc.startAngle(d[0] * (Math.PI / 180))
.endAngle((d[0] + 10) * (Math.PI / 180))
.cornerRadius(0);
return arc();
})
.attr("fill", function(d, i) {
return d3.schemeCategory10[i];
});
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
I'm using d3 text to draw some numbers, and use rotate to change the position, but it seems it changes more than I expect, as in the screenshot, how to let the left side numbers reverse, I think it may like 3D rotate, I don't know how to solve it , or the text I draw is wrong.
g.selectAll('text')
.data(sumArr)
.enter()
.append('text')
.text(function(d){
return d;
})
.style('fill', '#aeaeae')
.attr('x', function(d){
console.log(d, x(d))
return x(d) + R + 10;
})
.attr('y', 12 * SCALE)
.attr('font-size', 12 * SCALE)
.attr('transform', function(d,i){
return 'rotate(' + (300/30 * i - 125) + ')';
});
After playing a while with d3.js and looking at a lot of examples, I was able to draw multiple arcs. Each one starting and ending at an specific degree and and given radius.
var dataset = {
"2":[{"degree1":0, "degree2":1.5707963267949,
"label":"Sample Text Test"},
{"degree1":1.5707963267949, "degree2":3.1415926535898,
"label":"Lorem ipsum sample text"},
{"degree1":3.1415926535898, "degree2":4.7123889803847,
"label":"Sample Text Text"},
{"degree1":4.7123889803847, "degree2":6.2831853071796,
"label":"Lorem ipsum"}],
"1":[{"degree1":0, "degree2":3.1415926535898,
"label":"Sample"},
{"degree1":3.1415926535898, "degree2":6.2831853071796,
"label":"Text"}],
"0":[{"degree1":0, "degree2":6.2831853071796,
"label":""}]
},
width = 450,
height = 450,
radius = 75;
// Helper methods
var innerRadius = function(d, i, j) {
return 1 + radius * j;
};
var outerRadius = function(d, i, j) {
return radius * (j + 1);
};
var startAngle = function(d, i, j) {
return d.data.degree1;
};
var endAngle = function(d, i, j) {
return d.data.degree2;
};
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.startAngle(startAngle)
.endAngle(endAngle);
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width >> 1) + ',' + (height >> 1) + ')');
var level = svg.selectAll('g')
.data(function(d) {
return d3.values(dataset);
})
.enter()
.append('g');
var entry = level.selectAll('g')
.data(function(d, i) {
return pie(d);
})
.enter()
.append('g');
entry.append('path')
.attr('fill', '#aaa')
.attr('d', arc)
.attr('id', function(d, i, j) {
return 'arc' + i + '-' + j;
});
var label = entry.append('text')
.style('font-size', '20px')
.attr('dx', function(d, i, j) {
return Math.round((d.data.degree2 - d.data.degree1) * 180 / Math.PI);
})
.attr('dy', function(d, i, j) {
return ((radius * (j + 1)) - (1 + radius * j)) >> 1;
});
label.append('textPath')
.attr('xlink:href', function(d, i, j) {
return '#arc' + i + '-' + j;
})
.style('fill', '#000')
.text(function(d) {
return d.data.label;
});
See http://jsfiddle.net/3FP6P/2/ :
But some problem still exists:
How to center (horizonal und vertical) an text along an textpath of an any length inside an arc described by innerRadius, outerRadius, startAngle and endAngle?
The text occurs sometimes bold, sometimes not. Why?
The character spacing does not appear to be the same as if it is written inside a . Some letters stick more together as other ones. Why?
The letters are not located directly on the path. Some seem to have a little slip up or down. Why?
Vertical alignment
You can use another arc with radius (innerRadius + outerRadius) / 2 and use it as the textPath for the labels.
Note that even if you set the innerRadius == outerRadius, D3 will draw an path which moves clockwise and then anti-clockwise to doubles over itself. This becomes important while trying to figure out the horizontal centre of the path: it is at the 25% and 75% points while 0% and 50% points lie on the two tips of the arc.
Horizontal alignment
Use text-anchor: middle on the text element and set startOffset to 25% (or 75%) on the textPath.
Demo.
This is a more robust way than calculating the dx and dy by hand.
You should try out Lars's suggestions to further improve the quality and centring of the text, e.g. you might want to set text-rendering to optimizeLegibility and play with the baseline a bit.
Issues 2-4 are because of the font rendering. In my browser, the spacing and character size etc is consistent. You can try playing around with the text-rendering attribute to improve things.
To get the text centred, you will need to set the alignment-baseline and/or dominant-baseline attributes.
If this still doesn't give you the results you're looking for, try decreasing the font size. This may help because a slight rotation of a character will be less visible.