perspective_viewer/components/
number_column_style.rs1use perspective_client::config::Scalar;
14use yew::prelude::*;
15use yew::*;
16
17use super::form::color_range_selector::*;
18use super::form::number_field::NumberFieldProps;
19use super::modal::*;
20use super::style::LocalStyle;
21use crate::components::form::number_field::NumberField;
22use crate::components::form::select_enum_field::SelectEnumField;
23use crate::config::*;
24use crate::session::Session;
25use crate::utils::*;
26use crate::*;
27
28#[derive(PartialEq, Eq, Copy, Clone, Debug)]
29pub enum Side {
30 Fg,
31 Bg,
32}
33
34use Side::*;
35
36#[derive(Properties)]
42pub struct NumberColumnStyleProps {
43 #[cfg_attr(test, prop_or_default)]
44 pub config: Option<NumberColumnStyleConfig>,
45
46 #[cfg_attr(test, prop_or_default)]
47 pub default_config: NumberColumnStyleDefaultConfig,
48
49 #[prop_or_default]
50 pub on_change: Callback<ColumnConfigValueUpdate>,
51
52 #[prop_or_default]
53 pub weak_link: WeakScope<NumberColumnStyle>,
54
55 #[prop_or_default]
56 pub column_name: Option<String>,
57
58 pub session: Session,
60}
61
62impl ModalLink<NumberColumnStyle> for NumberColumnStyleProps {
63 fn weak_link(&self) -> &'_ WeakScope<NumberColumnStyle> {
64 &self.weak_link
65 }
66}
67
68impl PartialEq for NumberColumnStyleProps {
69 fn eq(&self, other: &Self) -> bool {
70 self.config == other.config
71 && self.default_config == other.default_config
72 && self.column_name == other.column_name
73 }
74}
75
76fn scalar_to_f64(scalar: &Scalar) -> f64 {
77 match scalar {
78 Scalar::Float(x) => *x,
79 Scalar::String(x) => x.parse::<f64>().unwrap_or_default(),
80 Scalar::Bool(x) => {
81 if *x {
82 1.0
83 } else {
84 0.0
85 }
86 },
87 Scalar::Null => 0.0,
88 }
89}
90
91fn set_default_gradient(session: &Session, ctx: &Context<NumberColumnStyle>) {
92 if let Some(column_name) = ctx.props().column_name.clone() {
93 let session = session.clone();
94 ctx.link().send_future(async move {
95 let view = session.get_view().unwrap();
96 let min_max = view.get_min_max(column_name).await.unwrap();
97 let abs_max = scalar_to_f64(&min_max.0)
98 .abs()
99 .max(scalar_to_f64(&min_max.1).abs());
100
101 let gradient = (abs_max * 100.).round() / 100.;
102 NumberColumnStyleMsg::DefaultGradientChanged(gradient)
103 });
104 }
105}
106
107#[derive(Debug)]
108pub enum NumberColumnStyleMsg {
109 PosColorChanged(Side, String),
110 NegColorChanged(Side, String),
111 NumberForeModeChanged(NumberForegroundMode),
112 NumberBackModeChanged(NumberBackgroundMode),
113 GradientChanged(Side, Option<f64>),
114 DefaultGradientChanged(f64),
115}
116
117pub struct NumberColumnStyle {
119 config: NumberColumnStyleConfig,
120 default_config: NumberColumnStyleDefaultConfig,
121 fg_mode: NumberForegroundMode,
122 bg_mode: NumberBackgroundMode,
123 pos_fg_color: String,
124 neg_fg_color: String,
125 pos_bg_color: String,
126 neg_bg_color: String,
127 fg_gradient: Option<f64>,
128 bg_gradient: Option<f64>,
129}
130
131impl Component for NumberColumnStyle {
132 type Message = NumberColumnStyleMsg;
133 type Properties = NumberColumnStyleProps;
134
135 fn create(ctx: &Context<Self>) -> Self {
136 ctx.set_modal_link();
137 set_default_gradient(&ctx.props().session, ctx);
138 Self::reset(
139 &ctx.props().config.clone().unwrap_or_default(),
140 &ctx.props().default_config.clone(),
141 )
142 }
143
144 fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
145 let mut new = Self::reset(
146 &ctx.props().config.clone().unwrap_or_default(),
147 &ctx.props().default_config.clone(),
148 );
149
150 set_default_gradient(&ctx.props().session, ctx);
151 std::mem::swap(self, &mut new);
152 true
153 }
154
155 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
156 match msg {
157 NumberColumnStyleMsg::PosColorChanged(side, val) => {
158 if side == Fg {
159 self.pos_fg_color = val;
160 self.config.pos_fg_color = Some(self.pos_fg_color.to_owned());
161 } else {
162 self.pos_bg_color = val;
163 self.config.pos_bg_color = Some(self.pos_bg_color.to_owned());
164 }
165
166 self.dispatch_config(ctx);
167 true
168 },
169 NumberColumnStyleMsg::NegColorChanged(side, val) => {
170 if side == Fg {
171 self.neg_fg_color = val;
172 self.config.neg_fg_color = Some(self.neg_fg_color.to_owned());
173 } else {
174 self.neg_bg_color = val;
175 self.config.neg_bg_color = Some(self.neg_bg_color.to_owned());
176 }
177
178 self.dispatch_config(ctx);
179 true
180 },
181 NumberColumnStyleMsg::NumberForeModeChanged(val) => {
182 self.fg_mode = val;
183 self.config.number_fg_mode = val;
184 if self.fg_mode.needs_gradient() {
185 self.config.fg_gradient = Some(self.fg_gradient.unwrap());
186 } else {
187 self.config.fg_gradient = None;
188 }
189
190 self.dispatch_config(ctx);
191 true
192 },
193 NumberColumnStyleMsg::NumberBackModeChanged(val) => {
194 self.bg_mode = val;
195 self.config.number_bg_mode = val;
196 if self.bg_mode.needs_gradient() {
197 self.config.bg_gradient = Some(self.bg_gradient.unwrap());
198 } else {
199 self.config.bg_gradient = None;
200 }
201
202 self.dispatch_config(ctx);
203 true
204 },
205 NumberColumnStyleMsg::GradientChanged(side, gradient) => {
206 match (side, gradient) {
207 (Fg, Some(x)) => {
208 self.fg_gradient = Some(x);
209 self.config.fg_gradient = Some(x);
210 },
211 (Fg, None) => {
212 self.fg_gradient = Some(self.default_config.fg_gradient);
213 self.config.fg_gradient = None;
214 },
215 (Bg, Some(x)) => {
216 self.bg_gradient = Some(x);
217 self.config.bg_gradient = Some(x);
218 },
219 (Bg, None) => {
220 self.bg_gradient = Some(self.default_config.bg_gradient);
221 self.config.bg_gradient = None;
222 },
223 };
224
225 self.dispatch_config(ctx);
226 false
227 },
228 NumberColumnStyleMsg::DefaultGradientChanged(gradient) => {
229 self.fg_gradient.get_or_insert(gradient);
230 self.bg_gradient.get_or_insert(gradient);
231 self.default_config.fg_gradient = gradient;
232 self.default_config.bg_gradient = gradient;
233 true
234 },
235 }
236 }
237
238 fn view(&self, ctx: &Context<Self>) -> Html {
239 let fg_mode_changed = ctx.link().callback(|x: Option<_>| {
240 NumberColumnStyleMsg::NumberForeModeChanged(x.unwrap_or_default())
241 });
242
243 let bg_mode_changed = ctx.link().callback(|x: Option<_>| {
244 NumberColumnStyleMsg::NumberBackModeChanged(x.unwrap_or_default())
245 });
246
247 let fg_controls = match self.fg_mode {
248 NumberForegroundMode::Disabled => html! {},
249 NumberForegroundMode::Color => html! {
250 <div class="row">
251 <ColorRangeSelector ..self.color_props("fg-color", Fg, false, ctx) />
252 </div>
253 },
254 NumberForegroundMode::Bar => html! {
255 <>
256 <div class="row">
257 <ColorRangeSelector ..self.color_props("bar-color", Fg, false, ctx) />
258 </div>
259 <NumberField ..self.max_value_props(Fg, ctx) />
260 </>
261 },
262 NumberForegroundMode::LabelBar => html! {
263 <>
264 <div class="row">
265 <ColorRangeSelector ..self.color_props("label-bar-color", Fg, false, ctx) />
266 </div>
267 <NumberField ..self.max_value_props(Fg, ctx) />
268 </>
269 },
270 };
271
272 let bg_controls = match self.bg_mode {
273 NumberBackgroundMode::Disabled => html! {},
274 NumberBackgroundMode::Color => html! {
275 <div class="row">
276 <ColorRangeSelector ..self.color_props("bg-color", Bg,false, ctx) />
277 </div>
278 },
279 NumberBackgroundMode::Gradient => html! {
280 <>
281 <div class="row">
282 <ColorRangeSelector ..self.color_props("gradient-color", Bg, true, ctx) />
283 </div>
284 <NumberField ..self.max_value_props(Bg, ctx) />
285 </>
286 },
287 NumberBackgroundMode::Pulse => html! {
288 <div class="row">
289 <ColorRangeSelector ..self.color_props("pulse-color", Bg, true, ctx) />
290 </div>
291 },
292 };
293
294 html! {
295 <>
296 <LocalStyle href={css!("column-style")} />
297 <div id="column-style-container" class="number-column-style-container">
298 <SelectEnumField<NumberForegroundMode>
299 label="foreground"
300 on_change={fg_mode_changed}
301 current_value={self.fg_mode}
302 />
303 { fg_controls }
304 <SelectEnumField<NumberBackgroundMode>
305 label="background"
306 on_change={bg_mode_changed}
307 current_value={self.bg_mode}
308 />
309 { bg_controls }
310 </div>
311 </>
312 }
313 }
314}
315
316impl NumberColumnStyle {
317 fn dispatch_config(&self, ctx: &Context<Self>) {
319 let mut config = self.config.clone();
320 match &self.config {
321 NumberColumnStyleConfig {
322 pos_fg_color: Some(pos_color),
323 neg_fg_color: Some(neg_color),
324 ..
325 } if *pos_color == self.default_config.pos_fg_color
326 && *neg_color == self.default_config.neg_fg_color =>
327 {
328 config.pos_fg_color = None;
329 config.neg_fg_color = None;
330 },
331 _ => {},
332 };
333
334 match &self.config {
335 NumberColumnStyleConfig {
336 pos_bg_color: Some(pos_color),
337 neg_bg_color: Some(neg_color),
338 ..
339 } if *pos_color == self.default_config.pos_bg_color
340 && *neg_color == self.default_config.neg_bg_color =>
341 {
342 config.pos_bg_color = None;
343 config.neg_bg_color = None;
344 },
345 _ => {},
346 };
347
348 let update = Some(config).filter(|config| config != &NumberColumnStyleConfig::default());
349
350 ctx.props()
351 .on_change
352 .emit(ColumnConfigValueUpdate::DatagridNumberStyle(update));
353 }
354
355 fn color_props(
356 &self,
357 id: &str,
358 side: Side,
359 is_gradient: bool,
360 ctx: &Context<Self>,
361 ) -> ColorRangeProps {
362 let on_pos_color = ctx
363 .link()
364 .callback(move |x| NumberColumnStyleMsg::PosColorChanged(side, x));
365 let on_neg_color = ctx
366 .link()
367 .callback(move |x| NumberColumnStyleMsg::NegColorChanged(side, x));
368
369 let default_config = self.default_config.clone();
370
371 props!(ColorRangeProps {
372 id: id.to_string(),
373 is_gradient,
374 pos_color: if side == Fg {
375 &self.pos_fg_color
376 } else {
377 &self.pos_bg_color
378 }
379 .to_owned(),
380 neg_color: if side == Fg {
381 &self.neg_fg_color
382 } else {
383 &self.neg_bg_color
384 }
385 .to_owned(),
386 on_pos_color,
387 on_neg_color,
388 on_reset: ctx.link().batch_callback(move |_| if side == Fg {
389 vec![
390 NumberColumnStyleMsg::PosColorChanged(
391 side,
392 default_config.pos_fg_color.clone(),
393 ),
394 NumberColumnStyleMsg::NegColorChanged(
395 side,
396 default_config.neg_fg_color.clone(),
397 ),
398 ]
399 } else {
400 vec![
401 NumberColumnStyleMsg::PosColorChanged(
402 side,
403 default_config.pos_bg_color.clone(),
404 ),
405 NumberColumnStyleMsg::NegColorChanged(
406 side,
407 default_config.neg_bg_color.clone(),
408 ),
409 ]
410 }),
411 is_modified: if side == Fg {
412 self.pos_fg_color != self.default_config.pos_fg_color
413 || self.neg_fg_color != self.default_config.neg_fg_color
414 } else {
415 self.pos_bg_color != self.default_config.pos_bg_color
416 || self.neg_bg_color != self.default_config.neg_bg_color
417 },
418 })
419 }
420
421 fn max_value_props(&self, side: Side, ctx: &Context<Self>) -> NumberFieldProps {
422 let on_change = ctx
423 .link()
424 .callback(move |x| NumberColumnStyleMsg::GradientChanged(side, x));
425
426 let value = if side == Fg {
427 self.fg_gradient.unwrap_or_default()
428 } else {
429 self.bg_gradient.unwrap_or_default()
430 };
431
432 props!(NumberFieldProps {
433 default: value,
434 current_value: value,
435 label: "max-value",
436 on_change
437 })
438 }
439
440 fn reset(
441 config: &NumberColumnStyleConfig,
442 default_config: &NumberColumnStyleDefaultConfig,
443 ) -> Self {
444 let mut config = config.clone();
445 let fg_gradient = config.fg_gradient;
446 let bg_gradient = config.bg_gradient;
447
448 let pos_fg_color = config
449 .pos_fg_color
450 .as_ref()
451 .unwrap_or(&default_config.pos_fg_color)
452 .to_owned();
453
454 let neg_fg_color = config
455 .neg_fg_color
456 .as_ref()
457 .unwrap_or(&default_config.neg_fg_color)
458 .to_owned();
459
460 let pos_bg_color = config
461 .pos_bg_color
462 .as_ref()
463 .unwrap_or(&default_config.pos_bg_color)
464 .to_owned();
465
466 let neg_bg_color = config
467 .neg_bg_color
468 .as_ref()
469 .unwrap_or(&default_config.neg_bg_color)
470 .to_owned();
471
472 config.pos_fg_color = Some(pos_fg_color.to_owned());
473 config.neg_fg_color = Some(neg_fg_color.to_owned());
474 let fg_mode = config.number_fg_mode;
475 config.pos_bg_color = Some(pos_bg_color.to_owned());
476 config.neg_bg_color = Some(neg_bg_color.to_owned());
477 let bg_mode = config.number_bg_mode;
478 Self {
479 config,
480 default_config: default_config.clone(),
481 fg_mode,
482 bg_mode,
483 pos_fg_color,
484 neg_fg_color,
485 pos_bg_color,
486 neg_bg_color,
487 fg_gradient,
488 bg_gradient,
489 }
490 }
491}