I'm trying to use D3.js to draw rectangles around a circle (think of drawing chairs around a table).
I've tried drawing each chair individually by setting it's x and y position and then rotating it, but I haven't had much luck.
I thought this might be a good approach:
group = container.append("g")
table = group.append("circle").
attr("cx", 100).
attr("cy", 100).
attr("r", 60).
attr("fill", "#FFF").
attr("stroke", "#b8b8b8").
attr("stroke-width", "2")
group
.append("rect")
.attr("x", 90)
.attr("y", 10)
.attr("width", 20)
.attr("height", 20)
group.attr("transform", "rotate(30, 100, 100)")
But I can't figure out how to make a transformation, redraw, and then make another transformation. Any ideas?
I ended up building upon my idea of using a rotation. Then, I just had to brush up a little on my geometry.
First, I find a point on the circle. The equation for finding a point on the circle, given you know the center-x and center-y (cx and cy) of the circle, is:
x = cx + (r * sin(a))
y = cx + (r * cos(a))
Where r is radius and a is the angle on the circle in radians.
Then, I drew every rectangle at the point (0, 0) and rotated them around the center of the circle.
Here is what my solution ended up looking like:
radians = (Math.PI * 0) / 180.0
x = 100 + (70 * Math.sin(radians))
y = 100 + (70 * Math.cos(radians))
for i in [0..360] by 30
container
.append("rect")
.attr("x", x - 10)
.attr("y", y)
.attr("width", 20)
.attr("height", 20)
.attr("transform", "rotate(#{i}, 100, 100)")
Note: Subtracting 10 from x accounts for the width of the rectangle.
Related
I have drawn a spiral using this code:
var width = 1000,
height = 1000,
start = 0,
end = 2.25,
numSpirals = 78,
margin = {top:50,bottom:50,left:50,right:50};
// Constructing the spiral:
// theta for the spiral
var theta = function(r) {
return numSpirals * Math.PI * r;
};
// the r works out the space within which the spiral can take shape - the width and height is set above
var r = d3.min([width, height]) / 2 - 40 ;
// The radius of the spiral
var radius = d3.scaleLinear()
.domain([start, end])
.range([40, r]);
// inserts svg into the DOM
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.left + margin.right)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// The path to draw the spiral needs data to inform it, points generates this, and is used in .datum(points) below
var points = d3.range(start, end + 0.02, (end - start) / 2000);
// this is the spiral, utilising the theta and radius generated above
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);
But, now I want to draw some lines on top of this spiral, following the existing path of the spiral. These lines will need to connect two different dates across two different columns within the array. I have tried a few approaches, such as d3.line but I can't get the lines to follow the spiral. I imagine I somehow need to reference the initial spiral? I am unsure how to proceed with this though.
I've created a function that will generate an SVG image with two lines and the curve of the angle between them. But I can't get the angle label to stay next to the angle. Because the SVG element itself has been flipped to simulated cartesian coordinates, the next needs to be flipped upside down again
The code is here: https://jsfiddle.net/wisehog/3s0g25vc/1/
I've tried using transform origin, translate and scale. But each time the angle label ends of the page. At the moment I tried setting the transform origin to the x and y coordinates of the text element and then tried to transform and scale it in order to flip it:
const angleText =
area
.append('text')
.text(`${midAngleDegrees}°`)
.attr('x', `${x + (angleRadius * Math.cos(midAnglePi))}`)
.attr('y', `${y + (angleRadius * Math.sin(midAnglePi))}`)
.attr('font-size', '0.8em')
const xOrigin = $('text')[0].attributes[0].value;
const yOrigin = $('text')[0].attributes[1].value;
angleText
.attr('transform-origin', `${xOrigin} ${yOrigin}`)
.attr('transform', `translate(0 ${yOrigin-10}) scale(1, -1)`)
Any help or hints would be appreciated.
Why not just to add some offset (10px for example) to each x and y coordinates for the label:
const angleText =
area
.append('text')
.text(`${midAngleDegrees}°`)
.attr('x', `${x + (angleRadius * Math.cos(midAnglePi)) + 10}`) // 10px offset
.attr('y', `${y + (angleRadius * Math.sin(midAnglePi)) + 10}`) // the same
.attr('font-size', '0.8em')
https://jsfiddle.net/3s0g25vc/18/
Hi everybody I am trying to rotate a rectangle while the user drag it with mouse. The rectangle follow a circular curve.
Below I attach my solution that is perfect, but the mouse is always on top left corner of the rectangle. I want that the mouse would be always in the center of the rectangle during dragging. How can I control it ?
Solution:
var drag = d3.drag().on("drag", function () {
var rect = d3.select(this);
var theta = Math.atan2(d3.event.y - height/2, d3.event.x - width/2) * 180 / Math.PI
rect
.attr("x", d3.event.x)
.attr("y", d3.event.y)
.attr('transform', `rotate(${theta + 90}, ${d3.event.x}, ${d3.event.y})`)
})
Full code of my solution you can see here: https://jsfiddle.net/hsspve49/
Offset the x and y attribute in the drag handler by the size of your rectangle, e.g.:
...
.attr("x", d3.event.x - 15) // half the width
.attr("y", d3.event.y - 35) // half the height
...
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.
In effort to learn D3.js, I took this example and made a plunker out of it
http://plnkr.co/edit/ynWB0GznvrMMRmqkaqaA?p=preview
Problem?
The data doesn't fit correctly and you can see that data is incomplete
How shall I fix it?
First thing is that your labels are incorrect. So change this:
.text(function(d) {
return 'd.data.age'; });
to this:
.text(function(d) {
return d.data.age; });
and you'll get the correct labels.
Second, the labels are getting cut off. This appears to be to do with the sizes specified for the pie chart. You can either increase the width specified (looks fine at 360 say):
var width = 360,
height = 300,
radius = Math.min(width, height) / 2;
Or bring the labels in closer to the graph:
.attr("transform", function(d) {
return "translate(" + ( (radius -50) * Math.sin( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) + "," + ( -1 * (radius - 50) * Math.cos( ((d.endAngle - d.startAngle) / 2) + d.startAngle ) ) + ")"; })
Where it says radius - x determines how close or far the labels will appear to the graph. The bigger x is, the closer they will be.