IoTReady - Server Side Time Series Plots With Elixir Using Contex

Contex makes creating server side plots easy with Elixir but the documentation could be better.

 · 3 min read

Server Side Time Series Plots With Elixir Using Contex


Background


At IoTReady, we are building a virtual IoT platform to help manufacturers track all of their products - whether these are born smart or not. For instance, a typical workflow we track in the Smart Grid industry looks something like this -


  1. A manufacturer produces a batch of, say, an insulation product
  2. The manufacturer ships certain units of this batch to a distributor
  3. An operator buys some units of this batch
  4. The operator installs the insulation product and captures notes and media (photos & videos)


At each stage of the flow, our mobile app scans QR codes and captures additional metadata like location and timestamps. Post installation we capture regular weather data for the installation location for analysis and preventive maintenance.


Tech Stack


Operational dashboards are a lot more fun (and useful) realtime, so we are building ours with Elixir and the Phoenix Framework. These choices deserve their own, longer, blog post. For now, we will focus on our charting library of choice.


We are big fans of Plotly and have used it extensively in the past. However, in this case we wanted to minimise JS code and do things server side as much as we could.


Step up Contex!


We discovered Contex via a blog post on the excellent Elixir School site. However, that post covers bar charts and the Contex documentation is a work-in-progress. Figuring out legends and version-to-documentation mismatches was particularly painful. Hence this post.


"Our dashboard is complementary to, rather than a replacement for, BI tools like Redash or Kibana. We needed something easy to use and customise that includes some visualisation.


Quick References


  1. The Contex documentation can be a little difficult to wrap your head around
  2. The unit tests and samples, on the other hand, are excellent resources


Data Source


We use the OpenWeatherMap API to grab basic weather data. Our Ecto schema looks a bit like this:


schema "weather" do
 field :latitude, :float
 field :longitude, :float
 field :temp, :float
 field :humidity, :float
 field :pressure, :float

 timestamps()
end


And the query for getting recent data looks like this:


@doc """
Gets weather data points for a given latitude and longitude tuple.

## Examples
   iex> get_weather({latitude, longitude}, limit)
   {:ok, [%Weather{}]}
"""
def get_weather({latitude, longitude}, limit \\ 100) do
 q = from w in Weather,
     where: [latitude: ^latitude, longitude: ^longitude],
     order_by: [desc: :inserted_at],
     limit: ^limit,
     select: %{temp: w.temp, humidity: w.humidity, pressure: w.pressure, inserted_at: w.inserted_at}
 Repo.all(q)
end



This query returns a list of maps that look like this:


[
 %{
   humidity: 73.0,
   inserted_at: ~N[2021-01-27 17:00:01],
   pressure: 1016.0,
   temp: 297.27
 },
 %{
   humidity: 73.0,
   inserted_at: ~N[2021-01-27 17:00:01],
   pressure: 1016.0,
   temp: 297.27
 }
]


Setting up Contex


Now that we have our data, time to set up Contex. Since it’s still early days for Contex, it’s best to work with the master branch off the Github repo rather than the 0.3.0 release on Hex. For instance, the 0.3.0 release does not include LinePlot, which we need.


# mix.exs
defp deps do
 [
   ...,
   {:contex, git: "https://github.com/mindok/contex"},
 ]
end


Plotting the data


It’s easiest to illustrate the plotting flow with code:


# Get the last 100 data points for {latitude, longitude}
weather_data = get_weather({latitude, longitude})


plot_options = %{
 top_margin: 5,
 right_margin: 5,
 bottom_margin: 5,
 left_margin: 5,
 show_x_axis: true,
 show_y_axis: true,
}

# Generate the SVG chart
weather_chart =
 weather_data
 # Flatten the map into a list of lists
 |> Enum.map(fn %{inserted_at: timestamp, temp: temp, humidity: humidity, pressure: pressure} ->
   [timestamp, temp, humidity, pressure]
 end)
 # Assign legend titles using list indices
 |> Dataset.new(["Time", "Temperature", "Humidity", "Pressure"])
 # Specify plot type (LinePlot), SVG dimensions, column mapping, title, label and legend
 |> Plot.new(
   LinePlot,
   600,
   300,
   mapping: %{x_col: "Time", y_cols: ["Temperature", "Humidity", "Pressure"]},
   plot_options: plot_options,
   title: "Weather",
   x_label: "Time",
   legend_setting: :legend_right
 )
 # Generate SVG
 |> Plot.to_svg()

# We are using Phoenix LiveView, so assign the chart to the socket
socket
 |> assign(:weather_chart, weather_chart)


After this, all that’s left to do is to embed the SVG in the HTML view and this is all it takes:


<%= @weather_chart %>


We faced some clipping of the legend text but that was easy to fix with this CSS:


.exc-legend {
    font-size: small;
}


Here’s the SVG in all its glory :-)


Where do we go from here


We think Contex is a pretty good fit for our needs. It’s definitely rough around the edges and there are plenty of use cases we are yet to explore like:



  1. interactivity (not a huge deal for us) and
  2. realtime updates (big deal).


Realtime updates are easy to implement but we want verify impact on server performance but then again, this is not an immediate concern.


Ideas, questions or corrections?


Write to us at hello@iotready.co






Tej Pochiraju

Tej holds a PhD in Wireless Hardware and has built and sold patents covering innovations in RFID and microwave processing. He has been building IoT products with enterprises, large and small, for nearly 15 years.

No comments yet

No comments yet. Start a new discussion.

Add Comment