Skip to content

Commit ce26879

Browse files
committed
Adds area shape generator
1 parent 7f2bbe0 commit ce26879

File tree

3 files changed

+126
-10
lines changed

3 files changed

+126
-10
lines changed

examples/LineChart.elm

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module LineChart exposing (..)
22

3-
import Visualization.Scale as Scale
3+
import Visualization.Scale as Scale exposing (ContinuousScale, ContinuousTimeScale)
44
import Visualization.Axis as Axis
55
import Visualization.List as List
66
import Visualization.Shape as Shape
@@ -11,46 +11,71 @@ import Date exposing (Date)
1111
import String
1212

1313

14+
w : Float
1415
w =
1516
600
1617

1718

19+
h : Float
1820
h =
1921
350
2022

2123

24+
padding : Float
2225
padding =
2326
30
2427

2528

29+
view : List ( Date, Float ) -> Svg msg
2630
view model =
2731
let
32+
xScale : ContinuousTimeScale
2833
xScale =
2934
Scale.time ( Date.fromTime 1448928000000, Date.fromTime 1456790400000 ) ( 0, w - 2 * padding )
3035

36+
yScale : ContinuousScale
3137
yScale =
3238
Scale.linear ( 0, 5 ) ( h - 2 * padding, 0 )
3339

40+
opts : Axis.Options a
3441
opts =
3542
Axis.defaultOptions
3643

44+
xAxis : Svg msg
3745
xAxis =
3846
Axis.axis { opts | orientation = Axis.Bottom, tickCount = List.length model } xScale
3947

48+
yAxis : Svg msg
4049
yAxis =
4150
Axis.axis { opts | orientation = Axis.Left, tickCount = 5 } yScale
4251

