Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/formatters #39

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/py-tabulator.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion .idea/watcherTasks.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion docs/examples/getting_started/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import pandas as pd
from pytabulator.shiny_bindings import output_tabulator, render_tabulator
from pytabulator.tabulator import Tabulator
from pytabulator.editors import NumberEditor, StarEditor, ProgressEditor
from pytabulator.formatters import StarFormatter
from shiny import App, render, ui

app_ui = ui.page_fluid(
Expand All @@ -15,7 +17,30 @@ def tabulator():
df = pd.read_csv(
"https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
)
return Tabulator(df, table_options={"height": 311})
"""
return Tabulator(df, options={"height": 311}).set_column_formatter(
"Pclass", "star", {"stars": 3}, hozAlign="center"
)
"""
return (
Tabulator(df)
.set_options(height=311)
# .set_column_formatter_star("Pclass", 3)
.set_column_formatter("Pclass", StarFormatter(stars=3), hoz_align="center")
.set_column_formatter_tick_cross("Survived", hoz_align="center")
# .set_column_editor("Fare", "number", dict(min=0, max=10))
.set_column_editor_number("Fare", min_=0, max_=5)
.set_column_title("Pclass", "PassengerClass")
.set_column_editor(["Name", "Sex"], "input", hoz_align="center")
.set_column_editor("PassengerId", NumberEditor(min=0, max=1000, step=1))
.set_column_editor("Pclass", StarEditor())
.set_column_formatter("Fare", "progress")
.set_column_editor(
"Fare",
ProgressEditor(min=0, max=100, element_attributes=dict(title="Hey ho")),
hoz_align="left",
)
)

@render.code
async def txt():
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/getting_started/shiny_express_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def selected_rows():

@render_tabulator
def tabulator():
return Tabulator(df, table_options).options(
return Tabulator(df, table_options).set_options(
editTriggerEvent="dblclick"
) # .options(selectableRows=True)

Expand Down Expand Up @@ -136,4 +136,4 @@ async def trigger_get_data():
@reactive.Effect
@reactive.event(input.tabulator_data)
def tabulator_data():
print(input.tabulator_data()[0])
print(input.tabulator_data())
147 changes: 147 additions & 0 deletions docs/examples/getting_started/shiny_express_all_new_style.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from random import randrange

import pandas as pd
from pytabulator import TableOptions, Tabulator, TabulatorContext, render_tabulator
from pytabulator.utils import create_columns
from pytabulator.formatters import ProgressFormatter, TickCrossFormatter
from pytabulator.editors import ListEditor, InputEditor, ProgressEditor
from shiny import reactive, render
from shiny.express import input, ui

# Fetch data
#
df = pd.read_csv(
"https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
)[["PassengerId", "Name", "Pclass", "Sex", "Age", "Fare", "Survived"]]

# Setup
#
table_options = TableOptions(
columns=create_columns(
df,
default_filter=True,
default_editor=True,
updates={
"Pclass": {
"formatter": "star",
"formatterParams": {"stars": 3},
"hozAlign": "center",
},
# "Survived": {"formatter": "tickCross"},
# "Fare": {"formatter": "progress", "hozAlign": "left"},
},
),
height=413,
pagination=True,
pagination_add_row="table",
layout="fitColumns",
index="PassengerId",
add_row_pos="top",
selectable_rows=True,
history=True,
)

# Shiny Express App
#
with ui.div(style="padding-top: 0px;"):
ui.input_action_button("trigger_download", "Download")
ui.input_action_button("add_row", "Add row")
ui.input_action_button("delete_selected_rows", "Delete selected rows")
ui.input_action_button("undo", "Undo")
ui.input_action_button("redo", "Redo")
ui.input_action_button("trigger_get_data", "Submit data")

ui.div(
ui.input_text("name", "Click on 'Add row' to add the Person to the table."),
style="padding-top: 20px;",
)
ui.div("Click on a row to print the name of the person.", style="padding: 10px;"),


@render.code
async def txt():
print(input.tabulator_row_clicked())
return input.tabulator_row_clicked()["Name"]


ui.div(
"Select multiple rows to print the names of the selected persons.",
style="padding: 10px;",
),


@render.code
def selected_rows():
data = input.tabulator_rows_selected()
output = [item["Name"] for item in data]
return "\n".join(output)


@render_tabulator
def tabulator():
return (
Tabulator(df, table_options)
.set_options(editTriggerEvent="dblclick")
.set_column_formatter("Fare", ProgressFormatter(), hoz_align="left")
.set_column_formatter("Survived", TickCrossFormatter(), hoz_align="center")
.set_column_editor("Sex", ListEditor())
.set_column_editor("Name", InputEditor())
.set_column_editor("Fare", ProgressEditor(), hoz_align="left")
)


@reactive.Effect
@reactive.event(input.trigger_download)
async def trigger_download():
print("download triggered")
async with TabulatorContext("tabulator") as table:
table.trigger_download("csv")


@reactive.Effect
@reactive.event(input.add_row)
async def add_row():
async with TabulatorContext("tabulator") as table:
table.add_row(
{
"Name": input.name() or "Hans",
"Age": randrange(55),
"Survived": randrange(2),
"PassengerId": randrange(10000, 20000, 1),
"SibSp": randrange(9),
}
)


@reactive.Effect
@reactive.event(input.delete_selected_rows)
async def delete_selected_rows():
async with TabulatorContext("tabulator") as table:
table.delete_selected_rows()


@reactive.Effect
@reactive.event(input.undo)
async def undo():
async with TabulatorContext("tabulator") as table:
table.undo()


@reactive.Effect
@reactive.event(input.redo)
async def redo():
async with TabulatorContext("tabulator") as table:
table.redo()


@reactive.Effect
@reactive.event(input.trigger_get_data)
async def trigger_get_data():
async with TabulatorContext("tabulator") as table:
table.trigger_get_data()


@reactive.Effect
@reactive.event(input.tabulator_data)
def tabulator_data():
print(input.tabulator_data())
4 changes: 4 additions & 0 deletions get-py-bindings.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
branch=${1:-dev}
# curl -O https://raw.githubusercontent.com/eodaGmbH/tabulator-bindings/${branch}/r-bindings/rtabulator.js
curl -o pytabulator/srcjs/pytabulator.js https://raw.githubusercontent.com/eodaGmbH/tabulator-bindings/refs/heads/feature/typescript/py-bindings/pytabulator.js
4 changes: 3 additions & 1 deletion pytabulator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""
from importlib.metadata import PackageNotFoundError, version

try:
Expand All @@ -12,7 +13,8 @@
from ._table_options_dc import TableOptionsDC as TableOptions

# print("dataclass")

"""
from .tabulator_options import TabulatorOptions as TableOptions
# from ._table_options_pydantic import TableOptionsPydantic as TableOptions
from .shiny_bindings import output_tabulator, render_data_frame, render_tabulator
from .tabulator import Tabulator
Expand Down
8 changes: 8 additions & 0 deletions pytabulator/_abstracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel

from ._utils import as_camel_dict_recursive


class MyBaseModel(BaseModel):
def to_dict(self) -> dict:
return as_camel_dict_recursive(self.model_dump(exclude_none=True))
20 changes: 17 additions & 3 deletions pytabulator/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ def set_theme(stylesheet):
def snake_to_camel_case(snake_str: str) -> str:
return snake_str[0].lower() + snake_str.title()[1:].replace("_", "")

# return "".join(
# [item if not i else item.title() for i, item in enumerate(snake_str.split("_"))]
# )

def as_camel_dict(snake_dict: dict) -> dict:
return {snake_to_camel_case(k): v for (k, v) in snake_dict.items() if v is not None}


def as_camel_dict_recursive(snake_dict: dict) -> dict:
camel_case_dict = {}
for k, v in snake_dict.items():
if v is not None:
camel_key = snake_to_camel_case(k) if "_" in k else k

if isinstance(v, dict):
camel_case_dict[camel_key] = as_camel_dict_recursive(v)
else:
camel_case_dict[camel_key] = v

return camel_case_dict
4 changes: 4 additions & 0 deletions pytabulator/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from pandas import read_csv

def titanic():
pass
89 changes: 89 additions & 0 deletions pytabulator/editors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from enum import Enum
from typing import Literal, Optional

from ._abstracts import MyBaseModel


class Editors(Enum):
INPUT = "input"
TEXTAREA = "textarea"
NUMBER = "number"
RANGE = "range"
TICK_CROSS = "tickCross"
STAR = "star"
PROGRESS = "progress"
LIST = "list"


class Editor(MyBaseModel):
_name: str = ""

@property
def name(self) -> str:
return self._name


class InputEditor(Editor):
_name: str = Editors.INPUT.value

search: Optional[bool] = None
mask: Optional[str] = None
select_contents: Optional[bool] = None
element_attributes: Optional[dict] = None


class TextareaEditor(Editor):
_name: str = Editors.TEXTAREA.value

mask: Optional[str] = None
select_contents: Optional[bool] = None
vertical_navigation: Literal["hybrid", "editor", "table"] = None
shift_enter_submit: Optional[bool] = None


class NumberEditor(Editor):
_name: str = Editors.NUMBER.value

min: Optional[float] = None
max: Optional[float] = None
step: Optional[float] = None
element_attributes: Optional[dict] = None
mask: Optional[str] = None
select_contents: Optional[bool] = None
vertical_navigation: Literal["editor", "table"] = None


class RangeEditor(Editor):
_name: str = Editors.RANGE.value

min: Optional[float] = None
max: Optional[float] = None
step: Optional[float] = None
element_attributes: Optional[dict] = None


class TickCrossEditor(Editor):
_name: str = Editors.TICK_CROSS.value

true_value: Optional[str] = None
false_value: Optional[str] = None
element_attributes: Optional[dict] = None


class StarEditor(Editor):
_name: str = Editors.STAR.value


class ProgressEditor(Editor):
_name: str = Editors.PROGRESS.value

min: Optional[float] = None
max: Optional[float] = None
element_attributes: Optional[dict] = None


class ListEditor(Editor):
_name: str = Editors.LIST.value

values: Optional[list] = None
values_lookup: Optional[bool] = True
Loading
Loading