use super::context::LoweringContext;
use crate::scene::*;
use std::collections::BTreeSet;
impl LoweringContext {
pub(super) fn ensure_default_plot_styles(&mut self) {
let existing = self
.presentation
.data
.styles
.iter()
.map(|style| style.id.as_str())
.collect::<BTreeSet<_>>();
let mut generated = Vec::new();
for plot in &mut self.ir.program.plots {
if !matches!(plot.value_type, PlotValueType::Number)
|| (plot.readout == ReadoutBehavior::Hidden && plot.color_slot.is_none())
{
continue;
}
let generated_slot = plot.color_slot.is_none();
let slot = plot
.color_slot
.get_or_insert_with(|| plot.id.clone())
.clone();
if generated_slot
&& !existing.contains(slot.as_str())
&& !generated
.iter()
.any(|style: &PresentationStyle| style.id == slot)
{
generated.push(PresentationStyle {
id: slot,
label: plot.label.clone(),
role: "neutral".to_owned(),
shade: "base".to_owned(),
required_input: None,
});
}
}
if self.presentation.data.columns.iter().any(|column| {
matches!(
&column.color,
PresentationColumnColor::Static { slot } if slot == "main"
)
}) && !existing.contains("main")
&& !generated
.iter()
.any(|style: &PresentationStyle| style.id == "main")
{
generated.push(PresentationStyle {
id: "main".to_owned(),
label: "Main".to_owned(),
role: "neutral".to_owned(),
shade: "base".to_owned(),
required_input: None,
});
}
self.presentation.data.styles.extend(generated);
}
pub(super) fn ensure_default_presentation_layers(&mut self, indicator_overlay: bool) {
if matches!(self.presentation.data.view, PresentationViewKind::Table) {
return;
}
let existing = self
.presentation
.data
.layers
.iter()
.map(|layer| layer.channel.as_str())
.collect::<BTreeSet<_>>();
let generated = self
.ir
.program
.plots
.iter()
.filter(|plot| {
plot.layer
&& !matches!(plot.value_type, PlotValueType::Boolean)
&& !existing.contains(plot.id.as_str())
})
.map(|plot| PresentationLayer {
channel: plot.id.clone(),
name: plot
.layer_title
.clone()
.unwrap_or_else(|| plot.label.clone()),
pane: plot.pane,
data_kind: Some(
plot.presentation_data_kind
.unwrap_or_else(|| default_data_kind_for_plot(plot)),
),
mark: Some(
plot.presentation_mark
.unwrap_or_else(|| default_mark_for_plot(plot)),
),
axis: Some(
plot.presentation_axis
.unwrap_or_else(|| default_axis_for_plot(indicator_overlay, plot)),
),
})
.collect::<Vec<_>>();
self.presentation.data.layers.extend(generated);
}
}
pub(super) fn default_data_kind_for_plot(plot: &PlotDescriptor) -> PresentationDataKind {
match plot.value_type {
PlotValueType::Ohlc => PresentationDataKind::Ohlc,
PlotValueType::Range => PresentationDataKind::Range,
PlotValueType::Boolean => PresentationDataKind::Scalar,
PlotValueType::Number => match plot.style {
PlotStyle::Histogram => PresentationDataKind::Histogram,
PlotStyle::Candles | PlotStyle::Bars => PresentationDataKind::Ohlc,
PlotStyle::Cloud | PlotStyle::BrokenCloud => PresentationDataKind::Range,
PlotStyle::Line | PlotStyle::Step | PlotStyle::Area => PresentationDataKind::Scalar,
},
}
}
pub(super) fn default_mark_for_plot(plot: &PlotDescriptor) -> PresentationMarkKind {
match plot.value_type {
PlotValueType::Ohlc => PresentationMarkKind::Candle,
PlotValueType::Range => PresentationMarkKind::Line,
PlotValueType::Boolean => PresentationMarkKind::Line,
PlotValueType::Number => match plot.style {
PlotStyle::Histogram => PresentationMarkKind::Histogram,
PlotStyle::Candles => PresentationMarkKind::Candle,
PlotStyle::Bars => PresentationMarkKind::RegularBar,
PlotStyle::Step => PresentationMarkKind::Step,
PlotStyle::Area => PresentationMarkKind::Area,
PlotStyle::Cloud | PlotStyle::BrokenCloud | PlotStyle::Line => {
PresentationMarkKind::Line
}
},
}
}
pub(super) fn default_axis_for_plot(
indicator_overlay: bool,
plot: &PlotDescriptor,
) -> PresentationAxis {
if (plot.overlay || indicator_overlay)
&& plot.pane == PaneTarget::Main
&& matches!(plot.scale, PlotScale::Price)
{
PresentationAxis::Primary
} else {
PresentationAxis::Secondary
}
}
pub(super) fn plot_value_supports_data_kind(
value_type: PlotValueType,
data_kind: PresentationDataKind,
) -> bool {
match value_type {
PlotValueType::Number => {
matches!(
data_kind,
PresentationDataKind::Scalar | PresentationDataKind::Histogram
)
}
PlotValueType::Boolean => matches!(data_kind, PresentationDataKind::Scalar),
PlotValueType::Ohlc => matches!(data_kind, PresentationDataKind::Ohlc),
PlotValueType::Range => matches!(data_kind, PresentationDataKind::Range),
}
}
pub(super) fn presentation_mark_compatible(
data_kind: PresentationDataKind,
mark: PresentationMarkKind,
) -> bool {
match data_kind {
PresentationDataKind::Ohlc => {
matches!(
mark,
PresentationMarkKind::Candle | PresentationMarkKind::Line
)
}
PresentationDataKind::Scalar => matches!(
mark,
PresentationMarkKind::Line
| PresentationMarkKind::Step
| PresentationMarkKind::Area
| PresentationMarkKind::Point
| PresentationMarkKind::Cross
| PresentationMarkKind::Segment
| PresentationMarkKind::HorizontalLevel
| PresentationMarkKind::RegularBar
),
PresentationDataKind::Range => matches!(mark, PresentationMarkKind::Line),
PresentationDataKind::Histogram => matches!(
mark,
PresentationMarkKind::Line
| PresentationMarkKind::Step
| PresentationMarkKind::Area
| PresentationMarkKind::Point
| PresentationMarkKind::Cross
| PresentationMarkKind::Segment
| PresentationMarkKind::HorizontalLevel
| PresentationMarkKind::RegularBar
| PresentationMarkKind::Histogram
| PresentationMarkKind::HistogramSignedOverlay
),
}
}
pub(super) fn numeric_mark_compatible(mark: PresentationMarkKind) -> bool {
!matches!(mark, PresentationMarkKind::Candle)
}
pub(super) fn valid_presentation_role(value: &str) -> bool {
matches!(
value.to_ascii_lowercase().as_str(),
"neutral" | "secondary" | "primary" | "success" | "danger" | "warning"
)
}
pub(super) fn valid_presentation_shade(value: &str) -> bool {
matches!(
value.to_ascii_lowercase().as_str(),
"faint" | "soft" | "base" | "strong" | "vivid"
)
}