ianmackenzie/elm-geometry-svg/1.0.0

NOTE: this package is not compatible with Elm 0.19.1

Geometry.Svg

Draw 2D elm-geometry values as SVG.

Reading this documentation

For the examples, assume that the following imports are present:

import Svg exposing (Svg)
import Svg.Attributes as Attributes
import Geometry.Svg as Svg

Also assume that any necessary elm-geometry modules/types have been imported using the following format:

import Point2d exposing (Point2d)

All examples use a Y-up coordinate system instead of SVG's Y-down (window) coordinate system; they were all rendered with a final relativeTo call to flip the example 'upside down' for display.

Geometry

These functions turn elm-geometry 2D values into SVG elements with geometric attributes such as points and transform set appropriately. Each function also accepts a list of additional SVG attributes such as fill or stroke that should be added to the resulting element.

lineSegment2d : List (Attribute msg) -> LineSegment2d -> Svg msg

Draw a LineSegment2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#lineSegment

lineSegment : Svg Never
lineSegment =
    Svg.lineSegment2d
        [ Attributes.stroke "blue"
        , Attributes.strokeWidth "5"
        ]
        (LineSegment2d.fromEndpoints
            ( Point2d.fromCoordinates ( 100, 100 )
            , Point2d.fromCoordinates ( 200, 200 )
            )
        )
triangle2d : List (Attribute msg) -> Triangle2d -> Svg msg

Draw a Triangle2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#triangle

triangle : Svg Never
triangle =
    Svg.triangle2d
        [ Attributes.stroke "blue"
        , Attributes.strokeWidth "10"
        , Attributes.strokeLinejoin "round"
        , Attributes.fill "orange"
        ]
        (Triangle2d.fromVertices
            ( Point2d.fromCoordinates ( 100, 100 )
            , Point2d.fromCoordinates ( 200, 100 )
            , Point2d.fromCoordinates ( 100, 200 )
            )
        )
polyline2d : List (Attribute msg) -> Polyline2d -> Svg msg

Draw a Polyline2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#polyline

polyline : Svg Never
polyline =
    Svg.polyline2d
        [ Attributes.stroke "blue"
        , Attributes.fill "none"
        , Attributes.strokeWidth "5"
        , Attributes.strokeLinecap "round"
        , Attributes.strokeLinejoin "round"
        ]
        (Polyline2d.fromVertices
            [ Point2d.fromCoordinates ( 100, 100 )
            , Point2d.fromCoordinates ( 120, 200 )
            , Point2d.fromCoordinates ( 140, 100 )
            , Point2d.fromCoordinates ( 160, 200 )
            , Point2d.fromCoordinates ( 180, 100 )
            , Point2d.fromCoordinates ( 200, 200 )
            ]
        )
polygon2d : List (Attribute msg) -> Polygon2d -> Svg msg

Draw a Polygon2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#polygon

polygon : Svg Never
polygon =
    Svg.polygon2d
        [ Attributes.stroke "blue"
        , Attributes.fill "orange"
        , Attributes.strokeWidth "3"
        ]
        (Polygon2d.with
            { outerLoop =
                [ Point2d.fromCoordinates ( 100, 200 )
                , Point2d.fromCoordinates ( 120, 150 )
                , Point2d.fromCoordinates ( 180, 150 )
                , Point2d.fromCoordinates ( 200, 200 )
                ]
            , innerLoops =
                [ [ Point2d.fromCoordinates ( 150, 185 )
                  , Point2d.fromCoordinates ( 165, 160 )
                  , Point2d.fromCoordinates ( 135, 160 )
                  ]
                ]
            }
        )
arc2d : List (Attribute msg) -> Arc2d -> Svg msg

Draw an Arc2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#arc

arc : Svg Never
arc =
    Svg.arc2d
        [ Attributes.stroke "blue"
        , Attributes.strokeWidth "5"
        ]
        (Arc2d.with
            { centerPoint =
                Point2d.fromCoordinates ( 100, 100 )
            , startPoint =
                Point2d.fromCoordinates ( 150, 50 )
            , sweptAngle = degrees 90
            }
        )
