Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| module Main exposing (main) | |
| import Html exposing (program, Html) | |
| import Html.Attributes as A | |
| import Http | |
| import Task | |
| import Color exposing (Color) | |
| import Data.Cars as Cars exposing (Car) | |
| import Data.Birdstrikes as Birdstrikes exposing (Birdstrike) | |
| import Facet.Axis as Axis | |
| import Facet.Channel as Channel | |
| import Facet.Encoding as Encoding exposing (Encoding) | |
| import Facet.Field as Field | |
| import Facet.Plot as Plot exposing (Plot) | |
| import Facet.Render.Svg exposing (render) | |
| import Facet.Scale as Scale | |
| import Facet.Scenegraph exposing (Scenegraph, ViewBox) | |
| import Facet.Scenegraph.Mark exposing (Behaviour(..)) | |
| import Facet.Scenegraph.Interpolate exposing (Interpolate(..)) | |
| main : Program Never Model Msg | |
| main = | |
| program | |
| { init = ( initialModel, Task.attempt LoadData loadData ) | |
| , update = update | |
| , subscriptions = \_ -> Sub.none | |
| , view = view | |
| } | |
| loadData : Task.Task Http.Error ( List Car, List Birdstrike ) | |
| loadData = | |
| Cars.cars () | |
| |> Task.andThen | |
| (\cars -> | |
| Birdstrikes.birdstrikes () | |
| |> Task.map ((,) cars) | |
| ) | |
| -- View ------------------------------------------------------------------------ | |
| view : Model -> Html msg | |
| view model = | |
| let | |
| content = | |
| case ( model.state, model.error ) of | |
| ( Loading, _ ) -> | |
| [ Html.h1 [] [ Html.text "Loading" ] ] | |
| ( Loaded, Just err ) -> | |
| [ Html.text err ] | |
| _ -> | |
| let | |
| carPlots = | |
| model.carScenegraphs | |
| |> List.map | |
| (\( maybePlot, info ) -> | |
| Maybe.map | |
| (\plot -> Html.div [] [ Html.hr [] [], Html.p [] [ Html.text info ], render Nothing Nothing plot ]) | |
| maybePlot | |
| ) | |
| |> List.filterMap identity | |
| birdStrikePlots = | |
| model.birdstrikesScenegraphs | |
| |> List.map | |
| (\( maybePlot, info ) -> | |
| Maybe.map | |
| (\plot -> Html.div [] [ Html.hr [] [], Html.p [] [ Html.text info ], render Nothing Nothing plot ]) | |
| maybePlot | |
| ) | |
| |> List.filterMap identity | |
| otherPlots = | |
| model.otherScenegraphs | |
| |> List.map | |
| (\( maybePlot, info ) -> | |
| Maybe.map | |
| (\plot -> Html.div [] [ Html.hr [] [], Html.p [] [ Html.text info ], render Nothing Nothing plot ]) | |
| maybePlot | |
| ) | |
| |> List.filterMap identity | |
| in | |
| [ Html.h1 [] [ Html.text "Car data" ] | |
| , Html.div [ A.style plotStyles ] carPlots | |
| , Html.h1 [] [ Html.text "Birdstrike data" ] | |
| , Html.div [ A.style plotStyles ] birdStrikePlots | |
| , Html.h1 [] [ Html.text "Other examples" ] | |
| , Html.div [ A.style plotStyles ] otherPlots | |
| ] | |
| in | |
| Html.div | |
| [ A.style wrapperStyles ] | |
| [ Html.div | |
| [ A.style contentStyles ] | |
| content | |
| ] | |
| wrapperStyles : List ( String, String ) | |
| wrapperStyles = | |
| [ ( "display", "flex" ) | |
| , ( "flex-direction", "row" ) | |
| , ( "justify-content", "center" ) | |
| ] | |
| contentStyles : List ( String, String ) | |
| contentStyles = | |
| [ ( "flex-basis", "768px" ) | |
| , ( "display", "flex" ) | |
| , ( "flex-direction", "column" ) | |
| ] | |
| plotStyles : List ( String, String ) | |
| plotStyles = | |
| [ ( "border", "1 0 1 0 solid #333" ), ( "padding", "8 0 0 8" ) ] | |
| -------------------------------------------------------------------------------- | |
| type alias Model = | |
| { state : State | |
| , cars : List Car | |
| , birdstrikes : List Birdstrike | |
| , error : Maybe String | |
| , carScenegraphs : List ( Maybe ( ViewBox, Scenegraph ), String ) | |
| , birdstrikesScenegraphs : List ( Maybe ( ViewBox, Scenegraph ), String ) | |
| , otherScenegraphs : List ( Maybe ( ViewBox, Scenegraph ), String ) | |
| } | |
| initialModel : Model | |
| initialModel = | |
| Model Loading [] [] Nothing [] [] [] | |
| type State | |
| = Loading | |
| | Loaded | |
| type Msg | |
| = LoadData (Result Http.Error ( List Car, List Birdstrike )) | |
| update : Msg -> Model -> ( Model, Cmd msg ) | |
| update msg model = | |
| case msg of | |
| LoadData (Err err) -> | |
| ( { model | state = Loaded, error = Just <| toString err }, Cmd.none ) | |
| LoadData (Ok ( cars, birdstrikes )) -> | |
| let | |
| carScenegraphs = | |
| [ ( compileWith cars scatterPlot, "A simple scatter plot" ) | |
| , ( compileWith cars scatterPlotReversed, "The same plot with the axes oriented on the top and right. Note that when faceting the labels will orient themselves to avoid the axes wherever you choose to place them." ) | |
| , ( compileWith cars scatterPlotFacetted, "The same plot faceted by origin and number of cylinders." ) | |
| , ( compileWith cars scatterPlot2, "A scatter plot with acceleration encoded as the size of the mark." ) | |
| , ( compileWith cars barPlot, "A bar plot using an `AggregateField` to find the average miles per gallon." ) | |
| , ( compileWith cars barPlotRow, "The same bar plot faceted by number of cylinders as a row." ) | |
| , ( compileWith cars barPlotColumn, "The same bar plot again now faceted by number of cylinders as a column." ) | |
| ] | |
| birdstrikesScenegraphs = | |
| [ ( compileWith birdstrikes birdBarPlot, "A bar plot with a square root scale to show skewed data." ) ] | |
| otherScenegraphs = | |
| [ ( compileWith vectorFieldData vectorFieldPlot, "A symbol plot using the built-in arrow shape to represent a vector field. Note that all legends are generated from the plot declaration automatically." ) | |
| , ( compileWith trailData trailPlot, "A trail plot doing nothing particularly interesting." ) | |
| , ( compileWith lineData linePlot, "A line plot showing the different interpolation methods applied to the sin function." ) | |
| , ( compileWith areaData areaBeginNew, "An area plot showing the `BeginNew` behaviour when missing data (encoded as `Maybe`) is encountered ." ) | |
| , ( compileWith areaData areaSkipMissing, "The same area plot showing the `SkipMissing` behaviour when missing data (encoded as `Maybe`) is encountered ." ) | |
| , ( compileWith boolData boolPlot, "A bar plot aggregating values over non-comparable fields using a custom band scale." ) | |
| , ( compileWith discreteData boxPlot, "A symbol plot with discrete positions and the size encoded as the sum of values at each coordinate." ) | |
| ] | |
| in | |
| ( { model | |
| | state = Loaded | |
| , cars = cars | |
| , birdstrikes = birdstrikes | |
| , error = Nothing | |
| , carScenegraphs = carScenegraphs | |
| , birdstrikesScenegraphs = birdstrikesScenegraphs | |
| , otherScenegraphs = otherScenegraphs | |
| } | |
| , Cmd.none | |
| ) | |
| compileWith : | |
| List data | |
| -> Plot data xdomain ydomain facetRow facetColumn | |
| -> Maybe ( ViewBox, Scenegraph ) | |
| compileWith data sg = | |
| Plot.compile sg data |> Result.toMaybe | |
| -- PLOTS ----------------------------------------------------------------------- | |
| -- Car data plots -------------------------------------------------------------- | |
| {-| Scatter plot of acceleration against displacement | |
| -} | |
| scatterPlot : Plot Car Float Float facetRow facetColumn | |
| scatterPlot = | |
| Plot.plot (Just "Acceleration vs. Displacement") | |
| |> Plot.xAxis (Axis.linearX (Just "acceleration") |> Axis.ticks 5 |> Axis.continuousDomain ( 0, 30 ) |> Axis.labelAngle angle) | |
| |> Plot.yAxis (Axis.linearY (Just "displacement") |> Axis.ticks 5 |> Axis.continuousDomain ( 0, 50 ) |> Axis.labelAngle angle) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer carPoint | |
| scatterPlotReversed : Plot Car Float Float facetRow facetColumn | |
| scatterPlotReversed = | |
| Plot.plot (Just "Acceleration vs. Displacement") | |
| |> Plot.xAxis (Axis.linearX (Just "acceleration") |> Axis.ticks 5 |> Axis.continuousDomain ( 0, 30 ) |> Axis.orientTop |> Axis.labelAngle angle) | |
| |> Plot.yAxis (Axis.linearY (Just "displacement") |> Axis.ticks 5 |> Axis.continuousDomain ( 0, 50 ) |> Axis.orientRight |> Axis.labelAngle angle) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer carPoint | |
| scatterPlotFacetted : Plot Car Float Float Int String | |
| scatterPlotFacetted = | |
| scatterPlot | |
| |> Plot.facetGrid | |
| { row = Field.scalar { name = Just "cylinders", extract = .cylinders } | |
| , column = Field.scalar { name = Just "origin", extract = .origin } | |
| } | |
| |> Plot.facetColumnFormat toShortCountry | |
| |> Plot.facetRowFormat (\n -> toString n ++ " cylinders") | |
| carPoint : Encoding Car Float Float | |
| carPoint = | |
| Encoding.point | |
| { x = Field.scalar { name = Just "acceleration", extract = .acceleration } |> Channel.positional | |
| , y = Field.maybeScalar { name = Just "miles per gallon", extract = .milesPerGallon } |> Channel.positional | |
| } | |
| |> Encoding.fill fillColorOrigin | |
| |> Encoding.fillOpacityConstant 0.5 | |
| fillColorOrigin : Channel.ColorChannel { a | origin : String } | |
| fillColorOrigin = | |
| Channel.color | |
| { formatDomain = toShortCountry | |
| , scale = Scale.category10 [ "USA", "Japan", "Europe" ] | |
| , field = Field.scalar { name = Just "origin", extract = .origin } | |
| } | |
| toShortCountry : String -> String | |
| toShortCountry str = | |
| if str == "Europe" then | |
| "EUR" | |
| else if str == "Japan" then | |
| "JPN" | |
| else | |
| str | |
| angle : number | |
| angle = | |
| 0 | |
| {-| Another scatter plot with size encoding | |
| -} | |
| scatterPlot2 : Plot Car Float Float facetRow facetColumn | |
| scatterPlot2 = | |
| Plot.plot (Just "Horsepower vs. Miles per Gallon") | |
| |> Plot.xAxis (Axis.linearX (Just "horsepower") |> Axis.ticks 8 |> Axis.continuousDomain ( 0, 240 ) |> Axis.labelAngle angle) | |
| |> Plot.yAxis (Axis.linearY (Just "miles per gallon") |> Axis.ticks 5 |> Axis.continuousDomain ( 0, 50 ) |> Axis.labelAngle angle) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer carPoint2 | |
| carPoint2 : Encoding Car Float Float | |
| carPoint2 = | |
| Encoding.point | |
| { x = Field.maybeScalar { name = (Just "horsepower"), extract = .horsepower >> Maybe.map toFloat } |> Channel.positional | |
| , y = Field.maybeScalar { name = (Just "miles per gallon"), extract = .milesPerGallon } |> Channel.positional | |
| } | |
| |> Encoding.fill fillColorOrigin | |
| |> Encoding.fillOpacityConstant 0.5 | |
| |> Encoding.size sizeAcceleration | |
| sizeAcceleration : Channel.FloatChannel { a | acceleration : Float } | |
| sizeAcceleration = | |
| Channel.float | |
| { formatDomain = twoDecimalPlaces | |
| , scale = Scale.linear { domain = ( 10, 30 ), range = ( 10, 400 ) } | |
| , field = Field.scalar { name = Just "acceleration", extract = .acceleration } | |
| } | |
| {-| Bar plot of average miles per gallon by origin | |
| -} | |
| barPlot : Plot Car String Float facetRow facetColumn | |
| barPlot = | |
| Plot.plot (Just "Average MPG by origin") | |
| |> Plot.xAxis (Axis.bandX (Just "Origin") |> Axis.labelFormat toShortCountry) | |
| |> Plot.yAxis (Axis.linearY (Just "Average Miles Per Gallon") |> Axis.ticks 4) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer bar | |
| bar : Encoding Car String Float | |
| bar = | |
| Encoding.bar | |
| { x = Field.scalar { name = Just "Origin", extract = .origin } |> Channel.positional | |
| , width = 800 | |
| , height = Field.maybeAggregate { name = Nothing, extract = averageMilesPerGallon } |> Channel.positional | |
| } | |
| |> Encoding.fill fillColorOrigin | |
| barPlotRow : Plot Car String Float Int facetColumn | |
| barPlotRow = | |
| barPlot | |
| |> Plot.facetRowWrap { row = Field.scalar { name = Nothing, extract = .cylinders }, maxPerRow = 5 } | |
| |> Plot.facetRowFormat (\n -> toString n ++ " cylinders") | |
| barPlotColumn : Plot Car String Float facetRow Int | |
| barPlotColumn = | |
| barPlot | |
| |> Plot.facetColumnWrap { column = Field.scalar { name = Nothing, extract = .cylinders }, maxPerColumn = 5 } | |
| |> Plot.facetColumnFormat (\n -> toString n ++ " cylinders") | |
| averageMilesPerGallon : List Car -> Maybe Float | |
| averageMilesPerGallon cars = | |
| case List.filterMap .milesPerGallon cars of | |
| [] -> | |
| Nothing | |
| ys -> | |
| let | |
| ( n, sum ) = | |
| List.foldl (\mpg ( n, total ) -> ( n + 1, total + mpg )) ( 0, 0.0 ) ys | |
| in | |
| Just <| sum / toFloat n | |
| -- Birdstrike plots ------------------------------------------------------------ | |
| birdBarPlot : Plot Birdstrike String Float facetRow facetColumn | |
| birdBarPlot = | |
| Plot.plot (Just "Total cost by species") | |
| |> Plot.xAxis (Axis.customBandX (Just "Species") |> Axis.labelAngle (pi / 2) |> Axis.labelFormat (truncateString 8)) | |
| |> Plot.yAxis (Axis.sqrtY (Just "Total Cost") |> Axis.continuousDomain ( 0, 2.0e6 ) |> Axis.labelFormat formatMoney) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer birdBar | |
| birdBar : Encoding Birdstrike String Float | |
| birdBar = | |
| Encoding.bar | |
| { x = Field.scalar { name = Just "Species", extract = .wildlifeSpecies } |> Channel.positional | |
| , width = 200 | |
| , height = Field.maybeAggregate { name = Just "Total Cost", extract = sumBy .costTotal } |> Channel.positional | |
| } | |
| sumBy : (a -> Float) -> List a -> Maybe Float | |
| sumBy f xs = | |
| let | |
| ttl = | |
| List.foldl (\x total -> total + f x) 0.0 xs | |
| in | |
| if ttl == 0 then | |
| Nothing | |
| else | |
| Just ttl | |
| truncateString : Int -> String -> String | |
| truncateString n str = | |
| if String.length str <= n then | |
| str | |
| else | |
| String.slice 0 n str ++ ".." | |
| formatMoney : Float -> String | |
| formatMoney value = | |
| let | |
| pow = | |
| floor <| logBase 10 value | |
| in | |
| if pow < 3 then | |
| "$" ++ toString value | |
| else if pow < 6 then | |
| "$" ++ (twoDecimalPlaces (value / 1000)) ++ "k" | |
| else if pow < 9 then | |
| "$" ++ (twoDecimalPlaces (value / 1000000)) ++ "mn" | |
| else if pow < 12 then | |
| "$" ++ (twoDecimalPlaces (value / 10 ^ 9)) ++ "bn" | |
| else | |
| "$" ++ (twoDecimalPlaces (value / 10 ^ 12)) ++ "tn" | |
| twoDecimalPlaces : Float -> String | |
| twoDecimalPlaces number = | |
| floor (number * 100) | |
| |> toFloat | |
| |> \x -> | |
| toString (x / 100) | |
| -- Other examples -------------------------------------------------------------- | |
| {-| Vector field example | |
| -} | |
| vectorFieldPlot : Plot { a | direction : Float, x : Float, y : Float, magnitude : Float } Float Float facetRow facetColumn | |
| vectorFieldPlot = | |
| Plot.plot (Just "A portion of the vector field for F(-y,x)") | |
| |> Plot.xAxis (Axis.linearX (Just "x")) | |
| |> Plot.yAxis (Axis.linearY (Just "y")) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer vectorSymbol | |
| vectorSymbol : | |
| Encoding.Encoding | |
| { a | |
| | direction : Float | |
| , x : comparable | |
| , y : comparable1 | |
| , magnitude : Float | |
| } | |
| comparable | |
| comparable1 | |
| vectorSymbol = | |
| Encoding.arrow { x = vectorX, y = vectorY } | |
| |> Encoding.angle vectorDirection | |
| |> Encoding.size vectorMagnitude | |
| |> Encoding.fill vectorFill | |
| |> Encoding.stroke vectorFill | |
| vectorX : Channel.PositionalChannel { a | x : comparable } comparable | |
| vectorX = | |
| Channel.positional | |
| (Field.scalar { name = Just "x", extract = .x }) | |
| vectorY : Channel.PositionalChannel { a | y : comparable } comparable | |
| vectorY = | |
| Channel.positional | |
| (Field.scalar { name = Just "y", extract = .y }) | |
| vectorMagnitude : Channel.FloatChannel { a | magnitude : Float } | |
| vectorMagnitude = | |
| Channel.float | |
| { formatDomain = toString | |
| , scale = Scale.linear { domain = ( 0, 9 ), range = ( 1, 400.0 ) } | |
| , field = Field.scalar { name = Just "magnitude", extract = .magnitude } | |
| } | |
| vectorMagnitude2 : Channel.FloatChannel { a | magnitude : Float } | |
| vectorMagnitude2 = | |
| Channel.float | |
| { formatDomain = toString | |
| , scale = Scale.linear { domain = ( 0, 9 ), range = ( 1, 3 ) } | |
| , field = Field.scalar { name = Just "magnitude", extract = .magnitude } | |
| } | |
| vectorFill : Channel.ColorChannel { a | magnitude : Float } | |
| vectorFill = | |
| Channel.color | |
| { formatDomain = toString | |
| , scale = Scale.rgb { domain = ( 0, 9 ), range = ( Color.green, Color.red ) } | |
| , field = Field.scalar { name = Just "magnitude", extract = .magnitude } | |
| } | |
| vectorDirection : Channel.FloatChannel { a | direction : Float } | |
| vectorDirection = | |
| Channel.float | |
| { formatDomain = \radian -> toString (floor <| radianToDegree radian) ++ "°" | |
| , scale = Scale.linear { domain = ( 0, 2.0 * pi ), range = ( 0, 2.0 * pi ) } | |
| , field = Field.scalar { name = Just "direction", extract = .direction } | |
| } | |
| radianToDegree : Float -> Float | |
| radianToDegree x = | |
| x * 180.0 / Basics.pi | |
| type alias VectorFieldData = | |
| { x : Float, y : Float, magnitude : Float, direction : Float } | |
| {-| F(x,y) = (-y, x) | |
| -} | |
| vectorFromPos : Int -> Int -> VectorFieldData | |
| vectorFromPos xIn yIn = | |
| let | |
| ( x, y ) = | |
| ( toFloat -yIn, toFloat xIn ) | |
| magnitude = | |
| sqrt (x * x + y * y) | |
| {- vector direction in Radians -} | |
| direction = | |
| norm <| atan2 -y x | |
| in | |
| VectorFieldData x y magnitude direction | |
| norm : Float -> Float | |
| norm rads = | |
| rads - turns (toFloat (floor (rads / (2 * pi)))) | |
| vectorFieldData : List VectorFieldData | |
| vectorFieldData = | |
| List.range -6 6 | |
| |> List.concatMap | |
| (\x -> | |
| List.range -6 6 | |
| |> List.map (\y -> vectorFromPos x y) | |
| ) | |
| -- Stream graph ---------------------------------------------------------------- | |
| {-| TODO: implement streamgraph example | |
| -} | |
| streamData : List (List { high : Float, low : Float, ticker : String, x : number }) | |
| streamData = | |
| [ { x = 0, high = 177.38, low = 176.42, ticker = "AAPL" } | |
| , { x = 1, high = 176.19, low = 174.83, ticker = "AAPL" } | |
| , { x = 2, high = 175.84, low = 173.85, ticker = "AAPL" } | |
| , { x = 3, high = 175.0, low = 173.04, ticker = "AAPL" } | |
| , { x = 4, high = 173.92, low = 171.7, ticker = "AAPL" } | |
| , { x = 5, high = 174.0, low = 171.53, ticker = "AAPL" } | |
| , { x = 6, high = 173.09, low = 169.85, ticker = "AAPL" } | |
| , { x = 7, high = 172.48, low = 168.2, ticker = "AAPL" } | |
| , { x = 8, high = 174.23, low = 172.08, ticker = "AAPL" } | |
| , { x = 9, high = 172.01, low = 164.77, ticker = "AAPL" } | |
| , { x = 10, high = 168.75, low = 164.88, ticker = "AAPL" } | |
| , { x = 11, high = 168.94, low = 164.47, ticker = "AAPL" } | |
| , { x = 0, high = 167.36, low = 165.67, ticker = "FB" } | |
| , { x = 1, high = 165.78, low = 163.39, ticker = "FB" } | |
| , { x = 2, high = 165.7, low = 163.77, ticker = "FB" } | |
| , { x = 3, high = 167.45, low = 163.1, ticker = "FB" } | |
| , { x = 4, high = 168.65, low = 163.25, ticker = "FB" } | |
| , { x = 5, high = 165.98, low = 157.01, ticker = "FB" } | |
| , { x = 6, high = 160.53, low = 156.04, ticker = "FB" } | |
| , { x = 7, high = 161.42, low = 156.81, ticker = "FB" } | |
| , { x = 8, high = 161.57, low = 156.65, ticker = "FB" } | |
| , { x = 9, high = 155.56, low = 150.51, ticker = "FB" } | |
| , { x = 10, high = 157.39, low = 150.81, ticker = "FB" } | |
| , { x = 11, high = 159.2, low = 154.11, ticker = "FB" } | |
| , { x = 0, high = 288.47, low = 282.51, ticker = "TSLA" } | |
| , { x = 1, high = 299.66, low = 289.01, ticker = "TSLA" } | |
| , { x = 2, high = 303.95, low = 295.98, ticker = "TSLA" } | |
| , { x = 3, high = 303.95, low = 293.68, ticker = "TSLA" } | |
| , { x = 4, high = 308.98, low = 299.66, ticker = "TSLA" } | |
| , { x = 5, high = 307.1, low = 293.68, ticker = "TSLA" } | |
| , { x = 6, high = 309.5, low = 289.21, ticker = "TSLA" } | |
| , { x = 7, high = 309.28, low = 295.5, ticker = "TSLA" } | |
| , { x = 8, high = 306.26, low = 288.2, ticker = "TSLA" } | |
| , { x = 9, high = 288.37, low = 252.0, ticker = "TSLA" } | |
| , { x = 10, high = 273.35, low = 254.49, ticker = "TSLA" } | |
| , { x = 11, high = 260.33, low = 244.59, ticker = "TSLA" } | |
| ] | |
| |> groupBy .x | |
| groupBy : (a -> comparable) -> List a -> List (List a) | |
| groupBy project xs = | |
| xs | |
| |> List.sortBy project | |
| |> groupByHelper [] [] project Nothing | |
| groupByHelper : List (List a) -> List a -> (a -> b) -> Maybe b -> List a -> List (List a) | |
| groupByHelper accu currentAccu project maybeKey xs = | |
| case xs of | |
| [] -> | |
| List.reverse (currentAccu :: accu) | |
| next :: rest -> | |
| if Maybe.map ((==) (project next)) maybeKey |> Maybe.withDefault False then | |
| groupByHelper accu (next :: currentAccu) project maybeKey rest | |
| else | |
| let | |
| newKey = | |
| Just <| project next | |
| newAccu = | |
| (List.reverse currentAccu) :: accu | |
| in | |
| groupByHelper newAccu [] project newKey rest | |
| -- Trail ----------------------------------------------------------------------- | |
| trailPlot : Plot { a | w : Maybe Float, x : Float, y : Float } Float Float facetRow facetColumn | |
| trailPlot = | |
| Plot.plot (Just "Trail mark example") | |
| |> Plot.xAxis (Axis.linearX (Just "x") |> Axis.continuousDomain ( 0, 12 )) | |
| |> Plot.yAxis (Axis.linearY (Just "y") |> Axis.continuousDomain ( 0, 8 )) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer trail | |
| trailX : Channel.PositionalChannel { a | x : comparableDomain } comparableDomain | |
| trailX = | |
| Field.vector { name = Nothing, extract = List.map .x } |> Channel.positional | |
| trailY : Channel.PositionalChannel { a | y : comparableDomain } comparableDomain | |
| trailY = | |
| Field.vector { name = Nothing, extract = List.map .y } |> Channel.positional | |
| trailWidth : Channel.FloatChannel { a | w : Maybe Float } | |
| trailWidth = | |
| Channel.float | |
| { formatDomain = toString | |
| , scale = Scale.linear { domain = ( 0, 10 ), range = ( 10, 40 ) } | |
| , field = Field.maybeVector { name = Nothing, extract = List.map .w } | |
| } | |
| trail : Encoding.Encoding { a | w : Maybe Float, x : comparableDomain, y : comparableDomain1 } comparableDomain comparableDomain1 | |
| trail = | |
| Encoding.trail | |
| { x = trailX | |
| , y = trailY | |
| , width = trailWidth | |
| , onMissing = SkipMissing | |
| } | |
| trailData : List { w : Maybe Float, x : Float, y : Float } | |
| trailData = | |
| [ { x = 2, y = 2, w = Just 4 } | |
| , { x = 4, y = 2, w = Just 6 } | |
| , { x = 7.4, y = 4.5, w = Just 8 } | |
| , { x = 8.6, y = 4.0, w = Just 11.5 } | |
| , { x = 10, y = 6.0, w = Just 3 } | |
| ] | |
| -- Line ----------------------------------------------------------------- | |
| linePlot : Plot ( Float, Float ) Float Float facetRow facetColumn | |
| linePlot = | |
| Plot.plot (Just "Interpolation methods") | |
| |> Plot.xAxis (Axis.linearX (Just "x") |> Axis.continuousDomain ( 0.0, 2.0 * pi )) | |
| |> Plot.yAxis (Axis.linearY (Just "y") |> Axis.continuousDomain ( -1.0, 1.0 )) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer point | |
| |> \plot -> List.foldl (\layer plot -> Plot.layer layer plot) plot lines | |
| methods : List ( Interpolate, Color ) | |
| methods = | |
| [ ( Linear, Color.rgb 31 119 180 ) | |
| , ( Basis, Color.rgb 255 127 14 ) | |
| , ( Bundle 0.5, Color.rgb 255 127 14 ) | |
| , ( Cardinal 0.5, Color.rgb 44 160 44 ) | |
| , ( CatmullRom 0.5, Color.rgb 214 39 40 ) | |
| , ( Monotone, Color.rgb 148 103 189 ) | |
| , ( Natural, Color.rgb 140 86 75 ) | |
| , ( Step, Color.rgb 227 119 194 ) | |
| , ( StepAfter, Color.rgb 17 127 127 ) | |
| , ( StepBefore, Color.rgb 188 189 34 ) | |
| ] | |
| lines : List (Encoding ( comparable, comparableDomain ) comparable comparableDomain) | |
| lines = | |
| methods | |
| |> List.map | |
| (\( method, color ) -> | |
| Encoding.line { x = lineX, y = lineY, interpolate = method, onMissing = SkipMissing } | |
| |> Encoding.strokeConstant color | |
| |> Encoding.strokeOpacityConstant 0.5 | |
| ) | |
| lineX : Channel.PositionalChannel ( comparable, a2 ) comparable | |
| lineX = | |
| Field.vector { name = Just "x", extract = List.sortBy Tuple.first >> List.map Tuple.first } | |
| |> Channel.positional | |
| lineY : Channel.PositionalChannel ( comparable, comparableDomain ) comparableDomain | |
| lineY = | |
| Field.vector { name = Just "sin x", extract = List.sortBy Tuple.first >> List.map Tuple.second } | |
| |> Channel.positional | |
| pointX : Channel.PositionalChannel ( comparableDomain, a2 ) comparableDomain | |
| pointX = | |
| Field.scalar { name = Just "x", extract = Tuple.first } |> Channel.positional | |
| pointY : Channel.PositionalChannel ( a1, comparableDomain ) comparableDomain | |
| pointY = | |
| Field.scalar { name = Just "sin x", extract = Tuple.second } |> Channel.positional | |
| point : Encoding ( comparable, comparableDomain ) comparable comparableDomain | |
| point = | |
| Encoding.point { x = pointX, y = pointY } | |
| |> Encoding.sizeConstant 25 | |
| |> Encoding.fillConstant Color.black | |
| lineData : List ( Float, Float ) | |
| lineData = | |
| List.range 0 marks | |
| |> List.map | |
| (\i -> | |
| let | |
| x = | |
| step * toFloat i | |
| in | |
| ( x, sin x ) | |
| ) | |
| marks : number | |
| marks = | |
| 51 | |
| step : Float | |
| step = | |
| (2 * pi) / 50.0 | |
| -- Aggregate by position ------------------------------------------------------- | |
| discreteData : List { n : Int, a : String, v : Float } | |
| discreteData = | |
| [ 0, 1, 2, 3, 4 ] | |
| |> List.concatMap | |
| (\n -> | |
| [ "a", "b", "c", "d" ] | |
| |> List.concatMap | |
| (\a -> | |
| let | |
| vs = | |
| if a == "c" || n % 2 == 0 then | |
| [ 0.1, 0.2, 0.3 ] | |
| else | |
| [ 0.3, 0.8, 1.2 ] | |
| in | |
| vs | |
| |> List.map (\v -> { n = n, a = a, v = v }) | |
| ) | |
| ) | |
| totalValue : Field.Field { a | v : number } number | |
| totalValue = | |
| Field.aggregate { name = Just "total value", extract = (List.map .v >> List.sum) } | |
| boxFill : Channel.ColorChannel { a | v : Float } | |
| boxFill = | |
| Channel.color | |
| { formatDomain = toString | |
| , scale = Scale.rgb { domain = ( 0.1, 4 ), range = ( Color.green, Color.red ) } | |
| , field = totalValue | |
| } | |
| boxSize : Channel.FloatChannel { a | v : Float } | |
| boxSize = | |
| Channel.float | |
| { formatDomain = toString | |
| , scale = Scale.linear { domain = ( 0.1, 4 ), range = ( 0, 4000 ) } | |
| , field = totalValue | |
| } | |
| boxEncoding : Encoding { r | a : comparable, n : comparable1, v : Float } comparable1 comparable | |
| boxEncoding = | |
| Encoding.square | |
| { x = Channel.positional <| Field.scalar { name = Nothing, extract = .n } | |
| , y = Channel.positional <| Field.scalar { name = Nothing, extract = .a } | |
| } | |
| -- |> Encoding.fillConstant Color.green | |
| |> | |
| Encoding.fill boxFill | |
| -- |> Encoding.stroke boxFill | |
| |> | |
| Encoding.size boxSize | |
| boxPlot : Plot { r | a : String, n : comparable, v : Float } comparable String facetRow facetColumn | |
| boxPlot = | |
| Plot.plot Nothing | |
| |> Plot.xAxis (Axis.bandX Nothing) | |
| |> Plot.yAxis (Axis.bandY Nothing |> Axis.labelFormat identity) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer boxEncoding | |
| -- Aggragte on non-comparable field -------------------------------------------- | |
| boolData : List ( Bool, Float ) | |
| boolData = | |
| [ ( True, 0.2 ), ( False, 0.25 ), ( False, 0.25 ), ( True, 0.3 ) ] | |
| compareBool : Bool -> Bool -> Order | |
| compareBool a b = | |
| if a == b then | |
| EQ | |
| else if a then | |
| GT | |
| else | |
| LT | |
| boolEncoding : Encoding ( Bool, Float ) Bool Float | |
| boolEncoding = | |
| Encoding.bar | |
| { x = Channel.positionalCompareWith { compareWith = compareBool, field = Field.scalar { name = Just "outcome", extract = Tuple.first } } | |
| , width = 400 | |
| , height = Channel.positional <| Field.aggregate { name = Just "probability mass", extract = List.map Tuple.second >> List.sum } | |
| } | |
| boolPlot : Plot ( Bool, Float ) Bool Float facetRow facetColumn | |
| boolPlot = | |
| Plot.plot (Just "A bar chart aggregating over non-comparable fields") | |
| |> Plot.xAxis (Axis.customBandX (Just "outcome")) | |
| |> Plot.yAxis (Axis.linearY (Just "probability mass") |> Axis.continuousDomain ( 0, 1 )) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer boolEncoding | |
| -- Area ------------------------------------------------------------------------ | |
| areaBeginNew : Plot { a | y : Maybe Float, x : Float } Float Float facetRow facetColumn | |
| areaBeginNew = | |
| Plot.plot (Just "An area chart with `BeginNew` behaviour") | |
| |> Plot.xAxis (Axis.linearX (Just "x") |> Axis.continuousDomain ( 0, 11 )) | |
| |> Plot.yAxis (Axis.linearY (Just "y") |> Axis.continuousDomain ( 0, 8 )) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer (areaEncoding BeginNew) | |
| |> Plot.layer (areaLine BeginNew) | |
| |> Plot.layer areaPoint | |
| areaSkipMissing : Plot { a | y : Maybe Float, x : Float } Float Float facetRow facetColumn | |
| areaSkipMissing = | |
| Plot.plot (Just "An area chart with `SkipMissing` behaviour") | |
| |> Plot.xAxis (Axis.linearX (Just "x") |> Axis.continuousDomain ( 0, 11 )) | |
| |> Plot.yAxis (Axis.linearY (Just "y") |> Axis.continuousDomain ( 0, 8 )) | |
| |> Plot.width 768 | |
| |> Plot.height 600 | |
| |> Plot.layer (areaEncoding SkipMissing) | |
| |> Plot.layer (areaLine SkipMissing) | |
| |> Plot.layer areaPoint | |
| areaPoint : Encoding { a | x : comparable, y : Maybe comparable1 } comparable comparable1 | |
| areaPoint = | |
| Encoding.point | |
| { x = Field.scalar { name = Just "x", extract = .x } |> Channel.positional | |
| , y = Field.maybeScalar { name = (Just "y"), extract = .y } |> Channel.positional | |
| } | |
| |> Encoding.sizeConstant 25 | |
| |> Encoding.fillConstant Color.black | |
| areaLine : Behaviour -> Encoding { a | y : Maybe comparable1, x : comparable } comparable comparable1 | |
| areaLine behaviour = | |
| Encoding.line { x = areaX, y = areaY, interpolate = Monotone, onMissing = behaviour } | |
| |> Encoding.strokeConstant (Color.rgb 255 127 14) | |
| |> Encoding.strokeWidthConstant 2 | |
| areaEncoding : Behaviour -> Encoding { a | y : Maybe number, x : comparable } comparable number | |
| areaEncoding behaviour = | |
| Encoding.hArea { x = areaX, y = areaY, interpolate = Monotone, onMissing = behaviour } | |
| |> Encoding.fillConstant (Color.rgb 255 127 14) | |
| |> Encoding.fillOpacityConstant 0.7 | |
| areaX : Channel.PositionalChannel { a | x : comparable } comparable | |
| areaX = | |
| Field.vector { name = Just "x", extract = List.sortBy .x >> List.map .x } | |
| |> Channel.positional | |
| areaY : Channel.PositionalChannel { a | x : comparable, y : Maybe comparableDomain } comparableDomain | |
| areaY = | |
| Field.maybeVector { name = Just "y", extract = (List.sortBy .x >> List.map .y) } | |
| |> Channel.positional | |
| areaData : List { x : Float, y : Maybe Float } | |
| areaData = | |
| [ { x = 1, y = Just 2 } | |
| , { x = 3, y = Just 5 } | |
| , { x = 4.5, y = Just 4.8 } | |
| , { x = 5.5, y = Nothing } | |
| , { x = 7.2, y = Just 6.4 } | |
| , { x = 8, y = Just 7.4 } | |
| , { x = 10, y = Just 5.3 } | |
| ] |