mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 08:57:08 +00:00
Commands used: find . -type f -name '*.js'| while read FILE; do if echo $FILE |grep -qv node_modules; then sed -ri 's/^\s+$//' "$FILE"; fi; done find . -type f -name '*.vue'| while read FILE; do if echo $FILE |grep -qv node_modules; then sed -ri 's/^\s+$//' "$FILE"; fi; done find . -type f -name '*.py'| while read FILE; do if echo $FILE |grep -qv node_modules; then sed -ri 's/^\s+$//' "$FILE"; fi; done
406 lines
9.1 KiB
Vue
406 lines
9.1 KiB
Vue
<template>
|
||
<v-card style="min-height:400px;">
|
||
<v-card-title class="headline">
|
||
Gun details
|
||
</v-card-title>
|
||
|
||
<v-container fluid fill-height>
|
||
<v-row>
|
||
<v-col>
|
||
<div class="graph-container" ref="graphHeat"></div>
|
||
</v-col>
|
||
</v-row>
|
||
</v-container>
|
||
|
||
<v-overlay :value="busy" absolute z-index="1">
|
||
<v-progress-circular indeterminate></v-progress-circular>
|
||
</v-overlay>
|
||
</v-card>
|
||
|
||
</template>
|
||
|
||
<style scoped>
|
||
.graph-container {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
|
||
import * as d3a from 'd3-array';
|
||
import Plotly from 'plotly.js-dist';
|
||
import { mapActions, mapGetters } from 'vuex';
|
||
import unpack from '@/lib/unpack.js';
|
||
import * as aes from '@/lib/graphs/aesthetics.js';
|
||
|
||
|
||
export default {
|
||
name: 'DougalGraphGunsDepth',
|
||
|
||
props: [ "data" ],
|
||
|
||
data () {
|
||
return {
|
||
graph: null,
|
||
graphHover: null,
|
||
busy: false,
|
||
resizeObserver: null,
|
||
// TODO: aspects should be a prop
|
||
aspects: [
|
||
"Mode", "Detect", "Autofire", "Aimpoint", "Firetime", "Delay",
|
||
"Delta",
|
||
"Depth", "Pressure", "Volume", "Filltime"
|
||
]
|
||
};
|
||
},
|
||
|
||
computed: {
|
||
//...mapGetters(['apiUrl'])
|
||
},
|
||
|
||
watch: {
|
||
|
||
data (newVal, oldVal) {
|
||
console.log("data changed");
|
||
|
||
if (newVal === null) {
|
||
this.busy = true;
|
||
} else {
|
||
this.busy = false;
|
||
this.plot();
|
||
}
|
||
},
|
||
|
||
violinplot () {
|
||
if (this.violinplot) {
|
||
this.plotViolin();
|
||
}
|
||
}
|
||
|
||
},
|
||
|
||
methods: {
|
||
|
||
plot () {
|
||
this.plotHeat();
|
||
},
|
||
|
||
async plotHeat () {
|
||
|
||
|
||
if (!this.data) {
|
||
console.log("missing data");
|
||
return;
|
||
}
|
||
|
||
function transform (data, aspects=["Depth", "Pressure"]) {
|
||
|
||
const facets = [
|
||
// Mode
|
||
{
|
||
params: {
|
||
name: "Mode",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{text}",
|
||
},
|
||
|
||
text: [ "Off", "Auto", "Manual", "Disabled" ],
|
||
|
||
conversion: (gun, shot) => {
|
||
switch (gun[3]) {
|
||
case "A":
|
||
return 1;
|
||
case "M":
|
||
return 2;
|
||
case "O":
|
||
return 0;
|
||
case "D":
|
||
return 3;
|
||
}
|
||
}
|
||
},
|
||
|
||
// Detect
|
||
{
|
||
params: {
|
||
name: "Detect",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{text}",
|
||
},
|
||
|
||
text: [ "Zero", "Peak", "Level" ],
|
||
|
||
conversion: (gun, shot) => {
|
||
switch (gun[4]) {
|
||
case "P":
|
||
return 1;
|
||
case "Z":
|
||
return 0;
|
||
case "L":
|
||
return 2;
|
||
}
|
||
}
|
||
},
|
||
|
||
// Autofire
|
||
{
|
||
params: {
|
||
name: "Autofire",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{text}",
|
||
},
|
||
|
||
text: [ "False", "True" ],
|
||
|
||
conversion: (gun, shot) => {
|
||
return gun[5] ? 1 : 0;
|
||
}
|
||
},
|
||
|
||
// Aimpoint
|
||
{
|
||
params: {
|
||
name: "Aimpoint",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||
},
|
||
|
||
conversion: (gun, shot) => gun[7]
|
||
},
|
||
|
||
// Firetime
|
||
{
|
||
params: {
|
||
name: "Firetime",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||
},
|
||
|
||
conversion: (gun, shot) => gun[2] == shot.meta.src_number ? gun[8] : null
|
||
},
|
||
|
||
// Delta
|
||
{
|
||
params: {
|
||
name: "Delta",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms",
|
||
// NOTE: These values are based on
|
||
// Grane + Snorre's ±1.5 ms spec. While a fairly
|
||
// common range, I still consider these min / max
|
||
// numbers to have been chosen semi-arbitrarily.
|
||
zmin: -2,
|
||
zmax: 2
|
||
},
|
||
|
||
conversion: (gun, shot) => gun[2] == shot.meta.src_number ? gun[7]-gun[8] : null
|
||
},
|
||
|
||
// Delay
|
||
{
|
||
params: {
|
||
name: "Delay",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||
},
|
||
|
||
conversion: (gun, shot) => gun[9]
|
||
},
|
||
|
||
// Depth
|
||
{
|
||
params: {
|
||
name: "Depth",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} m"
|
||
},
|
||
|
||
conversion: (gun, shot) => gun[10]
|
||
},
|
||
|
||
// Pressure
|
||
{
|
||
params: {
|
||
name: "Pressure",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} psi"
|
||
},
|
||
|
||
conversion: (gun, shot) => gun[11]
|
||
},
|
||
|
||
// Volume
|
||
{
|
||
params: {
|
||
name: "Volume",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} in³"
|
||
},
|
||
|
||
conversion: (gun, shot) => gun[12]
|
||
},
|
||
|
||
// Filltime
|
||
{
|
||
params: {
|
||
name: "Filltime",
|
||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||
},
|
||
|
||
// NOTE that filltime is applicable to the *non* firing guns
|
||
conversion: (gun, shot) => gun[2] == shot.meta.src_number ? null : gun[13]
|
||
}
|
||
|
||
|
||
];
|
||
|
||
// Get gun numbers
|
||
const guns = [...new Set(data.map( s => s.meta.guns.map( g => g[1] ) ).flat())];
|
||
|
||
// z eventually will have the structure:
|
||
// z = {
|
||
// [aspect]: [ // First shotpoint
|
||
// [ // Value for gun 0, gun 1, … ],
|
||
// …more shotpoints…
|
||
// ]
|
||
// }
|
||
const z = {};
|
||
|
||
// x is an array of shotpoints
|
||
const x = [];
|
||
|
||
// y is an array of gun numbers
|
||
const y = guns.map( gun => `G${gun}` );
|
||
|
||
// Build array of guns (i.e., populate z)
|
||
// We prefer to do this outside the shot-to-shot loop
|
||
// for efficiency
|
||
for (const facet of facets) {
|
||
const label = facet.params.name;
|
||
z[label] = Array(guns.length);
|
||
for (let i=0; i<guns.length; i++) {
|
||
z[label][i] = [];
|
||
}
|
||
}
|
||
|
||
// Populate array of guns with shotpoint data
|
||
for (let shot of data) {
|
||
x.push(shot.point);
|
||
|
||
for (const facet of facets) {
|
||
const label = facet.params.name;
|
||
const facetGunsArray = z[label];
|
||
|
||
for (const gun of shot.meta.guns) {
|
||
const gunIndex = gun[1]-1;
|
||
const facetGun = facetGunsArray[gunIndex];
|
||
facetGun.push(facet.conversion(gun, shot));
|
||
}
|
||
}
|
||
}
|
||
|
||
return aspects.map( (aspect, idx) => {
|
||
const facet = facets.find(el => el.params.name == aspect) || {};
|
||
|
||
const defaultParams = {
|
||
name: aspect,
|
||
type: "heatmap",
|
||
showscale: false,
|
||
x,
|
||
y,
|
||
z: z[aspect],
|
||
text: facet.text ? z[aspect].map(row => row.map(v => facet.text[v])) : undefined,
|
||
xaxis: "x",
|
||
yaxis: "y" + (idx > 0 ? idx+1 : "")
|
||
}
|
||
|
||
|
||
return Object.assign({}, defaultParams, facet.params);
|
||
});
|
||
}
|
||
|
||
const data = transform(this.data.items, this.aspects);
|
||
this.busy = false;
|
||
|
||
const layout = {
|
||
title: {text: "Gun details – sequence %{meta.sequence}"},
|
||
height: 200*this.aspects.length,
|
||
//autocolorscale: true,
|
||
/*
|
||
grid: {
|
||
rows: this.aspects.length,
|
||
columns: 1,
|
||
pattern: "coupled",
|
||
roworder: "bottom to top"
|
||
},
|
||
*/
|
||
//autosize: true,
|
||
// colorscale: "sequential",
|
||
|
||
xaxis: {
|
||
title: "Shotpoint",
|
||
showspikes: true
|
||
},
|
||
|
||
meta: this.data.meta
|
||
};
|
||
|
||
this.aspects.forEach ( (aspect, idx) => {
|
||
const num = idx+1;
|
||
const key = "yaxis" + num;
|
||
const anchor = "y" + num;
|
||
const segment = (1/this.aspects.length);
|
||
const margin = segment/20;
|
||
const domain = [
|
||
segment*idx + margin,
|
||
segment*num - margin
|
||
];
|
||
layout[key] = {
|
||
title: aspect,
|
||
anchor,
|
||
domain
|
||
}
|
||
});
|
||
|
||
const config = {
|
||
//editable: true,
|
||
displaylogo: false
|
||
};
|
||
|
||
this.graph = Plotly.newPlot(this.$refs.graphHeat, data, layout, config);
|
||
|
||
},
|
||
|
||
replot () {
|
||
if (!this.graph) {
|
||
return;
|
||
}
|
||
|
||
console.log("Replotting");
|
||
Object.values(this.$refs).forEach( ref => {
|
||
if (ref.data) {
|
||
console.log("Replotting", ref, ref.clientWidth, ref.clientHeight);
|
||
Plotly.relayout(ref, {
|
||
width: ref.clientWidth,
|
||
height: ref.clientHeight
|
||
});
|
||
}
|
||
});
|
||
},
|
||
|
||
...mapActions(["api"])
|
||
|
||
},
|
||
|
||
mounted () {
|
||
|
||
if (this.data) {
|
||
this.plot();
|
||
} else {
|
||
this.busy = true;
|
||
}
|
||
|
||
this.resizeObserver = new ResizeObserver(this.replot)
|
||
this.resizeObserver.observe(this.$refs.graphHeat);
|
||
},
|
||
|
||
beforeDestroy () {
|
||
if (this.resizeObserver) {
|
||
this.resizeObserver.unobserve(this.$refs.graphHeat);
|
||
}
|
||
}
|
||
|
||
};
|
||
</script>
|