ellipticalArc2d : List (Attribute msg) -> EllipticalArc2d -> Svg msg

Draw an EllipticalArc2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#ellipticalArc

ellipticalArc : Svg Never
ellipticalArc =
    Svg.ellipticalArc2d
        [ Attributes.stroke "blue"
        , Attributes.fill "none"
        , Attributes.strokeWidth "5"
        , Attributes.strokeLinecap "round"
        ]
        (EllipticalArc2d.with
            { centerPoint =
                Point2d.fromCoordinates ( 100, 10 )
            , xDirection = Direction2d.x
            , xRadius = 50
            , yRadius = 100
            , startAngle = 0
            , sweptAngle = degrees 180
            }
        )
circle2d : List (Attribute msg) -> Circle2d -> Svg msg

Draw a Circle2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#circle

circle : Svg Never
circle =
    Svg.circle2d
        [ Attributes.fill "orange"
        , Attributes.stroke "blue"
        , Attributes.strokeWidth "2"
        ]
        (Circle2d.with
            { centerPoint =
                Point2d.fromCoordinates ( 150, 150 )
            , radius = 10
            }
        )
ellipse2d : List (Attribute msg) -> Ellipse2d -> Svg msg

Draw an Ellipse2d as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#ellipse

ellipse : Svg Never
ellipse =
    Svg.ellipse2d
        [ Attributes.fill "orange"
        , Attributes.stroke "blue"
        , Attributes.strokeWidth "2"
        ]
        (Ellipse2d.with
            { centerPoint =
                Point2d.fromCoordinates ( 150, 150 )
            , xDirection =
                Direction2d.fromAngle (degrees -30)
            , xRadius = 60
            , yRadius = 30
            }
        )
quadraticSpline2d : List (Attribute msg) -> QuadraticSpline2d -> Svg msg

Draw a quadratic spline as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#quadraticSpline

quadraticSpline : Svg Never
quadraticSpline =
    let
        startPoint =
            Point2d.fromCoordinates ( 50, 50 )


        controlPoint =
            Point2d.fromCoordinates ( 100, 150 )


        endPoint =
            Point2d.fromCoordinates ( 150, 100 )


        spline =
            QuadraticSpline2d.with
                { startPoint = startPoint
                , controlPoint = controlPoint
                , endPoint = endPoint
                }


        points =
            [ startPoint, controlPoint, endPoint ]


        drawPoint point =
            Svg.circle2d []
                (Circle2d.withRadius 3 point)
    in
    Svg.g [ Attributes.stroke "blue" ]
        [ Svg.quadraticSpline2d
            [ Attributes.strokeWidth "3"
            , Attributes.strokeLinecap "round"
            , Attributes.fill "none"
            ]
            spline
        , Svg.polyline2d
            [ Attributes.strokeWidth "1"
            , Attributes.fill "none"
            , Attributes.strokeDasharray "3 3"
            ]
            (Polyline2d.fromVertices points)
        , Svg.g [ Attributes.fill "white" ]
            (List.map drawPoint points)
        ]
cubicSpline2d : List (Attribute msg) -> CubicSpline2d -> Svg msg

Draw a cubic spline as an SVG `` with the given attributes.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#cubicSpline

cubicSpline : Svg Never
cubicSpline =
    let
        startPoint =
            Point2d.fromCoordinates ( 50, 50 )


        startControlPoint =
            Point2d.fromCoordinates ( 100, 150 )


        endControlPoint =
            Point2d.fromCoordinates ( 150, 25 )


        endPoint =
            Point2d.fromCoordinates ( 200, 125 )


        spline =
            CubicSpline2d.with
                { startPoint = startPoint
                , startControlPoint = startControlPoint
                , endControlPoint = endControlPoint
                , endPoint = endPoint
                }


        points =
            [ startPoint
            , startControlPoint
            , endControlPoint
            , endPoint
            ]


        drawPoint point =
            Svg.circle2d []
                (Circle2d.withRadius 3 point)
    in
    Svg.g [ Attributes.stroke "blue" ]
        [ Svg.cubicSpline2d
            [ Attributes.strokeWidth "3"
            , Attributes.strokeLinecap "round"
            , Attributes.fill "none"
            ]
            spline
        , Svg.polyline2d
            [ Attributes.strokeWidth "1"
            , Attributes.fill "none"
            , Attributes.strokeDasharray "3 3"
            ]
            (Polyline2d.fromVertices points)
        , Svg.g [ Attributes.fill "white" ]
            (List.map drawPoint points)
        ]