43-
points =
44-
List.map (\( x, y ) -> Just ( Scale.convert xScale x, Scale.convert yScale y )) model
52+
areaGenerator : ( Date, Float ) -> Maybe ( ( Float, Float ), ( Float, Float ) )
53+
areaGenerator ( x, y ) =
54+
Just ( ( Scale.convert xScale x, fst (Scale.rangeExtent yScale) ), ( Scale.convert xScale x, Scale.convert yScale y ) )
55+
56+
lineGenerator : ( Date, Float ) -> Maybe ( Float, Float )
57+
lineGenerator ( x, y ) =
58+
Just ( Scale.convert xScale x, Scale.convert yScale y )
59+
60+
area : String
61+
area =
62+
List.map areaGenerator model
63+
|> Shape.area Shape.monotoneInXCurve
64+
65+
line : String
66+
line =
67+
List.map lineGenerator model
4568
|> Shape.line Shape.monotoneInXCurve
4669
in
4770
svg [ width (toString w ++ "px"), height (toString h ++ "px") ]
48-
[ g [ transform ("translate(" ++ toString padding ++ ", " ++ toString (h - padding) ++ ")") ]
71+
[ g [ transform ("translate(" ++ toString (padding - 1) ++ ", " ++ toString (h - padding) ++ ")") ]
4972
[ xAxis ]
50-
, g [ transform ("translate(" ++ toString padding ++ ", " ++ toString padding ++ ")") ]
73+
, g [ transform ("translate(" ++ toString (padding - 1) ++ ", " ++ toString padding ++ ")") ]
5174
[ yAxis ]
5275
, g [ transform ("translate(" ++ toString padding ++ ", " ++ toString padding ++ ")"), class "series" ]
53-
[ Svg.path [ d points, stroke "red", strokeWidth "3px", fill "none" ] [] ]
76+
[ Svg.path [ d area, stroke "none", strokeWidth "3px", fill "rgba(255, 0, 0, 0.54)" ] []
77+
, Svg.path [ d line, stroke "red", strokeWidth "3px", fill "none" ] []
78+
]
5479
]
5580

5681

readme.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ the dimensions of a plot.
2525
A thin layer that gives a simple DSL for drawing paths.
2626

2727
~~~elm
28-
path = begin
28+
myPath = begin
2929
|> moveTo 10 30
3030
|> lineTo 40 50
3131
|> arcTo 20 30 12 43 (pi / 2)
@@ -53,3 +53,13 @@ A bag of list processing methods that encapsulate common algorithms.
5353
Currently, it is almost a straight port of parts of the [D3](https://github.com/d3/d3) library
5454
by [Mike Bostock](https://bost.ocks.org/mike/). However since Elm provides a
5555
great DOM abstraction already, selections are not part of this library.
56+
57+
## Contributing
58+
59+
This library is still under active development, so please submit feature requests
60+
iff you are also willing to implement them. Bug reports are welcome.
61+
62+
Some things worth working on:
63+
64+
- [ ] Scales have a number of stubs in them for other scale types.
65+
- [ ] Shape could do with more line generators.

src/Visualization/Shape.elm

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Visualization.Shape exposing (line, linearCurve, monotoneInXCurve, Curve)
1+
module Visualization.Shape exposing (line, area, linearCurve, monotoneInXCurve, Curve)
22

33
{-| Visualizations typically consist of discrete graphical marks, such as symbols,
44
arcs, lines and areas. While the rectangles of a bar chart may be easy enough to
@@ -10,7 +10,7 @@ variety of shape generators for your convenience.
1010
1111
# Lines
1212
13-
@docs line
13+
@docs line, area
1414
1515
# Curves
1616
@@ -30,10 +30,24 @@ into drawing commands.
3030
-}
3131
type Curve
3232
= Line (List Point)
33+
| Area (List ( Point, Point ))
3334

3435

36+
applyRecursivelyForArea : (Curve -> List PathSegment) -> List ( Point, Point ) -> List PathSegment
37+
applyRecursivelyForArea fn points =
38+
let
39+
points0 =
40+
List.map fst points
41+
42+
points1 =
43+
List.reverse <| List.map snd points
44+
in
45+
case ( fn (Line points1), points1 ) of
46+
( _ :: tail, ( x, y ) :: _ ) ->
47+
(fn (Line points0) |> Path.lineTo x y) ++ (tail |> Path.close)
3548

36-
-- | Area (List Point) (List Point)
49+
_ ->
50+
[]
3751

3852

3953
{-| Produces a polyline through the specified points.
@@ -47,6 +61,9 @@ linearCurve part =
4761
Line (point :: points) ->
4862
Path.Move point :: List.map Path.Line points
4963

64+
Area points ->
65+
applyRecursivelyForArea linearCurve points
66+
5067

5168
{-| Produces a cubic spline that [preserves monotonicity](http://adsabs.harvard.edu/full/1990A%26A...239..443S)
5269
in y, assuming monotonicity in x, as proposed by Steffen in
@@ -98,6 +115,9 @@ monotoneInXCurve part =
98115
Line (point0 :: point1 :: points) ->
99116
finalize <| List.foldl helper ( point0, point1, Nothing, [ Path.Move point0 ] ) points
100117

118+
Area points ->
119+
applyRecursivelyForArea monotoneInXCurve points
120+
101121

102122
sign : Float -> Float
103123
sign x =
@@ -163,6 +183,20 @@ Points accepted are `Maybe`s, Nothing represent gaps in the data and correspondi
163183
gaps will be rendered in the line.
164184
165185
**Note:** A single point (surrounded by Nothing) may not be visible.
186+
187+
Usually you will need to convert your data into a format supported by this function.
188+
For example, if your data is a `List (Date, Float)`, you might use something like:
189+
190+
lineGenerator : ( Date, Float ) -> Maybe ( Float, Float )
191+
lineGenerator ( x, y ) =
192+
Just ( Scale.convert xScale x, Scale.convert yScale y )
193+
194+
linePath : List (Date, Float) -> String
195+
linePath data =
196+
List.map lineGenerator data
197+
|> Shape.line Shape.linearCurve
198+
199+
where `xScale` and `yScale` would be appropriate `Scale`s.
166200
-}
167201
line : (Curve -> List PathSegment) -> List (Maybe Point) -> String
168202
line curve data =
@@ -182,3 +216,50 @@ line curve data =
182216
( Just p1, Line [ p1 ] :: l )
183217
in
184218
toAttrString <| List.concatMap curve <| snd <| List.foldr makeCurves ( Nothing, [] ) data
219+
220+
221+
{-| The area generator produces an area, as in an area chart. An area is defined
222+
by two bounding lines, either splines or polylines. Typically, the two lines
223+
share the same x-values (x0 = x1), differing only in y-value (y0 and y1);
224+
most commonly, y0 is defined as a constant representing zero. The first line
225+
(the topline) is defined by x1 and y1 and is rendered first; the second line
226+
(the baseline) is defined by x0 and y0 and is rendered second, with the points
227+
in reverse order. With a `linearCurve` curve, this produces a clockwise polygon.
228+
229+
The data attribute you pass in should be a `[Just ((x0, y0), (x1, y1))]`. Passing
230+
in `Nothing` represents gaps in the data and corresponding gaps in the area will
231+
be rendered.
232+
233+
Usually you will need to convert your data into a format supported by this function.
234+
For example, if your data is a `List (Date, Float)`, you might use something like:
235+
236+
areaGenerator : ( Date, Float ) -> Maybe ( ( Float, Float ), ( Float, Float ) )
237+
areaGenerator ( x, y ) =
238+
Just ( ( Scale.convert xScale x, fst (Scale.rangeExtent yScale) ),
239+
( Scale.convert xScale x, Scale.convert yScale y ) )
240+
241+
areaPath : List (Date, Float) -> String
242+
areaPath data =
243+
List.map areaGenerator data
244+
|> Shape.area Shape.linearCurve
245+
246+
where `xScale` and `yScale` would be appropriate `Scale`s.
247+
-}
248+
area : (Curve -> List PathSegment) -> List (Maybe ( Point, Point )) -> String
249+
area curve data =
250+
let
251+
makeCurves datum ( prev, list ) =
252+
case ( prev, datum, list ) of
253+
( _, Nothing, l ) ->
254+
( Nothing, l )
255+
256+
( Nothing, Just pair, l ) ->
257+
( Just pair, Area [ pair ] :: l )
258+
259+
( Just p0, Just p1, (Area ps) :: l ) ->
260+
( Just p1, Area (p1 :: ps) :: l )
261+
262+
( Just p0, Just p1, l ) ->
263+
( Just p1, Area [ p1 ] :: l )
264+
in
265+
toAttrString <| List.concatMap curve <| snd <| List.foldr makeCurves ( Nothing, [] ) data

0 commit comments

Comments
 (0)