{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# `dcc.Store`\n", "\n", "`dcc.Store` 组件用于在浏览器中存储 JSON 数据。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 存储 Clicks" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from dash import Dash, html, dcc, Output, Input, State, callback\n", "from dash.exceptions import PreventUpdate\n", "\n", "# 这个样式表使按钮和表格看起来很漂亮。\n", "external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']\n", "\n", "app = Dash(__name__, external_stylesheets=external_stylesheets)\n", "\n", "app.layout = html.Div([\n", " # memory 存储在每次页面刷新时都会恢复到默认状态。\n", " dcc.Store(id='memory'),\n", " # local 存储只会在页面首次加载时获取初始数据,并保留数据直到被清除。\n", " dcc.Store(id='local', storage_type='local'),\n", " # 与 local 存储相同,但在浏览器/标签页关闭时会丢失数据。\n", " dcc.Store(id='session', storage_type='session'),\n", " html.Table([\n", " html.Thead([\n", " html.Tr(html.Th('Click to store in:', colSpan=\"3\")),\n", " html.Tr([\n", " html.Th(html.Button('memory', id='memory-button')),\n", " html.Th(html.Button('localStorage', id='local-button')),\n", " html.Th(html.Button('sessionStorage', id='session-button'))\n", " ]),\n", " html.Tr([\n", " html.Th('Memory clicks'),\n", " html.Th('Local clicks'),\n", " html.Th('Session clicks')\n", " ])\n", " ]),\n", " html.Tbody([\n", " html.Tr([\n", " html.Td(0, id='memory-clicks'),\n", " html.Td(0, id='local-clicks'),\n", " html.Td(0, id='session-clicks')\n", " ])\n", " ])\n", " ])\n", "])\n", "\n", "# 为每个存储创建两个回调\n", "for store in ('memory', 'local', 'session'):\n", " # 为适当的存储添加点击事件\n", " @callback(Output(store, 'data'),\n", " Input('{}-button'.format(store), 'n_clicks'),\n", " State(store, 'data'))\n", " def on_click(n_clicks, data):\n", " if n_clicks is None:\n", " # 防止存储组件的 None 回调非常重要。\n", " # 您不希望无故更新存储。\n", " raise PreventUpdate\n", " # 如果没有数据,提供默认的数据字典,点击次数为 0\n", " data = data or {'clicks': 0}\n", "\n", " data['clicks'] = data['clicks'] + 1\n", " return data\n", "\n", " # 在表格单元格中输出存储的点击次数\n", " @callback(Output('{}-clicks'.format(store), 'children'),\n", " # 由于我们在输出中使用了 data 属性,\n", " # 我们无法在加载时通过 data 属性获取初始数据。\n", " # 为了解决这个问题,您可以使用 modified_timestamp 作为输入,将数据作为状态。\n", " # 这个限制是由于初始的 None 回调造成的\n", " # https://github.com/plotly/dash-renderer/pull/81\n", " Input(store, 'modified_timestamp'),\n", " State(store, 'data'))\n", " def on_data(ts, data):\n", " if ts is None:\n", " raise PreventUpdate\n", "\n", " data = data or {}\n", "\n", " return data.get('clicks', 0)\n", "\n", "\n", "# if __name__ == '__main__':\n", "# app.run(debug=True, port=8077, threaded=True)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 在回调之间共享数据" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from dash import Dash, html, dcc, Input, Output, dash_table, callback\n", "from dash.exceptions import PreventUpdate\n", "\n", "import collections\n", "import pandas as pd\n", "\n", "\n", "app = Dash(__name__)\n", "\n", "df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')\n", "\n", "\n", "app.layout = html.Div([\n", " dcc.Store(id='memory-output'),\n", " dcc.Dropdown(df.country.unique(),\n", " ['Canada', 'United States'],\n", " id='memory-countries',\n", " multi=True),\n", " dcc.Dropdown({'lifeExp': 'Life expectancy', 'gdpPercap': 'GDP per capita'},\n", " 'lifeExp',\n", " id='memory-field'),\n", " html.Div([\n", " dcc.Graph(id='memory-graph'),\n", " dash_table.DataTable(\n", " id='memory-table',\n", " columns=[{'name': i, 'id': i} for i in df.columns]\n", " ),\n", " ])\n", "])\n", "\n", "\n", "@callback(Output('memory-output', 'data'),\n", " Input('memory-countries', 'value'))\n", "def filter_countries(countries_selected):\n", " if not countries_selected:\n", " # Return all the rows on initial load/no country selected.\n", " return df.to_dict('records')\n", "\n", " filtered = df.query('country in @countries_selected')\n", "\n", " return filtered.to_dict('records')\n", "\n", "\n", "@callback(Output('memory-table', 'data'),\n", " Input('memory-output', 'data'))\n", "def on_data_set_table(data):\n", " if data is None:\n", " raise PreventUpdate\n", "\n", " return data\n", "\n", "\n", "@callback(Output('memory-graph', 'figure'),\n", " Input('memory-output', 'data'),\n", " Input('memory-field', 'value'))\n", "def on_data_set_graph(data, field):\n", " if data is None:\n", " raise PreventUpdate\n", "\n", " aggregation = collections.defaultdict(\n", " lambda: collections.defaultdict(list)\n", " )\n", "\n", " for row in data:\n", "\n", " a = aggregation[row['country']]\n", "\n", " a['name'] = row['country']\n", " a['mode'] = 'lines+markers'\n", "\n", " a['x'].append(row[field])\n", " a['y'].append(row['year'])\n", "\n", " return {\n", " 'data': [x for x in aggregation.values()]\n", " }\n", "\n", "\n", "# if __name__ == '__main__':\n", "# app.run(debug=True, threaded=True, port=10450)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Storage 的限制\n", "\n", "- 浏览器的最大存储空间由以下因素决定:\n", " - 手机或笔记本电脑。\n", " - 在浏览器中,一个复杂的算法在配额管理(Quota Management)中实现。\n", " - UTF-16 的存储编码最终只能节省 UTF-8 的一半大小。\n", " - 一般来说,在大多数环境中存储 2MB 是安全的,而在大多数只有桌面的应用程序中存储 5~10MB 是安全的。\n", "- `modified_timestamp` 为只读。\n", "\n", "## 检索初始存储数据\n", "\n", "如果使用 `data` 属性作为输出,则无法使用 `data` 属性获得加载时的初始数据。为了应对这种情况,可以使用 `modified_timestamp` 作为 `Input`,使用 `data` 作为 `State`。\n", "\n", "## `dcc.Store` 属性\n", "\n", "`id`(字符串;必需):此组件的 ID,用于在回调中识别 Dash 组件。ID 需要在应用程序的所有组件中是唯一的。\n", "- `clear_data`(布尔;默认 `False`):设置为 `True` 删除 `data_key` 中包含的数据。\n", "- `data`(dict | list | number | string | boolean;可选):`id` 的存储数据。\n", "- `modified_timestamp`(数字;默认 `-1`):上次修改存储的时间。\n", "- `storage_type`('local', 'session', 'memory'(默认)):网络存储的类型。`memory`:只保留在内存中,刷新页面时重置。` local`:`window.localStorage`,浏览器退出后保留数据。`session`:window.sessionStorage,一旦浏览器退出,数据将被清除。" ] } ], "metadata": { "kernelspec": { "display_name": "py311", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.11.7" } }, "nbformat": 4, "nbformat_minor": 2 }