问题
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.
回答1:
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>
回答2:
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)
- 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)
- 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:
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.
来源:https://stackoverflow.com/questions/41022425/d3-arc-with-chevron-shaped-end