boundingBox2d : List (Attribute msg) -> BoundingBox2d -> Svg msg

Draw a bounding box as an SVG <rect> with the given attributes.

Transformations

These functions allow you to use all the normal elm-geometry 2D transformations on arbitrary fragments of SVG. For example,

Svg.mirrorAcross Axis2d.x
    (Svg.lineSegment2d [] lineSegment)

draws a line segment as SVG and then mirrors that SVG fragment. This is visually the same as

Svg.lineSegment2d []
    (LineSegment2d.mirrorAcross Axis2d.x lineSegment)

which instead mirrors the line segment first and then draws the mirrored line segment as SVG.

In the above example only a single SVG element was transformed, but all of these transformation functions work equally well on arbitrarily complex fragments of SVG such as nested groups of elements of different types:

Svg.rotateAround Point2d.origin (degrees 30)
    (Svg.g [ Attributes.stroke "blue" ]
        [ Svg.lineSegment2d [] lineSegment
        , Svg.circle2d [] someCircle
        , Svg.g [ Attributes.fill "orange" ]
            [ Svg.triangle2d [] firstTriangle
            , Svg.triangle2d [] secondTriangle
            ]
        ]
    )

If the transformation changes frequently (an animated rotation angle, for example) while the geometry itself remains constant, using an SVG transformation can be more efficient since the geometry itself does not have to be recreated (the SVG virtual DOM only has to update a transformation matrix).

scaleAbout : Point2d -> Float -> Svg msg -> Svg msg

Scale arbitrary SVG around a given point by a given scale.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#scaled

scaled : Svg Never
scaled =
    let
        scales =
            [ 1.0, 1.5, 2.25 ]


        referencePoint =
            Point2d.fromCoordinates ( 100, 100 )


        referencePoint =
            Svg.circle2d [ Attributes.fill "black" ]
                (Circle2d.with
                    { centerPoint = referencePoint
                    , radius = 3
                    }
                )


        scaledCircle : Float -> Svg Never
        scaledCircle scale =
            Svg.scaleAbout referencePoint scale circle
    in
    Svg.g []
        (referencePoint
            :: List.map scaledCircle scales
        )

Note how everything is scaled, including the stroke width of the circles. This may or may not be what you want; if you wanted the same stroke width on all circles, you could instead scale the Circle2d values themselves using Circle2d.scaleAbout and then draw the scaled circles with a specific stroke width using Svg.circle2d.

rotateAround : Point2d -> Float -> Svg msg -> Svg msg

Rotate arbitrary SVG around a given point by a given angle.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#rotated

rotated : Svg Never
rotated =
    let
        angles =
            List.range 0 9
                |> List.map
                    (\n -> degrees 30 * toFloat n)


        referencePoint =
            Point2d.fromCoordinates ( 200, 150 )


        referencePoint =
            Svg.circle2d [ Attributes.fill "black" ]
                (Circle2d.with
                    { centerPoint = referencePoint
                    , radius = 3
                    }
                )


        rotatedCircle : Float -> Svg Never
        rotatedCircle angle =
            Svg.rotateAround referencePoint angle circle
    in
    Svg.g []
        (referencePoint
            :: List.map rotatedCircle angles
        )
translateBy : Vector2d -> Svg msg -> Svg msg

Translate arbitrary SVG by a given displacement.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#translated

translated : Svg Never
translated =
    Svg.g []
        [ polyline
        , polyline
            |> Svg.translateBy
                (Vector2d.fromComponents ( 0, 40 ))
        , polyline
            |> Svg.translateBy
                (Vector2d.fromComponents ( 5, -60 ))
        ]
mirrorAcross : Axis2d -> Svg msg -> Svg msg

