Add histogram

This commit is contained in:
Darko Lukic 2018-03-24 18:18:05 +01:00
parent ebedcb0f17
commit 1e9dbb4df6
10 changed files with 274 additions and 39 deletions

View file

@ -20,10 +20,6 @@ hr {
background-color: rgba(255, 255, 255, 0.2);
}
.huge {
font-size: 32px;
}
#sidebar {
background-color: #222;
background-size: cover;

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -14,6 +14,7 @@
</div>
</div>
<div class="row">
<img v-if="graphIsLoading" ref="loader" style="margin: auto" :src="loaderUrl" />
<img ref="graph" style="width: 100%; height: 100%" />
</div>
@ -30,7 +31,6 @@
import { Datetime } from 'vue-datetime'
import moment from 'moment';
const FROM_DEFAULT = moment().add('-24', 'hours').toISOString();
const TO_DEFAULT = moment().toISOString();
@ -42,6 +42,8 @@ export default {
to: TO_DEFAULT,
lastFrom: FROM_DEFAULT,
lastTo: TO_DEFAULT,
graphIsLoading: true,
loaderUrl: require('../assets/images/loader.gif'),
}
},
components: {
@ -50,6 +52,8 @@ export default {
mounted() {
this.loadGraphImage();
this.updateDownloadUrl();
this.$refs.graph.onload = () => this.graphIsLoading = false;
},
methods: {
rangeUpdated() {
@ -75,6 +79,8 @@ export default {
let root = this.$http.options.root;
let binSize = 1;
this.$refs.graph.src = '';
this.graphIsLoading = true;
let imageUrl = `${root}histogram.png?from=${from}&to=${to}&bin_size=${binSize}`;
this.$refs.graph.src = imageUrl;
},

View file

@ -1,19 +1,35 @@
<template>
<div class="row">
<Value :value=lastTemperature icon="fa-thermometer-quarter" title="Temperature"></Value>
<Value :value=lastHumidity icon="fa-thermometer-quarter" title="Humidity"></Value>
<div>
<div class="row">
<div class="col-6">
<Histogram></Histogram>
</div>
<div class="col-6">
<Location :longitude=lastLongitude :latitude=lastLatitude></Location>
</div>
</div>
<div class="card-columns">
<Value :value=lastTemperature icon="fa-thermometer-quarter" title="Temperature"></Value>
<Value :value=lastHumidity icon="fa-thermometer-quarter" title="Humidity"></Value>
<Value :value=lastPressure icon="fa-thermometer-quarter" title="Pressure"></Value>
<TimeSeries title="Temperature (C)" dkey="TemperatureC"></TimeSeries>
<Value :value=lastAcceleration icon="fa-tachometer" title="Acceleration"></Value>
<Value :value=lastMagnet icon="fa-compass" title="Magnet"></Value>
<Value :value=detectorInfo icon="fa-info-circle" title="Info"></Value>
<Value :value=lastLocation icon="fa-map-marker" title="Location"></Value>
</div>
</div>
</template>
<script>
import Value from './Value.vue'
import TimeSeries from './TimeSeries.vue'
import Value from './Value.vue';
import TimeSeries from './TimeSeries.vue';
import Location from './Location.vue';
import Histogram from './Histogram.vue';
export default {
name: 'Dashboard',
components: { Value, TimeSeries },
components: { Value, Histogram, Location },
computed: {
times() {
return this.$store.getters.getTimeSeries();
@ -25,6 +41,47 @@ export default {
let value = this.$store.getters.getLastValue('Humidity');
return (!isNaN(value)) ? value.toFixed(2) : value;
},
lastAcceleration() {
let x = this.$store.getters.getLastValue('AccelX');
let y = this.$store.getters.getLastValue('AccelY');
let z = this.$store.getters.getLastValue('AccelZ');
x = (!isNaN(x)) ? x.toFixed(3) : x;
y = (!isNaN(x)) ? y.toFixed(3) : y;
z = (!isNaN(x)) ? z.toFixed(3) : z;
return `${x}\n${y}\n${z}`;
},
lastMagnet() {
let x = this.$store.getters.getLastValue('MagX');
let y = this.$store.getters.getLastValue('MagY');
let z = this.$store.getters.getLastValue('MagZ');
x = (!isNaN(x)) ? x.toFixed(4) : x;
y = (!isNaN(x)) ? y.toFixed(4) : y;
z = (!isNaN(x)) ? z.toFixed(4) : z;
return `${x}\n${y}\n${z}`;
},
lastLocation() {
let long = this.$store.getters.getLastValue('Longitude');
let lati = this.$store.getters.getLastValue('Latitude');
long = (!isNaN(long)) ? long.toFixed(6) : long;
lati = (!isNaN(lati)) ? lati.toFixed(6) : lati;
return `${long}\n${lati}`;
},
lastLongitude() {
return this.$store.getters.getLastValue('Longitude');
},
lastLatitude() {
return this.$store.getters.getLastValue('Latitude');
},
lastPressure() {
let value = this.$store.getters.getLastValue('Pressure');
return (!isNaN(value)) ? value.toFixed(0) : value;
},
detectorInfo() {
let name = this.$store.getters.getLastValue('DetectorName');
let version = this.$store.getters.getLastValue('DetectorVersion');
let serial = this.$store.getters.getLastValue('HardwareSerial');
return `${name}\n${version}\n${serial}`;
},
},
beforeCreate() {
this.$store.dispatch('requestSeries');

View file

@ -0,0 +1,108 @@
<template>
<div class="card card-default">
<div class="card-header">
<h5>Histogram</h5>
<p class="small">
Showing data in period {{from}} - {{to}} ({{period}}s),
<b>{{ numberOfEvents }}</b> events with
bin size <b>{{ binSize }}s</b>
</p>
</div>
<div class="card-body">
<canvas ref="canvas" style="height:300px"></canvas>
</div>
<div class="card-footer">
<input v-model="binSize" class="form-control" min="1" type="number"/>
</div>
</div>
</template>
<script>
import Chart from 'chart.js';
import moment from 'moment';
const DEFAULT_BIN_SIZE = 1;
export default {
name: 'Histogram',
props: ['title', 'dkey'],
data() {
return {
chart: null,
binSize: DEFAULT_BIN_SIZE,
}
},
computed: {
times() {
return this.$store.getters.getSeries('UTCUnixTime');
},
from() {
return moment(this.times[0] * 1000).format('LLL');
},
to() {
return moment(this.times[this.times.length - 1] * 1000).format('LLL');
},
numberOfEvents() {
return this.$store.getters.getNumberOfEvents();
},
period() {
return moment.duration(this.$store.getters.getPeriod() * 1000).asSeconds();
},
},
watch: {
'binSize': function() { this.generateGraph() },
'times': function() { this.generateGraph() },
},
methods: {
generateGraph() {
let list = this.times;
// Create histogram
let histogram = {}
list = list.map(x => x - (x - list[0]) % this.binSize);
list.forEach(x => histogram[x] = (histogram[x] || 0) + 1);
// Put histogram in chart
this.chart.data.datasets[0].data = Object.values(histogram);
this.chart.data.labels = Object.keys(histogram).map(x => moment(x * 1000).format('MMMM DD, YYYY HH:mm:ss'))
this.chart.update();
},
},
mounted() {
this.chart = new Chart(this.$refs.canvas, {
type: 'bar',
data: {
labels: this.times,
datasets: [{
label: this.title,
data: this.values,
backgroundColor: "rgba(153,51,255,0.4)"
}]
},
options: {
animation: false,
legend: {
display: false
},
scales: {
xAxes: [{
display: false
}]
},
},
});
},
}
</script>
<style>
.small {
font-size: 12px;
}
.card {
margin-bottom: 20px;
}
</style>

View file

@ -0,0 +1,38 @@
<template>
<div class="card card-default">
<div class="card-header">
<h5>Location</h5>
</div>
<div class="card-body">
<iframe class="google-map" ref="map" :src=googleMapsUrl></iframe>
</div>
</div>
</template>
<script>
const MAPS_KEY = 'AIzaSyBjX-IrwBEp7lncv8Q-OXsY549c5zNh_kY';
export default {
name: 'Location',
props: ['latitude', 'longitude'],
data() {
return {
chart: null,
}
},
computed: {
googleMapsUrl() {
return `https://www.google.com/maps/embed/v1/place?q=${this.latitude},${this.longitude}&key=${MAPS_KEY}`;
},
},
}
</script>
<style>
.google-map {
width: 100%;
height: 300px;
border: 0;
}
</style>

View file

@ -1,12 +1,10 @@
<template>
<div class="col-lg-6">
<div class="card card-default">
<div class="card-header">
<h5>{{ title }}</h5>
</div>
<div class="card-body">
<canvas ref="canvas" style="height:300px"></canvas>
</div>
<div class="card card-default">
<div class="card-header">
<h5>{{ title }}</h5>
</div>
<div class="card-body">
<canvas ref="canvas" style="height:300px"></canvas>
</div>
</div>
</template>
@ -69,4 +67,10 @@ export default {
*/
},
}
</script>
</script>
<style>
.card {
margin-bottom: 20px;
}
</style>

View file

@ -1,19 +1,17 @@
<template>
<div class="col-bg-3 col-md-4 col-sm-6 col-xs-12 item">
<div class="card card-default bg-primary">
<div class="card-body">
<div class="row">
<div class="col-3">
<i :class="[ ['fa', icon, 'fa-5x'].join(' ') ]"></i>
</div>
<div class="col-9 text-right">
<div class="huge">{{ value }}</div>
<div class="card-title">{{ title }}</div>
</div>
</div>
<div class="card card-default bg-light">
<div class="card-body">
<div class="row">
<div class="col-3">
<i :class="[ ['fa', icon, 'fa-5x'].join(' ') ]"></i>
</div>
<div class="col-9 text-right">
<pre class="huge">{{ value }}</pre>
<div class="card-title">{{ title }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
@ -24,7 +22,7 @@ export default {
</script>
<style>
.item {
padding-bottom: 20px;
.huge {
font-size: 24px;
}
</style>

View file

@ -3,7 +3,7 @@ import Vuex from 'vuex';
import moment from 'moment';
const SERIES_MAX_SIZE = 30;
const SERIES_MAX_SIZE = 100;
Vue.use(Vuex);
@ -32,6 +32,15 @@ const getters = {
}
return 'NA';
},
getNumberOfEvents: (state) => () => {
return state.series.length;
},
getPeriod: (state) => () => {
if (state.series.length > 1) {
return state.series[state.series.length - 1].UTCUnixTime - state.series[0].UTCUnixTime;
}
return 0;
},
isLogged: (state) => () => {
return (state.token !== null);
},
@ -52,7 +61,11 @@ const getters = {
const actions = {
requestSeries({ commit }) {
Vue.http.get('series?format=json').then(response => {
let params = {
format: 'json',
limit: SERIES_MAX_SIZE,
};
Vue.http.get('series', { params }).then(response => {
commit('setSeries', response.body);
});
},
@ -71,7 +84,15 @@ const actions = {
const mutations = {
setSeries(state, data) {
state.series.push(...data);
data.sort((l, r) => l.UTCUnixTime + l.SubSeconds > r.UTCUnixTime + r.SubSeconds ? 1 : -1);
for (let item of data) {
let last = state.series[state.series.length - 1];
let lastUTCUnixTime = last ? last.UTCUnixTime : 0;
let lastSubSeconds = last ? last.SubSeconds : 0;
if (item.UTCUnixTime + item.SubSeconds >= lastUTCUnixTime + lastSubSeconds) {
state.series.push(item);
}
}
state.series = state.series.slice(-SERIES_MAX_SIZE);
},
setAuth(state, token) {

View file

@ -19,12 +19,19 @@ module.exports = {
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
test: /\.(png|jpg|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.gif$/,
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'