SubPath
is the fundamental type in this package.
t
In most cases it should be created with functions from the Curve
module.
import Curve
import SubPath exposing (connect)
import Svg
import Svg.Attributes exposing (fill)
right =
Curve.linear [ ( 0, 0 ), ( 1, 0 ) ]
down =
Curve.linear [ ( 0, 0 ), ( 0, 1 ) ]
This module has several functions for composing subpaths
topRightCorner =
right
|> connect down
bottomLeftCorner
down
|> connect right
square =
topRightCorner
|> connect (reverse bottomLeftCorner)
|> close
And can generate svg elements
view : Svg msg
view =
Svg.svg [] [ SubPath.element square [ fill "none" ] ]
A subpath is one moveto command followed by an arbitrary number of drawto commands.
Note: Equality with the default ==
function in unreliable for SubPath
. The easiest way to check for
equality is to use SubPath.toString
on both arguments.
If you need more "fuzzy" equality use toStringWith
, for instance:
options : List Option
options =
[ decimalPlaces 3, mergeAdjacent ]
equalSubpaths : SubPath -> SubPath -> Bool
equalSubpaths a b =
toStringWith options a == toStringWith options b
with : LowLevel.Command.MoveTo -> List LowLevel.Command.DrawTo -> SubPath
Construct a subpath
Always try to use a function from Curve
over manual subpath construction!
import LowLevel.Command exposing (moveTo, lineTo)
SubPath.with (moveTo (0,0)) [ lineTo [ (10,10), (10, 20) ] ]
empty : SubPath
An empty subpath
element : SubPath -> List (Svg.Attribute msg) -> Svg msg
Construct an svg path element from a Path
with the given attributes
Svg.svg []
[ SubPath.element mySubPath [ stroke "black" ] ]
toString : SubPath -> String
Convert a subpath into SVG path notation
import Curve
line : SubPath
line = Curve.linear [ (0,0), (10,10), (10, 20) ]
SubPath.toString line --> "M0,0 L10,10 10,20"
toStringWith : List Option -> SubPath -> String
toString with options
Formatting options
decimalPlaces : Basics.Int -> Option
Set the maximum number of decimal places in the output
import Curve
line : SubPath
line = Curve.linear [ (0, 0), (1/3, 1/7) ]
SubPath.toString line
--> "M0,0 L0.3333333333333333,0.14285714285714285"
SubPath.toStringWith [ decimalPlaces 3 ] line
--> "M0,0 L0.333,0.143"
mergeAdjacent : Option
Join adjacent instructions where possible This can save a few characters, but more importantly makes comparison of subpaths (based on the ouput string) more reliable.
import Curve
right : SubPath
right = Curve.linear [ (0, 0), (1, 0) ]
down : SubPath
down = Curve.linear [ (0, 0), (0, 1) ]
line : SubPath
line =
right
|> continue down
SubPath.toString line
--> "M0,0 L1,0 L1,1"
SubPath.toStringWith [ mergeAdjacent ] line
--> "M0,0 L1,0 1,1"
reverse : SubPath -> SubPath
Reverse a subpath
The direction of a subpath can be important if you want to use SVG fills. Another use is in composing subpaths:
arrowHead : ( Float, Float ) -> Float -> SubPath
arrowHead location angle =
let
line =
Curve.linear [ ( 0, 0 ), ( 10, 0 ) ]
|> SubPath.translate location
a =
SubPath.rotate (angle - (pi + pi / 4)) line
b =
SubPath.rotate (angle + (pi + pi / 4)) line
in
SubPath.reverse a
|> SubPath.continue b
compress : SubPath -> SubPath
Try to merge adjacent instructions
This conversion is costly (timewise), but can shorten a subpath considerably, meaning other functions are faster.
Additionally, the toString output can become shorter.
import Curve
curve : SubPath
curve =
Curve.quadraticBezier ( 0, 0 )
[ ( ( 0.5, -0.5 ), ( 1.0, 0 ) ) ]
down : SubPath
down =
Curve.linear [ ( 0, 0 ), ( 0, 1 ) ]
curve
|> connect down
|> SubPath.toString
--> "M0,0 Q0.5,-0.5 1,0 L0,0 L0,1"
curve
|> continue down
|> SubPath.toString
--> "M0,0 Q0.5,-0.5 1,0 L1,1"
curve
|> continueSmooth down
|> SubPath.toString
--> "M0,0 Q0.5,-0.5 1,0 L1.707106781187,0.707106781187"
close curve
|> SubPath.toString
--> "M0,0 Q0.5,-0.5 1,0 Z"
continue : SubPath -> SubPath -> SubPath
Start the second subpath where the first one ends
connect : SubPath -> SubPath -> SubPath
Join two subpaths, connecting them with a straight line
continueSmooth : SubPath -> SubPath -> SubPath
Start the second subpath where the first one ends, and rotate it to continue smoothly
close : SubPath -> SubPath
Append a ClosePath at the end of the subpath (if none is present)
translate : ( Basics.Float, Basics.Float ) -> SubPath -> SubPath
Translate the subpath by a vector
rotate : Basics.Float -> SubPath -> SubPath
Rotate a subpath around its starting point by an angle (in radians).
scale : ( Basics.Float, Basics.Float ) -> SubPath -> SubPath
Scale the subpath in the x and y direction
For more complex scaling operations, define a transformation matrix and use mapCoordinate
.
mapCoordinate : (( Basics.Float, Basics.Float ) -> ( Basics.Float, Basics.Float )) -> SubPath -> SubPath
Map over all the 2D coordinates in a subpath
mapWithCursorState : (LowLevel.Command.CursorState -> LowLevel.Command.DrawTo -> b) -> SubPath -> List b
Map over each drawto with the CursorState available.
The CursorState contains the subpath start position and the current cursor position at the current DrawTo
The arc length parameterization is a way of expressing a curve in terms of its arc length. For instance, pointAlong
expects a distance, and returns the 2D coordinate reached
when walked that distance along the curve.
This is great for calculating the total length of your subpath (for instance to style based on the length) and to get evenly spaced points on the subpath.
The arc length parameterization as a binary tree of segments.
arcLengthParameterized : Basics.Float -> SubPath -> ArcLengthParameterized coordinates
Build an arc length parameterization from a subpath.
For calculating the parameterization, approximations are used. To bound the error that approximations introduce,
you can supply a tolerance
: Operations (arcLength, pointOn, ect.) are at most tolerance
away from the truth.
tolerance =
1.0e-4
parameterized =
arcLengthParameterized tolerance mySubPath
Note: keep the scale of your curve in mind. if the length of the curve is 100, then an
tolerance
of0.1
is probably enough for the difference not to be visible.Using a much smaller
tolerance
can really slow down your page.
arcLength : ArcLengthParameterized coordinates -> Basics.Float
Find the total arc length of an elliptical arc. This will be accurate to within the tolerance given when calling arcLengthParameterized.
import Curve
Curve.linear [ (0,0), (100, 0) ]
|> arcLengthParameterized 1e-4
|> arcLength
--> 100
evenlySpaced : Basics.Int -> ArcLengthParameterized coordinates -> List Basics.Float
Evenly splits the curve into count
segments, giving their length along the curve
evenlySpacedWithEndpoints : Basics.Int -> ArcLengthParameterized coordinates -> List Basics.Float
Similar to evenlySpaced
, but also gives the start and end point of the curve
evenlySpacedPoints : Int -> ArcLengthParameterized coordinates -> List ( Float, Float )
evenlySpacedPoints count parameterized =
evenlySpacedWithEndpoints count parameterized
|> List.filterMap (pointAlong parameterized)
evenlySpacedPoints : Basics.Int -> ArcLengthParameterized coordinates -> List ( Basics.Float, Basics.Float )
Find n
evenly spaced points on an arc length parameterized subpath
Includes the start and end point.
import Curve
curve : ArcLengthParameterized
curve =
Curve.linear [ (0,0), (10, 0) ]
|> arcLengthParameterized 1e-4
evenlySpacedPoints 1 curve
--> [ (5, 0) ]
evenlySpacedPoints 2 curve
--> [ (0, 0), (10, 0) ]
evenlySpacedPoints 5 curve
--> [(0,0),(2.5,0),(5,0),(7.5,0),(10,0)]
pointAlong : ArcLengthParameterized coordinates -> Basics.Float -> Maybe ( Basics.Float, Basics.Float )
A point at some distance along the curve.
import Curve
parameterized : ArcLengthParameterized
parameterized =
Curve.quadraticBezier (0,0) [ ( (5,0), (10, 0) ) ]
|> arcLengthParameterized 1e-4
pointAlong parameterized (arcLength parameterized / 2)
--> Just (5, 0)
tangentAlong : ArcLengthParameterized coordinates -> Basics.Float -> Maybe ( Basics.Float, Basics.Float )
The tangent along the curve
import Curve
parameterized : ArcLengthParameterized
parameterized =
Curve.quadraticBezier (0,0) [ ( (5,0), (10, 0) ) ]
|> parameterized 1e-4
tangentAlong parameterized (arcLength parameterized / 2)
--> Just (1, 0)
parameterValueToArcLength : ArcLengthParameterized coordinates -> Basics.Float -> Maybe Basics.Float
Find the arc length at some parameter value.
arcLengthToParameterValue : ArcLengthParameterized coordinates -> Basics.Float -> Maybe Basics.Float
Find the parameter value at some arc length
toSegments : SubPath -> List (Segment coordinates)
Convert a subpath to its Segment
s
import Curve
import Segment exposing (line)
Curve.linear [ (0,0), (10,10), (20, 10) ]
|> toSegments
--> [ line (0,0) (10,10) , line (10, 10) (20, 10) ]
fromSegments : List (Segment coordinates) -> SubPath
Convert a list of segments to a path
In the conversion, the starting point of a segment is discarded: It is assumed that for every two adjacent segments in the list, the first segment's end point is the second segment's starting point
import Curve
import Segment exposing (line)
[ line (0,0) (10,10) , line (10, 10) (20, 10) ]
|> fromSegments
|> SubPath.toStringWith [ mergeAdjacent ]
--> SubPath.toString <| Curve.linear [ (0,0), (10,10), (20, 10) ]
fromLowLevel : Path.LowLevel.SubPath -> SubPath
Converting a svg-path-lowlevel subpath into a one-true-path subpath. Used in parsing
Beware that the moveto is always interpreted as Absolute.
toLowLevel : SubPath -> Maybe Path.LowLevel.SubPath
Converting a one-true-path subpath into a svg-path-lowlevel subpath. Used in toString
unwrap : SubPath -> Maybe { moveto : LowLevel.Command.MoveTo, drawtos : List LowLevel.Command.DrawTo }
deconstruct a subpath into its components