Draw an arc between two points - javascript

I'm working on a proof of concept for an application that I think D3 might be a good fit for. Since I'm new to D3 I thought I would start off simple and build my way up to the application requirements. However, I seem to be hitting a snag on what I believe should be a very easy task with this library. I want to place two small circles on an SVG and then draw an arc or curve between them. Based on the documentation, I believe arcTo would be the best fit for this since I know the start and end points. For the life of me I cannot get it to draw the arc. The circles are drawn perfectly every time though.
var joints = [{x : 100, y : 200, r : 5},
{x : 150, y : 150, r : 5}];
var svg = d3.select("svg");
svg.selectAll("circle")
.data(joints)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; });
svg.selectAll("path").append("path").arcTo(100,200,150,150,50)
.attr("class", "link");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="800" />
I'm either going about this the wrong way or I don't fully understand how to append a path to an SVG. Can someone point me in the right direction? I haven't been able to find many examples of arcTo. Thank you!

You misunderstood what d3.path() is. According to the API:
The d3-path module lets you take [a HTML Canvas] code and additionally render to SVG.
And for d3.path():
d3.path(): Constructs a new path serializer that implements CanvasPathMethods.
As you can see, the d3-path module has only a bunch of methods that allow you to take a HTML canvas code and use it to draw SVG elements.
That being said, you cannot use arcTo straight away in the SVG, as you are doing right now. It should be:
var path = d3.path();
path.moveTo(100, 200);
path.arcTo(100,200,150,150,50)
... and then:
svg.append("path")
.attr("d", path.toString())
However, as an additional problem, arcTo is more complicated than that: the first two values are not the x and y of the starting point, but the coordinates of the first tangent.
Here is a demo, using different values for arcTo, which I think is what you want:
var joints = [{
x: 100,
y: 200,
r: 5
}, {
x: 150,
y: 150,
r: 5
}];
var svg = d3.select("svg");
svg.selectAll("circle")
.data(joints)
.enter().append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.r;
});
var path = d3.path();
path.moveTo(100, 200);
path.arcTo(100, 150, 150, 150, 50);
svg.append("path")
.attr("d", path.toString())
.attr("stroke", "firebrick")
.attr("stroke-width", 2)
.attr("fill", "none");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="250" />
An easy alternative is simply dropping the d3.path() and doing all this using just SVG code. There are plenty of examples showing how to draw an SVG arc from point A to point B with a given radius.

Related

Dotplot distribution (frequency count)

