Wind Direction & Speed Timeline

Fairly new to Grafana (have a lot of experience working in Tableau and other data visualization tools) and have built a handful of basic Time Series charts to get a better understanding of how Grafana works.

Currently working with Grafana ver 8.4.2 connected to an InfluxDB database. I would like to use Grafana to create a Wind Direction & Speed timeline. Below is an example I found of the type of chart I am looking to build. Basically the wind speed is plotted over time and then for each datapoint, the direction of the wind is represented by which way the arrow is pointing (North is up, South is down). In this example, the color represents the wind speed (higher velocity is red), however I am not really concerned about the colors right now.

Reading through the Grafana forums and doing some general web searching on similar concepts hasn’t really turned up anything. Plotting the wind speed on a time based chart is fairly straightforward and I have already accomplished that, however having a custom marker that
is dynamically rotated a set amount of degrees based on a value is more complicated. I did see a couple articles and Github issues for Grafana stating that custom markers on a line chart/time series panel isn’t supported - so that might already mean this is a no-go.

Is a chart/visualization like this where the marker is an image and the orientation is data-driven possible in Grafana? Looking for some clarification and guidance. If it is possible, pointing me in the direction to go and/or concepts to dig into would be much appreciated!

Thank you!

This looks so cool. Personally never seen such a visualization, it might involved svg + data combination. One way is to build your own stuff

or by using the svg panel that leverages some svg artistry

Could you please post some sample data? Something like this? maybe fill us in what winddir means

name	datetime	windgust	windspeed	winddir
98118, USA	2022-06-15T00:00:00	11.9	4.9	262
98118, USA	2022-06-15T01:00:00	14	9.6	37
98118, USA	2022-06-15T02:00:00	12.2	1.1	332
98118, USA	2022-06-15T03:00:00	9.7	0.3	353
98118, USA	2022-06-15T04:00:00	2.9	0	0
98118, USA	2022-06-15T05:00:00	5.4	0	0

Thank you for the reply and some examples to take a look at! It is a pretty sweet looking visualization (originally saw something similar in the Weather Underground mobile app) and at first I didn’t think would be too complex but I was probably just being naive. I will dig into those links posted and see what is possible/what I can work up.

Wind Gust/Wind Direction

Below are examples of how I am fetching Wind Gust and Wind Direction data from my InFluxDB.
The time is broken down into 10 minute windows right now. For the Wind Gust (sensor.windgust), the value for the is currently in MPH. For the Wind Direction (sensor.winddir), the value is a degree measurement (from 0° to 359°) that the wind is “originating from”. So if the wind was coming straight “out of the North”, then you would see a value of 0°; if the wind was coming “out of the East”, then you would see a value of 90°; and so on.

I am sure I will eventually be combining these into one query, but starting out in small steps.

Wind Gust

Flux Query
from(bucket: "WeatherData")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "sensor.windgust")
  |> filter(fn: (r) => r["_field"] == "value")
  |> aggregateWindow(every: 10m, fn: mean, createEmpty: false)
  |> yield(name: "mean")
Resulting Data
TIME                      VALUE
2022-06-05 20:10:00       5.78
2022-06-05 20:20:00       5.18
2022-06-05 20:30:00       6.46
2022-06-05 20:40:00       7.86
2022-06-05 20:50:00       5.24
2022-06-05 21:00:00       5.93
2022-06-05 21:10:00       4.92
2022-06-05 21:20:00       5.22
2022-06-05 21:30:00       4.95
2022-06-05 21:40:00       6.01
2022-06-05 21:50:00       4.52
2022-06-05 22:00:00       5.01
2022-06-05 22:10:00       5.56
2022-06-05 22:20:00       4.28
2022-06-05 22:30:00       3.94

Wind Direction

Flux Query
from(bucket: "WeatherData")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "sensor.winddir")
  |> filter(fn: (r) => r["_field"] == "value")
  |> aggregateWindow(every: 10m, fn: mean, createEmpty: false)
  |> yield(name: "mean")
Resulting Data
TIME                      VALUE
2022-06-05 20:10:00       156
2022-06-05 20:20:00       100
2022-06-05 20:30:00       93.7
2022-06-05 20:40:00       116
2022-06-05 20:50:00       143
2022-06-05 21:00:00       87.5
2022-06-05 21:10:00       87.8
2022-06-05 21:20:00       107
2022-06-05 21:30:00       131
2022-06-05 21:40:00       158
2022-06-05 21:50:00       156
2022-06-05 22:00:00       115
2022-06-05 22:10:00       126
2022-06-05 22:20:00       140
2022-06-05 22:30:00       167
1 Like

@jkuhlman
Finally got something going still WIP. Crazy wind going all kinds of direction :joy:

image

https://github.com/yosiasz/grafanadashboards/blob/main/windycity.json
1 Like

