Skip to content

Commit

Permalink
Merge pull request #529 from madeindjs/data-frame-editor
Browse files Browse the repository at this point in the history
feat: Implement Data frame editor.WF-66
  • Loading branch information
ramedina86 authored Nov 14, 2024
2 parents 00dcd1d + f379733 commit b50bcda
Show file tree
Hide file tree
Showing 20 changed files with 1,888 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@
{"id": "e296866a-75d2-4677-b55d-3c1456113b89", "type": "text", "content": {"text": "Refreshing automatically using a timer."}, "isCodeManaged": false, "parentId": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d", "position": 1}
{"id": "db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12", "type": "dataframe", "content": {"dataframe": "@{random_df}", "enableDownload": "", "enableSearch": "", "fontStyle": "monospace"}, "isCodeManaged": false, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121", "position": 1}
{"id": "fdf38e46-c01e-4a93-94d5-e187f9e4c823", "type": "text", "content": {"primaryTextColor": "#8a8a8a", "text": "_pgcf_ stands for \"Pigeon Coefficient\" and is a meaningless, randomly-generated value.", "useMarkdown": "yes"}, "isCodeManaged": false, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121", "position": 2}
{"id": "e2c46zr4072th36z", "type": "tab", "content": {"name": "Dataframe"}, "handlers": {}, "isCodeManaged": false, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360", "position": 6}
{"id": "qelh30k75scw87ma", "type": "dataframe", "content": {"dataframe": "@{editable_df}", "enableRecordAdd": "yes", "enableRecordUpdate": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "e2c46zr4072th36z", "position": 0}
39 changes: 38 additions & 1 deletion apps/hello/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import statistics

import numpy as np
Expand Down Expand Up @@ -65,6 +66,17 @@ def _get_main_df():
main_df = pd.read_csv("assets/main_df.csv")
return main_df


def _get_editable_df():

df = pd.DataFrame({
'number': [1, 2, 3],
'boolean': [True, False, True],
'object': [{"updatedAt": None}, {"updatedAt": None}, {"updatedAt": None}],
'text': ['one', 'two', 'three'],
})
return wf.EditableDataframe(df)

def _get_highlighted_members():
sample_df = _get_main_df().sample(3).set_index("name", drop=False)
sample = sample_df.to_dict("index")
Expand Down Expand Up @@ -126,11 +138,36 @@ def _update_scatter_chart(state):
)
state["scatter_chart"] = fig

# STATE INIT

# UPDATES DATAFRAME

# Subscribe this event handler to the `wf-dataframe-add` event
def on_editable_df_record_add(state, payload):
state['editable_df'].record_add(payload)


# Subscribe this event handler to the `wf-dataframe-update` event
def on_editable_df_record_change(state, payload):
payload['record']['object']['updatedAt'] = str(datetime.datetime.now())
state['editable_df'].record_update(payload)

# Subscribe this event handler to the `wf-dataframe-action` event
def on_editable_df_record_action(state, payload):
record_index = payload['record_index']
if payload['action'] == 'remove':
state['editable_df'].record_remove(payload)
if payload['action'] == 'open':
state['editable_df_open_text'] = str(state['editable_df'].record(record_index)['text'])
if payload['action'] == 'notify':
state.add_notification("info", "Test", "Notify on this row : " + str(state['editable_df'].record(record_index)))


# STATE INIT

initial_state = wf.init_state({
"main_df": _get_main_df(),
"editable_df": _get_editable_df(),
"editable_df_open_text": "<none>",
"highlighted_members": _get_highlighted_members(),
"random_df": _generate_random_df(),
"hue_rotation": 26,
Expand Down
4 changes: 2 additions & 2 deletions docs/framework/dataframe.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ def on_record_action(state, payload):
This event corresponds to a quick action in the drop-down menu to the left of the dataframe.
"""
record_index = payload['record_index']
if payload.action == 'remove':
if payload['action'] == 'remove':
state['mydf'].record_remove(payload)
if payload.action == 'open':
if payload['action'] == 'open':
state['record'] = state['df'].record(record_index) # dict representation of record
```

Expand Down
133 changes: 133 additions & 0 deletions src/ui/src/components/core/base/BaseDropdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<script setup lang="ts">
import { onMounted, onUnmounted, PropType, ref } from "vue";
defineProps({
options: {
type: Object as PropType<Record<string, string>>,
required: true,
},
});
const emits = defineEmits({
selected: (key: string) => typeof key === "string",
});
/**
* @deprecated use `useId` from Vue 3.5 when we upgrade
*/
function useId() {
return Math.random().toString();
}
const trigger = ref<Element>();
const popoverId = useId();
const popover = ref<Element & { hidePopover: () => void }>();
const closePopover = () => popover.value?.hidePopover();
const popoverTop = ref("unset");
const popoverLeft = ref("unset");
function computePopoverPosition() {
// the popover use an overlay with an absolute position, so we have to positionate it manually according to the trigger position
const boundingRect = trigger.value.getBoundingClientRect();
popoverTop.value = `${boundingRect.top + boundingRect.height + 4 + window.pageYOffset}px`;
popoverLeft.value = `${boundingRect.left + window.pageXOffset}px`;
}
function onItemClick(key: string) {
emits("selected", key);
closePopover();
}
onMounted(() => window.addEventListener("scroll", closePopover));
onUnmounted(() => window.removeEventListener("scroll", closePopover));
</script>

<template>
<div class="BaseDropdown">
<button
ref="trigger"
:popovertarget="popoverId"
class="BaseDropdown__trigger"
@click="computePopoverPosition"
>
<i class="material-symbols-outlined">more_horiz</i>
</button>
<div
:id="popoverId"
ref="popover"
class="BaseDropdown__dropdown"
popover
:style="{ top: popoverTop, left: popoverLeft }"
>
<button
v-for="[key, value] of Object.entries(options)"
:key="key"
class="BaseDropdown__dropdown_item"
tabindex="1"
@click.capture="onItemClick(key)"
>
{{ value }}
</button>
</div>
</div>
</template>

<style scoped>
.BaseDropdown {
position: relative;
}
.BaseDropdown__trigger {
cursor: pointer;
background-color: var(--separatorColor);
border: 1px solid var(--emptinessColor);
height: 16px;
width: 16px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.BaseDropdown__dropdown {
position: absolute;
z-index: 1;
top: 18px;
background-color: white;
flex-direction: column;
min-width: 120px;
margin: 0;
border-radius: 4px;
border: 1px solid var(--separatorColor);
box-shadow: var(--containerShadow);
}
.BaseDropdown__dropdown:popover-open {
display: flex;
}
.BaseDropdown__dropdown_item {
/* reset button */
background-color: transparent;
border: none;
text-align: left;
padding: 8px 16px;
border-bottom: 1px solid var(--separatorColor);
cursor: pointer;
}
.BaseDropdown__dropdown_item:focus {
outline-offset: -2px;
}
.BaseDropdown__dropdown_item:hover {
background-color: var(--separatorColor);
}
.BaseDropdown__dropdown_item:last-child {
border-bottom: unset;
}
</style>
Loading

0 comments on commit b50bcda

Please sign in to comment.