|
| 1 | +import dash_dynamic_grid_layout as dgl |
| 2 | +from dash import * |
| 3 | +from dash.exceptions import PreventUpdate |
| 4 | +import plotly.express as px |
| 5 | +import dash_leaflet as dl |
| 6 | +import dash_mantine_components as dmc |
| 7 | +from dash_iconify import DashIconify |
| 8 | +from datetime import datetime, date |
| 9 | +import json |
| 10 | +import random |
| 11 | +import string |
| 12 | +import full_calendar_component as fcc |
| 13 | +from datetime import datetime |
| 14 | + |
| 15 | +# Set the React version |
| 16 | +dash._dash_renderer._set_react_version("18.2.0") |
| 17 | + |
| 18 | +app = Dash(__name__) |
| 19 | + |
| 20 | +# Get today's date |
| 21 | +today = datetime.now() |
| 22 | + |
| 23 | +# Format the date |
| 24 | +formatted_date = today.strftime("%Y-%m-%d") |
| 25 | + |
| 26 | +# Sample data for the graph |
| 27 | +df = px.data.iris() |
| 28 | + |
| 29 | + |
| 30 | +# Create a Random String ID for the new component |
| 31 | +def generate_random_string(length): |
| 32 | + # Define the characters to choose from |
| 33 | + characters = string.ascii_letters + string.digits |
| 34 | + # Generate a random string |
| 35 | + random_string = "".join(random.choice(characters) for _ in range(length)) |
| 36 | + return random_string |
| 37 | + |
| 38 | + |
| 39 | +app.layout = dmc.MantineProvider( |
| 40 | + [ |
| 41 | + html.Div( |
| 42 | + [ |
| 43 | + html.Center(html.H4("json.dumps(current_layout)")), |
| 44 | + html.Hr(), |
| 45 | + html.Div(id="layout-output"), |
| 46 | + html.Hr(), |
| 47 | + dmc.Group([ |
| 48 | + html.H4("Add items or edit the layout ->"), |
| 49 | + dmc.Menu( |
| 50 | + [ |
| 51 | + dmc.MenuTarget( |
| 52 | + dmc.ActionIcon( |
| 53 | + DashIconify(icon="icon-park:add-web", width=20), |
| 54 | + size="lg", |
| 55 | + color="#fff", |
| 56 | + variant="filled", |
| 57 | + id="action-icon", |
| 58 | + n_clicks=0, |
| 59 | + mb=8, |
| 60 | + style={"backgroundColor": "#fff"}, |
| 61 | + ) |
| 62 | + ), |
| 63 | + dmc.MenuDropdown( |
| 64 | + [ |
| 65 | + dmc.MenuItem( |
| 66 | + "Add Dynamic Component", |
| 67 | + id="add-dynamic-component", |
| 68 | + n_clicks=0, |
| 69 | + ), |
| 70 | + dmc.MenuItem( |
| 71 | + "Edit Dynamic Layout", id="edit-mode", n_clicks=0 |
| 72 | + ), |
| 73 | + ] |
| 74 | + ), |
| 75 | + ], |
| 76 | + transitionProps={ |
| 77 | + "transition": "rotate-right", |
| 78 | + "duration": 150, |
| 79 | + }, |
| 80 | + position="right", |
| 81 | + ), |
| 82 | + ]), |
| 83 | + dgl.DashGridLayout( |
| 84 | + id="grid-layout", |
| 85 | + items=[ |
| 86 | + dgl.DraggableWrapper( |
| 87 | + children=[ |
| 88 | + dl.Map( |
| 89 | + dl.TileLayer(), |
| 90 | + center=[56, 10], |
| 91 | + zoom=6, |
| 92 | + style={ |
| 93 | + "height": "100vh", |
| 94 | + "width": "100vw", |
| 95 | + }, |
| 96 | + ), |
| 97 | + ], |
| 98 | + id="draggable-map", |
| 99 | + handleBackground="rgb(85,85,85)", |
| 100 | + ), |
| 101 | + dgl.DraggableWrapper( |
| 102 | + html.Img( |
| 103 | + src="https://picsum.photos/200/300", |
| 104 | + style={ |
| 105 | + "width": "100%", |
| 106 | + "height": "100%", |
| 107 | + "objectFit": "cover", |
| 108 | + }, |
| 109 | + ), |
| 110 | + id="draggable-image", |
| 111 | + ), |
| 112 | + dgl.DraggableWrapper( |
| 113 | + dcc.Graph( |
| 114 | + id="example-graph", |
| 115 | + figure=px.scatter( |
| 116 | + df, |
| 117 | + x="sepal_width", |
| 118 | + y="sepal_length", |
| 119 | + color="species", |
| 120 | + ), |
| 121 | + style={"height": "100%"}, |
| 122 | + ), |
| 123 | + id="draggable-graph", |
| 124 | + ), |
| 125 | + dgl.DraggableWrapper( |
| 126 | + dmc.ColorPicker( |
| 127 | + id="qr-color-picker", |
| 128 | + format="rgba", |
| 129 | + value="rgba(0, 0, 0, 1)", |
| 130 | + fullWidth=True, |
| 131 | + ), |
| 132 | + id="draggable-color-picker", |
| 133 | + ), |
| 134 | + dgl.DraggableWrapper( |
| 135 | + fcc.FullCalendarComponent( |
| 136 | + id="api_calendar", # Unique ID for the component |
| 137 | + initialView='dayGridMonth', # dayGridMonth, timeGridWeek, timeGridDay, listWeek, |
| 138 | + # dayGridWeek, dayGridYear, multiMonthYear, resourceTimeline, resourceTimeGridDay, resourceTimeLineWeek |
| 139 | + headerToolbar={ |
| 140 | + "left": "prev,next today", |
| 141 | + "center": "", |
| 142 | + "right": "", |
| 143 | + }, # Calendar header |
| 144 | + initialDate=f"{formatted_date}", # Start date for calendar |
| 145 | + editable=True, # Allow events to be edited |
| 146 | + selectable=True, # Allow dates to be selected |
| 147 | + events=[], |
| 148 | + nowIndicator=True, # Show current time indicator |
| 149 | + navLinks=True, # Allow navigation to other dates |
| 150 | + ), |
| 151 | + id="draggable-calendar" |
| 152 | + ), |
| 153 | + ], |
| 154 | + itemLayout=[ |
| 155 | + # wrapper id, x(0-12), y, w(0-12), h(0-12) |
| 156 | + {'i': 'draggable-map', 'x': 0, 'y': 0, 'w': 6, 'h': 4}, |
| 157 | + {'i': 'draggable-image', 'x': 4, 'y': 0, 'w': 4, 'h': 2}, |
| 158 | + {'i': 'draggable-graph', 'x': 0, 'y': 4, 'w': 6, 'h': 4}, |
| 159 | + {'i': 'draggable-color-picker', 'x': 6, 'y': 2, 'w': 3, 'h': 2}, |
| 160 | + {'i': 'draggable-calendar', 'x': 6, 'y': 4, 'w': 6, 'h': 4} |
| 161 | + ], |
| 162 | + showRemoveButton=False, |
| 163 | + showResizeHandles=False, |
| 164 | + rowHeight=150, |
| 165 | + cols={"lg": 12, "md": 10, "sm": 6, "xs": 4, "xxs": 2}, |
| 166 | + style={"height": "1500px", 'backgroundColor': 'green'}, |
| 167 | + compactType=None, |
| 168 | + autoSize = False, |
| 169 | + maxRows = 40, |
| 170 | + margin={"lg": [1, 1], "md": [1, 1], "sm": [6, 6], "xs": [4, 4], "xxs": [2, 2]}, #Margin between draggable components in pixels. |
| 171 | + allowOverlap = True, #Components can now overlap each other. |
| 172 | + draggableChildStyle = { ##Smaller padding, max height and backgroundcolor(for padding in practice) |
| 173 | + 'overflow': 'hidden', |
| 174 | + 'maxHeight': '100%', |
| 175 | + 'maxWidth': '100%', |
| 176 | + "border": "5px solid yellow", ## This sort of creates some coloured extra padding for the handles. |
| 177 | + "boxSizing": "border-box", # Ensures border doesn't mess up sizing |
| 178 | + }, |
| 179 | + |
| 180 | + ), |
| 181 | + dcc.Store(id="layout-store"), |
| 182 | + ], style={ 'backgroundColor': 'blue', 'overflowY':'hidden', 'height': '1200px'} |
| 183 | + ) |
| 184 | + ], |
| 185 | + id="mantine-provider", |
| 186 | + forceColorScheme="light", |
| 187 | +) |
| 188 | + |
| 189 | + |
| 190 | +@callback(Output("layout-store", "data"), Input("grid-layout", "currentLayout")) |
| 191 | +def store_layout(current_layout): |
| 192 | + return current_layout |
| 193 | + |
| 194 | + |
| 195 | +@callback( |
| 196 | + Output("grid-layout", "showRemoveButton"), |
| 197 | + Output("grid-layout", "showResizeHandles"), |
| 198 | + # show how to dynamically change the handle background color of a wrapped component |
| 199 | + Output("draggable-map", "handleBackground"), |
| 200 | + Input("edit-mode", "n_clicks"), |
| 201 | + State("grid-layout", "showRemoveButton"), |
| 202 | + State("grid-layout", "showResizeHandles"), |
| 203 | + prevent_initial_call=True, |
| 204 | +) |
| 205 | +def enter_editable_mode(n_clicks, current_remove, current_resize): |
| 206 | + print("Edit mode clicked:", n_clicks) # Debug print |
| 207 | + if n_clicks is None: |
| 208 | + raise PreventUpdate |
| 209 | + return not current_remove, not current_resize, "red" |
| 210 | + |
| 211 | + |
| 212 | +@callback(Output("layout-output", "children"), Input("grid-layout", "itemLayout")) |
| 213 | +def display_layout(current_layout): |
| 214 | + if current_layout and isinstance(current_layout, list): |
| 215 | + return html.Div(json.dumps(current_layout)) |
| 216 | + return "No layout data available" |
| 217 | + |
| 218 | + |
| 219 | +@callback( |
| 220 | + Output("grid-layout", "items"), |
| 221 | + Output("grid-layout", "itemLayout"), |
| 222 | + Input("add-dynamic-component", "n_clicks"), |
| 223 | + prevent_initial_call=True, |
| 224 | +) |
| 225 | +def add_dynamic_component(n): |
| 226 | + if n: |
| 227 | + items = Patch() |
| 228 | + new_id = generate_random_string(10) |
| 229 | + items.append( |
| 230 | + dgl.DraggableWrapper( |
| 231 | + dcc.Graph( |
| 232 | + figure=px.scatter( |
| 233 | + df, x="petal_width", y="petal_length", color="species" |
| 234 | + ), |
| 235 | + style={"height": "100%"}, |
| 236 | + ), |
| 237 | + id=new_id |
| 238 | + ) |
| 239 | + ) |
| 240 | + itemLayout = Patch() |
| 241 | + itemLayout.append({"i": f"{new_id}", "w": 6}) |
| 242 | + return items, itemLayout |
| 243 | + return no_update, no_update |
| 244 | + |
| 245 | +@callback( |
| 246 | + Output("grid-layout", "items", allow_duplicate=True), |
| 247 | + Input("grid-layout", "itemToRemove"), |
| 248 | + State("grid-layout", "itemLayout"), |
| 249 | + prevent_initial_call=True, |
| 250 | +) |
| 251 | +def remove_component(key, layout): |
| 252 | + if key: |
| 253 | + items = Patch() |
| 254 | + print(key) |
| 255 | + for i in range(len(layout)): |
| 256 | + if layout[i]['i'] == key: |
| 257 | + del items[i] |
| 258 | + break |
| 259 | + return items |
| 260 | + return no_update |
| 261 | + |
| 262 | + |
| 263 | +if __name__ == "__main__": |
| 264 | + app.run(debug=True) |
0 commit comments