You need to store the internal state of the plot in your model.
It can be as simple as:
type alias Model =
{ plotState : Plot.State Point }or if you have multiple plots on your page:
type alias Model =
{ plot1 : Plot.State Point
, plot2 : Plot.State Point
, plot3 : Plot.State Point
}All plots have the same initial state regardless of individual configuration.
init : Model
init =
{ plot1 = Plot.init
, plot2 = Plot.init
, plot3 = Plot.init
}Plot.Msg in your MsgInclude it within a constructor for easy pattern matching in your update function:
type Msg
= MyPlotMsg (Plot.Msg Point)
| MsgNotConcerningPlotsor if you need routing for several plots:
type PlotNr
= Plot1
| Plot2
| Plot3
type Msg
= ToPlot PlotNr (Plot.Msg ExampleType)Use this function to place your linechart within your view.
import Element as E
view model =
E.layout [ E.padding 20 ] <|
E.el
[ E.width <| px 800
, E.height <| px 600
]
<|
Plot.draw
800
600
myConfig
model.plotState
MyPlotMsgThe first two arguments are width and height, which mostly determines the aspect ratio of your plot since it fills the container you place it in.
Small dimension values in large containers can make the text in the plot unreasonably large.
If you, for some reason, are not using mdgriffith/elm-ui you can use this draw function instead which outputs regular Html msg.
Naturally you need to handle the plot messages in your update function. This is what Plot.update is for.
update : Msg -> Model -> Model
update msg model =
case msg of
MyPlotMsg plotMsg ->
{ model
| plotState =
Plot.update
myConfig
plotMsg
model.plotState
}If you need routing to update multiple plots:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ToPlot Plot1 plotMsg ->
( { model
| plot1 =
Plot.update
plotConfig1
plotMsg
model.plot1
}
, Cmd.none
)
ToPlot Plot2 plotMsg ->
( { model
| plot2 =
Plot.update
plotConfig2
plotMsg
model.plot2
}
, Cmd.none
)Let's say that you want to customize your configuration to show legends for your different lines and also extend the right margin so that the legends don't get cut off. You can do it like this:
myConfig =
let
default =
Plot.easyConfig points
in
{ default
| showLegends = True
, marginRight = 70
}What if your data isn't in the form of { x : Float, y : Float}?
Then you will need:
Let's say that your data is a list of this type:
type alias ExampleType =
{ time : Posix
, value : Float
, text : String
, otherValue : Float
}If you want to plot data like this you can't use easyConfig, because it only works with data of type Point.
Instead you will have to use defaultConfigWith to create your starting point configuration.
You need to supply it with two accessor functions (getters), one for each chart coordinate (x and y).
myDefaultConfig : Plot.Config ExampleType
myDefaultConfig =
Plot.defaultConfigWith
listOfExampleTypes
(.time >> Time.posixToMillis >> toFloat)
.value
myPointDecoderIt also needs a point decoder. The internals of the package needs a way to convert Point back to your type. You basically have to write a function that puts the coordinates of a Point into an "empty" instance of your type:
myPointDecoder { x, y } =
let
decodedX =
x |> floor |> Time.millisToPosix
in
ExampleType decodedX y "" 0If you want to start customizing every aspect of your plot you need to know about all the fields of the Plot.Config type:
So, what does all this mean?
lines : A list where you set how the different "lines" on your chart should look: color, point shape and legend name. Most importantly it is also here you supply your data to the configuration.
import LineChart
import LineChart.Colors as Colors
import LineChart.Dots as Dots
[ LineChart.line
Colors.tealLight
Dots.circle
"cos"
myCosData
, LineChart.line
Colors.goldLight
Dots.diamond
"polysin"
myPolySinData
]Note: This package uses the fork peterszerzo/line-charts since it contains necessary updates to terezka's great original package. I advise you to do the same, otherwise namespace collisions seem inevitable.
xAcc :
Accessor function (getter) from your data type to the float value used for x coordinates. If you want a time axis (don't forget to set xIsTime to True) for your Time.Posix values you can do something like this:
.time >> Time.posixToMillis >> toFloatyAcc : Accessor function (getter) from your data type to the float value used for y coordinates.
.rocketThrustpointDecoder :
The internals of the package needs a way to convert Point back to your type. You basically have to write a function that puts the coordinates of a Point into an "empty" instance of your type.
\{ x, y } -> RocketData 0 0 "" x 0 "" 0 y NothingxIsTime : Whether the x-axis should display its values as time or not.
Truelanguage : What language dates on time axes will be in.
import DateFormat.Language
DateFormat.Language.swedishnumberFormat : The function which turns floats to strings for the axis labels (on non-time axes).
import FormatNumber
import FormatNumber.Locales
\float ->
FormatNumber.format
FormatNumber.Locales.frenchLocale
floattimeZone :
Which timezone your Time.Posix values should be converted into for your time axis.
import Time
Time.utc -- This is the default.showLegends :
Whether legends for your plot lines should be drawn. If you turn this on you most likely also need to adjust marginRight for them to not get cut off.
TruelabelFunc : Hoverlabels for the x and y coordinates are always on, but if you want to add something extra on a row above them you use this field. One of the more straightforward ways to use this is to just use a string field from your data type:
.rocketInventorNameor something more involved:
\d -> d.firstName ++ " " ++ d.lastNamexAxisLabel / yAxisLabel : Strings for labeling the axes. Adjustment of margins and label offsets could be required to get the desired result.
"Rocket velocity [km/s]"marginTop / marginRight / marginBottom / marginLeft : Size in pixels of the different margins around the actual plot. This is real estate that may or may not be needed by tick labels, legends and axis labels.
60xAxisLabelOffsetX / xAxisLabelOffsetY / yAxisLabelOffsetX / yAxisLabelOffsetY : Distance in pixels of how much you want to adjust the positioning of the axis title labels.
20It can be useful to know what the defaults are:
{ lines = [ LineChart.line
Colors.tealLight
Dots.circle
""
inputPoints
]
, xAcc = .x
, yAcc = .y
, pointDecoder = \p -> p
, xIsTime = False
, language = english
, numberFormat = defaultFormat
, timezone = Time.utc
, showLegends = False
, labelFunc = \_ -> ""
, xAxisLabel = ""
, yAxisLabel = ""
, marginTop = 20
, marginRight = 30
, marginBottom = 30
, marginLeft = 60
, xAxisLabelOffsetX = 0
, xAxisLabelOffsetY = 0
, yAxisLabelOffsetX = 0
, yAxisLabelOffsetY = 0
}
defaultFormat : Float -> String
defaultFormat number =
FormatNumber.format
FormatNumber.Locales.usLocale
numberAbove are the defaults from easyConfig, where inputPoints comes from the user. The defaults for defaultConfigWith are the same except the fields xAcc, yAcc and pointDecoder which are user-provided as well.
Use this configuration as a starting point when your data is just a
List Point: