Spaces:
Sleeping
Sleeping
| import io | |
| import random | |
| from typing import List, Tuple | |
| import aiohttp | |
| import panel as pn | |
| import pandas as pd | |
| import plotly.express as px | |
| import numpy as np | |
| from PIL import Image | |
| from transformers import CLIPModel, CLIPProcessor | |
| from datetime import datetime, timedelta | |
| # Enable panel extensions | |
| pn.extension(design="fast", sizing_mode="stretch_width") | |
| # Icons list | |
| ICON_URLS = { | |
| "brand-github": "https://github.com/holoviz/panel", | |
| "brand-twitter": "https://twitter.com/Panel_Org", | |
| "brand-linkedin": "https://www.linkedin.com/company/panel-org", | |
| "message-circle": "https://discourse.holoviz.org/", | |
| "brand-discord": "https://discord.gg/AXRHnJU6sP", | |
| } | |
| # --- 1. SAMPLE DATA GENERATION --- | |
| def generate_sample_data(): | |
| np.random.seed(42) | |
| start_date = datetime(2024, 1, 1) | |
| dates = [start_date + timedelta(days=i) for i in range(540)] # 1.5 years of daily data | |
| regions = ['North', 'East', 'South', 'West'] | |
| categories = ['Electronics', 'Furniture', 'Office Supplies'] | |
| subcategories = { | |
| 'Electronics': ['Phones', 'Laptops', 'Accessories'], | |
| 'Furniture': ['Chairs', 'Tables', 'Bookcases'], | |
| 'Office Supplies': ['Paper', 'Art', 'Binders'] | |
| } | |
| data = [] | |
| for date in dates: | |
| num_orders = np.random.randint(1, 5) | |
| for _ in range(num_orders): | |
| region = np.random.choice(regions) | |
| cat = np.random.choice(categories) | |
| subcat = np.random.choice(subcategories[cat]) | |
| if cat == 'Electronics': | |
| base_sales = np.random.uniform(200, 1500) | |
| profit_factor = np.random.uniform(0.1, 0.25) | |
| elif cat == 'Furniture': | |
| base_sales = np.random.uniform(100, 800) | |
| profit_factor = np.random.uniform(0.02, 0.15) | |
| else: | |
| base_sales = np.random.uniform(10, 150) | |
| profit_factor = np.random.uniform(0.2, 0.45) | |
| if date.month in [11, 12]: | |
| base_sales *= np.random.uniform(1.2, 1.5) | |
| sales = round(base_sales, 2) | |
| profit = round(sales * profit_factor, 2) | |
| quantity = np.random.randint(1, 8) | |
| data.append({ | |
| 'Date': date, | |
| 'Region': region, | |
| 'Category': cat, | |
| 'Sub-Category': subcat, | |
| 'Sales': sales, | |
| 'Profit': profit, | |
| 'Quantity': quantity, | |
| 'Year': date.year | |
| }) | |
| return pd.DataFrame(data) | |
| df_data = generate_sample_data() | |
| # --- 2. CLIP ML CLASSIFIER MODEL CACHING --- | |
| def load_processor_model( | |
| processor_name: str, model_name: str | |
| ) -> Tuple[CLIPProcessor, CLIPModel]: | |
| processor = CLIPProcessor.from_pretrained(processor_name) | |
| model = CLIPModel.from_pretrained(model_name) | |
| return processor, model | |
| async def open_image_url(image_url: str) -> Image: | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(image_url) as resp: | |
| if resp.status != 200: | |
| raise Exception(f"HTTP status {resp.status}") | |
| return Image.open(io.BytesIO(await resp.read())) | |
| def get_similarity_scores(class_items: List[str], image: Image) -> List[float]: | |
| processor, model = load_processor_model( | |
| "openai/clip-vit-base-patch32", "openai/clip-vit-base-patch32" | |
| ) | |
| inputs = processor( | |
| text=class_items, | |
| images=[image], | |
| return_tensors="pt", | |
| ) | |
| outputs = model(**inputs) | |
| logits_per_image = outputs.logits_per_image | |
| class_likelihoods = logits_per_image.softmax(dim=1).detach().numpy() | |
| return class_likelihoods[0] | |
| # --- 3. WIDGET DEFINITIONS --- | |
| # Global Sidebar Filters | |
| date_min = df_data['Date'].min().to_pydatetime() | |
| date_max = df_data['Date'].max().to_pydatetime() | |
| date_range_slider = pn.widgets.DateRangeSlider( | |
| name='Filter Date Range', | |
| start=date_min, | |
| end=date_max, | |
| value=(date_min, date_max), | |
| sizing_mode="stretch_width" | |
| ) | |
| regions_list = sorted(list(df_data['Region'].unique())) | |
| region_select = pn.widgets.MultiChoice( | |
| name='Filter Regions', | |
| options=regions_list, | |
| value=regions_list, | |
| sizing_mode="stretch_width" | |
| ) | |
| categories_list = sorted(list(df_data['Category'].unique())) | |
| category_checkboxes = pn.widgets.CheckBoxGroup( | |
| name='Filter Categories', | |
| options=categories_list, | |
| value=categories_list, | |
| inline=False | |
| ) | |
| # Overview Tab Widgets | |
| name_input = pn.widgets.TextInput(name="Enter your name", value="Developer", placeholder="Type name here...", sizing_mode="stretch_width") | |
| color_picker = pn.widgets.ColorPicker(name="Choose card theme color", value="#20B2AA", sizing_mode="stretch_width") | |
| size_slider = pn.widgets.IntSlider(name="Font size adjustment", start=12, end=28, value=16, sizing_mode="stretch_width") | |
| # ML Tab Widgets | |
| image_selector = pn.widgets.Select( | |
| name="Select a sample image", | |
| options={ | |
| "Cat": "https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?q=80&w=600&auto=format&fit=crop", | |
| "Dog": "https://images.unsplash.com/photo-1543466835-00a7907e9de1?q=80&w=600&auto=format&fit=crop", | |
| "Parrot": "https://images.unsplash.com/photo-1552728089-57bdde30ebd3?q=80&w=600&auto=format&fit=crop", | |
| "Sports Car": "https://images.unsplash.com/photo-1503376780353-7e6692767b70?q=80&w=600&auto=format&fit=crop", | |
| "Mountain Landscape": "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?q=80&w=600&auto=format&fit=crop", | |
| "Custom URL (Enter below)": "custom" | |
| }, | |
| value="https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?q=80&w=600&auto=format&fit=crop", | |
| sizing_mode="stretch_width" | |
| ) | |
| custom_url_input = pn.widgets.TextInput( | |
| name="Custom Image URL", | |
| placeholder="Paste any public image URL here...", | |
| visible=False, | |
| sizing_mode="stretch_width" | |
| ) | |
| def update_custom_url_visibility(val): | |
| custom_url_input.visible = (val == "custom") | |
| image_selector.param.watch(lambda event: update_custom_url_visibility(event.new), 'value') | |
| class_names_input = pn.widgets.TextInput( | |
| name="Candidate Classes (comma-separated)", | |
| value="cat, dog, parrot, car, mountain", | |
| placeholder="e.g. cat, dog, parrot", | |
| sizing_mode="stretch_width" | |
| ) | |
| classify_btn = pn.widgets.Button(name="Run CLIP Inference", button_type="primary", sizing_mode="stretch_width") | |
| # Playground Tab Widgets | |
| latex_input = pn.widgets.TextInput( | |
| name="LaTeX Equation Editor", | |
| value=r"f(x) = \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}" | |
| ) | |
| latex_pane = pn.pane.LaTeX( | |
| pn.bind(lambda eq: f"$$\\text{{Output: }} {eq}$$", latex_input), | |
| align="center" | |
| ) | |
| markdown_editor = pn.widgets.TextAreaInput( | |
| name="Markdown Editor", | |
| value="### Markdown Live Preview!\n- **Bold text**\n- *Italics*\n- [Link to Panel](https://panel.holoviz.org)", | |
| height=120 | |
| ) | |
| markdown_pane = pn.pane.Markdown(pn.bind(lambda val: val, markdown_editor)) | |
| file_input = pn.widgets.FileInput(name="Upload File (CSV/Text)", accept=".csv,.txt") | |
| def file_details(data): | |
| if data is None: | |
| return "*No file uploaded yet. Upload a .csv or .txt file to view details.*" | |
| try: | |
| file_len = len(data) | |
| text_preview = data[:150].decode('utf-8', errors='ignore') | |
| return f"**File Size**: {file_len} bytes\n\n**First 150 characters**:\n```\n{text_preview}\n```" | |
| except Exception as e: | |
| return f"Failed to parse file: {str(e)}" | |
| file_details_pane = pn.pane.Markdown(pn.bind(file_details, file_input)) | |
| video_widget = pn.widgets.Video( | |
| value="https://assets.mixkit.co/videos/preview/mixkit-forest-stream-in-the-sunlight-529-large.mp4", | |
| loop=True, autoplay=False, sizing_mode="stretch_width", height=200 | |
| ) | |
| # --- 4. REACTIVE FUNCTIONS & CARD GENERATORS --- | |
| # KPI Cards generator | |
| def make_kpi_card(title, value, color="#20B2AA", icon="π΅"): | |
| return pn.pane.HTML(f""" | |
| <div style=" | |
| background: rgba(128, 128, 128, 0.08); | |
| border-left: 5px solid {color}; | |
| border-radius: 8px; | |
| padding: 15px 20px; | |
| box-shadow: 0 4px 10px rgba(0,0,0,0.05); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| min-width: 180px; | |
| flex: 1; | |
| "> | |
| <div> | |
| <span style="font-size: 13px; opacity: 0.7; text-transform: uppercase; font-weight: 600; display: block; margin-bottom: 5px;">{title}</span> | |
| <span style="font-size: 22px; font-weight: bold; color: var(--neutral-foreground-rest);">{value}</span> | |
| </div> | |
| <span style="font-size: 28px; line-height: 1;">{icon}</span> | |
| </div> | |
| """, sizing_mode="stretch_width") | |
| # Overview Greeting Card | |
| def greeting_card(name, color, size): | |
| style_content = f""" | |
| <div style=" | |
| background: linear-gradient(135deg, {color}, #2c3e50); | |
| padding: 30px; | |
| border-radius: 12px; | |
| text-align: center; | |
| color: white; | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.15); | |
| font-size: {size}px; | |
| transition: all 0.3s ease; | |
| margin-top: 10px; | |
| "> | |
| <h3 style="margin: 0; color: white;">Welcome to Panel, {name if name else "Developer"}! π</h3> | |
| <p style="font-size: 14px; opacity: 0.85; margin: 12px 0 0 0;"> | |
| This card is updating in real time using Panel reactive bindings. | |
| </p> | |
| </div> | |
| """ | |
| return pn.pane.HTML(style_content, sizing_mode="stretch_width") | |
| overview_interactive_card = pn.bind(greeting_card, name=name_input, color=color_picker, size=size_slider) | |
| # Data Dashboard generator | |
| def get_dashboard_layout(df_filtered): | |
| if df_filtered.empty: | |
| return pn.pane.Markdown("### β οΈ No data matches the selected filters. Please adjust them in the sidebar.") | |
| total_sales = df_filtered['Sales'].sum() | |
| total_profit = df_filtered['Profit'].sum() | |
| margin = (total_profit / total_sales) if total_sales > 0 else 0 | |
| total_qty = df_filtered['Quantity'].sum() | |
| kpi1 = make_kpi_card("Total Sales", f"${total_sales:,.2f}", "#20B2AA", "π°") | |
| kpi2 = make_kpi_card("Total Profit", f"${total_profit:,.2f}", "#4CAF50" if total_profit >= 0 else "#F44336", "π") | |
| kpi3 = make_kpi_card("Profit Margin", f"{margin:.1%}", "#FF9800", "π") | |
| kpi4 = make_kpi_card("Products Sold", f"{total_qty:,}", "#9C27B0", "π¦") | |
| kpis = pn.Row(kpi1, kpi2, kpi3, kpi4, sizing_mode="stretch_width", margin=(0, 0, 20, 0)) | |
| # 1. Line chart: Monthly trend | |
| df_monthly = df_filtered.groupby(df_filtered['Date'].dt.to_period('M')).agg({'Sales': 'sum', 'Profit': 'sum'}).reset_index() | |
| df_monthly['Date'] = df_monthly['Date'].dt.to_timestamp() | |
| fig_line = px.line( | |
| df_monthly, x='Date', y='Sales', title="Monthly Sales Trend", | |
| labels={'Sales': 'Sales ($)', 'Date': 'Month'}, | |
| template="plotly_white" | |
| ) | |
| fig_line.update_traces(line_color="#20B2AA", line_width=3) | |
| fig_line.update_layout( | |
| margin=dict(l=40, r=40, t=40, b=40), | |
| paper_bgcolor="rgba(0,0,0,0)", | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| font=dict(color="gray") | |
| ) | |
| chart_line = pn.pane.Plotly(fig_line, sizing_mode="stretch_width", height=350) | |
| # 2. Bar chart: Category | |
| df_cat = df_filtered.groupby(['Category', 'Sub-Category']).agg({'Sales': 'sum'}).reset_index() | |
| fig_bar = px.bar( | |
| df_cat, x='Sub-Category', y='Sales', color='Category', | |
| title="Sales by Category & Sub-Category", | |
| labels={'Sales': 'Sales ($)', 'Sub-Category': 'Sub-Category'}, | |
| color_discrete_sequence=["#20B2AA", "#FF9800", "#9C27B0"], | |
| template="plotly_white" | |
| ) | |
| fig_bar.update_layout( | |
| margin=dict(l=40, r=40, t=40, b=40), | |
| paper_bgcolor="rgba(0,0,0,0)", | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| font=dict(color="gray") | |
| ) | |
| chart_bar = pn.pane.Plotly(fig_bar, sizing_mode="stretch_width", height=350) | |
| # 3. Scatter plot | |
| fig_scatter = px.scatter( | |
| df_filtered, x='Sales', y='Profit', color='Category', size='Quantity', | |
| hover_data=['Sub-Category', 'Date'], title="Transaction Profitability (Sales vs Profit)", | |
| color_discrete_sequence=["#20B2AA", "#FF9800", "#9C27B0"], | |
| opacity=0.7, template="plotly_white" | |
| ) | |
| fig_scatter.update_layout( | |
| margin=dict(l=40, r=40, t=40, b=40), | |
| paper_bgcolor="rgba(0,0,0,0)", | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| font=dict(color="gray") | |
| ) | |
| chart_scatter = pn.pane.Plotly(fig_scatter, sizing_mode="stretch_width", height=350) | |
| layout = pn.Column( | |
| kpis, | |
| pn.Row(chart_line, chart_bar, sizing_mode="stretch_width", margin=(0, 0, 20, 0)), | |
| pn.Row(chart_scatter, sizing_mode="stretch_width"), | |
| sizing_mode="stretch_width" | |
| ) | |
| return layout | |
| def filter_and_render_dashboard(date_range, regions, categories): | |
| df_filtered = df_data.copy() | |
| start_dt, end_dt = date_range | |
| df_filtered = df_filtered[(df_filtered['Date'] >= start_dt) & (df_filtered['Date'] <= end_dt)] | |
| if regions: | |
| df_filtered = df_filtered[df_filtered['Region'].isin(regions)] | |
| else: | |
| df_filtered = df_filtered[df_filtered['Region'].isin([])] | |
| if categories: | |
| df_filtered = df_filtered[df_filtered['Category'].isin(categories)] | |
| else: | |
| df_filtered = df_filtered[df_filtered['Category'].isin([])] | |
| return get_dashboard_layout(df_filtered) | |
| interactive_dashboard = pn.panel( | |
| pn.bind(filter_and_render_dashboard, date_range=date_range_slider, regions=region_select, categories=category_checkboxes), | |
| sizing_mode="stretch_width" | |
| ) | |
| # ML Classification generator | |
| async def classify_image(url, classes_str): | |
| if not url or url == "custom": | |
| yield "##### β οΈ Please provide a valid image URL." | |
| return | |
| try: | |
| yield "##### β Fetching image..." | |
| pil_img = await open_image_url(url) | |
| img_pane = pn.pane.Image(pil_img, height=280, align="center") | |
| except Exception as e: | |
| yield f"##### π Failed to load image from URL: `{url}`. Error: {str(e)}" | |
| return | |
| yield "##### β Running CLIP Model (openai/clip-vit-base-patch32)..." | |
| try: | |
| class_items = [c.strip() for c in classes_str.split(",") if c.strip()] | |
| if not class_items: | |
| yield "##### β οΈ Please specify at least one class name." | |
| return | |
| scores = get_similarity_scores(class_items, pil_img) | |
| results_col = pn.Column( | |
| "##### π Classification Results", | |
| img_pane, | |
| sizing_mode="stretch_width" | |
| ) | |
| for name, score in zip(class_items, scores): | |
| bar = pn.indicators.Progress( | |
| value=int(score * 100), | |
| sizing_mode="stretch_width", | |
| bar_color="success" if score > 0.5 else "info", | |
| height=15 | |
| ) | |
| label = pn.pane.Markdown(f"**{name}**: {score:.2%}", margin=(5, 0, 0, 0)) | |
| results_col.append(pn.Column(label, bar, margin=(5, 0))) | |
| yield results_col | |
| except Exception as e: | |
| yield f"##### π Classification failed. Error: {str(e)}" | |
| def run_classification_on_click(clicks): | |
| url = image_selector.value | |
| if url == "custom": | |
| url = custom_url_input.value | |
| classes = class_names_input.value | |
| if clicks == 0: | |
| if url and url != "custom": | |
| try: | |
| img_pane = pn.pane.Image(url, height=280, align="center") | |
| return pn.Column("##### Image Preview", img_pane) | |
| except: | |
| pass | |
| return "##### π‘ Click 'Run CLIP Inference' to start classification." | |
| return pn.panel(classify_image(url, classes)) | |
| classification_output_area = pn.panel( | |
| pn.bind(run_classification_on_click, clicks=classify_btn), | |
| sizing_mode="stretch_width" | |
| ) | |
| # Reset output when inputs change | |
| def reset_clicks(event): | |
| classify_btn.clicks = 0 | |
| image_selector.param.watch(reset_clicks, 'value') | |
| custom_url_input.param.watch(reset_clicks, 'value') | |
| class_names_input.param.watch(reset_clicks, 'value') | |
| # --- 5. FOOTER SOCIAL LINKS --- | |
| footer_row = pn.Row(pn.Spacer(), align="center") | |
| for icon, url in ICON_URLS.items(): | |
| href_button = pn.widgets.Button(icon=icon, width=38, height=38, button_type="light") | |
| href_button.js_on_click(code=f"window.open('{url}')") | |
| footer_row.append(href_button) | |
| footer_row.append(pn.Spacer()) | |
| # --- 6. TEMPLATE ASSEMBLING --- | |
| template = pn.template.FastListTemplate( | |
| title="HoloViz Panel Interactive Showcase", | |
| sidebar=[ | |
| "## Dashboard Filters", | |
| "*(These filters apply to the **Data Analytics Dashboard** tab)*", | |
| date_range_slider, | |
| pn.Spacer(height=10), | |
| region_select, | |
| pn.Spacer(height=10), | |
| category_checkboxes, | |
| pn.Spacer(height=25), | |
| "### About HoloViz Panel", | |
| "Panel is a powerful Python library that lets you build high-performance interactive web applications, dashboards, and data portals entirely in Python.", | |
| "[Documentation](https://panel.holoviz.org)", | |
| "[GitHub Repository](https://github.com/holoviz/panel)" | |
| ], | |
| main=[ | |
| pn.Tabs( | |
| ("π Overview & Basics", pn.Column( | |
| pn.pane.Markdown(""" | |
| # Welcome to the HoloViz Panel Showcase! π | |
| This Space demonstrates how to build premium, fully interactive dashboards and web applications directly in Python using **Panel**. | |
| ### Why choose Panel? | |
| - **No HTML/CSS/JS required**: Build complex frontends completely in Python. | |
| - **Rich Ecosystem Integration**: Seamlessly connect Bokeh, Plotly, Altair, Matplotlib, PyTorch, and Hugging Face models. | |
| - **Reactive and Callback APIs**: Simple decorators or bindings to link widgets directly to code. | |
| - **Out-of-the-box templates**: Stunning themes like Fast, Material, and Bootstrap that support Dark/Light mode switching. | |
| """), | |
| pn.Spacer(height=15), | |
| pn.Row( | |
| pn.Column( | |
| "### 1. Interactive Greetings Widget", | |
| "Change the inputs below and watch the card update instantly.", | |
| name_input, | |
| color_picker, | |
| size_slider, | |
| margin=(0, 15) | |
| ), | |
| pn.Column( | |
| "### Live Preview", | |
| overview_interactive_card, | |
| margin=(0, 15) | |
| ), | |
| sizing_mode="stretch_width" | |
| ), | |
| pn.Spacer(height=20), | |
| pn.pane.Markdown(""" | |
| ### Check out other tabs: | |
| - **π Data Analytics Dashboard**: A full sales dashboard using Plotly Express linked dynamically to the sidebar filters. | |
| - **π€ CLIP Image Classifier**: Real-time AI classification using an OpenAI CLIP model cached in memory. | |
| - **π Widget Playground**: Live LaTeX editing, Markdown previewing, and file uploads. | |
| """) | |
| )), | |
| ("π Data Analytics Dashboard", pn.Column( | |
| "## Real-time Superstore Analytics", | |
| "Use the filters in the **left sidebar** to refine this dashboard in real-time.", | |
| pn.Spacer(height=10), | |
| interactive_dashboard | |
| )), | |
| ("π€ CLIP Image Classifier", pn.Column( | |
| "## AI Image Classification with CLIP", | |
| "This tab runs **OpenAI CLIP (clip-vit-base-patch32)** to classify images based on natural language descriptors.", | |
| pn.Spacer(height=10), | |
| pn.Row( | |
| pn.Column( | |
| image_selector, | |
| custom_url_input, | |
| class_names_input, | |
| pn.Spacer(height=10), | |
| classify_btn, | |
| width=320, | |
| margin=(0, 15) | |
| ), | |
| pn.Column( | |
| classification_output_area, | |
| margin=(0, 15) | |
| ), | |
| sizing_mode="stretch_width" | |
| ) | |
| )), | |
| ("π Widget Playground", pn.Column( | |
| "## Panel Interactive Playground", | |
| "Explore some of Panel's diverse widgets and dynamic rendering capabilities.", | |
| pn.Spacer(height=15), | |
| pn.Row( | |
| pn.Column( | |
| "### Live LaTeX Renderer", | |
| latex_input, | |
| latex_pane, | |
| margin=(0, 15) | |
| ), | |
| pn.Column( | |
| "### Live Markdown Editor", | |
| markdown_editor, | |
| markdown_pane, | |
| margin=(0, 15) | |
| ), | |
| sizing_mode="stretch_width" | |
| ), | |
| pn.Spacer(height=20), | |
| pn.Row( | |
| pn.Column( | |
| "### File Upload Inspector", | |
| file_input, | |
| file_details_pane, | |
| margin=(0, 15) | |
| ), | |
| pn.Column( | |
| "### Embedded Video Player", | |
| video_widget, | |
| margin=(0, 15) | |
| ), | |
| sizing_mode="stretch_width" | |
| ) | |
| )) | |
| ), | |
| pn.Spacer(height=40), | |
| footer_row | |
| ], | |
| accent_base_color="#20B2AA", | |
| header_background="#20B2AA", | |
| theme_toggle=True | |
| ) | |
| template.servable() |