Files
dougal-software/lib/www/client/source/src/components/graph-guns-pressure.vue
D. Berge 7d2fb5558a Hide switches to enable additional graphs.
All violin plots as well as position scatter plots and histograms
are shown by default. This is due to #338.

For some reason, having them enabled from the get go does not
cause any problems.
2025-08-15 18:09:51 +02:00

403 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<v-card style="min-height:400px;" outlined>
<v-card-title class="headline">
Gun pressures
<v-spacer></v-spacer>
<!--
<v-switch v-model="shotpoint" label="Shotpoint"></v-switch>
<v-switch class="ml-4" v-model="violinplot" label="Violin plot"></v-switch>
-->
</v-card-title>
<v-container fluid fill-height>
<v-row>
<v-col>
<div class="graph-container" ref="graphSeries"></div>
</v-col>
</v-row>
<v-row v-show="shotpoint">
<v-col>
<div class="graph-container" ref="graphBar"></div>
</v-col>
</v-row>
<v-row v-show="violinplot">
<v-col>
<div class="graph-container" ref="graphViolin"></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: 'DougalGraphGunsPressure',
props: [ "data", "settings" ],
data () {
return {
graph: null,
graphHover: null,
busy: false,
resizeObserver: null,
shotpoint: true,
violinplot: true
};
},
computed: {
//...mapGetters(['apiUrl'])
},
watch: {
data (newVal, oldVal) {
console.log("data changed");
if (newVal === null) {
this.busy = true;
} else {
this.busy = false;
this.plot();
}
},
settings () {
for (const key in this.settings) {
this[key] = this.settings[key];
}
},
shotpoint () {
if (this.shotpoint) {
this.replot();
}
this.$emit("update:settings", {[`${this.$options.name}.shotpoint`]: this.shotpoint});
},
violinplot () {
if (this.violinplot) {
this.plotViolin();
}
this.$emit("update:settings", {[`${this.$options.name}.violinplot`]: this.violinplot});
},
"$vuetify.theme.isDark" () {
this.plot();
}
},
methods: {
plot () {
this.plotSeries();
if (this.violinplot) {
this.plotViolin();
}
},
async plotSeries () {
function transformSeries (d, src_number, otherParams={}) {
const meta = src_number
? unpack(d, "meta").filter( s => s.src_number == src_number )
: unpack(d, "meta");
const guns = unpack(meta, "guns").map(s => s.filter(g => g[2] == src_number));;
const gunPressures = guns.map(s => s.map(g => g[11]));
const gunPressuresSorted = gunPressures.map(s => d3a.sort(s));
const gunVolumes = guns.map(s => s.map(g => g[12]));
const gunPressureWeights = gunVolumes.map( (s, sidx) => s.map( v => v/meta[sidx].volume ));
const gunsWeightedAvgPressure = gunPressures.map( (s, sidx) =>
d3a.sum(s.map( (pressure, gidx) => pressure * gunPressureWeights[sidx][gidx] )) / d3a.sum(gunPressureWeights[sidx])
);
const manifold = unpack(meta, "manifold");
const x = src_number
? unpack(d.filter(s => s.meta.src_number == src_number), "point")
: unpack(d, "point");
const traceManifold = {
name: "Manifold",
type: "scatter",
mode: "lines",
line: { ...aes.gunArrays[src_number || 1].avg.line, dash: "dot", width: 1 },
x,
y: manifold,
};
const tracesGunPressures = [{
type: "scatter",
mode: "lines",
x,
y: gunPressuresSorted.map(s => d3a.quantileSorted(s, 0.25)),
...aes.gunArrays[src_number || 1].min
},
{
type: "scatter",
mode: "lines",
fill: "tonexty",
x,
y: gunsWeightedAvgPressure,
...aes.gunArrays[src_number || 1].avg
},
{
type: "scatter",
mode: "lines",
fill: "tonexty",
x,
y: gunPressuresSorted.map(s => d3a.quantileSorted(s, 0.75)),
...aes.gunArrays[src_number || 1].max
}];
const tracesGunsPressuresIndividual = {
//name: `Array ${src_number} outliers`,
type: "scatter",
mode: "markers",
marker: {size: 2 },
hoverinfo: "skip",
x: gunPressuresSorted.map( (s, idx) =>
s.filter( g => g < d3a.quantileSorted(s, 0.05) || g > d3a.quantileSorted(s, 0.95))
.map( f => Array(f.length).fill(x[idx]) ).flat()
).flat(),
y: gunPressuresSorted.map( (s, idx) =>
s.filter( g => g < d3a.quantileSorted(s, 0.05) || g > d3a.quantileSorted(s, 0.95))
).flat(),
...aes.gunArrays[src_number || 1].out
};
const data = [ traceManifold, ...tracesGunPressures, tracesGunsPressuresIndividual ]
return data;
}
if (!this.data) {
console.log("missing data");
return;
}
const sources = [ ...new Set(unpack(this.data.items, "meta").map( s => s.src_number ))];
const data = sources.map( src_number => transformSeries(this.data.items, src_number) ).flat();
console.log("Sources", sources);
console.log(data);
this.busy = false;
const layout = {
//autosize: true,
title: {text: "Gun pressures sequence %{meta.sequence}"},
autocolorscale: true,
// colorscale: "sequential",
hovermode: "x",
yaxis: {
title: "Pressure (psi)",
//zeroline: false
},
xaxis: {
title: "Shotpoint",
showspikes: true
},
font: {
color: this.$vuetify.theme.isDark ? "#fff" : undefined
},
plot_bgcolor:"rgba(0,0,0,0)",
paper_bgcolor:"rgba(0,0,0,0)",
meta: this.data.meta
};
const config = {
editable: false,
displaylogo: false
};
this.graph = Plotly.newPlot(this.$refs.graphSeries, data, layout, config);
this.$refs.graphSeries.on('plotly_hover', (d) => {
const point = d.points[0].x;
const item = this.data.items.find(s => s.point == point);
const guns = item.meta.guns.filter( g => g[2] == item.meta.src_number );
const gunIds = guns.map( g => "G"+g[1] );
const pressures = unpack(guns, 11);
const volumes = unpack(guns, 12);
const maxVolume = d3a.max(volumes);
const data = [{
type: "bar",
x: gunIds,
y: pressures,
width: volumes.map( v => v/maxVolume ),
transforms: [{
type: "groupby",
groups: unpack(guns, 0)
}],
}];
const layout = {
title: {text: "Gun pressures shot %{meta.point}"},
height: 300,
yaxis: {
title: "Pressure (psi)",
range: [ Math.min(d3a.min(pressures), 1950), Math.max(d3a.max(pressures), 2050) ]
},
xaxis: {
title: "Gun number",
type: 'category'
},
font: {
color: this.$vuetify.theme.isDark ? "#fff" : undefined
},
plot_bgcolor:"rgba(0,0,0,0)",
paper_bgcolor:"rgba(0,0,0,0)",
meta: {
point
}
};
const config = { displaylogo: false };
Plotly.react(this.$refs.graphBar, data, layout, config);
});
},
async plotViolin () {
function transformViolin (d, opts = {}) {
const styles = [];
unpack(unpack(d, "meta"), "guns").flat().forEach(i => {
const gunId = i[1];
const arrayId = i[2];
if (!styles[gunId]) {
styles[gunId] = Object.assign({target: gunId}, aes.gunArrayViolins[arrayId]);
}
});
const data = {
type: 'violin',
x: unpack(unpack(unpack(d, "meta"), "guns").flat(), 1), // Gun number
y: unpack(unpack(unpack(d, "meta"), "guns").flat(), 11), // Gun pressure
points: 'none',
box: {
visible: true
},
line: {
color: 'green',
},
meanline: {
visible: true
},
transforms: [{
type: 'groupby',
groups: unpack(unpack(unpack(d, "meta"), "guns").flat(), 1),
styles: styles.filter(i => !!i)
}]
}
return data;
}
console.log("plot violin");
if (!this.data) {
console.log("missing data");
return;
}
console.log("Will plot sequence", this.data.meta.project, this.data.meta.sequence);
const data = [ transformViolin(this.data.items) ];
this.busy = false;
const layout = {
//autosize: true,
showlegend: false,
title: {text: "Individual gun pressures sequence %{meta.sequence}"},
autocolorscale: true,
// colorscale: "sequential",
yaxis: {
title: "Pressure (psi)",
zeroline: false
},
xaxis: {
title: "Gun number"
},
font: {
color: this.$vuetify.theme.isDark ? "#fff" : undefined
},
plot_bgcolor:"rgba(0,0,0,0)",
paper_bgcolor:"rgba(0,0,0,0)",
meta: this.data.meta
};
const config = {
editable: false,
displaylogo: false
};
this.graph = Plotly.newPlot(this.$refs.graphViolin, 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.graphSeries);
this.resizeObserver.observe(this.$refs.graphViolin);
this.resizeObserver.observe(this.$refs.graphBar);
},
beforeDestroy () {
if (this.resizeObserver) {
this.resizeObserver.unobserve(this.$refs.graphBar);
this.resizeObserver.unobserve(this.$refs.graphViolin);
this.resizeObserver.unobserve(this.$refs.graphSeries);
}
}
};
</script>