mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 11:37:08 +00:00
Add graphing component for shotpoint timing visualisations.
It operates in one of these modes: * facet="bars" (default): shows a barplot. * facet="lines": shows a lineplot. * facet="area": shows a lineplot where the area between the line(s) and y=0 is filled with a colour.
This commit is contained in:
@@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="graph"
|
||||||
|
class="graph-container"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.graph-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Plotly from 'plotly.js-dist';
|
||||||
|
import unpack from '@/lib/unpack.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DougalGraphProjectSequenceShotpointTiming",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
items: Array,
|
||||||
|
gunDataFormat: { type: String, default: "smsrc" },
|
||||||
|
facet: { type: String, default: "bars" }
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
plotted: false,
|
||||||
|
resizeObserver: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
config () {
|
||||||
|
return {
|
||||||
|
editable: false,
|
||||||
|
displayLogo: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
layout () {
|
||||||
|
return {
|
||||||
|
font: {
|
||||||
|
color: this.$vuetify.theme.isDark ? "#fff" : undefined
|
||||||
|
},
|
||||||
|
title: {text: "Shotpoint timing %{data[0].meta.subtitle}"},
|
||||||
|
xaxis: {
|
||||||
|
title: "Shotpoint"
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: "Time (s)"
|
||||||
|
},
|
||||||
|
plot_bgcolor:"rgba(0,0,0,0)",
|
||||||
|
paper_bgcolor:"rgba(0,0,0,0)"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
|
||||||
|
const items = this.items.map(i => {
|
||||||
|
return {
|
||||||
|
point: i.point,
|
||||||
|
tstamp: new Date(i.tstamp)
|
||||||
|
}
|
||||||
|
}).sort( (a, b) => a.tstamp - b.tstamp );
|
||||||
|
const x = [...unpack(items, "point")];
|
||||||
|
const y = items.map( (i, idx, ary) => (ary[idx+1]?.tstamp - i.tstamp)/1000 );
|
||||||
|
const src_number = unpack(this.items, ["meta", "raw", this.gunDataFormat, "src_number"]);
|
||||||
|
|
||||||
|
// We're dealing with intervals not points
|
||||||
|
x.pop(); y.pop(); src_number.pop();
|
||||||
|
|
||||||
|
const meta = {};
|
||||||
|
|
||||||
|
const stats = this.stats(x, y, src_number);
|
||||||
|
|
||||||
|
// We need to do the subtitle here rather than in layout as layout knows nothing
|
||||||
|
// about the number of arrays
|
||||||
|
|
||||||
|
if (stats.src_ids.length == 1) {
|
||||||
|
meta.subtitle = `<span style="font-size:smaller;">(μ = ${stats.avg.all.toFixed(2)} ±${stats.std.all.toFixed(2)} s)</span>`;
|
||||||
|
} else {
|
||||||
|
meta.subtitle = `<span style="font-size:smaller;">(μ = ${stats.avg.all.toFixed(2)} ±${stats.std.all.toFixed(2)} s)</span>`;
|
||||||
|
const per_source = [];
|
||||||
|
for (const key in stats.avg) {
|
||||||
|
if (key == "all") continue;
|
||||||
|
const s = `μ<sub>${key}</sub> = ${stats.avg[key].toFixed(2)} ±${stats.std[key].toFixed(2)} s`;
|
||||||
|
per_source.push(s);
|
||||||
|
}
|
||||||
|
meta.subtitle += `<br><span style="font-size:smaller;">` + per_source.join("; ") + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const trace0 = {
|
||||||
|
type: "bar",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
transforms: [{
|
||||||
|
type: "groupby",
|
||||||
|
groups: src_number,
|
||||||
|
styles: [
|
||||||
|
{target: 1, value: {line: {color: "green"}}},
|
||||||
|
{target: 2, value: {line: {color: "red"}}},
|
||||||
|
{target: 3, value: {line: {color: "blue"}}}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
meta
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (this.facet) {
|
||||||
|
case "lines":
|
||||||
|
trace0.type = "scatter";
|
||||||
|
break;
|
||||||
|
case "area":
|
||||||
|
trace0.type = "scatter";
|
||||||
|
trace0.fill = "tozeroy";
|
||||||
|
break;
|
||||||
|
case "bars":
|
||||||
|
default:
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
return [trace0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
items (cur, prev) {
|
||||||
|
if (cur != prev) {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"$vuetify.theme.isDark" () {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
plot () {
|
||||||
|
Plotly.newPlot(this.$refs.graph, this.data, this.layout, this.config);
|
||||||
|
this.plotted = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
replot () {
|
||||||
|
if (this.plotted) {
|
||||||
|
const ref = this.$refs.graph;
|
||||||
|
Plotly.relayout(ref, {
|
||||||
|
width: ref.clientWidth,
|
||||||
|
height: ref.clientHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stats (x, y, src_number) {
|
||||||
|
const avg = {};
|
||||||
|
const std = {};
|
||||||
|
|
||||||
|
const avg_all = (y.reduce((acc, cur) => acc + cur, 0) / y.length);
|
||||||
|
const std_all = Math.sqrt(y.reduce((acc, cur) => (cur-avg_all)**2 + acc, 0) / y.length);
|
||||||
|
|
||||||
|
avg.all = avg_all;
|
||||||
|
std.all = std_all;
|
||||||
|
|
||||||
|
const src_ids = new Set(src_number);
|
||||||
|
|
||||||
|
for (const src of src_ids) {
|
||||||
|
const v = y.filter((i, idx) => src_number[idx] == src);
|
||||||
|
const μ = (v.reduce((acc, cur) => acc + cur, 0) / v.length);
|
||||||
|
const σ = Math.sqrt(v.reduce((acc, cur) => (cur-μ)**2 + acc, 0) / v.length);
|
||||||
|
avg[src] = μ;
|
||||||
|
std[src] = σ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { avg, std, src_ids };
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.resizeObserver = new ResizeObserver(this.replot)
|
||||||
|
this.resizeObserver.observe(this.$refs.graph);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy () {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.unobserve(this.$refs.graph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user