1- import { For , Match , Show , Switch , createMemo , createUniqueId } from "solid-js" ;
1+ import { BmiClass } from "@classmodel/class/bmi" ;
2+ import {
3+ type Accessor ,
4+ For ,
5+ Match ,
6+ type Setter ,
7+ Show ,
8+ Switch ,
9+ createMemo ,
10+ createSignal ,
11+ createUniqueId ,
12+ } from "solid-js" ;
213import { getThermodynamicProfiles , getVerticalProfiles } from "~/lib/profiles" ;
3- import { type Analysis , deleteAnalysis , experiments } from "~/lib/store" ;
14+ import {
15+ type Analysis ,
16+ type ProfilesAnalysis ,
17+ type TimeseriesAnalysis ,
18+ deleteAnalysis ,
19+ experiments ,
20+ updateAnalysis ,
21+ } from "~/lib/store" ;
422import { MdiCog , MdiContentCopy , MdiDelete , MdiDownload } from "./icons" ;
523import LinePlot from "./plots/LinePlot" ;
624import { SkewTPlot } from "./plots/skewTlogP" ;
725import { Button } from "./ui/button" ;
826import { Card , CardContent , CardHeader , CardTitle } from "./ui/card" ;
27+ import {
28+ Select ,
29+ SelectContent ,
30+ SelectItem ,
31+ SelectTrigger ,
32+ SelectValue ,
33+ } from "./ui/select" ;
934
1035/** https://github.com/d3/d3-scale-chromatic/blob/main/src/categorical/Tableau10.js */
1136const colors = [
@@ -27,7 +52,11 @@ const linestyles = ["none", "5,5", "10,10", "15,5,5,5", "20,10,5,5,5,10"];
2752/** Very rudimentary plot showing time series of each experiment globally available
2853 * It only works if the time axes are equal
2954 */
30- export function TimeSeriesPlot ( ) {
55+ export function TimeSeriesPlot ( { analysis } : { analysis : TimeseriesAnalysis } ) {
56+ const xVariableOptions = [ "t" ] ; // TODO: separate plot types for timeseries and x-vs-y? Use time axis?
57+ // TODO: add nice description from config as title and dropdown option for the variable picker.
58+ const yVariableOptions = new BmiClass ( ) . get_output_var_names ( ) ;
59+
3160 const chartData = createMemo ( ( ) => {
3261 return experiments
3362 . filter ( ( e ) => e . running === false ) // Skip running experiments
@@ -41,9 +70,13 @@ export function TimeSeriesPlot() {
4170 color : colors [ ( j + 1 ) % 10 ] ,
4271 linestyle : linestyles [ i % 5 ] ,
4372 data :
44- perm . output ?. t . map ( ( tVal , i ) => ( {
45- x : tVal ,
46- y : perm . output ?. h [ i ] || Number . NaN ,
73+ perm . output ?. t . map ( ( tVal , ti ) => ( {
74+ x : perm . output
75+ ? perm . output [ analysis . xVariable ] [ ti ]
76+ : Number . NaN ,
77+ y : perm . output
78+ ? perm . output [ analysis . yVariable ] [ ti ]
79+ : Number . NaN ,
4780 } ) ) || [ ] ,
4881 } ;
4982 } ) ;
@@ -53,9 +86,13 @@ export function TimeSeriesPlot() {
5386 color : colors [ 0 ] ,
5487 linestyle : linestyles [ i ] ,
5588 data :
56- experimentOutput ?. t . map ( ( tVal , i ) => ( {
57- x : tVal ,
58- y : experimentOutput ?. h [ i ] || Number . NaN ,
89+ experimentOutput ?. t . map ( ( tVal , ti ) => ( {
90+ x : experimentOutput
91+ ? experimentOutput [ analysis . xVariable ] [ ti ]
92+ : Number . NaN ,
93+ y : experimentOutput
94+ ? experimentOutput [ analysis . yVariable ] [ ti ]
95+ : Number . NaN ,
5996 } ) ) || [ ] ,
6097 } ,
6198 ...permutationRuns ,
@@ -64,17 +101,50 @@ export function TimeSeriesPlot() {
64101 } ) ;
65102
66103 return (
67- < LinePlot
68- data = { chartData }
69- xlabel = "Time [s]"
70- ylabel = "Mixed-layer height [m]"
71- />
104+ < >
105+ { /* TODO: get label for yVariable from model config */ }
106+ < LinePlot
107+ data = { chartData }
108+ xlabel = { ( ) => "Time [s]" }
109+ ylabel = { ( ) => analysis . yVariable }
110+ />
111+ < div class = "flex justify-around" >
112+ < Picker
113+ value = { ( ) => analysis . xVariable }
114+ setValue = { ( v ) => updateAnalysis ( analysis , { xVariable : v } ) }
115+ options = { xVariableOptions }
116+ label = "x-axis"
117+ />
118+ < Picker
119+ value = { ( ) => analysis . yVariable }
120+ setValue = { ( v ) => updateAnalysis ( analysis , { yVariable : v } ) }
121+ options = { yVariableOptions }
122+ label = "y-axis"
123+ />
124+ </ div >
125+ </ >
72126 ) ;
73127}
74128
75- export function VerticalProfilePlot ( ) {
76- const variable = "theta" ;
77- const time = - 1 ;
129+ export function VerticalProfilePlot ( {
130+ analysis,
131+ } : { analysis : ProfilesAnalysis } ) {
132+ const [ variable , setVariable ] = createSignal ( "Potential temperature [K]" ) ;
133+
134+ // TODO also check time of permutations.
135+ const timeOptions = experiments
136+ . filter ( ( e ) => e . running === false )
137+ . flatMap ( ( e ) => ( e . reference . output ? e . reference . output . t : [ ] ) ) ;
138+ const variableOptions = {
139+ "Potential temperature [K]" : "theta" ,
140+ "Specific humidity [kg/kg]" : "q" ,
141+ } ;
142+ const classVariable = ( ) =>
143+ variableOptions [ analysis . variable as keyof typeof variableOptions ] ;
144+
145+ // TODO: refactor this? We could have a function that creates shared ChartData
146+ // props (linestyle, color, label) generic for each plot type, and custom data
147+ // formatting as required by specific chart
78148 const profileData = createMemo ( ( ) => {
79149 return experiments
80150 . filter ( ( e ) => e . running === false ) // Skip running experiments
@@ -86,7 +156,12 @@ export function VerticalProfilePlot() {
86156 color : colors [ ( j + 1 ) % 10 ] ,
87157 linestyle : linestyles [ i % 5 ] ,
88158 label : `${ e . name } /${ p . name } ` ,
89- data : getVerticalProfiles ( p . output , p . config , variable , time ) ,
159+ data : getVerticalProfiles (
160+ p . output ,
161+ p . config ,
162+ classVariable ( ) ,
163+ analysis . time ,
164+ ) ,
90165 } ;
91166 } ) ;
92167
@@ -103,20 +178,59 @@ export function VerticalProfilePlot() {
103178 dtheta : [ ] ,
104179 } ,
105180 e . reference . config ,
106- variable ,
107- time ,
181+ classVariable ( ) ,
182+ analysis . time ,
108183 ) ,
109184 } ,
110185 ...permutations ,
111186 ] ;
112187 } ) ;
113188 } ) ;
114189 return (
115- < LinePlot
116- data = { profileData }
117- xlabel = "Potential temperature [K]"
118- ylabel = "Height [m]"
119- />
190+ < >
191+ < LinePlot
192+ data = { profileData }
193+ xlabel = { variable }
194+ ylabel = { ( ) => "Height [m]" }
195+ />
196+ < Picker
197+ value = { ( ) => analysis . variable }
198+ setValue = { ( v ) => updateAnalysis ( analysis , { variable : v } ) }
199+ options = { Object . keys ( variableOptions ) }
200+ label = "variable: "
201+ />
202+ </ >
203+ ) ;
204+ }
205+
206+ type PickerProps = {
207+ value : Accessor < string > ;
208+ setValue : Setter < string > ;
209+ options : string [ ] ;
210+ label ?: string ;
211+ } ;
212+
213+ function Picker ( props : PickerProps ) {
214+ return (
215+ < div class = "flex items-center gap-2" >
216+ < p > { props . label } </ p >
217+ < Select
218+ class = "whitespace-nowrap"
219+ value = { props . value ( ) }
220+ disallowEmptySelection = { true }
221+ onChange = { props . setValue }
222+ options = { props . options }
223+ placeholder = "Select value..."
224+ itemComponent = { ( props ) => (
225+ < SelectItem item = { props . item } > { props . item . rawValue } </ SelectItem >
226+ ) }
227+ >
228+ < SelectTrigger aria-label = "Variable" class = "min-w-[100px]" >
229+ < SelectValue < string > > { ( state ) => state . selectedOption ( ) } </ SelectValue >
230+ </ SelectTrigger >
231+ < SelectContent />
232+ </ Select >
233+ </ div >
120234 ) ;
121235}
122236
@@ -228,10 +342,10 @@ export function AnalysisCard(analysis: Analysis) {
228342 < FinalHeights />
229343 </ Match >
230344 < Match when = { analysis . type === "timeseries" } >
231- < TimeSeriesPlot />
345+ < TimeSeriesPlot analysis = { analysis as TimeseriesAnalysis } />
232346 </ Match >
233347 < Match when = { analysis . type === "profiles" } >
234- < VerticalProfilePlot />
348+ < VerticalProfilePlot analysis = { analysis as ProfilesAnalysis } />
235349 </ Match >
236350 < Match when = { analysis . type === "skewT" } >
237351 < ThermodynamicPlot />
0 commit comments