This is pretty awesome - you have definitely made more progress than I did!

I’ll dig in to your Github repo and follow up with any questions.

Thank you!

1 Like

It can be done with the Apache ECharts plugin in 15 minutes: GitHub - VolkovLabs/volkovlabs-echarts-panel: Apache ECharts Panel for @grafana..

  • you can specify your own path for the arrow,
  • color palette and range is customizable,
  • data should be taken from the data source. I just used a constant,
  • can hide line with width: 0

Here is the code:

const wind = [
  {
    value: ['2022-06-15T00:00:00', 4.9],
    symbolRotate: 262
  },
  {
    value: ['2022-06-15T01:00:00', 9.6],
    symbolRotate: 37
  },
  {
    value: ['2022-06-15T02:00:00', 1.1],
    symbolRotate: 332
  },
  {
    value: ['2022-06-15T03:00:00', 0.3],
    symbolRotate: 353
  },
  {
    value: ['2022-06-15T04:00:00', 0],
    symbolRotate: 0
  },
  {
    value: ['2022-06-15T05:00:00', 3],
    symbolRotate: 110
  },
  {
    value: ['2022-06-15T06:00:00', 5],
    symbolRotate: 200
  },
  {
    value: ['2022-06-15T07:00:00', 7],
    symbolRotate: 250
  },
  {
    value: ['2022-06-15T08:00:00', 9],
    symbolRotate: 50
  },
  {
    value: ['2022-06-15T09:00:00', 6],
    symbolRotate: 0
  }
];

return {
  xAxis: {
    type: 'category'
  },
  yAxis: {
    type: 'value'
  },
  visualMap: {
    orient: 'horizontal',
    left: 'center',
    min: 0,
    max: 10,
    text: ['High', 'Low'],
    dimension: 1,
    inRange: {
      color: ['#65B581', '#FFCE34', '#FD665F']
    }
  },
  series: [
    {
      data: wind,
      type: 'line',
      symbol: 'path://M31 24.7343L21.7917 24.7343V0L9.20826 0L9.20826 24.7343H0L15.5 45L31 24.7343Z',
      symbolSize: 20,
      lineStyle: {
        width: 0.3
      }
    }
  ]
};
2 Likes

Wow this is awesome!

1 Like

@mikhailvolkov - definitely pretty neat!

Want to do a little more “polishing” and formatting, but overall it was straightforward to get this up and working with your examples (much appreciated) and digging through the documentation!

2 Likes

@jkuhlman That looks awesome!

Meanwhile, Apache ECharts plugin becomes available in the Grafana repository: Apache ECharts plugin for Grafana | Grafana Labs

1 Like

Boom GIF - Boom GIFs

@mikhailvolkov unreal, mind blown!

3 Likes

@jkuhlman That looks pretty sweet! Could you please post the (json or similar) code for the panel? Many thanks!

@dutarahisu - I’ll see if I can can put something together.

The Apache ECharts documentation is pretty good and there is A LOT of examples with full code that you can manipulate and play with. Going through the examples, I actually found a similar (although more complex) version of what I wanted here.

2 Likes

Many Thanks. My problem is that I don’t know how to access the data stored in my database. In the example mikhailvolkov posted, he uses the static data stored directly in “const wind”. Maybe you can show/tell me how to use data from a database (influxdb) with the echarts plugin. Kind regards

@dutarahisu, I would recommend starting with

and watch tutorials on how to access your data from the data source: Apache ECharts for Grafana - YouTube

We updated the repository yesterday with examples answering to similar questions: GitHub - VolkovLabs/volkovlabs-echarts-panel: Apache ECharts Panel for @grafana.

To use Apache ECharts with data from data sources, get each field in an array:

data.series.map((s) => {
  if (s.refId === 'logo') {
    images = s.fields.find((f) => f.name === 'body').values.buffer;
  } else if (s.refId === 'connections') {
    sources = s.fields.find((f) => f.name === 'source').values.buffer;
    targets = s.fields.find((f) => f.name === 'target').values.buffer;
  } else if (s.refId === 'nodes') {
    titles = s.fields.find((f) => f.name === 'title').values.buffer;
    descriptions = s.fields.find((f) => f.name === 'description').values.buffer;
  }
});

Please let me know if there is anything else.

Sorry Mikhail, I’m trying to adapt your code to my grafana but doesnt works.
Where is my mistake?

let wind ;
data.series.map((s) => {
wind = s.fields.find((f) => f.name === ‘weewx/windDir.mean’).values.buffer;
});

