{ "cells": [ { "cell_type": "markdown", "id": "2150fea2-682b-47c3-9d65-8ce0141f48bf", "metadata": {}, "source": [ "## Custom PDB Input Component using Vue.js\n", "\n", "[Panel](https://panel.holoviz.org/index.html) makes it easy to build data apps in Python using a wide range of [built in components](https://panel.holoviz.org/reference/index.html). Sometimes you want to go beyond those built in components and build [custom components](https://panel.holoviz.org/user_guide/Custom_Components.html) instead.\n", "\n", "The lessons below will build on each other, culminating in a Panel+Vue.js custom component inspired by medicinal chemistry. It’ll look like this:\n", "\n", "![Panel Vue.js Component](../../assets/VuePdbInput.gif)\n", "\n", "This guide is heavily inspired by [Web Development with Python and Vue.js Components](https://blog.reverielabs.com/python-vue-components/)." ] }, { "cell_type": "markdown", "id": "24d76fd9-5bc4-4a03-8572-980a903ed1aa", "metadata": {}, "source": [ "## What is a Vue Component?" ] }, { "cell_type": "markdown", "id": "04bb5df5-4a35-4bc0-9712-f19024e522f2", "metadata": {}, "source": [ "[Vue components](https://vuejs.org/v2/guide/components.html) are self-contained elements that can render HTML/CSS, store data within Javascript variables, and much more. Once defined, they are callable as HTML tags. For example, within existing HTML a Vue component can be rendered like below:\n", "\n", "![Vue Component](../../assets/vue_bootstrap_component.png)" ] }, { "cell_type": "markdown", "id": "ef9bc46b-a515-4160-99d4-b9a6e45dd2ce", "metadata": {}, "source": [ "Here, the Vue component tags `` are responsible for rendering a part of the frontend that takes user input. \n", "\n", "The components are usually defined in a .vue file and require Webpack to serve. \n", "\n", "With Panel you can take a **simpler approach**, there is no need to configure, learn and maintain an advanced javascript build tool chain to utilize Vue.js. We will show you how this is done below using Panels [ReactiveHTML](https://panel.holoviz.org/user_guide/Custom_Components.html#reactivehtml-components)." ] }, { "cell_type": "markdown", "id": "29f0d5b3-d6b7-432d-b06e-656d25239d13", "metadata": {}, "source": [ "## Lets Get Started\n", "\n", "In order to ensure that all the resources we need (such as Vue.js) are loaded appropriately we need to declare baseclasses which declare these dependencies **before** we run the `panel.extension`:" ] }, { "cell_type": "code", "execution_count": null, "id": "3aab1272-6397-4598-9d21-96b58aa00f7c", "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "import param\n", "import requests\n", "\n", "class BasicVueComponent(pn.reactive.ReactiveHTML):\n", " _template = \"\"\"\n", "
\n", " \n", "
\n", " \"\"\"\n", " \n", " _scripts = {\n", " \"render\": \"\"\"\n", " const template = \"
Hello Panel + Vue.js World!
\"\n", " const vue_component = {template: template}\n", " el=new Vue({\n", " el: container,\n", " components: {\n", " 'vue-component' : vue_component\n", " } \n", " })\n", " \"\"\"\n", " }\n", " \n", " __javascript__=[\n", " \"https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js\"\n", " ]\n", " \n", "class BootstrapVueComponent(BasicVueComponent):\n", " \n", " __javascript__=[\n", " \"https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js\",\n", " \"https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js\",\n", " ]\n", " __css__=[\n", " \"https://unpkg.com/bootstrap/dist/css/bootstrap.min.css\",\n", " \"https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css\",\n", " ]" ] }, { "cell_type": "markdown", "id": "7b1c4f74-1d41-4d70-88e5-6bc21bfd0416", "metadata": {}, "source": [ "Now that we have declared the components we run the extension:" ] }, { "cell_type": "code", "execution_count": null, "id": "c1865584-ffbc-48ec-b5f5-c61cf62a2c57", "metadata": {}, "outputs": [], "source": [ "pn.extension(sizing_mode=\"stretch_width\")" ] }, { "cell_type": "markdown", "id": "1732e6ed-d0cf-4a48-b03e-1fdd321a0a03", "metadata": {}, "source": [ "## Basic Vue Component\n", "\n", "Let's start by rendering the `BasicVueComponent` which simply renders a `Hello World` template using Vue.js:" ] }, { "cell_type": "code", "execution_count": null, "id": "61881ebc-1409-4638-b991-5fb825049386", "metadata": {}, "outputs": [], "source": [ "BasicVueComponent(height=40, width=190)" ] }, { "cell_type": "markdown", "id": "5830cb37-5c3e-434b-a776-f5ad2b2edfa2", "metadata": {}, "source": [ "## What are PDBs?\n", "\n", "This example will build a little example based on fetching and rendering so called PDBs. A PDB is a file format that stores 3-D structural information about a protein. This information is useful for example when designing drugs that inhibit disease-causing proteins. We will use the [KLIFS open source database of PDBs](https://klifs.net/).\n", "\n", "You can find more information about PDBs [here](https://blog.reverielabs.com/building-a-global-pdb-repository/)." ] }, { "cell_type": "markdown", "id": "d75fd650-5c40-41ad-bb1e-1c7b41fe62a7", "metadata": {}, "source": [ "## Getting PDBs\n", "\n", "We can get PDBs using KLIFS [`structured_pdb_list` rest endpoint](https://klifs.net/swagger/#/Structures/get_structures_pdb_list)." ] }, { "cell_type": "code", "execution_count": null, "id": "a87eb604-aac3-4884-ad47-ccff1bda701a", "metadata": {}, "outputs": [], "source": [ "URL = \"https://klifs.net/api/structures_pdb_list\"\n", "\n", "def get_pdb_data_from_klifs(pdb_id):\n", " if not pdb_id:\n", " return \"Please specify a PDB ID.\"\n", " \n", " params = {'pdb-codes': pdb_id}\n", " res = requests.get(url = URL, params = params)\n", " data = res.json()\n", " \n", " if res.status_code == 400:\n", " return f\"Error 400, Could not get PDB {pdb_id}\", data[1]\n", " \n", " return data[0]" ] }, { "cell_type": "markdown", "id": "615f6ed1-3868-4a1d-9293-b839f84747e5", "metadata": {}, "source": [ "Lets test the function:" ] }, { "cell_type": "code", "execution_count": null, "id": "85b96b75-1cfc-4118-ba07-f56ad04370fe", "metadata": {}, "outputs": [], "source": [ "get_pdb_data_from_klifs(\"4WSQ\") # Examples: 2xyu, 4WSQ" ] }, { "cell_type": "markdown", "id": "f67bcc01-042f-46c9-819c-038ee87d8baf", "metadata": {}, "source": [ "## PDBInput Component\n", "\n", "Now we will build a Vue.js component containing an input field and a button that will update the `value` parameter of the `PDBInput` component:" ] }, { "cell_type": "code", "execution_count": null, "id": "9c7a7439-af22-4e20-9715-bc6e4ed5ab8d", "metadata": {}, "outputs": [], "source": [ "class PDBInput(BootstrapVueComponent):\n", " \n", " value = param.String()\n", " \n", " _template = \"\"\"\n", "
\n", " \n", "
\n", " \"\"\"\n", " \n", " _scripts = {\n", " \"render\": \"\"\"\n", " const template = `\n", "
\n", " \n", " \n", " \n", " Retrieve PDB metadata\n", " \n", " \n", "
`\n", " const vue_component = {\n", " template: template,\n", " delimiters: ['[[', ']]'],\n", " data: function () {\n", " return {\n", " pdb_id: data.value,\n", " }\n", " },\n", " methods: {\n", " setPDBId() {\n", " data.value = this.pdb_id\n", " }\n", " }\n", " }\n", " const el = new Vue({\n", " el: container,\n", " components: {\n", " 'vue-component': vue_component\n", " } \n", " })\n", " \"\"\"\n", " }" ] }, { "cell_type": "code", "execution_count": null, "id": "86d11b07-0b42-4789-a310-9c3bb1a1437d", "metadata": {}, "outputs": [], "source": [ "pdb_input = PDBInput(height=90, max_width=800)\n", "pdb_input" ] }, { "cell_type": "markdown", "id": "4fe814e8-8dbb-4521-a024-7249e1643714", "metadata": {}, "source": [ "Please note how we *get* and *set* the `value` of the Parameterized python class using `data.value`." ] }, { "cell_type": "markdown", "id": "767b43fe-6a4b-4d9a-9c3d-ed18436a69c0", "metadata": {}, "source": [ "## Display the PDB Response\n", "\n", "Next we will [bind](https://panel.holoviz.org/user_guide/APIs.html#reactive-functions) the `get_pdb_data_from_klifs` function to the `value` parameter of the `pdb_input` component:" ] }, { "cell_type": "code", "execution_count": null, "id": "1eed96d7-6ae5-4fd9-a229-9f7eef4d294b", "metadata": {}, "outputs": [], "source": [ "iget_klifs_data = pn.bind(get_pdb_data_from_klifs, pdb_id=pdb_input.param.value)" ] }, { "cell_type": "code", "execution_count": null, "id": "81016456-99cf-45d6-a493-3535e4e76f13", "metadata": {}, "outputs": [], "source": [ "component = pn.Column(\n", " pdb_input, \n", " pn.panel(iget_klifs_data, theme=\"light\")\n", ")\n", "component" ] }, { "cell_type": "markdown", "id": "4ca00d63-ab9d-4a96-b0e1-985774950b94", "metadata": {}, "source": [ "## Lets wrap it up in a nice app" ] }, { "cell_type": "code", "execution_count": null, "id": "0df3314f-aec7-4c6e-851f-476ba768ae7c", "metadata": {}, "outputs": [], "source": [ "ACCENT = \"#0072B5\"\n", "INFO = \"\"\"# Featurize Protein Structure\n", "\n", "Use the Vue component below to retrieve PDB metadata from [KLIFS](https://klifs.net/). For example for `2xyu` or `4WSQ`\"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "12fca07c-49ae-4bac-b9de-56a6f46b2837", "metadata": {}, "outputs": [], "source": [ "pn.template.BootstrapTemplate(\n", " site=\"Awesome Panel\", title=\"Custom PDB Input Component using Vue.js\", main=[INFO, component], header_background=ACCENT\n", ").servable();" ] }, { "cell_type": "markdown", "id": "db4ec96c-8519-4bbd-9841-cda93a41e747", "metadata": {}, "source": [ "You can serve the app via `panel serve VuePdbInput.ipynb` and find it at https://localhost:5006/VuePdbInput.\n", "\n", "If you want to use Panel for Chemistry and Molecular Biology checkout [panel-chemistry](https://github.com/marcskovmadsen/panel-chemistry)." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }