Compare commits
2 Commits
235e14dcea
...
4124f6dd88
Author | SHA1 | Date |
---|---|---|
Joe Bellus | 4124f6dd88 | |
Joe Bellus | a315260faf |
|
@ -6,7 +6,7 @@ use std::{
|
|||
str::FromStr,
|
||||
};
|
||||
|
||||
use rhai::EvalAltResult;
|
||||
use rhai::{Dynamic, EvalAltResult, EvalContext, Expression, Position};
|
||||
|
||||
pub fn setup_engine(engine: &mut rhai::Engine) {
|
||||
//polar data frame
|
||||
|
@ -52,6 +52,18 @@ pub fn setup_engine(engine: &mut rhai::Engine) {
|
|||
engine.register_fn("min", script_functions::min);
|
||||
engine.register_fn("max", script_functions::max);
|
||||
engine.register_fn("first", script_functions::first);
|
||||
let _ = engine.register_custom_operator("gt", 200);
|
||||
let _ = engine.register_custom_operator("gte", 200);
|
||||
engine.register_fn("gt", script_functions::gt_op);
|
||||
engine.register_fn("gte", script_functions::gte_op);
|
||||
|
||||
engine
|
||||
.register_custom_syntax(
|
||||
["from", "$ident$", "$expr$", ":", "$expr$"], // the custom syntax
|
||||
true, // variables declared within this custom syntax
|
||||
implementation_df_select,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -339,6 +351,82 @@ mod script_functions {
|
|||
Ok(Series(s))
|
||||
}
|
||||
}
|
||||
pub fn gt_op(a: &str, b: rhai::Dynamic) -> DataFrameExpression {
|
||||
DataFrameExpression(polars::prelude::col(a).gt(DataFrameExpression::from(b)))
|
||||
}
|
||||
|
||||
pub fn gte_op(a: &str, b: rhai::Dynamic) -> DataFrameExpression {
|
||||
DataFrameExpression(polars::prelude::col(a).gt_eq(DataFrameExpression::from(b)))
|
||||
}
|
||||
}
|
||||
|
||||
fn implementation_df_select(
|
||||
context: &mut EvalContext,
|
||||
inputs: &[Expression],
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let df_name = inputs[0].get_string_value().ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorVariableNotFound(
|
||||
"variable not found".to_string(),
|
||||
Position::default(),
|
||||
))
|
||||
})?;
|
||||
|
||||
let df = context
|
||||
.scope()
|
||||
.get(df_name)
|
||||
.ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorVariableNotFound(
|
||||
format!("{} not found", df_name),
|
||||
Position::default(),
|
||||
))
|
||||
})?
|
||||
.clone()
|
||||
.try_cast::<DataFrame>()
|
||||
.ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorVariableNotFound(
|
||||
format!("{} not found", df_name),
|
||||
Position::default(),
|
||||
))
|
||||
})?;
|
||||
|
||||
let raw_filter_array = context.eval_expression_tree(&inputs[2])?;
|
||||
let filter_array = raw_filter_array
|
||||
.into_array()
|
||||
.map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorVariableNotFound(
|
||||
format!("{} value not an array", e),
|
||||
Position::default(),
|
||||
))
|
||||
})?
|
||||
.into_iter()
|
||||
.map(|i| i.cast::<DataFrameExpression>())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let raw_select_array = context.eval_expression_tree(&inputs[1])?;
|
||||
let select_array = raw_select_array.into_array().map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorVariableNotFound(
|
||||
format!("{} value not an array", e),
|
||||
Position::default(),
|
||||
))
|
||||
})?;
|
||||
|
||||
let select_expressions = select_array
|
||||
.iter()
|
||||
.map(|s| {
|
||||
filter_array
|
||||
.iter()
|
||||
.fold(polars::prelude::col(&s.to_string()), |acc, i| {
|
||||
acc.filter(i.0.clone())
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Dynamic::from(DataFrame(
|
||||
df.0.lazy()
|
||||
.select(&select_expressions)
|
||||
.collect()
|
||||
.map_err(|e| e.to_string())?,
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -494,4 +582,26 @@ s1 + s2
|
|||
.collect::<Vec<_>>();
|
||||
assert_eq!(s, vec![Some(18)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_dataframe_select_syntax() {
|
||||
let res = process(
|
||||
r#"
|
||||
let data = load_csv("test/data.csv");
|
||||
from data ["age"] : ["age" gt 18];
|
||||
"#,
|
||||
);
|
||||
|
||||
dbg!(&res);
|
||||
|
||||
let s = res
|
||||
.into_frame()
|
||||
.column("age")
|
||||
.unwrap()
|
||||
.i64()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(s, vec![Some(22), Some(32)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
name,age,
|
||||
alice,18
|
||||
sasha,22
|
||||
lacey,32
|
|
|
@ -63,7 +63,7 @@ impl AppDelegate<AppData> for Delegate {
|
|||
|
||||
if cmd.is(commands::DELETE_BLOCK) {
|
||||
if let Some(index) = cmd.get(commands::DELETE_BLOCK) {
|
||||
data.blocks.remove(*index);
|
||||
data.remove_block(*index);
|
||||
return druid::Handled::Yes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,7 +115,6 @@ pub fn header_separater() -> impl Widget<AppData> {
|
|||
ctx.fill(rect, &Color::rgb8(0, 0, 0))
|
||||
})
|
||||
.fix_height(1.0)
|
||||
.expand_width()
|
||||
}
|
||||
|
||||
pub struct ToolbarButtonController {
|
||||
|
|
|
@ -5,3 +5,5 @@ pub const PROCESS_BLOCK: Selector<usize> = Selector::new("process-block");
|
|||
pub const DELETE_BLOCK: Selector<usize> = Selector::new("delete-block");
|
||||
|
||||
pub const RENAME_BLOCK: Selector<usize> = Selector::new("rename-block");
|
||||
|
||||
pub const CLOSE_MODAL: Selector<()> = Selector::new("close-modal");
|
||||
|
|
|
@ -14,6 +14,15 @@ pub struct AppData {
|
|||
pub modals: Modals,
|
||||
}
|
||||
|
||||
impl AppData {
|
||||
pub fn remove_block(&mut self, idx: usize) {
|
||||
self.blocks.remove(idx);
|
||||
for (idx, block) in self.blocks.iter_mut().enumerate() {
|
||||
block.index = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
|
@ -9,5 +9,5 @@ pub struct RenameBlock {
|
|||
|
||||
#[derive(Clone, Data, Lens, Default, Debug)]
|
||||
pub struct Modals {
|
||||
pub rename_block: Option<RenameBlock>,
|
||||
pub rename_block: RenameBlock,
|
||||
}
|
||||
|
|
|
@ -41,10 +41,10 @@ impl AbacusEditor {
|
|||
let rects = layout.rects_for_range(0..1);
|
||||
let rect = rects.first().unwrap();
|
||||
let rect = Rect::new(
|
||||
rect.min_x() - 1.0,
|
||||
rect.min_y(),
|
||||
rect.min_x() + 1.0,
|
||||
rect.min_y() + 2.0,
|
||||
rect.min_x() + 3.0,
|
||||
rect.max_y() - 2.0,
|
||||
rect.max_y(),
|
||||
);
|
||||
ctx.fill(
|
||||
rect,
|
||||
|
@ -55,10 +55,10 @@ impl AbacusEditor {
|
|||
.last()
|
||||
{
|
||||
let cursor_rect = Rect::new(
|
||||
char_rect.max_x() + 3.0,
|
||||
char_rect.min_y() + 2.0,
|
||||
char_rect.max_x() + 5.0,
|
||||
char_rect.max_y() - 2.0,
|
||||
char_rect.max_x() - 1.0,
|
||||
char_rect.min_y(),
|
||||
char_rect.max_x() + 1.0,
|
||||
char_rect.max_y(),
|
||||
);
|
||||
|
||||
ctx.fill(
|
||||
|
@ -204,6 +204,10 @@ impl Widget<EditorData> for AbacusEditor {
|
|||
data.mode = EditMode::Insert;
|
||||
data.cursor_to_end_of_line();
|
||||
}
|
||||
"I" => {
|
||||
data.cursor_to_start_of_line();
|
||||
data.mode = EditMode::Insert;
|
||||
}
|
||||
"a" => {
|
||||
if e.mods.ctrl() {
|
||||
data.select_all();
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use crate::data;
|
||||
use druid::widget::{Controller, Padding};
|
||||
use druid::{
|
||||
widget::{Container, Label},
|
||||
RenderContext, Widget, WidgetPod,
|
||||
widget::{Container, Flex, Label, LensWrap, TextBox},
|
||||
Color, FontDescriptor, FontFamily, FontWeight, RenderContext, Widget, WidgetExt, WidgetPod,
|
||||
};
|
||||
use druid::{Data, LifeCycle, TextAlignment};
|
||||
|
||||
pub struct ModalContainer {
|
||||
child: WidgetPod<data::AppData, Box<dyn Widget<data::AppData>>>,
|
||||
|
@ -26,25 +28,40 @@ impl Widget<data::AppData> for ModalContainer {
|
|||
data: &mut data::AppData,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
if let druid::Event::Notification(n) = event {
|
||||
if n.is(crate::commands::CLOSE_MODAL) {
|
||||
self.modal = None;
|
||||
data.modals.rename_block = data::RenameBlock::default();
|
||||
ctx.children_changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let druid::Event::Command(c) = event {
|
||||
if let Some(idx) = c.get(crate::commands::RENAME_BLOCK) {
|
||||
println!("rename block");
|
||||
data.modals.rename_block = Some(data::RenameBlock {
|
||||
data.modals.rename_block = data::RenameBlock {
|
||||
block_index: *idx,
|
||||
name: data
|
||||
.blocks
|
||||
.get(*idx)
|
||||
.map(|i| i.name.clone())
|
||||
.unwrap_or_default(),
|
||||
input: String::new(),
|
||||
});
|
||||
input: data
|
||||
.blocks
|
||||
.get(*idx)
|
||||
.map(|i| i.name.clone())
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
self.modal = Some(WidgetPod::new(rename_block()).boxed());
|
||||
ctx.children_changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.modal.is_none() {
|
||||
if let Some(m) = self.modal.as_mut() {
|
||||
m.event(ctx, event, data, env);
|
||||
} else {
|
||||
self.child.event(ctx, event, data, env);
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +73,9 @@ impl Widget<data::AppData> for ModalContainer {
|
|||
data: &data::AppData,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
modal.lifecycle(ctx, event, data, env);
|
||||
}
|
||||
self.child.lifecycle(ctx, event, data, env);
|
||||
}
|
||||
|
||||
|
@ -66,6 +86,9 @@ impl Widget<data::AppData> for ModalContainer {
|
|||
data: &data::AppData,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
modal.update(ctx, data, env);
|
||||
}
|
||||
self.child.update(ctx, data, env);
|
||||
}
|
||||
|
||||
|
@ -76,6 +99,9 @@ impl Widget<data::AppData> for ModalContainer {
|
|||
data: &data::AppData,
|
||||
env: &druid::Env,
|
||||
) -> druid::Size {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
modal.layout(ctx, bc, data, env);
|
||||
}
|
||||
self.child.layout(ctx, bc, data, env)
|
||||
}
|
||||
|
||||
|
@ -92,5 +118,142 @@ impl Widget<data::AppData> for ModalContainer {
|
|||
}
|
||||
|
||||
fn rename_block() -> impl Widget<data::AppData> {
|
||||
Container::new(Label::new("rename block").with_text_color(druid::Color::WHITE))
|
||||
modal_container(
|
||||
"Rename Block",
|
||||
(300.0, 150.0),
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Label::new("Block name")
|
||||
.with_text_color(druid::Color::WHITE)
|
||||
.with_text_alignment(TextAlignment::Start)
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::SYSTEM_UI)
|
||||
.with_weight(FontWeight::BOLD)
|
||||
.with_size(14.0),
|
||||
)
|
||||
.fix_width(300.0),
|
||||
)
|
||||
.with_spacer(5.0)
|
||||
.with_child(LensWrap::new(
|
||||
LensWrap::new(
|
||||
TextBox::default()
|
||||
.lens(data::RenameBlock::input)
|
||||
.fix_width(300.0),
|
||||
data::Modals::rename_block,
|
||||
),
|
||||
data::AppData::modals,
|
||||
))
|
||||
.with_spacer(20.0)
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.must_fill_main_axis(true)
|
||||
.with_child(modal_action("Cancel").on_click(|ctx, _, _| {
|
||||
ctx.submit_notification(crate::commands::CLOSE_MODAL)
|
||||
}))
|
||||
.with_flex_spacer(1.0)
|
||||
.with_child(modal_action("Rename").on_click(
|
||||
|ctx, data: &mut data::AppData, _| {
|
||||
let input = data.modals.rename_block.input.clone();
|
||||
if let Some(blk) =
|
||||
data.blocks.get_mut(data.modals.rename_block.block_index)
|
||||
{
|
||||
blk.name = input;
|
||||
data.modals.rename_block = data::RenameBlock::default();
|
||||
ctx.submit_notification(crate::commands::CLOSE_MODAL)
|
||||
}
|
||||
},
|
||||
))
|
||||
.fix_width(300.0),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn modal_container<S: Into<druid::Size>>(
|
||||
title: &str,
|
||||
size: S,
|
||||
child: impl Widget<data::AppData> + 'static,
|
||||
) -> impl Widget<data::AppData> {
|
||||
let size = size.into();
|
||||
Flex::column()
|
||||
.with_child(modal_title(title, size.width))
|
||||
.with_child(Padding::new(15.0, child))
|
||||
.background(Color::rgb8(20, 20, 20))
|
||||
.center()
|
||||
.fix_size(size.width, size.height)
|
||||
}
|
||||
|
||||
fn modal_title(title: &str, width: f64) -> impl Widget<data::AppData> {
|
||||
Container::new(Padding::new(
|
||||
8.0,
|
||||
Flex::row()
|
||||
.with_spacer(10.0)
|
||||
.with_child(
|
||||
Label::new(title)
|
||||
.with_text_color(Color::rgb8(150, 150, 150))
|
||||
.with_text_alignment(TextAlignment::Start)
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::SYSTEM_UI)
|
||||
.with_weight(FontWeight::BOLD)
|
||||
.with_size(14.0),
|
||||
)
|
||||
.fix_width(width),
|
||||
)
|
||||
.with_spacer(10.0),
|
||||
))
|
||||
.background(Color::rgb8(10, 10, 10))
|
||||
}
|
||||
|
||||
fn modal_action<T: Data>(text: &str) -> impl Widget<T> {
|
||||
Container::new(Padding::new(5.0, Label::new(text)))
|
||||
.rounded(4.0)
|
||||
.controller(ModalActionController)
|
||||
}
|
||||
|
||||
pub struct ModalActionController;
|
||||
|
||||
impl<T: Data> Controller<T, Container<T>> for ModalActionController {
|
||||
fn event(
|
||||
&mut self,
|
||||
child: &mut Container<T>,
|
||||
ctx: &mut druid::EventCtx,
|
||||
event: &druid::Event,
|
||||
data: &mut T,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
ctx.set_cursor(&druid::Cursor::Pointer);
|
||||
child.event(ctx, event, data, env)
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
child: &mut Container<T>,
|
||||
ctx: &mut druid::LifeCycleCtx,
|
||||
event: &druid::LifeCycle,
|
||||
data: &T,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
match event {
|
||||
LifeCycle::HotChanged(true) => {
|
||||
child.set_background(Color::rgb8(100, 100, 100));
|
||||
ctx.request_paint();
|
||||
}
|
||||
LifeCycle::HotChanged(false) => {
|
||||
child.set_background(Color::TRANSPARENT);
|
||||
ctx.request_paint();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
child.lifecycle(ctx, event, data, env)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
child: &mut Container<T>,
|
||||
ctx: &mut druid::UpdateCtx,
|
||||
old_data: &T,
|
||||
data: &T,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
child.update(ctx, old_data, data, env)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,47 @@ pub fn output_block() -> impl Widget<Block> {
|
|||
.expand_width(),
|
||||
))
|
||||
}
|
||||
Output::DataFrame(frame) => {
|
||||
let mut flex = Flex::row();
|
||||
for series in frame.iter() {
|
||||
let mut col = Flex::column();
|
||||
col.add_child(
|
||||
Label::new(series.name())
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::MONOSPACE)
|
||||
.with_weight(FontWeight::BLACK),
|
||||
)
|
||||
.with_text_size(OUTPUT_FONT_SIZE)
|
||||
.expand_width()
|
||||
.padding(3.0)
|
||||
.border(Color::rgb8(60, 60, 60), 1.0),
|
||||
);
|
||||
|
||||
for v in series.iter() {
|
||||
col.add_child(
|
||||
Label::new(v.to_string())
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::MONOSPACE)
|
||||
.with_weight(FontWeight::MEDIUM),
|
||||
)
|
||||
.with_text_size(OUTPUT_FONT_SIZE)
|
||||
.expand_width()
|
||||
.padding(3.0)
|
||||
.border(Color::rgb8(60, 60, 60), 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
flex.add_flex_child(col, 1.0);
|
||||
}
|
||||
|
||||
Box::new(Padding::new(
|
||||
25.0,
|
||||
flex.padding(10.0)
|
||||
.background(Color::rgb8(30, 30, 30))
|
||||
.rounded(4.0)
|
||||
.expand_width(),
|
||||
))
|
||||
}
|
||||
_ => Box::new(Padding::new(
|
||||
0.0,
|
||||
Label::new("")
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"blocks": [
|
||||
{
|
||||
"name": "Block #1",
|
||||
"content": "dataframe(#{ names: [\"Alice\", \"Bob\", \"Charles\"], ages: [18,21,35] })"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"blocks": [
|
||||
{
|
||||
"name": "Things",
|
||||
"content": "let df = dataframe(#{\n names: [\"Alice\", \"Charles\", \"Bob\"], \n ages: [18,25,31],\n id: [1,2,3],\n amount: [10,0,12]\n});\n\n//df.select([column(\"ages\").filter(column(\"ages\") gt 20)]);\n\nlet df2 = from df [\"names\", \"ages\"] : [\"ages\" gt 20];\ndf2"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"blocks": [
|
||||
{
|
||||
"name": "Things",
|
||||
"content": "let x = 1;\nx+1"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue