Dashboard

../../../_images/dashboard.png

A refined and configurable version of this plugin has been added to the standard OVMS web UI and gets automatically configured by the vehicle modules with their respective vehicle parameters.

It’s a good example of the Highcharts gauge chart options and shows how to combine multiple gauge charts into a single container (div #gaugeset1) and how to style the charts using CSS.

It also includes a simple test data generator so can be tested without actual vehicle data.

To add a new vehicle parameter set for the standard dashboard, simply override the GetDashboardConfig() method in your vehicle class. Have a look at the existing overrides (for example Twizy, Kia Soul, Smart ED, …).

Install: not necessary for the standard dashboard. If you’re going to build your own dashboard from this, install it as a page type plugin.

dashboard.htm (hint: right click, save as)

  1<!-- Main -->
  2
  3<style>
  4@media (max-width: 767px) {
  5  .panel-single .panel-body {
  6    padding: 2px;
  7  }
  8}
  9.dashboard .highcharts-data-label text {
 10  font-size: 2em;
 11}
 12.dashboard .overlay {
 13  position: absolute;
 14  z-index: 10;
 15  top: 62%;
 16  text-align: center;
 17  left: 0%;
 18  right: 0%;
 19}
 20.dashboard .overlay .value {
 21  color: #04282b;
 22  background: #97b597;
 23  padding: 2px 5px;
 24  margin-right: 0.2em;
 25  text-align: center;
 26  border: 1px inset #c3c3c380;
 27  font-family: "Monaco", "Menlo", "Consolas", "QuickType Mono", "Lucida Console", "Roboto Mono", "Ubuntu Mono", "DejaVu Sans Mono", "Droid Sans Mono", monospace;
 28  display: inline-block;
 29}
 30.night .dashboard .overlay .value {
 31  color: #fff;
 32  background: #252525;
 33}
 34.dashboard .overlay .unit {
 35  color: #666;
 36  font-size: 12px;
 37}
 38.dashboard .overlay .range-value .value {
 39  margin-left: 10px;
 40  width: 100px;
 41}
 42.dashboard .overlay .energy-value {
 43  margin-top: 4px;
 44}
 45.dashboard .overlay .energy-value .value {
 46  margin-left: 18px;
 47  width: 100px;
 48  font-size: 12px;
 49}
 50</style>
 51
 52<div class="panel panel-primary" id="panel-dashboard">
 53  <div class="panel-heading">Dashboard</div>
 54  <div class="panel-body">
 55
 56    <div class="receiver get-window-resize" id="livestatus">
 57      <div class="dashboard" style="position: relative; width: 100%; height: 300px; margin: 0 auto">
 58        <div class="overlay">
 59          <div class="range-value"><span class="value">▲0 ▼0</span><span class="unit">km</span></div>
 60          <div class="energy-value"><span class="value">▲0.0 ▼0.0</span><span class="unit">kWh</span></div>
 61        </div>
 62        <div id="gaugeset1" style="width: 100%; height: 100%;"></div>
 63      </div>
 64    </div>
 65
 66  </div>
 67</div>
 68
 69
 70<!-- Chart -->
 71
 72<style>
 73.highcharts-plot-band, .highcharts-pane {
 74  fill-opacity: 0;
 75}
 76.highcharts-plot-band.border {
 77  stroke: #666666;
 78  stroke-width: 1px;
 79}
 80
 81.green-band {
 82	fill: #55BF3B;
 83	fill-opacity: 0.4;
 84}
 85.yellow-band {
 86	fill: #DDDF0D;
 87	fill-opacity: 0.5;
 88}
 89.red-band {
 90	fill: #DF5353;
 91	fill-opacity: 0.6;
 92}
 93.violet-band {
 94  fill: #9622ff;
 95  fill-opacity: 0.6;
 96}
 97.night .violet-band {
 98  fill: #9622ff;
 99  fill-opacity: 0.8;
100}
101
102.highcharts-gauge-series .highcharts-pivot {
103  stroke-width: 1px;
104  stroke: #757575;
105  fill-opacity: 1;
106  fill: black;
107}
108.highcharts-gauge-series.auxgauge .highcharts-pivot {
109  fill-opacity: 1;
110  fill: #fff;
111  stroke-width: 0;
112}
113.night .highcharts-gauge-series.auxgauge .highcharts-pivot {
114  fill: #000;
115}
116.highcharts-gauge-series .highcharts-dial {
117  fill: #d80000;
118  stroke: #000;
119  stroke-width: 0.5px;
120}
121.highcharts-yaxis-grid .highcharts-grid-line,
122.highcharts-yaxis-grid .highcharts-minor-grid-line {
123	display: none;
124}
125.highcharts-yaxis .highcharts-tick {
126	stroke-width: 2px;
127	stroke: #666666;
128}
129.night .highcharts-yaxis .highcharts-tick {
130  stroke: #e0e0e0;
131}
132.highcharts-yaxis .highcharts-minor-tick {
133  stroke-width: 1.8px;
134  stroke: #00000085;
135  stroke-dasharray: 6;
136  stroke-dashoffset: -4.8;
137  stroke-linecap: round;
138}
139.night .highcharts-yaxis .highcharts-minor-tick {
140  stroke: #ffffff85;
141}
142.highcharts-axis-labels {
143  fill: #000;
144  font-weight: bold;
145  font-size: 0.9em;
146}
147.night .highcharts-axis-labels {
148  fill: #ddd;
149}
150.highcharts-data-label text {
151  fill: #333333;
152}
153.night .highcharts-data-label text {
154  fill: #fff;
155}
156
157.highcharts-axis-labels.speed {
158  font-size: 1.2em;
159}
160</style>
161
162<script type="text/javascript">
163
164// Vehicle specific configuration:
165
166var vehicle_config_twizy = {
167  yAxis: [{
168    // Speed:
169    min: 0, max: 120,
170    plotBands: [
171      { from: 0, to: 70, className: 'green-band' },
172      { from: 70, to: 100, className: 'yellow-band' },
173      { from: 100, to: 120, className: 'red-band' }]
174  },{
175    // Voltage:
176    min: 45, max: 60,
177    plotBands: [
178      { from: 45, to: 47.5, className: 'red-band' },
179      { from: 47.5, to: 50, className: 'yellow-band' },
180      { from: 50, to: 60, className: 'green-band' }]
181  },{
182    // SOC:
183    min: 0, max: 100,
184    plotBands: [
185      { from: 0, to: 12.5, className: 'red-band' },
186      { from: 12.5, to: 25, className: 'yellow-band' },
187      { from: 25, to: 100, className: 'green-band' }]
188  },{
189    // Efficiency:
190    min: 0, max: 300,
191    plotBands: [
192      { from: 0, to: 150, className: 'green-band' },
193      { from: 150, to: 250, className: 'yellow-band' },
194      { from: 250, to: 300, className: 'red-band' }]
195  },{
196    // Power:
197    min: -10, max: 30,
198    plotBands: [
199      { from: -10, to: 0, className: 'violet-band' },
200      { from: 0, to: 15, className: 'green-band' },
201      { from: 15, to: 25, className: 'yellow-band' },
202      { from: 25, to: 30, className: 'red-band' }]
203  },{
204    // Charger temperature:
205    min: 20, max: 80, tickInterval: 20,
206    plotBands: [
207      { from: 20, to: 65, className: 'normal-band border' },
208      { from: 65, to: 80, className: 'red-band border' }]
209  },{
210    // Battery temperature:
211    min: -15, max: 65, tickInterval: 25,
212    plotBands: [
213      { from: -15, to: 0, className: 'red-band border' },
214      { from: 0, to: 50, className: 'normal-band border' },
215      { from: 50, to: 65, className: 'red-band border' }]
216  },{
217    // Inverter temperature:
218    min: 20, max: 80, tickInterval: 20,
219    plotBands: [
220      { from: 20, to: 70, className: 'normal-band border' },
221      { from: 70, to: 80, className: 'red-band border' }]
222  },{
223    // Motor temperature:
224    min: 50, max: 125, tickInterval: 25,
225    plotBands: [
226      { from: 50, to: 110, className: 'normal-band border' },
227      { from: 110, to: 125, className: 'red-band border' }]
228  }]
229};
230
231var vehicle_config_default = {
232  yAxis: [{
233    // Speed:
234    min: 0, max: 120,
235    plotBands: [
236      { from: 0, to: 70, className: 'green-band' },
237      { from: 70, to: 100, className: 'yellow-band' },
238      { from: 100, to: 120, className: 'red-band' }]
239  },{
240    // Voltage:
241    min: 310, max: 410,
242    plotBands: [
243      { from: 310, to: 325, className: 'red-band' },
244      { from: 325, to: 340, className: 'yellow-band' },
245      { from: 340, to: 410, className: 'green-band' }]
246  },{
247    // SOC:
248    min: 0, max: 100,
249    plotBands: [
250      { from: 0, to: 12.5, className: 'red-band' },
251      { from: 12.5, to: 25, className: 'yellow-band' },
252      { from: 25, to: 100, className: 'green-band' }]
253  },{
254    // Efficiency:
255    min: 0, max: 400,
256    plotBands: [
257      { from: 0, to: 200, className: 'green-band' },
258      { from: 200, to: 300, className: 'yellow-band' },
259      { from: 300, to: 400, className: 'red-band' }]
260  },{
261    // Power:
262    min: -50, max: 200,
263    plotBands: [
264      { from: -50, to: 0, className: 'violet-band' },
265      { from: 0, to: 100, className: 'green-band' },
266      { from: 100, to: 150, className: 'yellow-band' },
267      { from: 150, to: 200, className: 'red-band' }]
268  },{
269    // Charger temperature:
270    min: 20, max: 80, tickInterval: 20,
271    plotBands: [
272      { from: 20, to: 65, className: 'normal-band border' },
273      { from: 65, to: 80, className: 'red-band border' }]
274  },{
275    // Battery temperature:
276    min: -15, max: 65, tickInterval: 25,
277    plotBands: [
278      { from: -15, to: 0, className: 'red-band border' },
279      { from: 0, to: 50, className: 'normal-band border' },
280      { from: 50, to: 65, className: 'red-band border' }]
281  },{
282    // Inverter temperature:
283    min: 20, max: 80, tickInterval: 20,
284    plotBands: [
285      { from: 20, to: 70, className: 'normal-band border' },
286      { from: 70, to: 80, className: 'red-band border' }]
287  },{
288    // Motor temperature:
289    min: 50, max: 125, tickInterval: 25,
290    plotBands: [
291      { from: 50, to: 110, className: 'normal-band border' },
292      { from: 110, to: 125, className: 'red-band border' }]
293  }]
294};
295
296var vehicle_config = vehicle_config_twizy;
297
298var gaugeset1;
299
300function get_dashboard_data() {
301  var rmin = metrics["v.b.range.est"]||0, rmax = metrics["v.b.range.ideal"]||0;
302  var euse = metrics["v.b.energy.used"]||0, erec = metrics["v.b.energy.recd"]||0;
303  if (rmin > rmax) { var x = rmin; rmin = rmax; rmax = x; }
304  var md = {
305    range: { value: "▲" + rmax.toFixed(0) + " ▼" + rmin.toFixed(0) },
306    energy: { value: "▲" + euse.toFixed(1) + " ▼" + erec.toFixed(1) },
307    series: [
308      { data: [metrics["v.p.speed"]] },
309      { data: [metrics["v.b.voltage"]] },
310      { data: [metrics["v.b.soc"]] },
311      { data: [metrics["v.b.consumption"]] },
312      { data: [metrics["v.b.power"]] },
313      { data: [metrics["v.c.temp"]] },
314      { data: [metrics["v.b.temp"]] },
315      { data: [metrics["v.i.temp"]] },
316      { data: [metrics["v.m.temp"]] }],
317  };
318  return md;
319}
320
321function update_dashboard() {
322  var md = get_dashboard_data();
323  $('.range-value .value').text(md.range.value);
324  $('.energy-value .value').text(md.energy.value);
325  gaugeset1.update({ series: md.series });
326}
327
328function init_gaugeset1() {
329  var chart_config = {
330    chart: {
331      type: 'gauge',
332      spacing: [0, 0, 0, 0],
333      margin: [0, 0, 0, 0],
334      animation: { duration: 0, easing: 'swing' },
335    },
336    title: { text: null },
337    credits: { enabled: false },
338    tooltip: { enabled: false },
339
340    pane: [
341      { startAngle: -125, endAngle: 125, center: ['50%', '45%'], size: '80%' }, // Speed
342      { startAngle: 70, endAngle: 110, center: ['-20%', '20%'], size: '100%' }, // Voltage
343      { startAngle: 70, endAngle: 110, center: ['-20%', '60%'], size: '100%' }, // SOC
344      { startAngle: -110, endAngle: -70, center: ['120%', '20%'], size: '100%' }, // Efficiency
345      { startAngle: -110, endAngle: -70, center: ['120%', '60%'], size: '100%' }, // Power
346      { startAngle: -45, endAngle: 45, center: ['20%', '100%'], size: '30%' }, // Charger temperature
347      { startAngle: -45, endAngle: 45, center: ['40%', '100%'], size: '30%' }, // Battery temperature
348      { startAngle: -45, endAngle: 45, center: ['60%', '100%'], size: '30%' }, // Inverter temperature
349      { startAngle: -45, endAngle: 45, center: ['80%', '100%'], size: '30%' }], // Motor temperature
350    
351    responsive: {
352      rules: [{
353        condition: { minWidth: 0, maxWidth: 400 },
354        chartOptions: {
355          pane: [
356            { size: '60%' }, // Speed
357            { center: ['-20%', '20%'] }, // Voltage
358            { center: ['-20%', '60%'] }, // SOC
359            { center: ['120%', '20%'] }, // Efficiency
360            { center: ['120%', '60%'] }, // Power
361            { center: ['15%', '100%']   , size: '25%' }, // Charger temperature
362            { center: ['38.33%', '100%'], size: '25%' }, // Battery temperature
363            { center: ['61.66%', '100%'], size: '25%' }, // Inverter temperature
364            { center: ['85%', '100%']   , size: '25%' }], // Motor temperature
365          yAxis: [{ labels: { step: 1 } }], // Speed
366        },
367      },{
368        condition: { minWidth: 401, maxWidth: 450 },
369        chartOptions: {
370          pane: [
371            { size: '70%' }, // Speed
372            { center: ['-15%', '20%'] }, // Voltage
373            { center: ['-15%', '60%'] }, // SOC
374            { center: ['115%', '20%'] }, // Efficiency
375            { center: ['115%', '60%'] }, // Power
376            { center: ['15%', '100%']   , size: '27.5%' }, // Charger temperature
377            { center: ['38.33%', '100%'], size: '27.5%' }, // Battery temperature
378            { center: ['61.66%', '100%'], size: '27.5%' }, // Inverter temperature
379            { center: ['85%', '100%']   , size: '27.5%' }], // Motor temperature
380        },
381      },{
382        condition: { minWidth: 451, maxWidth: 600 },
383        chartOptions: {
384          pane: [
385            { size: '80%' }, // Speed
386            { center: ['-10%', '20%'] }, // Voltage
387            { center: ['-10%', '60%'] }, // SOC
388            { center: ['110%', '20%'] }, // Efficiency
389            { center: ['110%', '60%'] }], // Power
390        },
391      },{
392        condition: { minWidth: 601 },
393        chartOptions: {
394          pane: [
395            { size: '85%' }, // Speed
396            { center: ['0%', '20%'] }, // Voltage
397            { center: ['0%', '60%'] }, // SOC
398            { center: ['100%', '20%'] }, // Efficiency
399            { center: ['100%', '60%'] }], // Power
400        },
401      }]
402    },
403    
404    yAxis: [{
405      // Speed axis:
406      pane: 0, className: 'speed', title: { text: 'km/h' },
407      reversed: false,
408      minorTickInterval: 'auto', minorTickLength: 5, minorTickPosition: 'inside',
409      tickPixelInterval: 30, tickPosition: 'inside', tickLength: 13,
410      labels: { step: 2, distance: -28, x: 0, y: 5, zIndex: 2 },
411    },{
412      // Voltage axis:
413      pane: 1, className: 'voltage', title: { text: 'Volt', align: 'low', x: 90, y: 35, },
414      reversed: true,
415      minorTickInterval: 'auto', minorTickLength: 5, minorTickPosition: 'inside',
416      tickPixelInterval: 30, tickPosition: 'inside', tickLength: 13,
417      labels: { step: 1, distance: -25, x: 0, y: 5, zIndex: 2 },
418    },{
419      // SOC axis:
420      pane: 2, className: 'soc', title: { text: 'SOC', align: 'low', x: 85, y: 35 },
421      reversed: true,
422      minorTickInterval: 'auto', minorTickLength: 5, minorTickPosition: 'inside',
423      tickPixelInterval: 30, tickPosition: 'inside', tickLength: 13,
424      labels: { step: 1, distance: -25, x: 0, y: 5, zIndex: 2 },
425    },{
426      // Efficiency axis:
427      pane: 3, className: 'efficiency', title: { text: 'Wh/km', align: 'low', x: -125, y: 35 },
428      reversed: false,
429      minorTickInterval: 'auto', minorTickLength: 5, minorTickPosition: 'inside',
430      tickPixelInterval: 30, tickPosition: 'inside', tickLength: 13,
431      labels: { step: 1, distance: -25, x: 0, y: 5, zIndex: 2 },
432    },{
433      // Power axis:
434      pane: 4, className: 'power', title: { text: 'kW', align: 'low', x: -115, y: 35 },
435      reversed: false,
436      minorTickInterval: 'auto', minorTickLength: 5, minorTickPosition: 'inside',
437      tickPixelInterval: 30, tickPosition: 'inside', tickLength: 13,
438      labels: { step: 1, distance: -25, x: 0, y: 5, zIndex: 2 },
439    },{
440      // Charger temperature axis:
441      pane: 5, className: 'temp-charger', title: { text: 'CHG °C', y: 10 },
442      tickPosition: 'inside', tickLength: 10, minorTickInterval: null,
443      labels: { step: 1, distance: 3, x: 0, y: 0, zIndex: 2 },
444    },{
445      // Battery temperature axis:
446      pane: 6, className: 'temp-battery', title: { text: 'BAT °C', y: 10 },
447      tickPosition: 'inside', tickLength: 10, minorTickInterval: null,
448      labels: { step: 1, distance: 3, x: 0, y: 0, zIndex: 2 },
449    },{
450      // Inverter temperature axis:
451      pane: 7, className: 'temp-inverter', title: { text: 'PEM °C', y: 10 },
452      tickPosition: 'inside', tickLength: 10, minorTickInterval: null,
453      labels: { step: 1, distance: 3, x: 0, y: 0, zIndex: 2 },
454    },{
455      // Motor temperature axis:
456      pane: 8, className: 'temp-motor', title: { text: 'MOT °C', y: 10 },
457      tickPosition: 'inside', tickLength: 10, minorTickInterval: null,
458      labels: { step: 1, distance: 3, x: 0, y: 0, zIndex: 2 },
459    }],
460    
461    plotOptions: {
462      gauge: {
463        dataLabels: { enabled: false },
464        overshoot: 1
465      }
466    },
467    series: [{
468      // Speed value:
469      yAxis: 0, name: 'Speed', className: 'speed fullgauge', data: [0],
470      pivot: { radius: '10' },
471      dial: { radius: '88%', topWidth: 1, baseLength: '20%', baseWidth: 10, rearLength: '20%' },
472    },{
473      // Voltage value:
474      yAxis: 1, name: 'Voltage', className: 'voltage auxgauge', data: [0],
475      pivot: { radius: '85' },
476      dial: { radius: '95%', baseWidth: 5, baseLength: '90%' },
477    },{
478      // SOC value:
479      yAxis: 2, name: 'SOC', className: 'soc auxgauge', data: [0],
480      pivot: { radius: '85' },
481      dial: { radius: '95%', baseWidth: 5, baseLength: '90%' },
482    },{
483      // Efficiency value:
484      yAxis: 3, name: 'Efficiency', className: 'efficiency auxgauge', data: [0],
485      pivot: { radius: '85' },
486      dial: { radius: '95%', baseWidth: 5, baseLength: '90%' },
487    },{
488      // Power value:
489      yAxis: 4, name: 'Power', className: 'power auxgauge', data: [0],
490      pivot: { radius: '85' },
491      dial: { radius: '95%', baseWidth: 5, baseLength: '90%' },
492    },{
493      // Charger temperature value:
494      yAxis: 5, name: 'Charger temperature', className: 'temp-charger tempgauge', data: [0],
495      dial: { radius: '90%', baseWidth: 3, baseLength: '90%' },
496    },{
497      // Battery temperature value:
498      yAxis: 6, name: 'Battery temperature', className: 'temp-battery tempgauge', data: [0],
499      dial: { radius: '90%', baseWidth: 3, baseLength: '90%' },
500    },{
501      // Inverter temperature value:
502      yAxis: 7, name: 'Inverter temperature', className: 'temp-inverter tempgauge', data: [0],
503      dial: { radius: '90%', baseWidth: 3, baseLength: '90%' },
504    },{
505      // Motor temperature value:
506      yAxis: 8, name: 'Motor temperature', className: 'temp-motor tempgauge', data: [0],
507      dial: { radius: '90%', baseWidth: 3, baseLength: '90%' },
508    }]
509  };
510  
511  // Inject vehicle config:
512  for (var i = 0; i < chart_config.yAxis.length; i++) {
513    $.extend(chart_config.yAxis[i], vehicle_config.yAxis[i]);
514  }
515  
516  gaugeset1 = Highcharts.chart('gaugeset1', chart_config,
517    function (chart) {
518      chart.update({ chart: { animation: { duration: 1000, easing: 'swing' } } });
519      $('#livestatus').on("msg:metrics", function(e, update){
520        update_dashboard();
521      }).on("window-resize", function(e){
522        chart.reflow();
523      });
524    }
525  );
526}
527
528function init_charts() {
529  init_gaugeset1();
530}
531
532if (window.Highcharts) {
533  init_charts();
534} else {
535  $.ajax({
536    url: window.assets.charts_js,
537    dataType: "script",
538    cache: true,
539    success: function(){ init_charts(); }
540  });
541}
542
543</script>
544
545
546
547<!-- ***************** TESTDATENGENERATOR ****************** -->
548<br class="clearfix">
549<div id="viewportinfo">WxH</div>
550<div id="debugfs"></div>
551<button class="btn btn-info" id="mkmetrics">Make test metrics</button>
552<br class="clearfix">
553<script type="text/javascript">
554$("#mkmetrics").on("click", function(){
555    var msg = {
556      metrics: {
557        "v.b.range.est": Math.random()*80,
558        "v.b.range.ideal": Math.random()*80,
559        "v.b.energy.used": Math.random()*20,
560        "v.b.energy.recd": Math.random()*20,
561        "v.p.speed": Math.random()*120,
562        "v.b.voltage": 45 + Math.random()*15,
563        "v.b.soc": Math.random()*100,
564        "v.b.consumption": Math.random()*300,
565        "v.b.power": Math.random()*40 - 10,
566        "v.c.temp": Math.random()*80,
567        "v.b.temp": Math.random()*80,
568        "v.i.temp": Math.random()*80,
569        "v.m.temp": Math.random()*80,
570      }
571    };
572    $.extend(metrics, msg.metrics);
573    $(".receiver").trigger("msg:metrics", msg.metrics);
574});
575$(window).on("resize", function(){
576  $("#viewportinfo").text($(window).width() + " x " + $(window).height());
577}).trigger("resize");
578</script>