gampleman / elm-visualization

A data visualization library for Elm

Home Page:http://package.elm-lang.org/packages/gampleman/elm-visualization/latest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Run time exception when drawing line chart

jackalcooper opened this issue · comments

This is unfortunately an issue with Elm as the numbers still allow NaN values in them. (I suppose that would be difficult to avoid while relying on native numbers).

Chances are that this occurs as a division by zero kind of error. I could debug this a bit if I could see a minimal example that replicates the error.

@gampleman Thanks for your replying, Jakub. This should replicate the error:

module LineChart exposing (..)

import Visualization.Scale as Scale exposing (ContinuousScale, ContinuousTimeScale)
import Visualization.Axis as Axis
import Visualization.List as List
import Visualization.Shape as Shape
import Date
import Date.Extra exposing (..)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Date exposing (Date)
import String


w : Float
w =
    900


h : Float
h =
    450


padding : Float
padding =
    30


view : List ( Date, Float ) -> Svg msg
view model =
    let
        _ =
            Debug.log "data breaks line chart" model

        xScale : ContinuousTimeScale
        xScale =
            let
                start =
                    Date.fromTime 1448928000000

                end =
                    Date.fromTime 1456790400000

                ( start_, _ ) =
                    Maybe.withDefault ( start, 0 ) (List.head model)

                ( end_, _ ) =
                    Maybe.withDefault ( start, 0 ) (List.reverse model |> List.head)
            in
                Scale.time ( add Day -1 start_, add Day 1 end_ ) ( 0, w - 2 * padding )

        yScale : ContinuousScale
        yScale =
            Scale.linear ( 0, 5 ) ( h - 2 * padding, 0 )

        opts : Axis.Options a
        opts =
            Axis.defaultOptions

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

        yAxis : Svg msg
        yAxis =
            Axis.axis { opts | orientation = Axis.Left, tickCount = 5 } yScale

        areaGenerator : ( Date, Float ) -> Maybe ( ( Float, Float ), ( Float, Float ) )
        areaGenerator ( x, y ) =
            Just ( ( Scale.convert xScale x, Tuple.first (Scale.rangeExtent yScale) ), ( Scale.convert xScale x, Scale.convert yScale y ) )

        lineGenerator : ( Date, Float ) -> Maybe ( Float, Float )
        lineGenerator ( x, y ) =
            Just ( Scale.convert xScale x, Scale.convert yScale y )

        area : String
        area =
            List.map areaGenerator model
                |> Shape.area Shape.monotoneInXCurve

        line : String
        line =
            List.map lineGenerator model
                |> Shape.line Shape.monotoneInXCurve
    in
        svg [ width (toString w ++ "px"), height (toString h ++ "px") ]
            [ g [ transform ("translate(" ++ toString (padding - 1) ++ ", " ++ toString (h - padding) ++ ")") ]
                [ xAxis ]
            , g [ transform ("translate(" ++ toString (padding - 1) ++ ", " ++ toString padding ++ ")") ]
                [ yAxis ]
            , g [ transform ("translate(" ++ toString padding ++ ", " ++ toString padding ++ ")"), class "series" ]
                [ Svg.path [ d area, stroke "none", strokeWidth "3px", fill "rgba(255, 0, 0, 0.54)" ] []
                , Svg.path [ d line, stroke "red", strokeWidth "3px", fill "none" ] []
                ]
            ]



-- From here onwards this is simply example boilerplate.
-- In a real app you would load the data from a server and parse it, perhaps in
-- a separate module.


main =
    view model



-- Here we simply define the data inline. The examples don't include logic for fetching and parsing this data.


model =
    [ ( Date.fromTime 1483521850, 0.107 )
    , ( Date.fromTime 1483521850, 0.266 )
    , ( Date.fromTime 1483521850, 0.285 )
    , ( Date.fromTime 1483521827, 0.084 )
    , ( Date.fromTime 1483521751, 0.081 )
    , ( Date.fromTime 1483521649, 0.073 )
    , ( Date.fromTime 1483521635, 0.049 )
    , ( Date.fromTime 1483521699, 0.359 )
    , ( Date.fromTime 1483521698, 0.089 )
    , ( Date.fromTime 1483521688, 0.146 )
    , ( Date.fromTime 1483521658, 0.112 )
    , ( Date.fromTime 1483521657, 0.055 )
    , ( Date.fromTime 1483521858, 0.069 )
    , ( Date.fromTime 1483521842, 0.049 )
    , ( Date.fromTime 1483521750, 0.126 )
    , ( Date.fromTime 1483521747, 0.1 )
    , ( Date.fromTime 1483521746, 0.132 )
    , ( Date.fromTime 1483521650, 0.063 )
    , ( Date.fromTime 1483521646, 0.059 )
    , ( Date.fromTime 1483521659, 0.078 )
    , ( Date.fromTime 1483521881, 0.054 )
    , ( Date.fromTime 1483521820, 0.057 )
    , ( Date.fromTime 1483521729, 0.167 )
    , ( Date.fromTime 1483521753, 0.063 )
    , ( Date.fromTime 1483521661, 0.057 )
    , ( Date.fromTime 1483521863, 0.061 )
    , ( Date.fromTime 1483521750, 0.07 )
    , ( Date.fromTime 1483521745, 0.126 )
    , ( Date.fromTime 1483521722, 0.126 )
    , ( Date.fromTime 1483521642, 0.436 )
    , ( Date.fromTime 1483521628, 0.068 )
    , ( Date.fromTime 1483521679, 0.067 )
    ]

I changed a little bit about your original code form the line chart example. You can also just replace the model = [...] part of your example code with my model = [...] part.

The problem here is that you are using the monotoneInXCurve curve generator. It states in the docs "assuming monotonicity in x" which is not the most clear.

What it means is that it will only work if the data you pass it is sorted on x and contains no duplicates.

You can fix your example either by doing this pre-processing or switching to the linearCurve generator.


I think in testing this I might have found a bug, where Shape.line and Shape.area produce different results. So what I'll want to do is:

  • update the documentation to be clearer on what's required for the monotone generators
  • add a fuzz test for line and areas