Mirror arbitrary SVG across a given axis.

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#mirrored

mirrored : Svg Never
mirrored =
    let
        horizontalAxis =
            Axis2d.with
                { originPoint =
                    Point2d.fromCoordinates ( 0, 220 )
                , direction = Direction2d.x
                }


        horizontalSegment =
            LineSegment2d.along horizontalAxis 50 250


        angledAxis =
            Axis2d.with
                { originPoint =
                    Point2d.fromCoordinates ( 0, 150 )
                , direction =
                    Direction2d.fromAngle (degrees -10)
                }


        angledSegment =
            LineSegment2d.along angledAxis 50 250
    in
    Svg.g []
        [ polygon
        , Svg.mirrorAcross horizontalAxis polygon
        , Svg.mirrorAcross angledAxis polygon
        , Svg.g
            [ Attributes.strokeWidth "0.5"
            , Attributes.stroke "black"
            , Attributes.strokeDasharray "3 3"
            ]
            [ Svg.lineSegment2d [] horizontalSegment
            , Svg.lineSegment2d [] angledSegment
            ]
        ]

Coordinate transformations

Similar to the above transformations, these functions allow elm-geometry coordinate conversion transformations to be applied to arbitrary SVG elements.

relativeTo : Frame2d -> Svg msg -> Svg msg

Convert SVG expressed in global coordinates to SVG expressed in coordinates relative to a given reference frame. Using relativeTo can be useful for transforming between model space and screen space - SVG coordinates start in the top left, so positive Y is down, while in mathematical/geometric contexts positive Y is usually up.

For example, you might develop an SVG scene in a coordinate system where X and Y each range from 0 to 300 and positive Y is up. To turn this into a 300x300 SVG drawing, first define the top-left SVG frame (coordinate system) in terms of the model coordinate system:

topLeftPoint =
    Point2d.fromCoordinates ( 0, 300 )


topLeftFrame =
    Frame2d.atPoint topLeftPoint |> Frame2d.flipY

(As expressed in the model frame, the top-left SVG frame is at the point (0, 300) and its Y direction is equal to the global negative Y direction.) If scene is an SVG element representing your scene, you could then transform it into top-left SVG window coordinates and render the result to HTML with

Svg.svg
    [ Attributes.width "300"
    , Attributes.height "300"
    ]
    [ Svg.relativeTo topLeftFrame scene ]
placeIn : Frame2d -> Svg msg -> Svg msg

Take SVG defined in local coordinates relative to a given reference frame, and return that SVG expressed in global coordinates.

This can be useful for taking a chunk of SVG and 'stamping' it in different positions with different orientations:

https://ianmackenzie.github.io/elm-geometry-svg/1.0.0/DocumentationExamples.html#placed

placed : Svg Never
placed =
    let
        vertices =
            [ Point2d.origin
            , Point2d.fromCoordinates ( 40, 0 )
            , Point2d.fromCoordinates ( 50, 25 )
            , Point2d.fromCoordinates ( 10, 25 )
            ]


        stamp =
            Svg.polygon2d
                [ Attributes.fill "orange"
                , Attributes.stroke "blue"
                , Attributes.strokeWidth "2"
                ]
                (Polygon2d.fromVertices vertices)


        frames =
            [ Frame2d.atPoint
                (Point2d.fromCoordinates ( 25, 25 ))
            , Frame2d.atPoint
                (Point2d.fromCoordinates ( 100, 25 ))
            , Frame2d.atPoint
                (Point2d.fromCoordinates ( 175, 25 ))
                |> Frame2d.rotateBy (degrees 20)
            , Frame2d.atPoint
                (Point2d.fromCoordinates ( 25, 150 ))
            , Frame2d.atPoint
                (Point2d.fromCoordinates ( 100, 100 ))
                |> Frame2d.rotateBy (degrees 20)
            , Frame2d.atPoint
                (Point2d.fromCoordinates ( 150, 150 ))
                |> Frame2d.rotateBy (degrees -30)
            ]
    in
    Svg.g []
        (frames
            |> List.map
                (\frame -> Svg.placeIn frame stamp)
        )