{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "pn.extension('perspective')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``Perspective`` pane provides an interactive visualization component for large, real-time datasets built on the [Perspective](https://perspective.finos.org/) project.\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): List of displayed columns.\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",
    "* **``selectable``** (bool, default=True): Whether rows are selectable\n",
    "* **``split_by``** (list):  A list of columns to pivot by. e.g. `[\"x\", \"y\"]`\n",
    "* **``sort``** (list): List of sorting specs, e.g. `[[\"x\", \"desc\"]]`\n",
    "* **``plugin``** (str): The name of a plugin to display the data. For example 'datagrid' or 'd3_xy_scatter'.\n",
    "* **``plugin_config``** (dict): Configuration for the PerspectiveViewerPlugin \n",
    "* **``toggle_config``** (bool): Whether to show the config menu.\n",
    "* **``theme``** (str): The theme of the viewer, available options include `'material'`, `'material-dark'`, `'monokai'`, `'solarized'`, `'solarized-dark'` and `'vaporwave'`\n",
    "\n",
    "___\n",
    "\n",
    "The `Perspective` pane renders columns of data specified as a dictionary of lists or arrays and pandas DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = {'x': [1, 2, 3], 'y': [1, 2, 3]}\n",
    "\n",
    "pn.pane.Perspective(data, width=1000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also provide configuration options for the viewer plugins:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "random_df = pd.DataFrame(np.random.randn(20, 2), columns=list('XY'))\n",
    "\n",
    "pn.pane.Perspective(random_df, width=1000, plugin_config={\n",
    "    'columns': {\n",
    "        'X': {'gradient': 0.7836544570728833, 'neg_color': '#f07160', 'number_color_mode': 'gradient', 'pos_color': '#7dc3f0'},\n",
    "        'Y': {'gradient': 1.5673089141457666, 'neg_color': '#ff9485', 'number_color_mode': 'gradient', 'pos_color': '#607785'}\n",
    "    }\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also set the various config settings such as choosing the `columns` to display or the `theme` from Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(np.random.randn(400, 4), columns=list('ABCD')).cumsum()\n",
    "\n",
    "stream_perspective = pn.pane.Perspective(\n",
    "    df, plugin='d3_y_line', columns=['A', 'B', 'C', 'D'], theme='material-dark',\n",
    "    sizing_mode='stretch_width', height=500, margin=0\n",
    ")\n",
    "\n",
    "stream_perspective"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `Perspective` pane also supports `stream` and `patch` methods allowing us to efficiently update the data. The amount of data to keep in the streaming buffer can be controlled via the `rollover` option:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rollover = pn.widgets.IntInput(name='Rollover', value=500)\n",
    "\n",
    "def stream():\n",
    "    data = df.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": {},
   "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, 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": {},
   "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": {},
   "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 try out the effect of these parameters interactively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.Row(perspective.controls(jslink=True), perspective)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
