Dashboard

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>