11import type { Config } from "@classmodel/class/config" ;
2- import { calculatePlume , transposePlumeData } from "@classmodel/class/fire" ;
2+ import { FirePlume , calculatePlume , noPlume } from "@classmodel/class/fire" ;
33import {
4- type ClassTimeSeries ,
54 type OutputVariableKey ,
6- getOutputAtTime ,
75 outputVariables ,
86} from "@classmodel/class/output" ;
97import {
108 type ClassProfile ,
11- NoProfile ,
129 generateProfiles ,
10+ noProfile ,
1311} from "@classmodel/class/profiles" ;
12+ import type { ClassData } from "@classmodel/class/runner" ;
1413import * as d3 from "d3" ;
1514import { saveAs } from "file-saver" ;
1615import { toBlob } from "html-to-image" ;
@@ -78,7 +77,7 @@ interface FlatExperiment {
7877 color : string ;
7978 linestyle : string ;
8079 config : Config ;
81- output ?: ClassTimeSeries ;
80+ output ?: ClassData ;
8281}
8382
8483// Create a derived store for looping over all outputs:
@@ -119,7 +118,7 @@ const flatObservations: () => Observation[] = createMemo(() => {
119118} ) ;
120119
121120const _allTimes = ( ) =>
122- new Set ( flatExperiments ( ) . flatMap ( ( e ) => e . output ?. utcTime ?? [ ] ) ) ;
121+ new Set ( flatExperiments ( ) . flatMap ( ( e ) => e . output ?. timeseries . utcTime ?? [ ] ) ) ;
123122const uniqueTimes = ( ) => [ ...new Set ( _allTimes ( ) ) ] . sort ( ( a , b ) => a - b ) ;
124123
125124// TODO: could memoize all reactive elements here, would it make a difference?
@@ -133,11 +132,15 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
133132
134133 const allX = ( ) =>
135134 flatExperiments ( ) . flatMap ( ( e ) =>
136- e . output ? e . output [ analysis . xVariable as OutputVariableKey ] : [ ] ,
135+ e . output
136+ ? e . output . timeseries [ analysis . xVariable as OutputVariableKey ]
137+ : [ ] ,
137138 ) ;
138139 const allY = ( ) =>
139140 flatExperiments ( ) . flatMap ( ( e ) =>
140- e . output ? e . output [ analysis . yVariable as OutputVariableKey ] : [ ] ,
141+ e . output
142+ ? e . output . timeseries [ analysis . yVariable as OutputVariableKey ]
143+ : [ ] ,
141144 ) ;
142145
143146 const granularities : Record < string , number | undefined > = {
@@ -157,12 +160,12 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
157160 ...formatting ,
158161 data :
159162 // Zip x[] and y[] into [x, y][]
160- output ?. t . map ( ( _ , t ) => ( {
163+ output ?. timeseries . t . map ( ( _ , t ) => ( {
161164 x : output
162- ? output [ analysis . xVariable as OutputVariableKey ] [ t ]
165+ ? output . timeseries [ analysis . xVariable as OutputVariableKey ] [ t ]
163166 : Number . NaN ,
164167 y : output
165- ? output [ analysis . yVariable as OutputVariableKey ] [ t ]
168+ ? output . timeseries [ analysis . yVariable as OutputVariableKey ] [ t ]
166169 : Number . NaN ,
167170 } ) ) || [ ] ,
168171 } ;
@@ -241,7 +244,7 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
241244export function VerticalProfilePlot ( {
242245 analysis,
243246} : { analysis : ProfilesAnalysis } ) {
244- const variableOptions = {
247+ const profileVariables = {
245248 "Potential temperature [K]" : "theta" ,
246249 "Virtual potential temperature [K]" : "thetav" ,
247250 "Specific humidity [kg/kg]" : "qt" ,
@@ -255,11 +258,11 @@ export function VerticalProfilePlot({
255258 "Density [kg/m³]" : "rho" ,
256259 "Relative humidity [%]" : "rh" ,
257260 } as const satisfies Record < string , keyof ClassProfile > ;
261+ type PlumeVariable = "theta" | "qt" | "thetav" | "T" | "Td" | "rh" | "w" ;
258262
259263 const classVariable = ( ) =>
260- variableOptions [ analysis . variable as keyof typeof variableOptions ] ;
264+ profileVariables [ analysis . variable as keyof typeof profileVariables ] ;
261265
262- type PlumeVariable = "theta" | "qt" | "thetav" | "T" | "Td" | "rh" | "w" ;
263266 function isPlumeVariable ( v : string ) : v is PlumeVariable {
264267 return [ "theta" , "qt" , "thetav" , "T" , "Td" , "rh" , "w" ] . includes ( v ) ;
265268 }
@@ -269,57 +272,65 @@ export function VerticalProfilePlot({
269272 const observations = ( ) =>
270273 flatObservations ( ) . map ( ( o ) => observationsForProfile ( o , classVariable ( ) ) ) ;
271274
275+ function extractLines < T extends Record < string , number [ ] > > (
276+ data : T ,
277+ xvar : keyof T ,
278+ yvar : keyof T ,
279+ ) {
280+ const xs = data [ xvar ] ?? [ ] ;
281+ const ys = data [ yvar ] ?? [ ] ;
282+
283+ const n = Math . min ( xs . length , ys . length ) ;
284+
285+ const result = new Array ( n ) ;
286+ for ( let i = 0 ; i < n ; i ++ ) {
287+ result [ i ] = { x : xs [ i ] , y : ys [ i ] } ;
288+ }
289+
290+ return result ;
291+ }
292+
272293 const profileData = ( ) =>
273294 flatExperiments ( ) . map ( ( e ) => {
274295 const { config, output, ...formatting } = e ;
275- const t = output ?. utcTime . indexOf ( uniqueTimes ( ) [ analysis . time ] ) ;
276- if ( config . sw_ml && output && t !== undefined && t !== - 1 ) {
277- const outputAtTime = getOutputAtTime ( output , t ) ;
278- return { ...formatting , data : generateProfiles ( config , outputAtTime ) } ;
279- }
280- return { ...formatting , data : NoProfile } ;
296+
297+ const targetTime = uniqueTimes ( ) [ analysis . time ] ;
298+ const t = output ?. timeseries . utcTime . indexOf ( targetTime ) ;
299+
300+ const profile =
301+ ( t != null && t !== - 1 && output ?. profiles ?. [ t ] ) || noProfile ;
302+
303+ return {
304+ ...formatting ,
305+ data : extractLines ( profile , classVariable ( ) , "z" ) ,
306+ } ;
281307 } ) ;
282308
283309 const firePlumes = ( ) =>
284- flatExperiments ( ) . map ( ( e , i ) => {
310+ flatExperiments ( ) . map ( ( e ) => {
285311 const { config, output, ...formatting } = e ;
286- if ( config . sw_fire && isPlumeVariable ( classVariable ( ) ) ) {
287- const plume = transposePlumeData (
288- calculatePlume ( config , profileData ( ) [ i ] . data ) ,
289- ) ;
290- return {
291- ...formatting ,
292- linestyle : "4" ,
293- data : plume . z . map ( ( z , i ) => ( {
294- x : plume [ classVariable ( ) as PlumeVariable ] [ i ] ,
295- y : z ,
296- } ) ) ,
297- } ;
298- }
299- return { ...formatting , data : [ ] } ;
300- } ) ;
301312
302- // TODO: There should be a way that this isn't needed.
303- const profileDataForPlot = ( ) =>
304- profileData ( ) . map ( ( { data, label, color, linestyle } ) => ( {
305- label,
306- color,
307- linestyle,
308- data : data . z . map ( ( z , i ) => ( {
309- x : data [ classVariable ( ) ] [ i ] ,
310- y : z ,
311- } ) ) ,
312- } ) ) as ChartData < Point > [ ] ;
313+ const targetTime = uniqueTimes ( ) [ analysis . time ] ;
314+ const t = output ?. timeseries . utcTime . indexOf ( targetTime ) ;
315+
316+ const plume = ( t != null && t !== - 1 && output ?. plumes ?. [ t ] ) || noPlume ;
313317
318+ return {
319+ ...formatting ,
320+ linestyle : "4" ,
321+ data : extractLines ( plume , classVariable ( ) as PlumeVariable , "z" ) ,
322+ } ;
323+ } ) ;
324+
314325 const allX = ( ) => [
315326 ...firePlumes ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . x ) ) ,
316327 ...profileDataForPlot ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . x ) ) ,
317328 ...observations ( ) . flatMap ( ( obs ) => obs . data . map ( ( d ) => d . x ) ) ,
318329 ] ;
319330 const allY = ( ) => [
320- ...firePlumes ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . y ) ) ,
321- ...profileDataForPlot ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . y ) ) ,
322- ...observations ( ) . flatMap ( ( obs ) => obs . data . map ( ( d ) => d . y ) ) ,
331+ ...firePlumes ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . z ) ) ,
332+ ...profileDataForPlot ( ) . flatMap ( ( p ) => p . data . map ( ( d ) => d . z ) ) ,
333+ ...observations ( ) . flatMap ( ( obs ) => obs . data . map ( ( d ) => d . z ) ) ,
323334 ] ;
324335
325336 const xLim = ( ) => getNiceAxisLimits ( allX ( ) , 0 ) ;
@@ -385,7 +396,7 @@ export function VerticalProfilePlot({
385396 < Picker
386397 value = { ( ) => analysis . variable }
387398 setValue = { ( v ) => changeVar ( v ) }
388- options = { Object . keys ( variableOptions ) }
399+ options = { Object . keys ( profileVariables ) }
389400 label = "variable: "
390401 />
391402 { TimeSlider (
@@ -482,7 +493,7 @@ export function ThermodynamicPlot({ analysis }: { analysis: SkewTAnalysis }) {
482493 const outputAtTime = getOutputAtTime ( output , t ) ;
483494 return { ...formatting , data : generateProfiles ( config , outputAtTime ) } ;
484495 }
485- return { ...formatting , data : NoProfile } ;
496+ return { ...formatting , data : noProfile } ;
486497 } ) ;
487498
488499 const firePlumes = ( ) =>
0 commit comments