kopia lustrzana https://github.com/deathbeds/ipydrawio
555 wiersze
16 KiB
Plaintext
555 wiersze
16 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Demo of Data-Driven Decks with Drawio"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import base64\n",
|
|
"import json\n",
|
|
"import pathlib as P\n",
|
|
"import tempfile\n",
|
|
"import urllib.parse\n",
|
|
"from concurrent.futures import ThreadPoolExecutor\n",
|
|
"from io import BytesIO\n",
|
|
"\n",
|
|
"import ipywidgets as W\n",
|
|
"import jinja2\n",
|
|
"import lxml.etree as E\n",
|
|
"import PyPDF2\n",
|
|
"import requests\n",
|
|
"import requests_cache\n",
|
|
"import tornado.ioloop\n",
|
|
"import traitlets as T\n",
|
|
"from nbconvert.filters.markdown_mistune import markdown2html_mistune\n",
|
|
"from PIL import Image\n",
|
|
"from tornado.concurrent import run_on_executor"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"requests_cache.install_cache(\"ddddd\", allowable_methods=[\"GET\", \"POST\"])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Slides"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Slides(W.HTML):\n",
|
|
" \"\"\"some number of slides, as PDF\"\"\"\n",
|
|
"\n",
|
|
" pdf = T.Unicode()\n",
|
|
" error = T.Unicode()\n",
|
|
"\n",
|
|
" def __init__(self, *args, **kwargs):\n",
|
|
" if not kwargs.get(\"layout\"):\n",
|
|
" kwargs[\"layout\"] = {\n",
|
|
" \"display\": \"flex\",\n",
|
|
" \"flex_flow\": \"column wrap\",\n",
|
|
" \"flex\": \"1\",\n",
|
|
" \"height\": \"100%\",\n",
|
|
" }\n",
|
|
" super().__init__(*args, **kwargs)\n",
|
|
"\n",
|
|
" @T.observe(\"pdf\", \"error\")\n",
|
|
" def _on_pdf(self, change):\n",
|
|
" if self.error:\n",
|
|
" self.value = f\"<code>{self.error}</code>\"\n",
|
|
" elif self.pdf:\n",
|
|
" url = f\"data:application/pdf;base64,{self.pdf}\"\n",
|
|
" self.value = f\"\"\"\n",
|
|
" <a href=\"{url}\">Preview</a>\n",
|
|
" <iframe\n",
|
|
" src=\"{url}\"\n",
|
|
" style=\"border: 0; min-width: 400px; min-height: 400px; width: 100%; height: 100%;\">\n",
|
|
" </iframe>\n",
|
|
" \"\"\""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### DrawioSlides"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Before we begin\n",
|
|
"\n",
|
|
"> ## This is **NOT READY** for prime-time\n",
|
|
"> Start the **demo** export server. It will try to install its dependencies with `jlpm`, and requires `nodejs`.\n",
|
|
"> ```bash\n",
|
|
"> !python scripts/drawio_export_demo.py\n",
|
|
"> ```"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class DrawioSlides(Slides):\n",
|
|
" \"\"\"Slides built with drawio-export from drawio XML\"\"\"\n",
|
|
"\n",
|
|
" executor = ThreadPoolExecutor(max_workers=1)\n",
|
|
" ipynb = T.Unicode()\n",
|
|
" xml = T.Union([T.Unicode(), T.Bytes()])\n",
|
|
" params = T.Dict()\n",
|
|
" url = T.Unicode(default_value=\"http://localhost:8000\")\n",
|
|
"\n",
|
|
" CORE_PARAMS = {\"format\": \"pdf\", \"base64\": \"1\"}\n",
|
|
"\n",
|
|
" @run_on_executor\n",
|
|
" def update_pdf(self):\n",
|
|
" # this really needs to be a queue\n",
|
|
" self.error = \"\"\n",
|
|
" self.pdf = \"\"\n",
|
|
" self.value = \"<blockquote>rendering...</blockqoute>\"\n",
|
|
" if isinstance(self.xml, str):\n",
|
|
" xml = self.xml\n",
|
|
" elif isinstance(self.xml, bytes):\n",
|
|
" xml = base64.b64encode(self.xml).decode(\"utf-8\")\n",
|
|
" try:\n",
|
|
" r = requests.post(\n",
|
|
" self.url,\n",
|
|
" timeout=None,\n",
|
|
" data=dict(xml=xml, **self.params, **self.CORE_PARAMS),\n",
|
|
" )\n",
|
|
" if r.status_code != 200:\n",
|
|
" self.error = r.text\n",
|
|
" else:\n",
|
|
" self.pdf = r.text\n",
|
|
" except Exception as err:\n",
|
|
" self.error = str(err)\n",
|
|
"\n",
|
|
" @T.observe(\"ipynb\")\n",
|
|
" def _on_ipynb(self, change=None):\n",
|
|
" self.error = \"\"\n",
|
|
" try:\n",
|
|
" self.xml = json.loads(self.ipynb)[\"metadata\"][\"jupyterlab-drawio\"][\"xml\"]\n",
|
|
" except Exception as err:\n",
|
|
" self.error = str(err)\n",
|
|
"\n",
|
|
" @T.observe(\"xml\")\n",
|
|
" def _on_xml(self, change=None):\n",
|
|
" tornado.ioloop.IOLoop.current().add_callback(self.update_pdf)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/vnd.jupyter.widget-view+json": {
|
|
"model_id": "1a1637093703405681ef262d7fba7bdf",
|
|
"version_major": 2,
|
|
"version_minor": 0
|
|
},
|
|
"text/plain": [
|
|
"DrawioSlides(value='', layout=Layout(display='flex', flex='1', flex_flow='column wrap', height='100%'))"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"HOW_IT_WORKS = P.Path(\"testfiles/How it works.dio\")\n",
|
|
"how_it_works = DrawioSlides(xml=HOW_IT_WORKS.read_text())\n",
|
|
"how_it_works"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Other formats, like `.dio.ipynb` can also be rendered."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/vnd.jupyter.widget-view+json": {
|
|
"model_id": "b33a191a4c684951ad10a436fa353c25",
|
|
"version_major": 2,
|
|
"version_minor": 0
|
|
},
|
|
"text/plain": [
|
|
"DrawioSlides(value='', layout=Layout(display='flex', flex='1', flex_flow='column wrap', height='100%'))"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"DIAGRAM_NOTEBOOK = P.Path(\"Diagram Notebook.dio.ipynb\")\n",
|
|
"diagram_notebook = DrawioSlides(ipynb=DIAGRAM_NOTEBOOK.read_text())\n",
|
|
"diagram_notebook"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/vnd.jupyter.widget-view+json": {
|
|
"model_id": "9f0fe3b39aa34b808d5efa456ac15885",
|
|
"version_major": 2,
|
|
"version_minor": 0
|
|
},
|
|
"text/plain": [
|
|
"DrawioSlides(value='', layout=Layout(display='flex', flex='1', flex_flow='column wrap', height='100%'))"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"DIAGRAM_SVG = P.Path(\"testfiles/A.dio.svg\")\n",
|
|
"diagram_svg = DrawioSlides(xml=DIAGRAM_SVG.read_text())\n",
|
|
"diagram_svg"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/vnd.jupyter.widget-view+json": {
|
|
"model_id": "a7d8a91d3b0b4ec1beb611a012e5f799",
|
|
"version_major": 2,
|
|
"version_minor": 0
|
|
},
|
|
"text/plain": [
|
|
"DrawioSlides(value='', layout=Layout(display='flex', flex='1', flex_flow='column wrap', height='100%'))"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"DIAGRAM_PNG = P.Path(\"testfiles/B.dio.png\")\n",
|
|
"diagram_png = DrawioSlides(xml=DIAGRAM_PNG.read_bytes())\n",
|
|
"diagram_png"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"#### TemplatedDrawioSlides"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class TemplatedDrawioSlides(DrawioSlides):\n",
|
|
" template = T.Unicode()\n",
|
|
" context = T.Dict()\n",
|
|
"\n",
|
|
" AMP = \"&\"\n",
|
|
" _AMP_ = \"_____AMP_____\"\n",
|
|
"\n",
|
|
" @T.observe(\"context\", \"template\")\n",
|
|
" def _on_context(self, change):\n",
|
|
" env = jinja2.Environment(\n",
|
|
" extensions=[\"jinja2.ext.i18n\", \"jinja2.ext.autoescape\"],\n",
|
|
" autoescape=jinja2.select_autoescape([\"html\", \"xml\"]),\n",
|
|
" )\n",
|
|
"\n",
|
|
" self.xml = self.smudge(\n",
|
|
" env.from_string(self.clean(self.template)).render(\n",
|
|
" **{\n",
|
|
" key: self.markdown(value)\n",
|
|
" for key, value in (self.context or {}).items()\n",
|
|
" },\n",
|
|
" ),\n",
|
|
" )\n",
|
|
"\n",
|
|
" def clean(self, txt):\n",
|
|
" return txt.replace(self.AMP, self._AMP_)\n",
|
|
"\n",
|
|
" def smudge(self, txt):\n",
|
|
" return txt.replace(self._AMP_, self.AMP)\n",
|
|
"\n",
|
|
" def markdown(self, md):\n",
|
|
" return markdown2html_mistune(md).replace(self.AMP, self._AMP_)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/vnd.jupyter.widget-view+json": {
|
|
"model_id": "82ca1a2997c2488bbfa6826c4164acc0",
|
|
"version_major": 2,
|
|
"version_minor": 0
|
|
},
|
|
"text/plain": [
|
|
"TemplatedDrawioSlides(value='', layout=Layout(display='flex', flex='1', flex_flow='column wrap', height='100%'…"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"TEMPLATE = P.Path(\"testfiles/template deck.dio\")\n",
|
|
"title = TemplatedDrawioSlides(\n",
|
|
" template=TEMPLATE.read_text(),\n",
|
|
" context={\"hero\": \"<h1>???</h1>\", \"title\": \"_No title here yet..._\"},\n",
|
|
")\n",
|
|
"title"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"logo = \"\"\"<img src=\"https://upload.wikimedia.org/wikipedia/commons/3/38/Jupyter_logo.svg\" width=\"200\" height=\"200\"></image>\"\"\""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"title.context = {\"title\": \"The _title_ can contain `Markdown`\", \"hero\": logo}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"ideas = []\n",
|
|
"for i in range(4):\n",
|
|
" ideas += [\n",
|
|
" TemplatedDrawioSlides(\n",
|
|
" template=TEMPLATE.read_text(),\n",
|
|
" context={\n",
|
|
" \"title\": f\"# Idea {i + 1}\",\n",
|
|
" \"abstract\": f\"This is idea {i + 1}. It's better than [idea {i}](#idea-{i})\",\n",
|
|
" \"hero\": (logo + \"\\n\\n\") * (1 + 1),\n",
|
|
" },\n",
|
|
" ),\n",
|
|
" ]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Deck"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Deck MK1 Prototype\n",
|
|
"\n",
|
|
"The simplest deck prototype is just a box."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"deck = W.HBox(\n",
|
|
" [title, how_it_works, *ideas, diagram_notebook, diagram_png, diagram_svg],\n",
|
|
" layout={\"display\": \"flex\", \"flex_flow\": \"row wrap\"},\n",
|
|
")\n",
|
|
"# deck"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Deck MK2 Prototype\n",
|
|
"\n",
|
|
"This builds a composite deck."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 22,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class Deck(W.HBox):\n",
|
|
" composite = T.Unicode()\n",
|
|
" preview = T.Instance(Slides)\n",
|
|
"\n",
|
|
" def __init__(self, *args, **kwargs):\n",
|
|
" if \"layout\" not in kwargs:\n",
|
|
" kwargs[\"layout\"] = {\"display\": \"flex\", \"flex_flow\": \"row wrap\"}\n",
|
|
" super().__init__(*args, **kwargs)\n",
|
|
"\n",
|
|
" @T.default(\"preview\")\n",
|
|
" def _default_preview(self):\n",
|
|
" slides = Slides()\n",
|
|
" T.dlink((self, \"composite\"), (slides, \"pdf\"))\n",
|
|
" self.update_composite()\n",
|
|
" return slides\n",
|
|
"\n",
|
|
" @T.observe(\"children\")\n",
|
|
" def _on_children(self, change):\n",
|
|
" self.update_composite()\n",
|
|
"\n",
|
|
" def extract_diagrams(self, child):\n",
|
|
" if isinstance(child.xml, str):\n",
|
|
" node = E.fromstring(child.xml)\n",
|
|
" elif isinstance(child.xml, bytes):\n",
|
|
" img = Image.open(BytesIO(child.xml))\n",
|
|
" node = E.fromstring(urllib.parse.unquote(img.info[\"mxfile\"]))\n",
|
|
"\n",
|
|
" tag = node.tag\n",
|
|
"\n",
|
|
" if tag == \"mxfile\":\n",
|
|
" for diagram in node.xpath(\"//diagram\"):\n",
|
|
" yield diagram\n",
|
|
" elif tag == \"mxGraphModel\":\n",
|
|
" diagram = E.Element(\"diagram\")\n",
|
|
" diagram.append(node)\n",
|
|
" yield diagram\n",
|
|
" elif tag == \"{http://www.w3.org/2000/svg}svg\":\n",
|
|
" diagrams = E.fromstring(node.attrib[\"content\"]).xpath(\"//diagram\")\n",
|
|
" for diagram in diagrams:\n",
|
|
" yield diagram\n",
|
|
"\n",
|
|
" def update_composite(self):\n",
|
|
" tree = E.fromstring(\"\"\"<mxfile version=\"13.3.6\"></mxfile>\"\"\")\n",
|
|
" with tempfile.TemporaryDirectory() as td:\n",
|
|
" tdp = P.Path(td)\n",
|
|
" merger = PyPDF2.PdfFileMerger()\n",
|
|
" for i, child in enumerate(self.children):\n",
|
|
" for diagram in self.extract_diagrams(child):\n",
|
|
" tree.append(diagram)\n",
|
|
" next_pdf = tdp / f\"doc-{i}.pdf\"\n",
|
|
" wrote = next_pdf.write_bytes(\n",
|
|
" base64.b64decode(child.pdf.encode(\"utf-8\")),\n",
|
|
" )\n",
|
|
" if wrote:\n",
|
|
" merger.append(PyPDF2.PdfFileReader(str(next_pdf)))\n",
|
|
" output_pdf = tdp / \"output.pdf\"\n",
|
|
" final_pdf = tdp / \"final.pdf\"\n",
|
|
" merger.write(str(output_pdf))\n",
|
|
" self.composite_xml = E.tostring(tree).decode(\"utf-8\")\n",
|
|
" final = PyPDF2.PdfFileWriter()\n",
|
|
" final.appendPagesFromReader(PyPDF2.PdfFileReader(str(output_pdf), \"rb\"))\n",
|
|
" final.addAttachment(\"drawing.drawio\", self.composite_xml.encode(\"utf-8\"))\n",
|
|
" with final_pdf.open(\"wb\") as fpt:\n",
|
|
" final.write(fpt)\n",
|
|
" self.composite = base64.b64encode(final_pdf.read_bytes()).decode(\"utf-8\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 26,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"new_deck = Deck(deck.children)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 27,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"application/vnd.jupyter.widget-view+json": {
|
|
"model_id": "30e626b251464502ad0f6f36755f2bd4",
|
|
"version_major": 2,
|
|
"version_minor": 0
|
|
},
|
|
"text/plain": [
|
|
"Slides(value='\\n <a href=\"data:application/pdf;base64,JVBERi0xLjMKMSAwIG9iago8PAovVHlwZSAvUGFnZ…"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"new_deck.preview"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.9.2"
|
|
},
|
|
"toc-autonumbering": true
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|