3

I've drawn an arc using D3.js which by default has square shaped ends.

var arc = d3.arc()
    .innerRadius(0)
    .outerRadius(100)
    .startAngle(0)
    .endAngle(Math.PI);
d3.selectAll('svg')
    .append('path')
    .attr('d', function() {
        return arc();
    });

How can I draw an arc with a chevron shape on one end of it.

Chevron shape

Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
  • SVG path maybe. Are you asking for something like force directed arcs, going towards one direction around the circle? And the ends are not square shaped, they are just flat lines. – Vlad Dec 16 '16 at 17:02
  • 1
    What do you mean by *"...an arc with a chevron shape on one end of it"*? What is that *arc* supposed to look like? Where is the *end* of that arc, where the chevron is to be drawn at. Could you scribble the visual result you are after? – altocumulus Dec 16 '16 at 17:24
  • Can you place the exact shape that you want. Or define how your object should look like. – Shalin Patel Dec 16 '16 at 18:00

2 Answers2

6

I think I understand what you are looking for, so I'll give it a go:

As you probably guess from the d3.js documentation, d3.arc() does not have the methods needed to make a point at one end. Padding and rounded corners are applied on both ends, and I can't see how they would work to form a point at both ends let alone one.

Two solutions come to mind (and there are probably many that I can't even conceive of)

  1. Lop off the end of each arc, based on its end angle, and append a triangle or other similar shape (alternatively, apply some sort of mask to trim the end into a point)
  2. Attempt to rework d3.arc() to your needs, taking up the invitation to develop/refine d3 in a modular fashion.

Personally, I think option one is probably much less clean and probably harder to design. Option two should be doable, and with this encouragement to dive in and make modules:

Small files are nice, but modularity is also about making D3 more fun. Microlibraries are easier to understand, develop and test. They make it easier for new people to get involved and contribute. They reduce the distinction between a “core module” and a “plugin”, and increase the pace of development in D3 features. (https://github.com/d3/d3/blob/master/CHANGES.md)

I thought I'd give this a go.


I've put together an attempt that might be a start for a chevron tipped arc module based on the d3.arc() function.

The rounded corners portion of the d3.arc() function in the d3-shape.js module is likely the best place to look as it shows modifications to the arc ends. The portions of the module that modify the arc, in the event of rounded corners, look like:

      context.arc(t0.cx, t0.cy, rc1, Math.atan2(t0.y01, t0.x01), Math.atan2(t0.y11, t0.x11), !cw);
      context.arc(0, 0, r1, Math.atan2(t0.cy + t0.y11, t0.cx + t0.x11), Math.atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw);
      context.arc(t1.cx, t1.cy, rc1, Math.atan2(t1.y11, t1.x11), Math.atan2(t1.y01, t1.x01), !cw);

The outer edge is handled first (and shown above). The first line is the rounding on the rear outside corner, the third line is the rounding on the forward outside corner. Simply removing the third line allows for a pointed arc (if you remove it from the inside edge too). Then the remaining challenge is making the other end of the arc flat, which I did by using the start angle and the inner & outer radii to find the corners of the arc to create a flat end.

The end result was something like:

// get tail coordinate (outer)
    var tailOuter = {};
    tailOuter.x = Math.cos(a0) * r1; // a0 = starting angle
    tailOuter.y = Math.sin(a0) * r1; // r1 = outer radius

     context.moveTo(tailOuter.x, tailOuter.y);
     context.arc(0, 0, r1, Math.atan2(t0.cy + t0.y11, t0.cx + t0.x11), Math.atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw);

I've put together a quick and dirty module that takes the d3.arc() function and creates a d3.cheveronArc() function instead. It's a gutted modification to d3.arc() and has only four methods (inner/outerRadius(),start/endAngle()). It has no means to check for parameters that will likely cause misbehavior (eg: chevron is longer than the arc). It is merely a proof of concept, though I am happy with how it looks for a rather quick attempt:

enter image description here

As you might notice, the inner most circle has an odd shape near its tail, small inner radii seem to cause some problems like that.

The code can be viewed at: http://bl.ocks.org/andrew-reid/3375e602cc6c00c4e3ea4799d171ee27

Looking at it, I feel like I want to add the option to add the inverse of the chevron to the rear end of the arcs for a better visual effect, but that's a different problem.

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
  • Good answer. The only tweak I think should be made would be to not center the point, but rather to ensure that you are creating an isosceles triangle. Doing an image search for "circle arrow", all of the images use isosceles arrows as their point. – Paul S Dec 17 '16 at 16:55
  • 1
    Don't know why he didn't mention it himself (perhaps just forgot about this question), but here's Andrew Reid's improved version: https://bl.ocks.org/Andrew-Reid/51d2665a976266abbaa62eae1ba80cdc It doesn't have the odd-tail-issue and can render chevrons at the end too. – alex3683 Apr 05 '18 at 11:45
  • Can you please suggest, How to import it in TypeScript? – Vishal Jan 06 '20 at 08:55
3

I would just use d3's path generator with an SVG marker. Add any shape to any path. Edit the "ends" by editing the marker definition. Use D3 path generator to define your arc (or any path).

It's worth noting that if you take this approach you have to use the d3 path generator rather than the d3 arc generator because the arc generator implicitly closes the path (putting your "end" marker back at the beginning of the path).

In my example, I added the chevron to the start as well just to show that it's as trivial as adding .attr("marker-start","url(#chevron)") and .attr("marker-end","url(#chevron)")

D3 Path Generator | https://github.com/d3/d3-path/blob/master/README.md#path

SVG Markers | https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker


edit: and now that I think of it, you can probably use d3.symbols to generate your markers/ends for you instead of manually defining the shape path. The chevron would have to be custom, but you could probably use the triangle symbol.

D3 Symbols | https://github.com/d3/d3-shape#symbols


console.clear()

var path = d3.path()
path.arc(225,80,70,1,-.5)

var path2 = d3.path()
path2.moveTo(20,20)
path2.bezierCurveTo(150,300,200,0,450,100)

d3.select("svg").append("path")
  .attr("d", path2.toString())
  .attr("stroke","steelblue")
  .attr("fill","none")
  .attr("stroke-width","20")
  .attr("marker-start","url(#chevron)")
  .attr("marker-end","url(#chevron)")

d3.select("svg").append("path")
  .attr("d", path.toString())
  .attr("stroke","#43A2CA")
  .attr("fill","none")
  .attr("stroke-width","20")
  .attr("marker-start","url(#chevron)")
  .attr("marker-end","url(#chevron)")
<script src="https://unpkg.com/d3@4.4.0"></script>
<?xml version="1.0"?>
<svg width="500" height="200" viewBox="0 0 500 200">

  <defs>
    <marker id="chevron"
      viewBox="0 0 20 20" refX="10" refY="10" 
      markerUnits="userSpaceOnUse"
      markerWidth="20" markerHeight="20"
      orient="auto" fill="black">
      <path d="M0 0 10 0 20 10 10 20 0 20 10 10Z" />
    </marker>
  </defs>

</svg>
Steve Ladavich
  • 3,472
  • 20
  • 27
  • After a second look, I really should have awarded the bounty to you, apologies. Markers are a really cool feature I was not aware of. Now the only trouble I have is that animating a path so it looks like it's being drawn does not also animate location of the marker. – Muhammad Rehan Saeed Dec 19 '16 at 11:11
  • @MuhammadRehanSaeed, no worries man. Sometimes there are multiple good answers to a question and it's really cool that Andrew Reid made a custom d3-chevronArc to match your exact use case. Let me know if you end up posting a new question about the animation issue. – Steve Ladavich Dec 19 '16 at 17:23