11import type { Config } from "@classmodel/class/config" ;
2- import { type ClassOutput , outputVariables } from "@classmodel/class/output" ;
2+ import { type Parcel , calculatePlume } from "@classmodel/class/fire" ;
3+ import {
4+ type ClassOutput ,
5+ type OutputVariableKey ,
6+ getOutputAtTime ,
7+ outputVariables ,
8+ } from "@classmodel/class/output" ;
9+ import {
10+ type ClassProfile ,
11+ NoProfile ,
12+ generateProfiles ,
13+ } from "@classmodel/class/profiles" ;
314import * as d3 from "d3" ;
415import { saveAs } from "file-saver" ;
516import { toBlob } from "html-to-image" ;
@@ -18,8 +29,6 @@ import {
1829import { createStore } from "solid-js/store" ;
1930import type { Observation } from "~/lib/experiment_config" ;
2031import {
21- getThermodynamicProfiles ,
22- getVerticalProfiles ,
2332 observationsForProfile ,
2433 observationsForSounding ,
2534} from "~/lib/profiles" ;
@@ -34,10 +43,10 @@ import {
3443} from "~/lib/store" ;
3544import { MdiCamera , MdiDelete , MdiImageFilterCenterFocus } from "./icons" ;
3645import { AxisBottom , AxisLeft , getNiceAxisLimits } from "./plots/Axes" ;
37- import { Chart , ChartContainer } from "./plots/ChartContainer" ;
46+ import { Chart , ChartContainer , type ChartData } from "./plots/ChartContainer" ;
3847import { Legend } from "./plots/Legend" ;
39- import { Line } from "./plots/Line" ;
40- import { SkewTPlot } from "./plots/skewTlogP" ;
48+ import { Line , Plume , type Point } from "./plots/Line" ;
49+ import { SkewTPlot , type SoundingRecord } from "./plots/skewTlogP" ;
4150import { Button } from "./ui/button" ;
4251import { Card , CardContent , CardHeader , CardTitle } from "./ui/card" ;
4352import {
@@ -115,23 +124,20 @@ const uniqueTimes = () => [...new Set(_allTimes())].sort((a, b) => a - b);
115124
116125// TODO: could memoize all reactive elements here, would it make a difference?
117126export function TimeSeriesPlot ( { analysis } : { analysis : TimeseriesAnalysis } ) {
118- const symbols = Object . fromEntries (
119- outputVariables . map ( ( v ) => [ v . key , v . symbol ] ) ,
120- ) ;
121- const getKey = Object . fromEntries (
122- outputVariables . map ( ( v ) => [ v . symbol , v . key ] ) ,
123- ) ;
127+ const vars = Object . entries ( outputVariables ) ;
128+ const symbols = Object . fromEntries ( vars . map ( ( [ k , v ] ) => [ k , v . symbol ] ) ) ;
129+ const getKey = Object . fromEntries ( vars . map ( ( [ k , v ] ) => [ v . symbol , k ] ) ) ;
124130 const labels = Object . fromEntries (
125- outputVariables . map ( ( v ) => [ v . key , `${ v . symbol } [${ v . unit } ]` ] ) ,
131+ vars . map ( ( [ k , v ] ) => [ k , `${ v . symbol } [${ v . unit } ]` ] ) ,
126132 ) ;
127133
128134 const allX = ( ) =>
129135 flatExperiments ( ) . flatMap ( ( e ) =>
130- e . output ? e . output [ analysis . xVariable ] : [ ] ,
136+ e . output ? e . output [ analysis . xVariable as OutputVariableKey ] : [ ] ,
131137 ) ;
132138 const allY = ( ) =>
133139 flatExperiments ( ) . flatMap ( ( e ) =>
134- e . output ? e . output [ analysis . yVariable ] : [ ] ,
140+ e . output ? e . output [ analysis . yVariable as OutputVariableKey ] : [ ] ,
135141 ) ;
136142
137143 const granularity = ( ) => ( analysis . xVariable === "t" ? 600 : undefined ) ;
@@ -146,8 +152,12 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
146152 data :
147153 // Zip x[] and y[] into [x, y][]
148154 output ?. t . map ( ( _ , t ) => ( {
149- x : output ? output [ analysis . xVariable ] [ t ] : Number . NaN ,
150- y : output ? output [ analysis . yVariable ] [ t ] : Number . NaN ,
155+ x : output
156+ ? output [ analysis . xVariable as OutputVariableKey ] [ t ]
157+ : Number . NaN ,
158+ y : output
159+ ? output [ analysis . yVariable as OutputVariableKey ] [ t ]
160+ : Number . NaN ,
151161 } ) ) || [ ] ,
152162 } ;
153163 } ) ;
@@ -225,10 +235,16 @@ export function VerticalProfilePlot({
225235} : { analysis : ProfilesAnalysis } ) {
226236 const variableOptions = {
227237 "Potential temperature [K]" : "theta" ,
228- "Specific humidity [kg/kg]" : "q" ,
238+ "Virtual potential temperature [K]" : "thetav" ,
239+ "Specific humidity [kg/kg]" : "qt" ,
229240 "u-wind component [m/s]" : "u" ,
230241 "v-wind component [m/s]" : "v" ,
231- } ;
242+ "Pressure [Pa]" : "p" ,
243+ "Exner function [-]" : "exner" ,
244+ "Temperature [K]" : "T" ,
245+ "Dew point temperature [K]" : "Td" ,
246+ "Density [kg/m³]" : "rho" ,
247+ } as const satisfies Record < string , keyof ClassProfile > ;
232248
233249 const classVariable = ( ) =>
234250 variableOptions [ analysis . variable as keyof typeof variableOptions ] ;
@@ -240,26 +256,43 @@ export function VerticalProfilePlot({
240256 flatExperiments ( ) . map ( ( e ) => {
241257 const { config, output, ...formatting } = e ;
242258 const t = output ?. t . indexOf ( uniqueTimes ( ) [ analysis . time ] ) ;
243- return {
244- ...formatting ,
245- data :
246- t !== - 1 // -1 now means "not found in array" rather than last index
247- ? getVerticalProfiles (
248- e . output ,
249- e . config ,
250- classVariable ( ) ,
251- analysis . time ,
252- )
253- : [ ] ,
254- } ;
259+ if ( config . sw_ml && output && t !== undefined && t !== - 1 ) {
260+ const outputAtTime = getOutputAtTime ( output , t ) ;
261+ return { ...formatting , data : generateProfiles ( config , outputAtTime ) } ;
262+ }
263+ return { ...formatting , data : NoProfile } ;
264+ } ) ;
265+
266+ const firePlumes = ( ) =>
267+ flatExperiments ( ) . map ( ( e , i ) => {
268+ const { config, output, ...formatting } = e ;
269+ if ( config . sw_fire ) {
270+ return {
271+ ...formatting ,
272+ data : calculatePlume ( config , profileData ( ) [ i ] . data ) ,
273+ } ;
274+ }
275+ return { ...formatting , data : [ ] } ;
255276 } ) ;
256277
278+ // TODO: There should be a way that this isn't needed.
279+ const profileDataForPlot = ( ) =>
280+ profileData ( ) . map ( ( { data, label, color, linestyle } ) => ( {
281+ label,
282+ color,
283+ linestyle,
284+ data : data . z . map ( ( z , i ) => ( {
285+ x : data [ classVariable ( ) ] [ i ] ,
286+ y : z ,
287+ } ) ) ,
288+ } ) ) as ChartData < Point > [ ] ;
289+
257290 const allX = ( ) => [
258- ...profileData ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . x ) ) ,
291+ ...profileDataForPlot ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . x ) ) ,
259292 ...observations ( ) . flatMap ( ( obs ) => obs . data . map ( ( d ) => d . x ) ) ,
260293 ] ;
261294 const allY = ( ) => [
262- ...profileData ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . y ) ) ,
295+ ...profileDataForPlot ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . y ) ) ,
263296 ...observations ( ) . flatMap ( ( obs ) => obs . data . map ( ( d ) => d . y ) ) ,
264297 ] ;
265298
@@ -289,6 +322,10 @@ export function VerticalProfilePlot({
289322 setResetPlot ( analysis . id ) ;
290323 }
291324
325+ const showPlume = createMemo ( ( ) => {
326+ return [ "theta" , "qt" , "thetav" , "T" , "Td" ] . includes ( classVariable ( ) ) ;
327+ } ) ;
328+
292329 return (
293330 < >
294331 < div class = "flex flex-col gap-2" >
@@ -301,7 +338,7 @@ export function VerticalProfilePlot({
301338 < Chart id = { analysis . id } title = "Vertical profile plot" >
302339 < AxisBottom domain = { xLim } label = { analysis . variable } />
303340 < AxisLeft domain = { yLim } label = "Height[m]" />
304- < For each = { profileData ( ) } >
341+ < For each = { profileDataForPlot ( ) } >
305342 { ( d ) => (
306343 < Show when = { toggles [ d . label ] } >
307344 < Line { ...d } />
@@ -315,6 +352,18 @@ export function VerticalProfilePlot({
315352 </ Show >
316353 ) }
317354 </ For >
355+ < For each = { firePlumes ( ) } >
356+ { ( d ) => (
357+ < Show when = { toggles [ d . label ] } >
358+ < Show when = { showPlume ( ) } >
359+ < Plume
360+ d = { d }
361+ variable = { classVariable as ( ) => keyof Parcel }
362+ />
363+ </ Show >
364+ </ Show >
365+ ) }
366+ </ For >
318367 </ Chart >
319368 </ ChartContainer >
320369 < Picker
@@ -405,27 +454,56 @@ function Picker(props: PickerProps) {
405454}
406455
407456export function ThermodynamicPlot ( { analysis } : { analysis : SkewTAnalysis } ) {
408- const skewTData = ( ) =>
457+ const profileData = ( ) =>
409458 flatExperiments ( ) . map ( ( e ) => {
410459 const { config, output, ...formatting } = e ;
411460 const t = output ?. t . indexOf ( uniqueTimes ( ) [ analysis . time ] ) ;
412- return {
413- ...formatting ,
414- data :
415- t !== - 1 // -1 now means "not found in array" rather than last index
416- ? getThermodynamicProfiles ( e . output , e . config , t )
417- : [ ] ,
418- } ;
461+ if ( config . sw_ml && output && t !== undefined && t !== - 1 ) {
462+ const outputAtTime = getOutputAtTime ( output , t ) ;
463+ return { ...formatting , data : generateProfiles ( config , outputAtTime ) } ;
464+ }
465+ return { ...formatting , data : NoProfile } ;
419466 } ) ;
420467
468+ const firePlumes = ( ) =>
469+ flatExperiments ( ) . map ( ( e , i ) => {
470+ const { config, output, ...formatting } = e ;
471+ if ( config . sw_fire ) {
472+ return {
473+ ...formatting ,
474+ color : "#ff0000" ,
475+ label : `${ formatting . label } - fire plume` ,
476+ data : calculatePlume ( config , profileData ( ) [ i ] . data ) ,
477+ } ;
478+ }
479+ return { ...formatting , data : [ ] } ;
480+ } ) as ChartData < SoundingRecord > [ ] ;
481+
421482 const observations = ( ) =>
422483 flatObservations ( ) . map ( ( o ) => observationsForSounding ( o ) ) ;
423484
485+ // TODO: There should be a way that this isn't needed.
486+ const profileDataForPlot = ( ) =>
487+ profileData ( ) . map ( ( { data, label, color, linestyle } ) => ( {
488+ label,
489+ color,
490+ linestyle,
491+ data : data . p . map ( ( p , i ) => ( {
492+ p : p / 100 ,
493+ T : data . T [ i ] ,
494+ Td : data . Td [ i ] ,
495+ } ) ) ,
496+ } ) ) as ChartData < SoundingRecord > [ ] ;
497+
424498 return (
425499 < >
426500 < SkewTPlot
427501 id = { analysis . id }
428- data = { ( ) => [ ...skewTData ( ) , ...observations ( ) ] }
502+ data = { ( ) => [
503+ ...profileDataForPlot ( ) ,
504+ ...observations ( ) ,
505+ ...firePlumes ( ) ,
506+ ] }
429507 />
430508 { TimeSlider (
431509 ( ) => analysis . time ,
0 commit comments