How to make d3 milestone Shape - d3.js

I'm trying to align a down triangle with a rectangle to make a group that can be used to represent a milestone. Any ideas why this code only shows 2 triangles and how to move them to centre bottom of rectangle or any other methods to achieve the same goal?
http://jsfiddle.net/sjp700/Pej4M/
tri.enter().append("path")
.attr("d", d3.svg.symbol().type("triangle-down"))
.style("fill", "black")
.attr("transform", function (d) { return "translate(" + xRange(d.start) + "," + yRange(d.Duration) + ")"; });

As pointed out in the comments, the reason you're seeing only two rectangles is that some of the data is bound to existing paths. To fix, assign a special class to the symbols that you can select by:
var tri = vis.selectAll("path.tri").data(datar);
For the positioning of the symbols, you need to use the same values you use for the rectangles. The y position needs to be offset by a constant so that the symbols appear at the bottom and the x position by half the duration -- I'm guessing that this is what you really want to show as you're currrently hardcoding everything to length 50.
.attr("transform", function (d) { return "translate(" + (xRange(d.start) + 25) + "," + (yRange(d.start) + 15) + ")"; });
Complete demo here.

Related

Add text in rect svg and append it to arc in donut chart

I wanted to add labels to each arc in donut chart. I've added by taking the centroid of each arc and adding, but somehow it is not adding in correct position. I can't figure it out so I need some help regarding it. I've added my code in codepen. The link is here.
My donut should look like this.
Sample code is:
svg.selectAll(".dataText")
.data(data_ready)
.enter()
.each(function (d) {
var centroid = arc.centroid(d);
d3.select(this)
.append('rect')
.attr("class", "dataBG_" + d.data.value.label)
.attr('x', (centroid[0]) - 28)
.attr('y', (centroid[1]) - 5)
.attr('rx', '10px')
.attr('ry', '10px')
.attr("width", 50)
.attr("height", 20)
.style('fill', d.data.value.color)
.style("opacity", 1.0);
d3.select(this)
.append('text')
.attr("class", "dataText_" + d.data.value.label)
.style('fill', 'white')
.style("font-size", "11px")
.attr("dx", (centroid[0]) - 7)
.attr("dy", centroid[1] + 7)
.text(Math.round((d.data.value.value)) + "%");
});
Thanks in advance.
The difference between the "bad" state on codepen and the desired state is that in the one you don't like, you take the centroid and then you center your text on it. The centroid of a thick arc is the midpoint of the arc that runs from the midpoint of one line-segment cap to the other. This is roughly "center of mass" of the shape if it had some finite thickness and were a physical object. I don't think it's what you want. What you want is the midpoint of the outer arc. There's no function to generate it, but it's easy enough to calculate. Also, I think you want to justify your text differently for arcs whose text-anchor point is on the left hand of the chart from those on the right half. I'm going copy your code and modify it, with comments explaining.
// for some reason I couldn't get Math.Pi to work in d3.js, so
// I'm just going to calculate it once here in the one-shot setup
var piValue = Math.acos(-1);
// also, I'm noting the inner radius here and calculating the
// the outer radius (this is similar to what you do in codepen.)
var innerRadius = 40
var thickness = 30
var outerRadius = innerRadius + thickness
svg.selectAll(".dataText")
.data(data_ready)
.enter()
.each(function (d) {
// I'm renaming "centroid" to "anchor - just a
// point that relates to where you want to put
// the label, regardless of what it means geometrically.
// no more call to arc.centroid
// var centroid = arc.centroid(d);
// calculate the angle halfway between startAngle and
// endAngle. We can just average them because the convention
// seems to be that angles always increase, even if you
// if you pass the 2*pi/0 angle, and that endAngle
// is always greater than startAngle. I subtract piValue
// before dividing by 2 because in "real" trigonometry, the
// convention is that a ray that points in the 0 valued
// angles are measured against the positive x-axis, which
// is angle 0. In D3.pie conventions, the 0-angle points upward
// along the y-axis. Subtracting pi/2 to all angles before
// doing any trigonometry fixes that, because x and y
// are handled normally.
var bisectAngle = (d.startAngle + d.endAngle - piValue) / 2.0
var anchor = [ outerRadius * Math.cos(bisectAngle), outerRadius * Math.sin(bisectAngle) ];
d3.select(this)
.append('rect')
.attr("class", "dataBG_" + d.data.value.label)
// now if you stopped and didn't change anything more, you'd
// have something kind of close to what you want, but to get
// it closer, you want the labels to "swing out" from the
// from the circle - to the left on the left half of the
// the chart and to the right on the right half. So, I'm
// replacing your code with fixed offsets to code that is
// sensitive to which side we're on. You probably also want
// to replace the constants with something related to the
// the dynamic size of the label background, but I leave
// that as an "exercise for the reader".
// .attr('x', anchor[0] - 28)
// .attr('y', anchor[1] - 5)
.attr('x', anchor[0] < 0 ? anchor[0] - 48 : anchor[0] - 2)
.attr('y', anchor[1] - 10
.attr('rx', '10px')
.attr('ry', '10px')
.attr("width", 50)
.attr("height", 20)
.style('fill', d.data.value.color)
.style("opacity", 1.0);
d3.select(this)
.append('text')
.attr("class", "dataText_" + d.data.value.label)
.style('fill', 'white')
.style("font-size", "11px")
// changing the text centering code to match the box
// box-centering code above. Again, rather than constants,
// you're probably going to want something a that
// that adjusts to the size of the background box
// .attr("dx", anchor[0] - 7)
// .attr("dy", anchor[1] + 7)
.attr("dx", anchor[0] < 0 ? anchor[0] - 28 : anchor[0] + 14)
.attr("dy", anchor[1] + 4)
.text(Math.round((d.data.value.value)) + "%");
});
I tested. this code on your codepen example. I apologize if I affected your example for everyone - I'm not familiar with codepen and I don't know the collaboration rules. This is all just meant by way of suggestion, it can be made a lot more efficient with a few tweaks, but I wanted to keep it parallel to make it clear what I was changing and why. Hope this gives you some good ideas.

How to produce axes that do not intersect at (0, 0)?

I would like to generate axes that do not intersect at (0, 0) (and also do not necessarily coincide with the edges of a plot), as shown in the example below.
How can I do this with d3?
You will first need to figure out where you want to display the axis. If they are fixed to canvas, take ratios of width and height.
Here's an example that I made:
http://vida.io/documents/zB4P4fjHz79um3qzX
x-axis is at to 2/3 of height:
.attr("transform", "translate(0," + height * 2 / 3 + ")")
And y-axis is at 1/3 of width:
.attr("transform", "translate(" + width / 3 + ", 0)")
If you need the axis relative to range of values, calculate them based on range. For example:
var domain = d3.extent(data, function(d) { return d.y_axis; })
var y_axis_pos = width * (y_axis_value - domain[1]) / (domain[0] - domain[1]);
// svg code...
.attr("transform", "translate(" + y_axis_pos + ", 0)")
From the D3.js API documentation:
to change the position of the axis with respect to the plot, specify a transform attribute on the containing g element.

d3 on brush triangle symbols disappears

The grey strip in the lower area allows the user to spread the data across x axis .fiddle Everything is resizing apart from the triangle symbols which are generated initially but disappear as soon as I redraw using the brush (ps: this happen only when i run the code from the same code from the browser and not jsfiddle!)
My axis increases in length but 0min is repeated 3 to 4 times instead of showing 0.1 0.2 0.3 etc
milestone_marks.selectAll(".milestone_markers")
.data(milestones)
.enter().append("path")
.attr("d", d3.svg.symbol().type('triangle-down'))
.attr('class', 'milestone_markers')
.attr("transform", function (d) {
return "translate(" + x__axis(d.processing_time) + "," + -7 + ")";})
and in the brushed function I write the following:
d3.select('.milestone_marks').selectAll('path').data(milestones).attr("d", d3.svg.symbol().type('triangle-down')).attr("transform", function (d) {
return "translate(" + x__axis(d.processing_time) + "," + -7 + ")";})
Any inputs will be appreciated.

d3 line chart labels overlap

I've created a line chart based on the example found here:
http://bl.ocks.org/mbostock/3884955
However, with my data the line labels (cities) end up overlapping because the final values on the y-axis for different lines are frequently close together. I know that I need to compare the last value for each line and move the label up or down when the values differ by 12 units or less. My thought is to look at the text labels that are written by this bit of code
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
If the y(d.value.temperature) values differ by 12 or less, move the values apart until they have at least 12 units between them. Any thoughts on how to get this done? This is my first d3 project and the syntax is still giving me fits!
You're probably better off passing in all the labels at once -- this is also more in line with the general d3 idea. You could then have code something like this:
svg.selectAll("text.label").data(data)
.enter()
.append("text")
.attr("transform", function(d, i) {
var currenty = y(d.value.temperature);
if(i > 0) {
var previousy = y(data[i-1].value.temperature),
if(currenty - previousy < 12) { currenty = previousy + 12; }
}
return "translate(" + x(d.value.date) + "," + currenty + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
This does not account for the fact that the previous label may have been moved. You could get the position of the previous label explicitly and move the current one depending on that. The code would be almost the same except that you would need to save a reference to the current element (this) such that it can be accessed later.
All of this will not prevent the labels from being potentially quite far apart from the lines they are labelling in the end. If you need to move every label, the last one will be pretty far away. A better course of action may be to create a legend separately where you can space labels and lines as necessary.
Consider using a D3 force layout to place the labels. See an example here: https://bl.ocks.org/wdickerson/bd654e61f536dcef3736f41e0ad87786
Assuming you have a data array containing objects with a value property, and a scale y:
// Create some nodes
const labels = data.map(d => {
return {
fx: 0,
targetY: y(d.value)
};
});
// Set up the force simulation
const force = d3.forceSimulation()
.nodes(labels)
.force('collide', d3.forceCollide(10))
.force('y', d3.forceY(d => d.targetY).strength(1))
.stop();
// Execute thte simulation
for (let i = 0; i < 300; i++) force.tick();
// Assign values to the appropriate marker
labels.sort((a, b) => a.y - b.y);
data.sort((a, b) => b.value - a.value);
data.forEach((d, i) => d.y = labels[i].y);
Now your data array will have a y property representing its optimal position.
Example uses D3 4.0, read more here: https://github.com/d3/d3-force

What is the proper way to both rotate and translate text in d3?

I have an array with two strings and I want them to align with two circles (see example: http://bl.ocks.org/3028447)
I'm currently doing this:
.attr("transform", function(d, i) { return "translate(" + x(i)+",0) rotate(-45," + x(1)+"," + 0+") "; })
I was sure there was a simpler way to do it, something like this:
.attr("transform", function(d, i) { return "translate(" + x(i)+",0) rotate(-45) "; })
but when I use that I get this (http://bl.ocks.org/3028512), and I don't understand why.
You've combined your transform with x and y attributes:
.attr("y", 0)
.attr("x", 60)
These get applied before the transform (i.e., before the rotation), hence the text is not in the same position as the circles. Sometimes this technique is useful; the x moves the text parallel to the text’s baseline. So, if you wanted to position the text slightly outside the circle, you could change the x value to 6 rather than 60.

Resources