return {
xAxis: {
type: ‘category’
},
yAxis: {
type: ‘value’
},
visualMap: {
orient: ‘horizontal’,
left: ‘center’,
min: 0,
max: 10,
text: [‘High’, ‘Low’],
dimension: 1,
inRange: {
color: [‘#65B581’, ‘#FFCE34’, ‘#FD665F’]
}
},
series: [
{
data: wind,
type: ‘line’,
symbol: ‘path://M31 24.7343L21.7917 24.7343V0L9.20826 0L9.20826 24.7343H0L15.5 45L31 24.7343Z’,
symbolSize: 20,
lineStyle: {
width: 0.3
}
}
]
};

@Alvaro_Villa wind should be filled with hashes consisting of value as an array and symbolRotate if you need it.

Setting wind with mean values is not enough.

I would suggest trying something like this to get values from 3 columns with names time, value, and rotate and then combine them in the correct structure.

  const time = feedback.fields.find((f) => f.name === 'time').values.buffer;
  const value = feedback.fields.find((f) => f.name === 'value').values.buffer;
  const rotate = feedback.fields.find((f) => f.name === 'rotate').values.buffer;

  /**
   * Set Wind from 3 arrays
   */
  wind = ids.map((id, index) => {
    return { symbolRotate[index], value: [time[index], value[index]] };
  });

Hope it helps.

1 Like

weewx/windDir.mean is the name of the data from a InfluxDB data. In this field my meteo station says me the wind direction in degrees, at the same time, InfluxDB store de time data.

I need to use this to draw the graphics.

Many thanks for your help!!

@Alvaro_Villa This should work for Time and Wind Direction. Do you have speed in another field?:

  const time = feedback.fields.find((f) => f.name === 'Time').values.buffer;
  const value = feedback.fields.find((f) => f.name === 'value').values.buffer;
  const rotate = feedback.fields.find((f) => f.name === 'weewx/windDir.mean').values.buffer;

  /**
   * Set Wind from 3 arrays
   */
  wind = ids.map((id, index) => {
    return { symbolRotate[index], value: [time[index], value[index]] };
  });

Yes, in windSpeed_kph. I’ve tried this:

let wind;
const time = feedback.fields.find((f) => f.name === ‘Time’).values.buffer;
const value = feedback.fields.find((f) => f.name === ‘weewx/windSpeed_kph’).values.buffer;
const rotate = feedback.fields.find((f) => f.name === ‘weewx/windDir.mean’).values.buffer;

/**

  • Set Wind from 3 arrays
    */
    wind = ids.map((id, index) => {
    return { symbolRotate[index], value: [time[index], value[index]] };
    });

return {
xAxis: {
type: ‘category’
},
yAxis: {
type: ‘value’
},
visualMap: {
orient: ‘horizontal’,
left: ‘center’,
min: 0,
max: 10,
text: [‘High’, ‘Low’],
dimension: 1,
inRange: {
color: [‘#65B581’, ‘#FFCE34’, ‘#FD665F’]
}
},
series: [
{
data: wind,
type: ‘line’,
symbol: ‘path://M31 24.7343L21.7917 24.7343V0L9.20826 0L9.20826 24.7343H0L15.5 45L31 24.7343Z’,
symbolSize: 20,
lineStyle: {
width: 0.3
}
}
]
};

But:

4</<@http://(ip_of_my_server):3000/public/plugins/volkovlabs-echarts-panel/module.js:2:2732731
Nu@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:737997
15303/t.unstable_runWithPriority@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:2618248
Ho@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:678647
Ru@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:737460
vu@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:728988
21317/qo/<@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:678870
15303/t.unstable_runWithPriority@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:2618248
Ho@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:678647
qo@http://(ip_of_my_server):3000/public/build/5290.5730ffe229a3937f6f65.js:2:678817

@Alvaro_Villa Here is the working script tested with Static Data Source:

let wind;
data.series.map((s) => {
  const time = s.fields.find((f) => f.name === 'Time').values.buffer;
  const value = s.fields.find((f) => f.name === 'weewx/windSpeed_kph').values.buffer;
  const rotate = s.fields.find((f) => f.name === 'weewx/windDir.mean').values.buffer;

  /**
  Set Wind from 3 arrays
  */
  wind = time.map((id, index) => {
    return { symbolRotate: rotate[index], value: [time[index], value[index]] };
  });

});

return {
  xAxis: {
    type: 'category'
  },
  yAxis: {
    type: 'value'
  },
  visualMap: {
    orient: 'horizontal',
    left: 'center',
    min: 0,
    max: 10,
    text: ['High', 'Low'],
    dimension: 1,
    inRange: {
      color: ['#65B581', '#FFCE34', '#FD665F']
    }
  },
  series: [
    {
      data: wind,
      type: 'line',
      symbol: 'path://M31 24.7343L21.7917 24.7343V0L9.20826 0L9.20826 24.7343H0L15.5 45L31 24.7343Z',
      symbolSize: 20,
      lineStyle: {
        width: 0.3
      }
    }
  ]
};