+ let mut selected_columns = vec![html! {
+
>()}
/>
- };
+ }];
+
+ if !inactive_children.is_empty() {
+ selected_columns.push(html! {
+
+ })
+ }
html! {
<>
@@ -411,15 +433,7 @@ impl Component for ColumnSelector {
skip_empty=true
orientation={Orientation::Vertical}
>
- { selected_columns }
- if !inactive_children.is_empty() {
-
- }
+ { for selected_columns }
>
}
diff --git a/rust/perspective-viewer/src/rust/components/column_selector/config_selector.rs b/rust/perspective-viewer/src/rust/components/column_selector/config_selector.rs
index 4e1e4b282e..654fcc7211 100644
--- a/rust/perspective-viewer/src/rust/components/column_selector/config_selector.rs
+++ b/rust/perspective-viewer/src/rust/components/column_selector/config_selector.rs
@@ -64,6 +64,7 @@ pub enum ConfigSelectorMsg {
TransposePivots,
ViewCreated,
New(DragTarget, InPlaceColumn),
+ UpdateGroupRollupMode(GroupRollupMode),
}
#[derive(Clone)]
@@ -136,11 +137,9 @@ impl Component for ConfigSelector {
ctx.props().onselect.emit(());
false
},
- ConfigSelectorMsg::Close(index, DragTarget::GroupBy) => {
- let mut group_by = ctx.props().session.get_view_config().group_by.clone();
- group_by.remove(index);
+ ConfigSelectorMsg::UpdateGroupRollupMode(mode) => {
let config = ViewConfigUpdate {
- group_by: Some(group_by),
+ group_rollup_mode: Some(mode),
..ViewConfigUpdate::default()
};
@@ -149,9 +148,45 @@ impl Component for ConfigSelector {
.map(ApiFuture::spawn)
.unwrap_or_log();
- ctx.props().onselect.emit(());
false
},
+ ConfigSelectorMsg::Close(index, DragTarget::GroupBy) => {
+ if ctx.props().session.get_view_config().group_rollup_mode == GroupRollupMode::Total
+ {
+ let requirements = ctx.props().renderer.metadata();
+
+ let rollup_features = ctx
+ .props()
+ .session
+ .metadata()
+ .get_features()
+ .map(|x| x.get_group_rollup_modes())
+ .unwrap();
+
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
+
+ ctx.link()
+ .send_message(ConfigSelectorMsg::UpdateGroupRollupMode(
+ group_rollups.first().cloned().unwrap(),
+ ));
+ false
+ } else {
+ let mut group_by = ctx.props().session.get_view_config().group_by.clone();
+ group_by.remove(index);
+ let config = ViewConfigUpdate {
+ group_by: Some(group_by),
+ ..ViewConfigUpdate::default()
+ };
+
+ ctx.props()
+ .update_and_render(config)
+ .map(ApiFuture::spawn)
+ .unwrap_or_log();
+
+ ctx.props().onselect.emit(());
+ false
+ }
+ },
ConfigSelectorMsg::Close(index, DragTarget::SplitBy) => {
let mut split_by = ctx.props().session.get_view_config().split_by.clone();
split_by.remove(index);
@@ -228,6 +263,7 @@ impl Component for ConfigSelector {
ctx.props().onselect.emit(());
false
},
+
ConfigSelectorMsg::SetFilterValue(index, input) => {
let mut filter = ctx.props().session.get_view_config().filter.clone();
@@ -438,11 +474,15 @@ impl Component for ConfigSelector {
let config = session.get_view_config();
let transpose = ctx.link().callback(|_| ConfigSelectorMsg::TransposePivots);
let column_dropdown = self.column_dropdown.clone();
- let class = if dragdrop.get_drag_column().is_some() {
- "dragdrop-highlight"
- } else {
- ""
- };
+ let mut class = classes!();
+
+ if dragdrop.get_drag_column().is_some() {
+ class.push("dragdrop-highlight");
+ }
+
+ if config.group_rollup_mode == GroupRollupMode::Total {
+ class.push("group-rollup-mode-total");
+ }
let dragend = Callback::from({
let dragdrop = dragdrop.clone();
@@ -452,57 +492,51 @@ impl Component for ConfigSelector {
let metadata = session.metadata();
let features = metadata.get_features().unwrap();
let requirements = renderer.metadata();
+ let on_group_rollup_mode = ctx
+ .link()
+ .callback(ConfigSelectorMsg::UpdateGroupRollupMode);
- let on_group_rollup_mode = Callback::from({
- let props = ctx.props().clone();
- move |x| {
- let config = ViewConfigUpdate {
- group_rollup_mode: Some(x),
- ..ViewConfigUpdate::default()
- };
+ let rollup_features = ctx
+ .props()
+ .session
+ .metadata()
+ .get_features()
+ .map(|x| x.get_group_rollup_modes())
+ .unwrap();
- props
- .update_and_render(config)
- .map(ApiFuture::spawn)
- .unwrap_or_log();
- }
- });
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
html! {
- if !config.group_by.is_empty() {
- if requirements.group_rollups.as_ref().map(|x| x.len()).unwrap_or_default() > 1 {
-
if features.group_by {
>()}
@@ -515,7 +549,7 @@ impl Component for ConfigSelector {
action={DragTarget::GroupBy}
column={group_by.clone()}
{dragdrop}
- {session}
+ opt_session={session}
>
}
@@ -546,9 +580,8 @@ impl Component for ConfigSelector {
+ opt_session={session}>
}
}) }
diff --git a/rust/perspective-viewer/src/rust/components/column_selector/pivot_column.rs b/rust/perspective-viewer/src/rust/components/column_selector/pivot_column.rs
index 5812f875eb..71ecedab51 100644
--- a/rust/perspective-viewer/src/rust/components/column_selector/pivot_column.rs
+++ b/rust/perspective-viewer/src/rust/components/column_selector/pivot_column.rs
@@ -26,11 +26,15 @@ pub struct PivotColumnProps {
/// Column name.
pub column: String,
+ #[prop_or_default]
+ pub column_type: Option,
+
/// The drag starte of this column, if applicable.
pub action: DragTarget,
// State
- pub session: Session,
+ #[prop_or_default]
+ pub opt_session: Option,
pub dragdrop: DragDrop,
}
@@ -74,12 +78,13 @@ impl Component for PivotColumn {
move |_event| dragdrop.notify_drag_end()
});
- let col_type = ctx
- .props()
- .session
- .metadata()
- .get_column_table_type(&ctx.props().column)
- .unwrap_or(ColumnType::Integer);
+ let col_type = ctx.props().column_type.unwrap_or_else(|| {
+ ctx.props()
+ .opt_session
+ .as_ref()
+ .and_then(|x| x.metadata().get_column_table_type(&ctx.props().column))
+ .unwrap_or(ColumnType::Integer)
+ });
html! {
::Properties: DragDropListItemProps,
{
pub parent: Scope
,
+
pub dragdrop: DragDrop,
pub name: &'static str,
pub column_dropdown: ColumnDropDownElement,
pub exclude: HashSet,
pub children: ChildrenWithProps,
+ #[prop_or_default]
+ pub disabled: bool,
+
#[prop_or_default]
pub is_dragover: Option<(
usize,
@@ -59,6 +65,7 @@ where
&& self.children == other.children
&& self.allow_duplicates == other.allow_duplicates
&& self.is_dragover == other.is_dragover
+ && self.disabled == other.disabled
}
}
@@ -283,20 +290,34 @@ where
let column_dropdown = ctx.props().column_dropdown.clone();
let exclude = ctx.props().exclude.clone();
let on_select = ctx.props().parent.callback(V::create);
+ let class = classes!("rrow");
+ let is_enabled = true;
+
html! {
-
+
{ columns_html }
- if ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag) {
+ if ctx.props().disabled && ctx.props().is_dragover.is_none() {
+
+ } else if ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag) {
} else if invalid_drag {
diff --git a/rust/perspective-viewer/src/rust/components/containers/scroll_panel.rs b/rust/perspective-viewer/src/rust/components/containers/scroll_panel.rs
index cff0c99fb5..b9f29e6069 100644
--- a/rust/perspective-viewer/src/rust/components/containers/scroll_panel.rs
+++ b/rust/perspective-viewer/src/rust/components/containers/scroll_panel.rs
@@ -34,6 +34,9 @@ pub struct ScrollPanelProps {
#[prop_or_default]
pub viewport_ref: Option,
+ #[prop_or_default]
+ pub initial_width: Option,
+
#[prop_or_default]
pub class: Classes,
@@ -52,6 +55,9 @@ pub struct ScrollPanelProps {
#[prop_or_default]
pub on_resize: Option>>,
+ #[prop_or_default]
+ pub on_auto_width: Callback,
+
#[prop_or_default]
pub on_dimensions_reset: Option>>,
@@ -125,7 +131,7 @@ impl Component for ScrollPanel {
Self {
viewport_ref: Default::default(),
viewport_height: 0f64,
- viewport_width: 0f64,
+ viewport_width: ctx.props().initial_width.unwrap_or_default(),
content_window: None,
needs_rerender: true,
total_height,
@@ -150,6 +156,7 @@ impl Component for ScrollPanel {
self.viewport_height = rect.height() - 8.0;
self.viewport_width = self.viewport_width.max(rect.width() - 6.0);
+ ctx.props().on_auto_width.emit(self.viewport_width);
re_render
},
ScrollPanelMsg::CalculateWindowContent => self.calculate_window_content(ctx),
diff --git a/rust/perspective-viewer/src/rust/components/containers/split_panel.rs b/rust/perspective-viewer/src/rust/components/containers/split_panel.rs
index d504135a2b..2fe4b4b709 100644
--- a/rust/perspective-viewer/src/rust/components/containers/split_panel.rs
+++ b/rust/perspective-viewer/src/rust/components/containers/split_panel.rs
@@ -201,11 +201,11 @@ impl Component for SplitPanel {
let count = iter.len();
let contents = html! {
<>
-
+
for (i, x) in iter {
if i == 0 {
if count == 1 {
-
+
{x}
>
} else {
diff --git a/rust/perspective-viewer/src/rust/components/plugin_selector.rs b/rust/perspective-viewer/src/rust/components/plugin_selector.rs
index 219bdad458..cb44d0d2b5 100644
--- a/rust/perspective-viewer/src/rust/components/plugin_selector.rs
+++ b/rust/perspective-viewer/src/rust/components/plugin_selector.rs
@@ -85,12 +85,15 @@ impl Component for PluginSelector {
let prev_metadata = renderer.metadata();
let requirements = metadata.as_ref().unwrap_or(&*prev_metadata);
+ let rollup_features = session
+ .metadata()
+ .get_features()
+ .map(|x| x.get_group_rollup_modes())
+ .unwrap();
+
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
let mut update = ViewConfigUpdate {
- group_rollup_mode: requirements
- .group_rollups
- .as_ref()
- .and_then(|x| x.first())
- .cloned(),
+ group_rollup_mode: group_rollups.first().cloned(),
..ViewConfigUpdate::default()
};
diff --git a/rust/perspective-viewer/src/rust/js/plugin.rs b/rust/perspective-viewer/src/rust/js/plugin.rs
index 973e553998..991fe40654 100644
--- a/rust/perspective-viewer/src/rust/js/plugin.rs
+++ b/rust/perspective-viewer/src/rust/js/plugin.rs
@@ -150,7 +150,7 @@ pub struct ViewConfigRequirements {
pub max_cells: Option,
pub name: String,
pub render_warning: bool,
- pub group_rollups: Option>,
+ group_rollups: Option>,
}
impl ViewConfigRequirements {
@@ -160,6 +160,17 @@ impl ViewConfigRequirements {
.map(|x| index < x.len() - 1)
.unwrap_or(false)
}
+
+ pub fn get_group_rollups(&self, rollup_features: &[GroupRollupMode]) -> Vec {
+ self.group_rollups
+ .clone()
+ .map(|x| {
+ x.into_iter()
+ .filter(|y| rollup_features.is_empty() || rollup_features.contains(y))
+ .collect()
+ })
+ .unwrap_or_default()
+ }
}
impl JsPerspectiveViewerPlugin {
diff --git a/rust/perspective-viewer/src/rust/session/column_defaults_update.rs b/rust/perspective-viewer/src/rust/session/column_defaults_update.rs
index 74ee6b45d7..16e755d826 100644
--- a/rust/perspective-viewer/src/rust/session/column_defaults_update.rs
+++ b/rust/perspective-viewer/src/rust/session/column_defaults_update.rs
@@ -31,24 +31,18 @@ pub impl ViewConfigUpdate {
columns: &[Option],
requirements: &ViewConfigRequirements,
) {
- if requirements
- .group_rollups
- .as_ref()
- .map(|x| {
- !x.contains(
- self.group_rollup_mode
- .as_ref()
- .unwrap_or(&GroupRollupMode::Rollup),
- )
- })
- .unwrap_or_default()
- {
- self.group_rollup_mode = requirements
- .group_rollups
- .as_ref()
- .and_then(|x| x.first())
- .cloned();
+ let rollup_features = metadata
+ .get_features()
+ .map(|x| x.get_group_rollup_modes())
+ .unwrap_or_default();
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
+ if !group_rollups.contains(
+ self.group_rollup_mode
+ .as_ref()
+ .unwrap_or(&GroupRollupMode::Rollup),
+ ) {
+ self.group_rollup_mode = group_rollups.first().cloned();
tracing::error!(
"Setting plugin-advised rollup mode {:?}",
self.group_rollup_mode
diff --git a/rust/perspective-viewer/src/themes/botanical.less b/rust/perspective-viewer/src/themes/botanical.less
new file mode 100644
index 0000000000..74b986d1a7
--- /dev/null
+++ b/rust/perspective-viewer/src/themes/botanical.less
@@ -0,0 +1,142 @@
+// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
+// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
+// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
+// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
+// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
+// ┃ Copyright (c) 2017, the Perspective Authors. ┃
+// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
+// ┃ This file is part of the Perspective library, distributed under the terms ┃
+// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
+// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+@import "icons.less";
+@import "intl.less";
+
+@import url("ref://pro.less");
+
+// Register theme for auto-detection
+perspective-viewer,
+perspective-viewer[theme="Botanical"] {
+ --theme-name: "Botanical";
+}
+
+perspective-viewer[theme="Botanical"] {
+ @include perspective-viewer-botanical;
+}
+
+perspective-copy-menu[theme="Botanical"],
+perspective-export-menu[theme="Botanical"],
+perspective-dropdown[theme="Botanical"],
+perspective-date-column-style[theme="Botanical"],
+perspective-datetime-column-style[theme="Botanical"],
+perspective-number-column-style[theme="Botanical"],
+perspective-string-column-style[theme="Botanical"] {
+ @include perspective-modal-botanical;
+}
+
+@mixin perspective-viewer-botanical {
+ @include perspective-viewer-pro;
+ @include perspective-viewer-botanical--colors;
+ @include perspective-viewer-botanical--datagrid;
+ @include perspective-viewer-botanical--d3fc;
+ @include perspective-viewer-botanical--openlayers;
+}
+
+@mixin perspective-modal-botanical {
+ @include perspective-modal-pro;
+ @include perspective-viewer-botanical--colors;
+
+ background-color: #1a2e1a;
+ border: 1px solid #3d5c3d;
+}
+
+@mixin perspective-viewer-botanical--colors {
+ background-color: #1a2e1a;
+ color: #e0ead8;
+ --icon--color: #e0ead8;
+ --active--color: #5a9e4b;
+ --error--color: #e8836a;
+ --inactive--color: #526b4a;
+ --inactive--border-color: #3d5c3d;
+ --plugin--background: #1a2e1a;
+ --modal-target--background: rgba(224, 234, 216, 0.05);
+ --active--background: rgba(90, 158, 75, 0.5);
+ --expression--operator-color: #b8c9ad;
+ --expression--function-color: #7bc96f;
+ --expression--error-color: rgb(232, 131, 106);
+ --calendar--filter: invert(1);
+ --warning--color: #1a2e1a;
+ --warning--background: var(--icon--color);
+
+ --select-arrow--background-image: var(
+ --select-arrow-light--background-image
+ );
+
+ --select-arrow--hover--background-image: var(
+ --select-arrow-dark--background-image
+ );
+
+ // Syntax
+ --code-editor-symbol--color: #e0ead8;
+ --code-editor-literal--color: #a8d8a0;
+ --code-editor-operator--color: rgb(206, 176, 104);
+ --code-editor-comment--color: rgb(120, 160, 100);
+ --code-editor-column--color: #c9a0d8;
+}
+
+@mixin perspective-viewer-botanical--openlayers {
+ --map-tile-url: "http://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png";
+ --map-attribution--filter: invert(1) hue-rotate(180deg);
+ --map-element-background: #1e3420;
+ --map-category-1: rgb(90, 158, 75);
+ --map-category-2: rgb(206, 176, 104);
+ --map-category-3: rgb(160, 110, 180);
+ --map-category-4: rgb(80, 170, 150);
+ --map-category-5: rgb(140, 170, 90);
+ --map-category-6: rgb(200, 120, 140);
+ --map-category-7: rgb(100, 150, 190);
+ --map-category-8: rgb(210, 140, 80);
+ --map-category-9: rgb(130, 120, 190);
+ --map-category-10: rgb(170, 200, 110);
+ --map-gradient: linear-gradient(#e8836a 0%, #1a2e1a 50%, #5a9e4b 100%);
+}
+
+@mixin perspective-viewer-botanical--datagrid {
+ --rt-pos-cell--color: #7bc96f;
+ --rt-neg-cell--color: #ebac21;
+}
+
+@mixin perspective-viewer-botanical--d3fc {
+ --d3fc-legend--text: #b8c9ad;
+ --d3fc-treedata--labels: #e0ead8;
+ --d3fc-treedata--hover-highlight: #e0ead8;
+ --d3fc-tooltip--color: #e0ead8;
+ --d3fc-axis-ticks--color: #b8c9ad;
+ --d3fc-axis--lines: #526b4a;
+ --d3fc-gridline--color: #2a4228;
+ --d3fc-tooltip--background: rgba(30, 52, 32, 1);
+ --d3fc-tooltip--border-color: #1a2e1a;
+ --d3fc-legend--background: var(--plugin--background);
+
+ --d3fc-series: rgb(90, 158, 75);
+ --d3fc-series-1: rgb(90, 158, 75);
+ --d3fc-series-2: rgb(206, 176, 104);
+ --d3fc-series-3: rgb(160, 110, 180);
+ --d3fc-series-4: rgb(80, 170, 150);
+ --d3fc-series-5: rgb(140, 170, 90);
+ --d3fc-series-6: rgb(200, 120, 140);
+ --d3fc-series-7: rgb(100, 150, 190);
+ --d3fc-series-8: rgb(210, 140, 80);
+ --d3fc-series-9: rgb(130, 120, 190);
+ --d3fc-series-10: rgb(170, 200, 110);
+
+ --d3fc-full--gradient: linear-gradient(
+ #e8836a 0%,
+ #1a2e1a 50%,
+ #5a9e4b 100%
+ );
+
+ --d3fc-positive--gradient: linear-gradient(#1a2e1a 0%, #5a9e4b 100%);
+ --d3fc-negative--gradient: linear-gradient(#e8836a 0%, #1a2e1a 100%);
+}
diff --git a/rust/perspective-viewer/src/themes/themes.less b/rust/perspective-viewer/src/themes/themes.less
index ef70b6015d..13cb32aa5a 100644
--- a/rust/perspective-viewer/src/themes/themes.less
+++ b/rust/perspective-viewer/src/themes/themes.less
@@ -18,4 +18,5 @@
@import "vaporwave.less";
@import "gruvbox.less";
@import "gruvbox-dark.less";
-@import "dracula.less";
\ No newline at end of file
+@import "dracula.less";
+@import "botanical.less";
\ No newline at end of file
diff --git a/tools/test/results.tar.gz b/tools/test/results.tar.gz
index 31f2865855..564106469f 100644
Binary files a/tools/test/results.tar.gz and b/tools/test/results.tar.gz differ