kopia lustrzana https://github.com/manuelkasper/sotlas-frontend
248 wiersze
7.1 KiB
Vue
248 wiersze
7.1 KiB
Vue
<template>
|
||
<div>
|
||
<div class="columns is-5 is-variable">
|
||
<div class="column is-half">
|
||
<h4 class="title is-4">Activations per year</h4>
|
||
<BarChart v-if="activationsPerYear" :data="activationsPerYear" labelField="year" valueField="activations" valueFieldB="bonusActivations" name="Activations" nameB="with Bonus" :xIsSeries="true" />
|
||
</div>
|
||
<div class="column is-half">
|
||
<h4 class="title is-4">Activations per association</h4>
|
||
<PieChart v-if="activationsPerAssociation" :data="activationsPerAssociation" labelField="association" valueField="activations" name="Activations" />
|
||
</div>
|
||
</div>
|
||
<div v-if="moreStats" class="columns is-5 is-variable">
|
||
<div class="column is-half">
|
||
<h4 class="title is-4">Activations by altitude</h4>
|
||
<BarChart v-if="activationsPerAltitude" :data="activationsPerAltitude" labelField="altitude" valueField="activations" name="Activations" :xIsSeries="true" />
|
||
</div>
|
||
<div class="column is-half">
|
||
<h4 class="title is-4 no-margin-bottom">Number of QSOs per activation</h4>
|
||
<PercentageChart v-if="qsosPerActivation" :data="qsosPerActivation" labelField="qsos" valueField="activations" name="Activations" :maxSlices="11" />
|
||
|
||
<template v-if="modes">
|
||
<h4 class="title is-4 no-margin-bottom">Number of QSOs by mode</h4>
|
||
<PercentageChart :data="modes" labelField="mode" valueField="qsos" name="QSOs" />
|
||
</template>
|
||
|
||
<template v-if="bands">
|
||
<h4 class="title is-4 no-margin-bottom">Number of QSOs by band</h4>
|
||
<PercentageChart :data="bands" labelField="band" valueField="qsos" name="QSOs" />
|
||
</template>
|
||
|
||
<div v-if="!authenticated" class="stats-teaser">
|
||
<div>
|
||
<font-awesome-icon :icon="['far', 'chart-bar']" /> Log in for more statistics
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="more-button" v-else>
|
||
<b-button @click="moreStats = true" type="is-info" icon-left="chart-bar">More stats</b-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import moment from 'moment'
|
||
import utils from '../mixins/utils.js'
|
||
import BarChart from '../components/BarChart.vue'
|
||
import PieChart from '../components/PieChart.vue'
|
||
import PercentageChart from '../components/PercentageChart.vue'
|
||
|
||
export default {
|
||
props: {
|
||
activations: Array
|
||
},
|
||
mixins: [utils],
|
||
components: {
|
||
BarChart, PieChart, PercentageChart
|
||
},
|
||
computed: {
|
||
activationsPerYear () {
|
||
if (this.activations.length === 0) {
|
||
return null
|
||
}
|
||
|
||
let years = {}
|
||
let yearsBonus = {}
|
||
this.activations.forEach(activation => {
|
||
let year = moment.utc(activation.date).year()
|
||
if (!years[year]) {
|
||
years[year] = 0
|
||
yearsBonus[year] = 0
|
||
}
|
||
years[year]++
|
||
if (activation.bonus > 0) {
|
||
yearsBonus[year]++
|
||
}
|
||
})
|
||
|
||
return Object.keys(years).sort().map(year => {
|
||
return { year, activations: years[year], bonusActivations: yearsBonus[year] }
|
||
})
|
||
},
|
||
activationsPerAssociation () {
|
||
if (this.activations.length === 0) {
|
||
return null
|
||
}
|
||
|
||
let associations = {}
|
||
this.activations.forEach(activation => {
|
||
let association = activation.summit.code.substring(0, activation.summit.code.indexOf('/'))
|
||
if (!associations[association]) {
|
||
associations[association] = 0
|
||
}
|
||
associations[association]++
|
||
})
|
||
|
||
return Object.keys(associations).sort().map(association => {
|
||
return { association, activations: associations[association] }
|
||
})
|
||
},
|
||
activationsPerAltitude () {
|
||
return this.makeBands(this.activations.map(activation => { return activation.summit.altitude }), 500, ' m', 'altitude', 'activations')
|
||
},
|
||
qsosPerActivation () {
|
||
return this.makeBands(this.activations.map(activation => { return activation.qsos }), 10, '', 'qsos', 'activations', true, 110)
|
||
},
|
||
modes () {
|
||
if (this.activations.length === 0) {
|
||
return null
|
||
}
|
||
|
||
let modes = {}
|
||
let totalQsos = 0
|
||
this.activations.forEach(activation => {
|
||
if (activation.modeQsos) {
|
||
Object.keys(activation.modeQsos).forEach(mode => {
|
||
if (!modes[mode]) {
|
||
modes[mode] = 0
|
||
}
|
||
modes[mode] += activation.modeQsos[mode]
|
||
})
|
||
}
|
||
totalQsos += activation.qsos
|
||
})
|
||
|
||
let modesArr = []
|
||
let modeQsosSum = 0
|
||
Object.keys(modes).forEach(mode => {
|
||
modesArr.push({ mode: mode.toUpperCase(), qsos: modes[mode] })
|
||
modeQsosSum += modes[mode]
|
||
})
|
||
if (modesArr.length > 0) {
|
||
if (modeQsosSum < totalQsos) {
|
||
modesArr.push({ mode: 'Other', qsos: (totalQsos - modeQsosSum) })
|
||
}
|
||
return modesArr
|
||
} else {
|
||
return null
|
||
}
|
||
},
|
||
bands () {
|
||
if (this.activations.length === 0) {
|
||
return null
|
||
}
|
||
|
||
let bands = {}
|
||
this.activations.forEach(activation => {
|
||
if (activation.bandQsos) {
|
||
Object.keys(activation.bandQsos).forEach(band => {
|
||
if (!bands[band]) {
|
||
bands[band] = 0
|
||
}
|
||
bands[band] += activation.bandQsos[band]
|
||
})
|
||
}
|
||
})
|
||
|
||
let bandsArr = []
|
||
Object.keys(bands).forEach(band => {
|
||
bandsArr.push({ band, qsos: bands[band] })
|
||
})
|
||
if (bandsArr.length > 0) {
|
||
bandsArr.sort((a, b) => {
|
||
return b.qsos - a.qsos
|
||
})
|
||
return bandsArr
|
||
} else {
|
||
return null
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
makeBands (data, interval, suffix, bandKey, valueKey, ranges = false, maxBand) {
|
||
if (!data) {
|
||
return null
|
||
}
|
||
|
||
let bands = {}
|
||
let maxValue = 0
|
||
data.forEach(value => {
|
||
let band = Math.ceil(value / interval) * interval
|
||
if (maxBand && band > maxBand) {
|
||
band = maxBand
|
||
}
|
||
if (!bands[band]) {
|
||
bands[band] = 0
|
||
}
|
||
bands[band]++
|
||
if (maxValue < band) {
|
||
maxValue = band
|
||
}
|
||
})
|
||
|
||
for (let value = interval; value < maxValue; value += interval) {
|
||
if (bands[value] === undefined) {
|
||
bands[value] = 0
|
||
}
|
||
}
|
||
|
||
return Object.keys(bands).sort((a, b) => { return parseInt(a) - parseInt(b) }).map(value => {
|
||
value = parseInt(value)
|
||
let label = '< ' + value + suffix
|
||
if (ranges) {
|
||
if (value === maxBand) {
|
||
label = '> ' + (value - interval) + suffix
|
||
} else {
|
||
label = (value - interval + 1) + '-' + value + suffix
|
||
}
|
||
}
|
||
return { [bandKey]: label, [valueKey]: bands[value] }
|
||
})
|
||
}
|
||
},
|
||
data () {
|
||
return {
|
||
moreStats: false
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.more-button {
|
||
text-align: center;
|
||
}
|
||
.no-margin-bottom {
|
||
margin-bottom: 0;
|
||
}
|
||
.stats-teaser {
|
||
text-align: center;
|
||
margin-top: 1em;
|
||
}
|
||
.stats-teaser div {
|
||
font-size: 1.5rem;
|
||
color: #ccc;
|
||
border: 2px dashed #ccc;
|
||
display: inline-block;
|
||
padding: 0.5em 0.75em;
|
||
border-radius: 0.75em;
|
||
}
|
||
.stats-teaser .fa-chart-bar {
|
||
font-size: 2rem;
|
||
vertical-align: middle;
|
||
margin-right: 0.3em;
|
||
}
|
||
</style>
|