diff --git a/.gitignore b/.gitignore index 48a9358..2875827 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,7 @@ venv/ ENV/ env.bak/ venv.bak/ +uv.lock # Spyder project settings .spyderproject diff --git a/docs/api_reference/api_reference.py b/docs/api_reference/api_reference.py index db3f811..c3f2dbb 100644 --- a/docs/api_reference/api_reference.py +++ b/docs/api_reference/api_reference.py @@ -1165,3 +1165,44 @@ def ex_loading2(): LoadingT, title="Loading") + +# Charts +def ex_line_chart(): + return ApexChart( + opts={ + "chart": {"type":"line", "zoom":{"enabled": False}, "toolbar":{"show":False}}, + "series": [{"name":"Desktops", "data": [186, 305, 237, 73, 209, 214, 355]}], + "xaxis": {"categories":["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]} + }, + cls='max-w-md max-h-md' + ) + +def ex_pie_chart(): + return ApexChart( + opts={ + "chart": {"type":"pie", "zoom":{"enabled": False}, "toolbar":{"show":False}}, + "series": [186, 305, 237, 73, 209, 214, 355], + "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"] + }, + cls='max-w-md max-h-md' + ) + +docs_charts = create_doc_section( + H1("Charts API Reference"), + P( + "MonsterUI supports ", A("ApexCharts", href="https://apexcharts.com/", cls='underline'), + ", a javascript library for rendering different charts like line and pie charts. ", + "See the full list of chart types ", A("here.", href="https://apexcharts.com/javascript-chart-demos/", cls='underline'), + ), + P("To render a chart you'll need to include the ApexChart js in your app headers like this"), + CodeSpan("app, rt = fast_app(hdrs=Theme.blue.headers(apex_charts=True))"), + P("Then create an " , CodeSpan("ApexChart"), " component as shown in the examples below."), + P("Generally, you should be able to take any chart from the ApexChart docs, convert the chart's options var to a python dict and plug it straight into MonsterUI's ApexChart component."), + H2("Example usage", cls="mt-4"), + H4("Line chart", cls="mt-4"), + fn2code_string(ex_line_chart), + H4("Pie chart", cls="mt-4"), + fn2code_string(ex_pie_chart), + ApexChart, + title="Charts" +) diff --git a/docs/main.py b/docs/main.py index 842cda0..8c45f99 100644 --- a/docs/main.py +++ b/docs/main.py @@ -25,7 +25,7 @@ def _not_found(req, exc): app,rt = fast_app(exception_handlers={404:_not_found}, pico=False, - hdrs=(*Theme.blue.headers(highlightjs=True), Link(rel="icon", type="image/x-icon", href="/favicon.ico"), + hdrs=(*Theme.blue.headers(highlightjs=True,apex_charts=True), Link(rel="icon", type="image/x-icon", href="/favicon.ico"), Link(rel="stylesheet", href="/custom_theme.css", type="text/css")), ) diff --git a/monsterui/_modidx.py b/monsterui/_modidx.py index 331f5ef..62e6416 100644 --- a/monsterui/_modidx.py +++ b/monsterui/_modidx.py @@ -56,6 +56,7 @@ 'monsterui.franken.Accordion': ('franken.html#accordion', 'monsterui/franken.py'), 'monsterui.franken.AccordionItem': ('franken.html#accordionitem', 'monsterui/franken.py'), 'monsterui.franken.Address': ('franken.html#address', 'monsterui/franken.py'), + 'monsterui.franken.ApexChart': ('franken.html#apexchart', 'monsterui/franken.py'), 'monsterui.franken.Article': ('franken.html#article', 'monsterui/franken.py'), 'monsterui.franken.ArticleMeta': ('franken.html#articlemeta', 'monsterui/franken.py'), 'monsterui.franken.ArticleTitle': ('franken.html#articletitle', 'monsterui/franken.py'), diff --git a/monsterui/core.py b/monsterui/core.py index bc639c6..820744a 100644 --- a/monsterui/core.py +++ b/monsterui/core.py @@ -88,6 +88,7 @@ def _headers_theme(color, mode='auto', radii=ThemeRadii.sm, shadows=ThemeShadows 'franken_icons': "https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/icon.iife.js", 'tailwind': "https://cdn.tailwindcss.com/3.4.16", 'daisyui': "https://cdn.jsdelivr.net/npm/daisyui@4.12.24/dist/full.min.css", + 'apex_charts': "https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/chart.iife.js", 'highlight_js': "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js", 'highlight_python': "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/python.min.js", 'highlight_light_css': "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/atom-one-light.css", @@ -182,7 +183,7 @@ def _generate_next_value_(name, start, count, last_values): return name violet = auto() zinc = auto() - def _create_headers(self, urls, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm): + def _create_headers(self, urls, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, apex_charts=False, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm): "Create header elements with given URLs" hdrs = [ fh.Link(rel="stylesheet", href=urls['franken_css']), @@ -198,6 +199,7 @@ def _create_headers(self, urls, mode='auto', icons=True, daisy=True, highlightjs if icons: hdrs.append(fh.Script(type="module", src=urls['franken_icons'])) if daisy: hdrs += [fh.Link(rel="stylesheet", href=urls['daisyui']), daisy_styles] + if apex_charts: hdrs += [fh.Script(type='module', src=urls['apex_charts'])] if highlightjs: hdrs += [ @@ -251,12 +253,12 @@ def _create_headers(self, urls, mode='auto', icons=True, daisy=True, highlightjs ] return hdrs - def headers(self, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm ): + def headers(self, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, apex_charts=False, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm ): "Create frankenui and tailwind cdns" - return self._create_headers(HEADER_URLS, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, radii=radii, shadows=shadows, font=font) + return self._create_headers(HEADER_URLS, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, apex_charts=apex_charts, radii=radii, shadows=shadows, font=font) - def local_headers(self, mode='auto', static_dir='static', icons=True, daisy=True, highlightjs=False, katex=True, radii='md', shadows='sm', font='sm'): + def local_headers(self, mode='auto', static_dir='static', icons=True, daisy=True, highlightjs=False, katex=True, apex_charts=False, radii='md', shadows='sm', font='sm'): "Create headers using local files downloaded from CDNs" Path(static_dir).mkdir(exist_ok=True) local_urls = dict([_download_resource(url, static_dir) for url in HEADER_URLS.items()]) - return self._create_headers(local_urls, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, radii=radii, shadows=shadows, font=font) + return self._create_headers(local_urls, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, apex_charts=apex_charts, radii=radii, shadows=shadows, font=font) diff --git a/monsterui/franken.py b/monsterui/franken.py index d692f7c..8d7b72b 100644 --- a/monsterui/franken.py +++ b/monsterui/franken.py @@ -17,14 +17,14 @@ 'SliderNav', 'Slider', 'DropDownNavContainer', 'TabContainer', 'CardT', 'CardTitle', 'CardHeader', 'CardBody', 'CardFooter', 'CardContainer', 'Card', 'TableT', 'Table', 'Td', 'Th', 'Tbody', 'TableFromLists', 'TableFromDicts', 'apply_classes', 'render_md', 'get_franken_renderer', 'ThemePicker', 'LightboxContainer', - 'LightboxItem'] + 'LightboxItem', 'ApexChart'] # %% ../nbs/02_franken.ipynb import fasthtml.common as fh from .foundations import * from fasthtml.common import Div, P, Span, FT from enum import Enum, auto -from fasthtml.components import Uk_select,Uk_input_tag,Uk_icon,Uk_input_range +from fasthtml.components import Uk_select,Uk_input_tag,Uk_icon,Uk_input_range, Uk_chart from functools import partial from itertools import zip_longest from typing import Union, Tuple, Optional, Sequence @@ -36,6 +36,7 @@ import mistletoe from lxml import html, etree import fasthtml.components as fh_comp +import json # %% ../nbs/02_franken.ipynb class TextT(VEnum): @@ -1627,3 +1628,13 @@ def LightboxItem(*c, # Component that when clicked will open the lightbox (often )->FT: # A(... href, data_alt, cls., ...) "Anchor tag with appropriate structure to go inside a `LightBoxContainer`" return fh.A(*c, href=href, data_alt=data_alt, cls=stringify(cls), **kwargs) + +# %% ../nbs/02_franken.ipynb +def ApexChart(*, + opts:Dict, # ApexChart options used to render your chart (e.g. {"chart":{"type":"line"}, ...}) + cls: Enum | str | tuple = (), # Classes for the outer container + **kws, # Additional args for the outer container + )->FT: # Div(Uk_chart(Script(...))) + "Apex chart component" + js=NotStr(f"") + return Div(Uk_chart(js), cls=stringify(cls), **kws) diff --git a/nbs/01_core.ipynb b/nbs/01_core.ipynb index f1904b8..c7a954e 100644 --- a/nbs/01_core.ipynb +++ b/nbs/01_core.ipynb @@ -219,6 +219,7 @@ " 'franken_icons': \"https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/icon.iife.js\",\n", " 'tailwind': \"https://cdn.tailwindcss.com/3.4.16\",\n", " 'daisyui': \"https://cdn.jsdelivr.net/npm/daisyui@4.12.24/dist/full.min.css\",\n", + " 'apex_charts': \"https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/chart.iife.js\",\n", " 'highlight_js': \"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js\",\n", " 'highlight_python': \"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/python.min.js\",\n", " 'highlight_light_css': \"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/atom-one-light.css\",\n", @@ -333,7 +334,7 @@ " violet = auto()\n", " zinc = auto()\n", "\n", - " def _create_headers(self, urls, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm):\n", + " def _create_headers(self, urls, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, apex_charts=False, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm):\n", " \"Create header elements with given URLs\"\n", " hdrs = [\n", " fh.Link(rel=\"stylesheet\", href=urls['franken_css']),\n", @@ -349,6 +350,7 @@ "\n", " if icons: hdrs.append(fh.Script(type=\"module\", src=urls['franken_icons']))\n", " if daisy: hdrs += [fh.Link(rel=\"stylesheet\", href=urls['daisyui']), daisy_styles]\n", + " if apex_charts: hdrs += [fh.Script(type='module', src=urls['apex_charts'])]\n", " \n", " if highlightjs:\n", " hdrs += [\n", @@ -402,15 +404,15 @@ " ]\n", " return hdrs\n", "\n", - " def headers(self, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm ):\n", + " def headers(self, mode='auto', icons=True, daisy=True, highlightjs=False, katex=True, apex_charts=False, radii=ThemeRadii.sm, shadows=ThemeShadows.sm, font=ThemeFont.sm ):\n", " \"Create frankenui and tailwind cdns\"\n", - " return self._create_headers(HEADER_URLS, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, radii=radii, shadows=shadows, font=font) \n", + " return self._create_headers(HEADER_URLS, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, apex_charts=apex_charts, radii=radii, shadows=shadows, font=font) \n", " \n", - " def local_headers(self, mode='auto', static_dir='static', icons=True, daisy=True, highlightjs=False, katex=True, radii='md', shadows='sm', font='sm'):\n", + " def local_headers(self, mode='auto', static_dir='static', icons=True, daisy=True, highlightjs=False, katex=True, apex_charts=False, radii='md', shadows='sm', font='sm'):\n", " \"Create headers using local files downloaded from CDNs\"\n", " Path(static_dir).mkdir(exist_ok=True)\n", " local_urls = dict([_download_resource(url, static_dir) for url in HEADER_URLS.items()])\n", - " return self._create_headers(local_urls, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, radii=radii, shadows=shadows, font=font)" + " return self._create_headers(local_urls, mode=mode, icons=icons, daisy=daisy, highlightjs=highlightjs, katex=katex, apex_charts=apex_charts, radii=radii, shadows=shadows, font=font)" ] }, { diff --git a/nbs/02_franken.ipynb b/nbs/02_franken.ipynb index 4d902fd..a9ea586 100644 --- a/nbs/02_franken.ipynb +++ b/nbs/02_franken.ipynb @@ -42,7 +42,7 @@ "from monsterui.foundations import *\n", "from fasthtml.common import Div, P, Span, FT\n", "from enum import Enum, auto\n", - "from fasthtml.components import Uk_select,Uk_input_tag,Uk_icon,Uk_input_range\n", + "from fasthtml.components import Uk_select,Uk_input_tag,Uk_icon,Uk_input_range, Uk_chart\n", "from functools import partial\n", "from itertools import zip_longest\n", "from typing import Union, Tuple, Optional, Sequence\n", @@ -53,7 +53,8 @@ "from mistletoe.span_token import Image\n", "import mistletoe\n", "from lxml import html, etree\n", - "import fasthtml.components as fh_comp" + "import fasthtml.components as fh_comp\n", + "import json" ] }, { @@ -93,7 +94,6 @@ "app,rt = fh.fast_app(pico=False, hdrs=(Theme.violet.headers()),\n", " static_path=os.path.abspath('.'), nb_hdrs=False,\n", " bodykw={\"class\":\"uk-overflow-auto min-h-screen\"})\n", - "\n", "server = JupyUvi(app)\n", "Show = partial(HTMX, app=app)" ] @@ -451,7 +451,7 @@ { "data": { "text/html": [ - " " + " " ], "text/plain": [ "" @@ -2106,7 +2106,7 @@ "from fasthtml.jupyter import *\n", "from monsterui.all import *\n", "from functools import partial\n", - "app, rt = fast_app(hdrs=Theme.blue.headers())\n", + "app, rt = fast_app(hdrs=Theme.blue.headers(apex_charts=True))\n", "server = JupyUvi(app, port=8008)" ] }, @@ -2981,7 +2981,7 @@ { "data": { "text/html": [ - " " + " " ], "text/plain": [ "" @@ -3549,17 +3549,154 @@ }, { "cell_type": "markdown", - "id": "ad606199", + "id": "626d7354", "metadata": {}, "source": [ - "## export -" + "### Apex Charts\n", + "FrankenUI supports [ApexCharts](https://franken-ui.dev/docs/2.0/chart-introduction) which is a javascript [library](https://apexcharts.com/) for rendering charts. \n", + "\n", + "ApexCharts supports many different chart types like line charts, scatter plots and bar charts. See the full list [here](https://apexcharts.com/javascript-chart-demos/).\n", + "\n", + "To render a simple line chart we need to include the ApexChart [js](https://cdn.jsdelivr.net/npm/franken-ui@2.0.0/dist/js/chart.iife.js) in our app headers and then generate the following html.\n", + "\n", + "```html\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "4cab7bd6", + "metadata": {}, + "source": [ + "Let's create a simple component that will take a dictionary that describes the chart and create the html shown above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "124849e8", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def ApexChart(*, \n", + " opts:Dict, # ApexChart options used to render your chart (e.g. {\"chart\":{\"type\":\"line\"}, ...})\n", + " cls: Enum | str | tuple = (), # Classes for the outer container\n", + " **kws, # Additional args for the outer container\n", + " )->FT: # Div(Uk_chart(Script(...)))\n", + " \"Apex chart component\"\n", + " js=NotStr(f\"\")\n", + " return Div(Uk_chart(js), cls=stringify(cls), **kws)" + ] + }, + { + "cell_type": "markdown", + "id": "4bf68297", + "metadata": {}, + "source": [ + "Let's render a simple line chart example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08c8c968", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```html\n", + "
\n", + "
\n", + "\n", + "```" + ], + "text/plain": [ + "div((uk-chart(('',),{}),),{'class': 'max-w-md max-h-md'})" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chart = ApexChart(\n", + " opts={\n", + " \"chart\": {\"type\": \"line\", \"zoom\": {\"enabled\": False}, \"toolbar\": {\"show\": False}},\n", + " \"series\":[{\"name\": \"Desktops\", \"data\": [186, 305, 237, 73, 209, 214, 355]}],\n", + " \"xaxis\": {\"categories\":[\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\"]}\n", + " },\n", + " cls='max-w-md max-h-md'\n", + ")\n", + "chart" ] }, { "cell_type": "code", "execution_count": null, + "id": "1158ce84", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Show(chart)" + ] + }, + { + "cell_type": "markdown", + "id": "36e36f28", + "metadata": {}, + "source": [ + "To include the ApexChart js in your app headers set `apex_charts=True` like this\n", + "\n", + "`app, rt = fast_app(hdrs=Theme.blue.headers(apex_charts=True))`" + ] + }, + { + "cell_type": "markdown", "id": "e05a5ad6", "metadata": {}, + "source": [ + "## export -" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "909c1b2f", + "metadata": {}, "outputs": [], "source": [ "#|hide\n",