Skip to content

Commit

Permalink
- ADD: Added multi-trait timeline chart.
Browse files Browse the repository at this point in the history
- ADD: Added markers to example dataset.
  • Loading branch information
sebastian-raubach committed May 24, 2022
1 parent c56e58c commit f7c63b4
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/components/DataPointEntry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ export default {
},
setDateToday: function (index) {
Vue.set(this.values, index, this.getTodayString())
this.traverseForm(index + 1)
},
getToday: function () {
const today = new Date()
Expand Down
126 changes: 126 additions & 0 deletions src/components/MultiTraitTimeline.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<template>
<div v-if="trait">
<h2><span :style="{ color: storeTraitColors[traitIndex % storeTraitColors.length] }"><BIconCircleFill /> {{ trait.name }} <b-badge variant="light" class="ml-1">{{ getTraitTypeText(trait) }}</b-badge></span></h2>
<div ref="tlTrait" />
</div>
</template>

<script>
import { mapGetters } from 'vuex'
import { BIconCircleFill } from 'bootstrap-vue'
const Plotly = require('plotly.js/lib/core')
// Only register the chart types we're actually using to reduce the final bundle size
Plotly.register([
require('plotly.js/lib/scatter')
])
export default {
components: {
BIconCircleFill
},
props: {
trait: {
type: Object,
default: () => null
}
},
computed: {
...mapGetters([
'storeDatasetName',
'storeDarkMode',
'storeTraits',
'storeTraitColors'
]),
traitIndex: function () {
return this.storeTraits.findIndex(t => t.name === this.trait.name)
},
safeDatasetName: function () {
if (this.storeDatasetName) {
return this.storeDatasetName.replace(/[^a-z0-9]/gi, '-').toLowerCase()
} else {
return ''
}
}
},
watch: {
trait: function () {
this.plot()
},
storeDarkMode: function () {
this.plot()
}
},
methods: {
plot: function () {
const storeData = this.$store.state.dataset ? this.$store.state.dataset.data : null
if (!storeData || storeData.size < 1 || !this.trait) {
return
}
Plotly.purge(this.$refs.tlTrait)
const traitIndex = this.storeTraits.findIndex(t => t.name === this.trait.name)
// For each field cell
const traces = Array.from(storeData.values()).filter(c => c.values[traitIndex] !== undefined && c.values[traitIndex] !== null)
.map(c => {
return {
type: 'scatter',
mode: 'lines+markers',
name: c.name,
x: c.dates[traitIndex],
y: c.values[traitIndex]
}
})
const layout = {
hovermode: 'x',
height: 500,
autosize: true,
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent',
xaxis: {
automargin: true,
showgrid: false,
zeroline: true,
showline: true,
title: { text: this.$t('chartLabelTimeseriesTime'), font: { color: this.storeDarkMode ? 'white' : 'black' } },
tickfont: { color: this.storeDarkMode ? 'white' : 'black' }
},
yaxis: {
automargin: true,
showgrid: false,
zeroline: true,
showline: true,
rangemode: 'tozero',
title: { text: this.trait.name, font: { color: this.storeDarkMode ? 'white' : 'black' } },
tickfont: { color: this.storeDarkMode ? 'white' : 'black' }
},
legend: { orientation: 'h', x: 1, y: 1.2, xanchor: 'right', font: { color: this.storeDarkMode ? 'white' : 'black' } }
}
const config = {
responsive: true,
toImageButtonOptions: {
format: 'png',
filename: `timeline-${this.safeDatasetName}-${this.trait.name}-${new Date().toISOString().split('T')[0]}`,
width: 1280,
height: 720
},
displaylogo: false
}
Plotly.newPlot(this.$refs.tlTrait, traces, layout, config)
}
},
mounted: function () {
this.plot()
}
}
</script>

<style>
</style>
2 changes: 1 addition & 1 deletion src/example-data.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/plugins/i18n/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@
"pageShareText": "<p>Daten und Versuchseinstellungen können einfach zwischen GridScore Geräten per QR Codes geteilt werden. Eine Internetverbindung ist erforderlich zum Teilen und Berechtigungen zum Benutzen der Kamera müssen ggf. erteilt werden.</p><p>Zum Teilen von Daten oder Einstellungen von Gerät A, nutze den Export Abschnitt um einen QR Code zu erstellen. Auf Gerät B, nutze den Import Abschnitt um den QR Code mithilfe des Knopfes und dann der Kamera zu scannen.</p><p>Daten die per GridScore-Server geteilt können nur mit der spezifischen Bezeichnung die um QR-Code codiert ist abgefragt werden. Daten werden automatisch nach 30 Tagen inaktivität vom Server gelöscht. Daten auf den lokalen Geräten werden nicht automatisch gelöscht und können jederzeit, auch nach Ablauf der 30 Tage Inaktivität, erneut geteilt werden</p>",
"pageStatsTitle": "Merkmalsstatistiken",
"pageStatsText": "Diese Seite zeigt eine Übersicht über die Statistiken pro Merkmal. Numerische Merkmale und Daten werden mit Boxplots dargestellt, während Freitext und kategorische Merkmale mit Balkendiagrammen veranschaulicht werden.",
"pageTimelineMultiTitle": "Multi-Merkmals-Diagramme",
"pageTimelineMultiText": "Die Diagramme unten zeigen die Entwicklung von Werten der Multi-Merkmale. Jede Linie repräsentiert eine Pflanze/Beet.",
"pageTimelineText": "Das folgende Diagramm zeit den Prozentsatz an bewerteten Beeten im Laufe der Zeit. Dies hebt die Entwicklung der einzelnen Beete hervor während die Zeit vergeht und zeigt die Beziehungen zwischen Merkmalen. Die x-Achse zeigt Zeit und die y-Achse zeigt den Prozentsatz an bewerteten Beeten. Jedes Merkmal wird in der entsprechenden Farbe dargestellt.",
"pageTimelineTitle": "Zeitreihe",
"pageUuidImportTitle": "Versuchsimport",
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/i18n/en_GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@
"pageShareText": "<p>Data or trial setup configurations can easily be shared between GridScore devices using QR codes. An internet connection is required to share and you may have to grant permissions to use the device's camera.</p><p>To share data or a configuration from device A, use the export section below to generate a QR code. Then on device B, use the import section and scan the QR code using the button and then the camera.</p><p>Data shared using the GridScore server is only accessible using the unique identifier encoded in the QR code. Data will automatically be deleted from the server after 30 days of inactivity. Data on your local device will not be deleted automatically and can be shared again at any time, even after those 30 days of inactivity.</p>",
"pageStatsTitle": "Trait statistics",
"pageStatsText": "This page shows overview statistics for each trait. Numeric and date traits are visualized in a box plot while text and categorical traits are shown in bar charts.",
"pageTimelineMultiTitle": "Multi-trait charts",
"pageTimelineMultiText": "The charts below show the development of values per multi-trait. Each line represents a plant/plot.",
"pageTimelineText": "The following chart shows the percentage of scored plots over time. This highlights the development of plots as time goes on as well as the relationship between traits. The x-axis shows time, the y-axis shows the percentage of scored plots. Each trait is shown in their respective color.",
"pageTimelineTitle": "Timeline",
"pageUuidImportTitle": "Trial import",
Expand Down
12 changes: 12 additions & 0 deletions src/views/Timeline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@
<p>{{ $t('pageTimelineText') }}</p>
<div id="timeseries-chart" class="time-chart" v-if="storeDatasetId && storeTraits && storeTraits.length > 0"/>
<h3 v-else>{{ $t('labelNoData') }}</h3>

<div v-if="multiTraits && multiTraits.length > 0" class="mt-3">
<h1>{{ $t('pageTimelineMultiTitle') }}</h1>
<MultiTraitTimeline :trait="mt" v-for="mt in multiTraits" :key="`mt-${mt.name}`" />
</div>
</div>
</template>

<script>
import { mapGetters } from 'vuex'
import MultiTraitTimeline from '@/components/MultiTraitTimeline'
const Plotly = require('plotly.js/lib/core')
Expand All @@ -21,6 +27,9 @@ Plotly.register([
* Shows the timeline for data recording of each trait as a percentage of all plots scored.
*/
export default {
components: {
MultiTraitTimeline
},
watch: {
storeLocale: function () {
this.plot()
Expand All @@ -47,6 +56,9 @@ export default {
} else {
return ''
}
},
multiTraits: function () {
return this.storeTraits.filter(t => t.mType === 'multi')
}
},
methods: {
Expand Down

0 comments on commit f7c63b4

Please sign in to comment.