I am trying to replicate a histogram that is made up of dots. Here is a good reference:
My data has the following format:
{'name':'Company1', 'aum':42, 'growth':16},
{'name':'Company2', 'aum':36, 'growth':24},
{'name':'Company3', 'aum':34, 'growth':19},
...
In my case I'm binning by aum and color coding by growth. Everything seemed fine up until I went to set the cy attribute.
graphGroup.selectAll('circ')
.data(data)
.enter()
.append('circ')
.attr('r',8)
.attr('cx', function(d) { return xScale(d.aum);})
.attr('cy', ??????)
.style('fill', function(d) { return colorScale(d.growth);});
The only way I can think of to handle this is to re-engineer the data to give it an explicit y index value or something. However, this is not supported in my statistics suite and the n is too large for me to manually do it.
Question
Given my current data structure, can d3 help me with anything ad hoc to get the correct y value? I was thinking a counter might work, but I was unable to devise a bin-specific counter. Maybe d3 has a more elegant solution than a counter.
...can d3 help me with anything ad hoc to get the correct y value?
No, there is no native method for this. But here is where most people get D3 wrong: D3 is not a charting tool (except for the axis module, D3 doesn't paint anything!), it's just a collection of JavaScript methods. This fact simultaneously gives D3 its weakness (steep learning curve) and its advantage (you can do almost anything with D3).
In your case, for instance, all I'd do is separate the data in bins (using a histogram generator, for instance, or just manipulating the data directly) and then appending the circles in each bin/group using their indices.
It can be just something like this:
circle.attr("cy", function(_, i) {
return maxValue - circleRadius * i;
});
This will append the first circle of each bin (group) at the base of the SVG, and then each subsequent one in a smaller y coordinate, according to its index.
Check this basic demo:
const data = d3.range(100).map(() => Math.random());
const svg = d3.select("svg");
const xScale = d3.scalePoint(d3.range(10).map(d => d / 10), [20, 480])
.padding(0.5);
const binData = d3.histogram()
.domain([0, 1])(data);
const g = svg.selectAll(null)
.data(binData)
.enter()
.append("g")
.attr("transform", d => "translate(" + xScale(~~(100 * d.x0) / 100) + ",0)");
g.selectAll(null)
.data(d => d)
.enter()
.append("circle")
.attr("r", 5)
.attr("cy", function(_, i) {
return 220 - 13 * i;
})
.style("fill", "gray")
.style("stroke", "black");
const axisGroup = svg.append("g")
.attr("transform", "translate(0,230)")
.call(d3.axisBottom(xScale));
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="250"></svg>

d3j snake curved path animation

I am interested in trying to create a controlled curved path. Is there a way to plot specific coordinates and styling to mimic something like this design. I imagine it as a kind of 2D Donnie Darko time tunnel or slinkey/snake.
update 1 - journey path 1
http://jsfiddle.net/0ht35rpb/241/
update 2 - journey 2
** I've given it a softer look with stroke-linecap: round -- http://jsfiddle.net/0ht35rpb/243/
update 3 - journey 3
http://jsfiddle.net/0ht35rpb/245/
^ I've started to create multiple path lines - be good to organise this so its easier to make/control
-- essentially the journey will need to consist of the key gates to pass and corners -- and maybe have different colors/speeds to take on.
update 4- journey 4 - 18/10/2017
I've upgraded this to v4 - and made a getCoord function - so the journeys can be made and ran from a series of ids
http://jsfiddle.net/0ht35rpb/257/
I've adapted some path animation code - but I am not sure how to control or modify the path to hit specific coordinates.
//animation curved path.
http://jsfiddle.net/0ht35rpb/217/
//static curved path
http://jsfiddle.net/0ht35rpb/215/
//dot plots
http://jsfiddle.net/0ht35rpb/222/
How would I draw a line from do1 to dot3 -- or animate a curved path following multiple dot points?
var width = 600;
var height = 400;
var bezierLine = d3.svg.line()
.x(function(d) { return d[0]; })
.y(function(d) { return d[1]; })
.interpolate("basis");
var svg = d3.select("#bezier-demo")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.append('path')
.attr("d", bezierLine([[0, 40], [25, 70], [50, 100], [100, 50], [150, 20], [200, 130], [300, 120]]))
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("fill", "none")
.transition()
.duration(2000)
.attrTween("stroke-dasharray", function() {
var len = this.getTotalLength();
return function(t) { return (d3.interpolateString("0," + len, len + ",0"))(t) };
});
I made a simple js fiddle where i have 3 point with a curve. When you click it add a point to the curve and transition to it:
https://jsfiddle.net/cs00L0ok/
here is the onclick that add the new point
svg.on("click", function (d) {
// add a nex anchor point
circle_data.push({
x: d3.event.x,
y: d3.event.y
});
d3.select("path")
.transition()
.duration(2000)
.attr("d", bezierLine(circle_data))
})
I let you look into the jsfidddle to see the transition to the new point.
You can see how i control my path. I hope that help you. come back to me if you have answer / want more info

D3: Cannot append simple path to svg container

I am not succeding in appending a path on a simple svg container.
It considers my "path" element as a text instead.
Here is my code:
// Create the SVG
var tracer = {};
tracer.$container = document.getElementById('container');
tracer.$svg = $('<svg class="svg-d3-table" style="width:100%; height:100%">');
tracer.$container.append(tracer.$svg);
// Specify the path points
var pathInfo = [{x:0, y:60},
{x:50, y:110},
{x:90, y:70},
{x:140, y:100}];
// Specify the function for generating path data
var pathLine = d3.line()
.x(function(d){return d.x;})
.y(function(d){return d.y;})
.curve(d3.curveBasis);
// Append path
tracer.$svg.append("path")
.attr("d", pathLine(pathInfo))
.attr("stroke", "white")
.attr("stroke-width", 8)
.attr("fill", "none");
Instead of having a new path element as
<path d="M314,352L314,352C314,352,314,352,..."></path>
It comes with the following:
<svg class="svg-d3-table" style="..." d="M0,60L8.3,68.3.7,...">path</svg>
What am I missing?
PS: Sorry, I come from c++ and can be struggled with some very basic js operations.
Thank you very much.
Kind Regards.
As others have pointed out in the comments, avoid mixing jquery and d3.
The root of your problems though is that your tracer.$svg is a jquery selector but you are treating it as a d3 selector. While both of them have an append method, they are two wholly different beasts (and as #altocumulus points out, jquery don't play so nice manipulatively SVG).
Here's your code as I would write it with just d3:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="container" style="background-color: steelblue"></div>
<script>
// Create the SVG
var tracer = {};
tracer.$container = d3.select('#container');
tracer.$svg = tracer.$container.append("svg")
.attr("class", "svg-d3-table")
.style("width", "100%")
.style("height", "100%");
// Specify the path points
var pathInfo = [{
x: 0,
y: 60
}, {
x: 50,
y: 110
}, {
x: 90,
y: 70
}, {
x: 140,
y: 100
}];
// Specify the function for generating path data
var pathLine = d3.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.curve(d3.curveBasis);
// Append path
tracer.$svg.append("path")
.attr("d", pathLine(pathInfo))
.attr("stroke", "white") //<-- need a color?
.attr("stroke-width", 8)
.attr("fill", "none");
</script>
</body>
</html>
Thanks to altocumulus:
jquery's append not working with svg element?
Is exactly the kind of information I was looking for better understanding of manipulating DOM (but still shows me how much front-end development can be akward).
Thank to Gerardo Furtado:
Indeed the need of different lib leads easily to confusing behavior and makes JS hard to handle when working on an existing project (how to determine namespace by reading code, avoid collision...)
I handled my problem by only managing my svg using D3 as you adviced:
tracer.$svg = tracer.capsule.$svg = d3.select("#" + tracer.defaultName)
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("id", tracer.defaultName + "-svg")
.attr("class", "svg-d3-table")
.attr("xmlns", "http://www.w3.org/2000/svg");
Thank you for your comments.

D3 Tree Diagram Curved Line Start Position

Summary
Using the example below I've created a database driven tree view with blocks of information instead of the circle nodes.
Interactive d3.js tree diagram
Please see the example screenshot below:
The idea is for the lines to start from where the block ends. I assume it's something to do with the following function:
// Custom projection
var linkProjection = d3.svg.diagonal()
.source(function (d) {
return { "y": d.source.y, "x": d.source.x };
})
.target(function (d) {
return { "y": d.target.y, "x": d.target.x };
})
.projection(function (d) {
return [d.y, d.x];
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.style("fill", "none")
.style("stroke", "#d1d6da")
.style("stroke-width", "1")
.attr("d", function (d) {
var s = { x: source.x0, y: source.y0 };
var t = { x: source.x0, y: source.y0 };
return linkProjection({ source: s, target: t });
});
I have tried adding the block width to the y coordinate but although it starts from the correct position with the drawing it ends at the start of the block again.
Any suggestions?
Without more code it's hard to say, but I think it has to do with the coordinate system. x,y point to the center of the object I think. So you'll have to translate y to the the right by half the length of your box.
i.e. The parents previous position (x,y) points to the center of the parent.

generate clipPaths for multiple elements in d3.js

I am trying to create partially filled circles like the ones in the final NYT political convention visualization: http://www.nytimes.com/interactive/2012/09/06/us/politics/convention-word-counts.html
The two clearest code examples I've found for clipPaths in d3 ( https://gist.github.com/1067636 and http://bl.ocks.org/3422480) create individual div elements with unique ids for each clip-path and then apply these paths to single elements.
I can not figure out how to go from these examples to a visualization with a unique circular clipPath for each element in a set of elements based on data values so that I can create my effect.
Here is what I have so far:
Given data with the following structure:
data = [
{value: 500, pctFull: 0.20, name: "20%"},
{value: 250, pctFull: 0.75, name: "75%"},
{value: 700, pctFull: 0.50, name: "50%"},
]
1) Create a force diagram with a circle for each object in the dataset. The area of the circle is derived from the objects value.
2) Calculate k (and h) from a proportion (pctFull) for each datapoint using the algorithm from the mbostock example http://bl.ocks.org/3422480
3) Use k to generate a rect for each datapoint that covers the appropriate area of the circle.
I think the illustration would be done if I could limit the visibility of each rect to its respective circle but this is where I am stuck. I've tried a bunch of things, none of which have worked.
Here's the jsfilddle: http://jsfiddle.net/G8YxU/2/
See a working fiddle here: http://jsfiddle.net/nrabinowitz/79yry/
// blue circle
node.append("circle")
.attr("r", function(d, i) {return rVals[i];})
.style("fill", "#80dabe")
.style("stroke", "#1a4876");
// clip path for the brown circle
node.append("clipPath")
// make an id unique to this node
.attr('id', function(d) { return "clip" + d.index })
// use the rectangle to specify the clip path itself
.append('rect')
.attr("x", function(d, i){ return rVals[i] * (-1);})
.attr("width", function(d, i){ return rVals[i] * 2;})
.attr("y", function(d, i) {return rVals[i] - (2 * rVals[i] * kVals[i]);})
.attr("height", function(d, i) {return 2 * rVals[i] * kVals[i];});
// brown circle
node.append("circle")
// clip with the node-specific clip path
.attr("clip-path", function(d) { return "url(#clip" + d.index + ")"})
.attr("r", function(d, i) {return rVals[i];})
.style("fill", "#dabe80")
.style("stroke", "#1a4876");
It looks like the only way to specify a clip path for an element is to use the url(IRI) notation in the clip-path attribute, which means that you'll need a unique id for each clip path based on the node data. I've used the form clip<node index> for the id - so each node gets its own clip path, and other sub-elements of the node can refer to it.
It seemed easiest, following Mike's example, to make two circles of different colors and use the rectangle itself for the clip path, rather than making a circle-based clip path. But you could do it either way.

Categories

Resources