{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "import panel as pn\n", "import pandas as pd\n", "import numpy as np\n", "import random\n", "from datetime import datetime, timedelta\n", "\n", "pn.extension('perspective')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Perspective` pane provides a powerful visualization component for large, real-time datasets built on the [Perspective Project](https://perspective.finos.org/). **`Perspective` brings *excel-like* capabilities to your data app**. Check out the [Perspective Examples Gallery](https://perspective.finos.org/examples/) for inspiration.\n", "\n", "The `Perspective` pane is a very good alternative to the [`Tabulator`](../widgets/Tabulator.ipynb) widget.\n", "\n", "## Parameters:\n", "\n", "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n", "\n", "* **`aggregates`** (dict): Aggregation spec, e.g. {x: \"distinct count\"}\n", "* **`columns`** (list or dict): List of column names to display or a dictionary with column configuration options.\n", "* **`columns_config`** (dict): Column configuration allowing specification of formatters, coloring and a variety of other attributes for each column.\n", "* **`editable`** (bool, default=True): Whether items are editable.\n", "* **`expressions`** (list): List of expressions, e.g. `['\"x\"+\"y\"']`\n", "* **`filters`** (list): A list of filters, e.g. `[[\"x\", \"<\", 3], [\"y\", \"contains\", \"abc\"]]`.\n", "* **`group_by`** (list): List of columns to group by, e.g. `[\"x\", \"y\"]`\n", "* **`object`** (dict or pd.DataFrame): The plot data declared as a dictionary of arrays or a DataFrame.\n", "* **`plugin_config`** (dict): Configuration for the PerspectiveViewerPlugin \n", "* **`plugin`** (str): The name of a plugin to display the data. For example `'datagrid'` or `'d3_xy_scatter'`.\n", "* **`selectable`** (bool, default=True): Whether rows are selectable\n", "* **`settings`** (bool, default=True): Whether to show the settings panel.\n", "* **`sort`** (list): List of sorting specs, e.g. `[[\"x\", \"desc\"]]`\n", "* **`split_by`** (list): A list of columns to pivot by. e.g. `[\"x\", \"y\"]`\n", "* **`theme`** (str): The theme of the viewer, available options include `'pro'`, `'pro-dark'`, `'monokai'`, `'solarized'`, `'solarized-dark'` and `'vaporwave'`\n", "* **`title`** (str): Title for the Perspective Viewer.\n", "\n", "##### Callbacks\n", "\n", "* **`on_click`**: Allows registering callbacks which are given `PerspectiveClickEvent` objects containing the `config`, `column_names` and `row` of the clicked item.\n", "\n", "___\n", "\n", "The `Perspective` pane renders columns of data specified as a dictionary of lists or arrays and pandas DataFrames:" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Basic Examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "data = {\n", " 'int': [random.randint(-10, 10) for _ in range(9)],\n", " 'float': [random.uniform(-10, 10) for _ in range(9)],\n", " 'date': [(datetime.now() + timedelta(days=i)).date() for i in range(9)],\n", " 'datetime': [(datetime.now() + timedelta(hours=i)) for i in range(9)],\n", " 'category': ['Category A', 'Category B', 'Category C', 'Category A', 'Category B',\n", " 'Category C', 'Category A', 'Category B', 'Category C',],\n", " 'link': ['https://panel.holoviz.org/', 'https://discourse.holoviz.org/', 'https://github.com/holoviz/panel']*3,\n", "}\n", "df = pd.DataFrame(data)\n", "\n", "pn.pane.Perspective(df, width=1000)" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "Try interacting with the `Perspective` pane.\n", "\n", "- The three vertical dots in the upper, left corner will toggle the *configuration menu*\n", "- The three vertical lines at the top of each column will toggle a *column configuration menu*.\n", "- The top menu will provide you with options to change the *plugin* as well as *group*, *split*, *order* and *filter* your data\n", "- The bottom menu will provide you with options to *reset*, *download* and *copy* as well as *change the theme*" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "The `index` is shown by default. If you want to not display it by default, you can provide the list of `columns` to display." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "pn.pane.Perspective(df, columns=list(df.columns), width=1000)" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "You might also hide the *config menu* via the `settings` parameter." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "pn.pane.Perspective(df, columns=[\"float\"], group_by=[\"category\"], plugin='d3_y_bar', settings=False, width=1000, height=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try interacting with the `Perspective` pane by toggling the *config menu* via the 3 vertical dots in the upper, left corner." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plugin Configuration\n", "\n", "You can configure the active *plugin* manually as shown for the *Datagrid* below\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "You can also configure the *columns* configuration programmatically via the `columns_config` parameter" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "pn.pane.Perspective(df, columns=list(df.columns), width=1000, columns_config={\n", " 'int': {'number_fg_mode': 'color', 'neg_fg_color': '#880808', 'pos_fg_color': '#008000', \"fixed\": 0},\n", " 'float': {'number_fg_mode': \"bar\", 'neg_fg_color': '#880808', 'pos_fg_color': '#008000', 'fg_gradient': 7.93, },\n", " 'category': {'string_color_mode': 'series', 'format': 'italics'},\n", " 'date': {\"dateStyle\": \"short\", \"datetime_color_mode\": \"foreground\", \"color\": \"#008000\"},\n", " 'datetime': {\"timeZone\": \"Asia/Shanghai\", \"dateStyle\": \"full\", \"timeStyle\": \"full\", \"datetime_color_mode\": \"background\", \"color\": \"#880808\"},\n", " 'link': {'format': 'link', 'string_color_mode': 'foreground', 'color': '#008000'},\n", "})" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "Please note\n", "\n", "- You can also use *named* colors like 'green' when you provide the `plugin_config`. But if you do, then they will not be set in the *color picker* in the *column config menu*.\n", "\n", "For more detail about the available options see the [Column Configuration Options Section](#column-configuration-options) below." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Timezone Handling\n", "\n", "The underlying Perspective Viewer assumes *non-timezone* aware datetimes are in UTC time. And it displays them in your local time zone by default.\n", "\n", "If your data is not time-zone aware you can make them. My servers time zone is 'cet' and I can make them aware as shown below." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "df_aware = df.copy(deep=True)\n", "df_aware['datetime'] = df_aware['datetime'].dt.tz_localize(\"cet\")\n", "pn.Column(df_aware.head(3), pn.pane.Perspective(df_aware, columns=list(df.columns), width=1000))" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "As shown in the section above you can then force the datetimes to be shown in a specific timezone." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "pn.pane.Perspective(df_aware, width=1000, columns=list(df.columns), plugin_config={'columns': {'datetime': {\"timeZone\": \"Europe/London\", \"timeStyle\": \"full\"}}})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Streaming and Patching" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "The `Perspective` pane also supports `stream` and `patch` methods allowing us to efficiently update the data" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "df_stream = pd.DataFrame(np.random.randn(400, 4), columns=list('ABCD')).cumsum()\n", "\n", "stream_perspective = pn.pane.Perspective(\n", " df_stream, plugin='d3_y_line', columns=['A', 'B', 'C', 'D'], theme='pro-dark',\n", " sizing_mode='stretch_width', height=500, margin=0\n", ")\n", "\n", "stream_perspective" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets start the streaming.\n", "\n", "The amount of data to keep in the streaming buffer can be controlled via the `rollover` option:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "rollover = pn.widgets.IntInput(name='Rollover', value=500)\n", "\n", "def stream():\n", " data = df_stream.iloc[-1] + np.random.randn(4)\n", " stream_perspective.stream(data, rollover.value)\n", "\n", "cb = pn.state.add_periodic_callback(stream, 50)\n", "\n", "pn.Row(cb.param.period, rollover)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively we can also `patch` the data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "mixed_df = pd.DataFrame({'A': np.arange(10), 'B': np.random.rand(10), 'C': [f'foo{i}' for i in range(10)]})\n", "\n", "perspective = pn.pane.Perspective(mixed_df, columns=list(mixed_df), height=500)\n", "\n", "perspective" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The easiest way to patch the data is by supplying a dictionary as the patch value. The dictionary should have the following structure:\n", "\n", "```python\n", "{\n", " column: [\n", " (index: int or slice, value),\n", " ...\n", " ],\n", " ...\n", "}\n", "```\n", " \n", "As an example, below we will patch the 'A' and 'C' columns. On the `'A'` column we will replace the 0th row and on the `'C'` column we replace the first two rows:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "perspective.patch({'A': [(0, 3)], 'C': [(slice(0, 1), 'bar')]})\n", "\n", "perspective" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Deleting rows can be achieved by streaming the data you want to become visible and setting rollover equal to the row count of new data. Effectively, deleting old rows. Removing specific rows by index in a similar manner as patching is currently not supported." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "data = {'A': np.arange(10)}\n", "\n", "perspective = pn.pane.Perspective(data, height=500)\n", "\n", "perspective" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "smaller_data = {'A': np.arange(5)}\n", "\n", "perspective.stream(smaller_data, rollover=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Controls\n", "\n", "The `Perspective` pane exposes a number of options which can be changed from both Python and Javascript.\n", "\n", "