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