Saturday, November 19, 2022

Technical Post: Polar charts in Grafana using ae3e-plotly-panel plugin; formatting tips for energy visualizations using dates on the angular axis

I've been banging away on a mini-project and want to publish some of the technical details before they're forgotten, because some were not intuitive to me (as a non-programmer) to figure out. 

If you're browsing my energy post series, you may want to skip this one if you're not interested in programming! 

The problem

Economist Edgardo Sepulveda published some fascinating graphs that show differences between electricity supplied by Ontario's nuclear power plants and grid-connected wind and solar installations. Here is an example:


I wanted to replicate these (to the best of my ability!) for my Saskatchewan Electricity Mix project (Twitter, website). I managed to get it working (see near the end of this post), but it was tricky so I wanted to leave a trail of breadcrumbs for "future me" and others who might want to replicate this visualization with other datasets. 

Technology:

  • Grafana as a visualization engine (I'm running v9.2.4 as of this post) 
  • influxdb2 as a data repository and source to Grafana. Note: nothing in this post is specific to influxdb. 

Obstacles to overcome:

I'll expand on each of these in more detail:

  1. Grafana does not support polar charts
  2. The ae3e-plotly-panel Grafana plugin supports polar charts, but the config instructions for Grafana are virtually non-existent (for a non-programmer this is a big roadblock!) 
  3. Plotly does not currently support using dates on the angular axis

Adding polar chart support to Grafana via Plotly 

Here's the easiest step of the whole process: installing Plotly. 

Assuming your Grafana instance is connected to the internet, navigate to Configuration > Plugins. Search "Plotly" and install the ae3e version:


The natel version of the plugin is limited to XY plots and others, but the ae3e version supports any chart in Plotly, which is huge. 

Note: Plotly has been developed for a few different languages, including (most popularly?) Python and Javascript. The one Grafana/ae3e uses is Javascript, so be sure to add "Javascript" to your Plotly Google searches later on. 


Using Plotly in Grafana

When you spin up a new Plotly visualization in Grafana, you have none of the GUI elements you're accustomed to configuring. You have a bunch of text fields where you write Javascript code. 


The ae3e Github page gives the most bare-bones configuration example but doesn't really break it down for dummies like me. From Github:

let x  = data.series[0].fields[0].values.buffer
let y  = data.series[0].fields[1].values.buffer

let serie = {
    x : x,
    y : y,
    name : variables.project //where project is the name of a Grafana's variable 
}

return {
    data : [serie],
    config : {
        displayModeBar: false
    }
}

What I would have loved to have spelled out for me is that data.series[0].fields[0].values.buffer is the first column of data in the first Grafana query, and data.series[0].fields[1].values.buffer is the second column of data in the first query. 

If you have multiple queries (as I do in the previous screenshot) those datasets and fields can be accessed sequentially: data.series[1].fields[0].values.buffer would the first column of data in the second query, data.series[2].fields[1].values.buffer would be the second column of data in the third query, etc. 

The simplest configuration for a Plotly graph, then, is to create a Grafana query that returns two variables from your data source, literally take the text from the code above and plop it in the "Script" box in the Grafana Plotly plugin. 

Another good resource with examples on Plotly and Grafana is this stackoverflow post.


Creating polar charts with dates on the angular axis 

The main challenge to recreating Edgardo's great visualization is Grafana currently does not support dates on the polar angular axis, despite some clues in the documentation that it does.

The first step, then, is converting the timestamp in your source data to degrees (i.e. between 0 and 360). 

My source data is aggregated daily, so here's how I'm doing it in Grafana: taking the timestamp, extracting the day of year (0 to 365), and multiplying by 360 degrees per 365 days to get a "day of year" in degrees. 

  |> map(fn: (r) => ({ r with day_of_year: float(v: date.yearDay(t: r._time)) * 360.0 / 365.0}))  

Yes, I know: there's a leap year edge case I'm not accounting for - but I have until 2024 to solve that. 

The data stream looks like this (I strip out :



The next step is configuring the scatterpolar chart layout. Here's the code I've got in the "Layout" pane of the Grafana Plotly interface, with some notes following:

{
  "polar": {
    "angularaxis": {
      "tickmode": "array",
      "ticktext": [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
        ""
      ],
      "tickvals": [
        0,
        30.57534247,
        58.1917808219178,
        88.76712329,
        118.3561644,
        148.9315068,
        178.5205479,
        209.0958904,
        239.6712329,
        269.260274,
        299.8356164,
        329.4246575,
        360
      ]
    },
    "radialaxis": {
      "angle": 0,
      "autorange": false,
      "range": [
        0,
        40
      ],
      "type": "linear",
      "visible": true
    }
  },
  "showlegend": true
}

Notes on the above:

  • This is a variant on a solution from this stackoverflow post, essentially just describing an array of strings and their positions on the circle. tickmode, ticktext, and tickvals are the key parameters.
  • Depending on how you want the datetime labels positioned (e.g. if you wanted "Jan" at the centre of the time window and an empty tick denoting the start and end, like Edgardo's version, you could expand those arrays as needed. Here's how I arrived at these numbers. 
  • Be sure to scale the radialaxis appropriately for your data (or figure out how to code it dynamically). 

Here's how the Script portion of the Grafana Plotly panel is configured: 

console.log(data)
var trace = {
  name: 'Coal',
  line: {
      color: "#825631",
      width: 2
    },
  r: data.series[0].fields[0].values.buffer,
  theta: data.series[0].fields[1].values.buffer,
  type: 'scatterpolar'
};

//trace2 through 6 config omitted

data = [trace,trace2,trace3,trace4,trace5,trace6];

layout = {
  title:'Title<br><span style="font-size: 14px;">Subtitle</span>'
}

return {data,layout};

And that's it! Putting it all together, we get a visualization like this: 


I built a second query to visualize all energy sources on the same unitless scale, where 1 is the scaled maximum of each individual energy source in the time span:


One of the nice features of the Grafana Plotly plugin is you can disable traces easily, as I've done on this chart to just compare natural gas and wind "peakiness". 


Downsides of my approach 

  • I'm probably under-utilizing smart Javascript programming to do some smarter things. 
  • For example, hard-coding the date/time scaling instead of figuring out a clever way to generate it dynamically from the Grafana time range parameters. Please leave a comment if you figure this out! 
  • When a user mouses over the chart, they see decimal "days", e.g. "308.7123". So it's not a great way to zero in on individual data points, but that's not really the intent. 
  • There is probably a better way to include larger datasets than the six manual queries in my example - in fact, I see the solution now (just having more columns and naming different the data.series[0].fields[n].values.buffer fields in this string - perhaps harder to keep track of, though).

Hopefully these clues help save someone else the hours of trial-and-error config I spent trying to get a polar chart with dates on the angular axis working! 

Note: I have not yet published these on skelectricity.info yet... want to wait a little while and ensure they're fully tested and working first. 

My next energy post should be back on-topic to energy, nuclear, and Saskatchewan! 

No comments:

Post a Comment