diff --git a/getting-started/reference-backend/apps.json b/getting-started/reference-backend/apps.json index d0f172e..e22abd0 100644 --- a/getting-started/reference-backend/apps.json +++ b/getting-started/reference-backend/apps.json @@ -694,6 +694,18 @@ "include_metadata": "false" } } + }, + { + "i": "omni-widget-type", + "x": 19, + "y": 21, + "w": 10, + "h": 22, + "state": { + "params": { + "prompt": "test" + } + } } ] }, diff --git a/getting-started/reference-backend/main.py b/getting-started/reference-backend/main.py index 9d29e20..50a5441 100644 --- a/getting-started/reference-backend/main.py +++ b/getting-started/reference-backend/main.py @@ -1,36 +1,32 @@ # Import required libraries -import json +import asyncio import base64 -import requests -from pathlib import Path -from fastapi import FastAPI, HTTPException, Body, Query -from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse, HTMLResponse -from datetime import datetime, timedelta -import plotly.graph_objects as go -from plotly_config import get_theme_colors, base_layout, get_toolbar_config +import json import random -from pydantic import BaseModel, Field -from uuid import UUID -from typing import Any, List, Literal, List +from datetime import datetime, timedelta from functools import wraps -import asyncio +from pathlib import Path +from typing import Any, List, Literal +from uuid import UUID +import plotly.graph_objects as go +import requests +from fastapi import Body, FastAPI, HTTPException, Query +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import HTMLResponse, JSONResponse +from plotly_config import base_layout, get_theme_colors, get_toolbar_config +from pydantic import BaseModel, Field # Initialize FastAPI application with metadata app = FastAPI( title="Simple Backend", description="Simple backend app for OpenBB Workspace", - version="0.0.1" + version="0.0.1", ) # Define allowed origins for CORS (Cross-Origin Resource Sharing) # This restricts which domains can access the API -origins = [ - "https://pro.openbb.co", - "https://pro.openbb.dev", - "http://localhost:1420" -] +origins = ["https://pro.openbb.co", "https://pro.openbb.dev", "http://localhost:1420"] # Configure CORS middleware to handle cross-origin requests # This allows the specified origins to make requests to the API @@ -44,11 +40,13 @@ ROOT_PATH = Path(__file__).parent.resolve() + @app.get("/") def read_root(): """Root endpoint that returns basic information about the API""" return {"Info": "Hello World"} + # Initialize empty dictionary for widgets WIDGETS = {} @@ -58,13 +56,14 @@ def register_widget(widget_config): Decorator that registers a widget configuration in the WIDGETS dictionary. Args: - widget_config (dict): The widget configuration to add to the WIDGETS - dictionary. This should follow the same structure as other entries + widget_config (dict): The widget configuration to add to the WIDGETS + dictionary. This should follow the same structure as other entries in WIDGETS. Returns: function: The decorated function. """ + def decorator(func): @wraps(func) async def async_wrapper(*args, **kwargs): @@ -91,6 +90,7 @@ def sync_wrapper(*args, **kwargs): if asyncio.iscoroutinefunction(func): return async_wrapper return sync_wrapper + return decorator @@ -100,10 +100,10 @@ def sync_wrapper(*args, **kwargs): @app.get("/widgets.json") def get_widgets(): """Returns the configuration of all registered widgets - + The widgets are automatically registered through the @register_widget decorator and stored in the WIDGETS dictionary from registry.py - + Returns: dict: The configuration of all registered widgets """ @@ -116,7 +116,7 @@ def get_widgets(): @app.get("/apps.json") def get_apps(): """Apps configuration file for the OpenBB Workspace - + Returns: JSONResponse: The contents of apps.json file """ @@ -128,108 +128,122 @@ def get_apps(): # Simple markdown widget # Note that the gridData specifies the size of the widget in the OpenBB Workspace -@register_widget({ - "name": "Markdown Widget", - "description": "A markdown widget", - "type": "markdown", - "endpoint": "markdown_widget", - "gridData": {"w": 12, "h": 4}, -}) +@register_widget( + { + "name": "Markdown Widget", + "description": "A markdown widget", + "type": "markdown", + "endpoint": "markdown_widget", + "gridData": {"w": 12, "h": 4}, + } +) @app.get("/markdown_widget") def markdown_widget(): """Returns a markdown widget""" return "# Markdown Widget" + # Simple markdown widget with category and subcategory # Note that the category and subcategory specify the category and subcategory of the widget in the OpenBB Workspace -# This is important to organize the widgets in the OpenBB Workspace, but also for AI agents to find the best +# This is important to organize the widgets in the OpenBB Workspace, but also for AI agents to find the best # widgets to utilize for a given task -@register_widget({ - "name": "Markdown Widget with Category and Subcategory", - "description": "A markdown widget with category and subcategory", - "type": "markdown", - "category": "Widgets", - "subcategory": "Markdown Widgets", - "endpoint": "markdown_widget_with_category_and_subcategory", - "gridData": {"w": 12, "h": 4}, -}) +@register_widget( + { + "name": "Markdown Widget with Category and Subcategory", + "description": "A markdown widget with category and subcategory", + "type": "markdown", + "category": "Widgets", + "subcategory": "Markdown Widgets", + "endpoint": "markdown_widget_with_category_and_subcategory", + "gridData": {"w": 12, "h": 4}, + } +) @app.get("/markdown_widget_with_category_and_subcategory") def markdown_widget_with_category_and_subcategory(): """Returns a markdown widget with category and subcategory""" - return f"# Markdown Widget with Category and Subcategory" + return "# Markdown Widget with Category and Subcategory" # Markdown Widget with Error Handling # This is a simple widget that demonstrates how to handle errors -@register_widget({ - "name": "Markdown Widget with Error Handling", - "description": "A markdown widget with error handling", - "type": "markdown", - "endpoint": "markdown_widget_with_error_handling", - "gridData": {"w": 12, "h": 4}, -}) +@register_widget( + { + "name": "Markdown Widget with Error Handling", + "description": "A markdown widget with error handling", + "type": "markdown", + "endpoint": "markdown_widget_with_error_handling", + "gridData": {"w": 12, "h": 4}, + } +) @app.get("/markdown_widget_with_error_handling") def markdown_widget_with_error_handling(): """Returns a markdown widget with error handling""" - raise HTTPException( - status_code=500, - detail="Error that just occurred" - ) + raise HTTPException(status_code=500, detail="Error that just occurred") + # Markdown Widget with a Run button # The run button is a button that will execute the code in the widget # this is useful for widgets that are not static and require some computation # that may take a while to complete - e.g. running a model, fetching data, etc. -@register_widget({ - "name": "Markdown Widget with Run Button", - "description": "A markdown widget with a run button", - "type": "markdown", - "endpoint": "markdown_widget_with_run_button", - "gridData": {"w": 12, "h": 4}, - "runButton": True, -}) +@register_widget( + { + "name": "Markdown Widget with Run Button", + "description": "A markdown widget with a run button", + "type": "markdown", + "endpoint": "markdown_widget_with_run_button", + "gridData": {"w": 12, "h": 4}, + "runButton": True, + } +) @app.get("/markdown_widget_with_run_button") def markdown_widget_with_run_button(): """Returns a markdown widget with current time""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"### Current time: {current_time}" + # Markdown Widget with a Short Refetch Interval # The refetch interval is the interval at which the widget will be refreshed # Use lower values for real-time data (e.g., 60000 for 1-minute updates) # Higher values recommended for static or slowly changing data -@register_widget({ - "name": "Markdown Widget with Short Refetch Interval", - "description": "A markdown widget with a short refetch interval", - "type": "markdown", - "endpoint": "markdown_widget_with_short_refetch_interval", - "gridData": {"w": 12, "h": 4}, - "refetchInterval": 1000 -}) +@register_widget( + { + "name": "Markdown Widget with Short Refetch Interval", + "description": "A markdown widget with a short refetch interval", + "type": "markdown", + "endpoint": "markdown_widget_with_short_refetch_interval", + "gridData": {"w": 12, "h": 4}, + "refetchInterval": 1000, + } +) @app.get("/markdown_widget_with_short_refetch_interval") def markdown_widget_with_short_refetch_interval(): """Returns a markdown widget with current time""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"### Current time: {current_time}" + # Markdown Widget with a Short Refetch Interval and a Run Button # The refresh interval is set to 10000ms (10 seconds) but the run button is enabled # which means that the user can refresh the widget manually -@register_widget({ - "name": "Markdown Widget with Short Refetch Interval and a Run Button", - "description": "A markdown widget with a short refetch interval and a run button", - "type": "markdown", - "endpoint": "markdown_widget_with_short_refetch_interval_and_run_button", - "gridData": {"w": 12, "h": 4}, - "refetchInterval": 10000, - "runButton": True -}) +@register_widget( + { + "name": "Markdown Widget with Short Refetch Interval and a Run Button", + "description": "A markdown widget with a short refetch interval and a run button", + "type": "markdown", + "endpoint": "markdown_widget_with_short_refetch_interval_and_run_button", + "gridData": {"w": 12, "h": 4}, + "refetchInterval": 10000, + "runButton": True, + } +) @app.get("/markdown_widget_with_short_refetch_interval_and_run_button") def markdown_widget_with_short_refetch_interval_and_run_button(): """Returns a markdown widget with current time""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"### Current time: {current_time}" + # Structured API Widget Example # Demonstrates how to organize API endpoints by vendor/domain for better maintainability # Benefits: @@ -239,110 +253,121 @@ def markdown_widget_with_short_refetch_interval_and_run_button(): # - Improved code organization and readability # Example structure: /vendor1/endpoint1, /vendor1/endpoint2, /vendor2/endpoint1, /vendor2/endpoint2, ... # This can be done not only based on vendors, but also based on the type of data, e.g. /stocks/endpoint1, /commodities/endpoint2, etc. -@register_widget({ - "name": "Markdown Widget with better structured API", - "description": "A simple markdown widget with a better structured API", - "type": "markdown", - "endpoint": "/vendor1/markdown_widget_with_better_structured_api", - "gridData": {"w": 12, "h": 4}, - "refetchInterval": 10000, - "runButton": True -}) +@register_widget( + { + "name": "Markdown Widget with better structured API", + "description": "A simple markdown widget with a better structured API", + "type": "markdown", + "endpoint": "/vendor1/markdown_widget_with_better_structured_api", + "gridData": {"w": 12, "h": 4}, + "refetchInterval": 10000, + "runButton": True, + } +) @app.get("/vendor1/markdown_widget_with_better_structured_api") def markdown_widget_with_better_structured_api(): """Returns a markdown widget with current time""" return "vendor1/markdown_widget_with_better_structured_api" + # Markdown Widget with Stale Time # The stale time is the time after which the data will be considered stale # and you will see a refresh button in the widget becoming orange to indicate that the data is stale -@register_widget({ - "name": "Markdown Widget with Stale Time", - "description": "A markdown widget with stale time", - "type": "markdown", - "endpoint": "markdown_widget_with_stale_time", - "gridData": {"w": 12, "h": 4}, - "staleTime": 5000 -}) +@register_widget( + { + "name": "Markdown Widget with Stale Time", + "description": "A markdown widget with stale time", + "type": "markdown", + "endpoint": "markdown_widget_with_stale_time", + "gridData": {"w": 12, "h": 4}, + "staleTime": 5000, + } +) @app.get("/markdown_widget_with_stale_time") def markdown_widget_with_stale_time(): """Returns a markdown widget with current time""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"### Current time: {current_time}" + # Markdown Widget with Refetch Interval and Stale Time # The refetch interval is set to 10000ms (10 seconds) and the stale time is set to 5000ms (5 seconds) # Data older than stale time will make the refresh button in the widget become orange to indicate that the data is stale # and once it reaches the refetch interval, the widget will be refreshed and the indicator will turn green again -@register_widget({ - "name": "Markdown Widget with Refetch Interval and Shorter Stale Time", - "description": "A markdown widget with a short refetch interval and a shorter stale time", - "type": "markdown", - "endpoint": "markdown_widget_with_refetch_interval_and_shorter_stale_time", - "gridData": {"w": 12, "h": 4}, - "refetchInterval": 10000, - "staleTime": 5000 -}) +@register_widget( + { + "name": "Markdown Widget with Refetch Interval and Shorter Stale Time", + "description": "A markdown widget with a short refetch interval and a shorter stale time", + "type": "markdown", + "endpoint": "markdown_widget_with_refetch_interval_and_shorter_stale_time", + "gridData": {"w": 12, "h": 4}, + "refetchInterval": 10000, + "staleTime": 5000, + } +) @app.get("/markdown_widget_with_refetch_interval_and_shorter_stale_time") def markdown_widget_with_refetch_interval_and_shorter_stale_time(): """Returns a markdown widget with current time""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"### Current time: {current_time}" + # Markdown Widget with Image from URL # This is a simple widget that demonstrates how to display an image from a URL -@register_widget({ - "name": "Markdown Widget with Image from URL", - "description": "A markdown widget with an image from a URL", - "type": "markdown", - "endpoint": "markdown_widget_with_image_from_url", - "gridData": {"w": 20, "h": 20}, -}) +@register_widget( + { + "name": "Markdown Widget with Image from URL", + "description": "A markdown widget with an image from a URL", + "type": "markdown", + "endpoint": "markdown_widget_with_image_from_url", + "gridData": {"w": 20, "h": 20}, + } +) @app.get("/markdown_widget_with_image_from_url") def markdown_widget_with_image_from_url(): """Returns a markdown widget with an image from a URL""" # Use a simpler, more reliable image URL image_url = "https://api.star-history.com/svg?repos=openbb-finance/OpenBB&type=Date&theme=dark" - + try: response = requests.get(image_url, timeout=10) response.raise_for_status() # Raise an exception for bad status codes - + # Verify the response is actually an image - content_type = response.headers.get('content-type', '') - if not content_type.startswith('image/'): + content_type = response.headers.get("content-type", "") + if not content_type.startswith("image/"): raise HTTPException( status_code=500, - detail=f"URL did not return an image. Content-Type: {content_type}" + detail=f"URL did not return an image. Content-Type: {content_type}", ) # Convert the image to base64 - image_base64 = base64.b64encode(response.content).decode('utf-8') - + image_base64 = base64.b64encode(response.content).decode("utf-8") + # Return the markdown with the base64 image return f"" - + except requests.RequestException as e: raise HTTPException( - status_code=500, - detail=f"Failed to fetch image: {str(e)}" + status_code=500, detail=f"Failed to fetch image: {str(e)}" ) from e except Exception as e: raise HTTPException( - status_code=500, - detail=f"Error processing image: {str(e)}" + status_code=500, detail=f"Error processing image: {str(e)}" ) from e # Markdown Widget with Local Image # This is a simple widget that demonstrates how to display a local image -@register_widget({ - "name": "Markdown Widget with Local Image", - "description": "A markdown widget with a local image", - "type": "markdown", - "endpoint": "markdown_widget_with_local_image", - "gridData": {"w": 20, "h": 20}, -}) +@register_widget( + { + "name": "Markdown Widget with Local Image", + "description": "A markdown widget with a local image", + "type": "markdown", + "endpoint": "markdown_widget_with_local_image", + "gridData": {"w": 20, "h": 20}, + } +) @app.get("/markdown_widget_with_local_image") def markdown_widget_with_local_image(): """Returns a markdown widget with a local image""" @@ -350,33 +375,32 @@ def markdown_widget_with_local_image(): try: with open("img.png", "rb") as image_file: # Convert the image to base64 - image_base64 = base64.b64encode(image_file.read()).decode('utf-8') + image_base64 = base64.b64encode(image_file.read()).decode("utf-8") return f"" except FileNotFoundError: - raise HTTPException( - status_code=500, - detail="Image file not found" - ) from e + raise HTTPException(status_code=500, detail="Image file not found") from e except Exception as e: raise HTTPException( - status_code=500, - detail=f"Error reading image: {str(e)}" + status_code=500, detail=f"Error reading image: {str(e)}" ) from e # Simple HTML widget with mockup data # Note that the gridData specifies the size of the widget in the OpenBB Workspace -@register_widget({ - "name": "HTML Widget", - "description": "A HTML widget with interactive dashboard", - "type": "html", - "endpoint": "html_widget", - "gridData": {"w": 40, "h": 20}, -}) +@register_widget( + { + "name": "HTML Widget", + "description": "A HTML widget with interactive dashboard", + "type": "html", + "endpoint": "html_widget", + "gridData": {"w": 40, "h": 20}, + } +) @app.get("/html_widget", response_class=HTMLResponse) def html_widget(): """Returns an HTML widget with mockup data""" - return HTMLResponse(content=""" + return HTMLResponse( + content="""
@@ -563,83 +587,51 @@ def html_widget(): -""") +""" + ) -@register_widget({ - "name": "Metric Widget", - "description": "A metric widget", - "endpoint": "metric_widget", - "gridData": { - "w": 5, - "h": 5 - }, - "type": "metric" -}) +@register_widget( + { + "name": "Metric Widget", + "description": "A metric widget", + "endpoint": "metric_widget", + "gridData": {"w": 5, "h": 5}, + "type": "metric", + } +) @app.get("/metric_widget") def metric_widget(): # Data structure data = [ - { - "label": "Total Users", - "value": "1,234,567", - "delta": "12.5" - }, - { - "label": "Active Sessions", - "value": "45,678", - "delta": "-2.3" - }, - { - "label": "Revenue (USD)", - "value": "$89,432", - "delta": "8.9" - }, - { - "label": "Conversion Rate", - "value": "3.2%", - "delta": "0.0" - }, - { - "label": "Avg. Session Duration", - "value": "4m 32s", - "delta": "0.5" - } + {"label": "Total Users", "value": "1,234,567", "delta": "12.5"}, + {"label": "Active Sessions", "value": "45,678", "delta": "-2.3"}, + {"label": "Revenue (USD)", "value": "$89,432", "delta": "8.9"}, + {"label": "Conversion Rate", "value": "3.2%", "delta": "0.0"}, + {"label": "Avg. Session Duration", "value": "4m 32s", "delta": "0.5"}, ] return JSONResponse(content=data) + # Simple table widget # Utilize mock data for demonstration purposes on how a table widget can be used -@register_widget({ - "name": "Table Widget", - "description": "A table widget", - "type": "table", - "endpoint": "table_widget", - "gridData": {"w": 12, "h": 4}, -}) +@register_widget( + { + "name": "Table Widget", + "description": "A table widget", + "type": "table", + "endpoint": "table_widget", + "gridData": {"w": 12, "h": 4}, + } +) @app.get("/table_widget") def table_widget(): """Returns a mock table data for demonstration""" mock_data = [ - { - "name": "Ethereum", - "tvl": 45000000000, - "change_1d": 2.5, - "change_7d": 5.2 - }, - { - "name": "Bitcoin", - "tvl": 35000000000, - "change_1d": 1.2, - "change_7d": 4.8 - }, - { - "name": "Solana", - "tvl": 8000000000, - "change_1d": -0.5, - "change_7d": 2.1 - } + {"name": "Ethereum", "tvl": 45000000000, "change_1d": 2.5, "change_7d": 5.2}, + {"name": "Bitcoin", "tvl": 35000000000, "change_1d": 1.2, "change_7d": 4.8}, + {"name": "Solana", "tvl": 8000000000, "change_1d": -0.5, "change_7d": 2.1}, ] return mock_data @@ -673,77 +665,64 @@ def table_widget(): # Example: "left""right" # headerTooltip: Tooltip text for the column header. # Example: "This is a tooltip" -@register_widget({ - "name": "Table Widget with Column Definitions", - "description": "A table widget with column definitions", - "type": "table", - "endpoint": "table_widget_with_column_definitions", - "gridData": {"w": 20, "h": 6}, - "data": { - "table": { - "columnsDefs": [ - { - "field": "name", - "headerName": "Asset", - "cellDataType": "text", - "formatterFn": "none", - "renderFn": "titleCase", - "width": 120, - "pinned": "left" - }, - { - "field": "tvl", - "headerName": "TVL (USD)", - "headerTooltip": "Total Value Locked", - "cellDataType": "number", - "formatterFn": "int", - "width": 150 - }, - { - "field": "change_1d", - "headerName": "24h Change", - "cellDataType": "number", - "formatterFn": "percent", - "width": 120, - "maxWidth": 150, - "minWidth": 70, - }, - { - "field": "change_7d", - "headerName": "7d Change", - "cellDataType": "number", - "formatterFn": "percent", - "width": 120, - "maxWidth": 150, - "minWidth": 70, - "hide": True - }, - ] - } - }, -}) +@register_widget( + { + "name": "Table Widget with Column Definitions", + "description": "A table widget with column definitions", + "type": "table", + "endpoint": "table_widget_with_column_definitions", + "gridData": {"w": 20, "h": 6}, + "data": { + "table": { + "columnsDefs": [ + { + "field": "name", + "headerName": "Asset", + "cellDataType": "text", + "formatterFn": "none", + "renderFn": "titleCase", + "width": 120, + "pinned": "left", + }, + { + "field": "tvl", + "headerName": "TVL (USD)", + "headerTooltip": "Total Value Locked", + "cellDataType": "number", + "formatterFn": "int", + "width": 150, + }, + { + "field": "change_1d", + "headerName": "24h Change", + "cellDataType": "number", + "formatterFn": "percent", + "width": 120, + "maxWidth": 150, + "minWidth": 70, + }, + { + "field": "change_7d", + "headerName": "7d Change", + "cellDataType": "number", + "formatterFn": "percent", + "width": 120, + "maxWidth": 150, + "minWidth": 70, + "hide": True, + }, + ] + } + }, + } +) @app.get("/table_widget_with_column_definitions") def table_widget_with_column_definitions(): """Returns a mock table data for demonstration""" mock_data = [ - { - "name": "Ethereum", - "tvl": 45000000000, - "change_1d": 2.5, - "change_7d": 5.2 - }, - { - "name": "Bitcoin", - "tvl": 35000000000, - "change_1d": 1.2, - "change_7d": 4.8 - }, - { - "name": "Solana", - "tvl": 8000000000, - "change_1d": -0.5, - "change_7d": 2.1 - } + {"name": "Ethereum", "tvl": 45000000000, "change_1d": 2.5, "change_7d": 5.2}, + {"name": "Bitcoin", "tvl": 35000000000, "change_1d": 1.2, "change_7d": 4.8}, + {"name": "Solana", "tvl": 8000000000, "change_1d": -0.5, "change_7d": 2.1}, ] return mock_data @@ -751,7 +730,7 @@ def table_widget_with_column_definitions(): # Simple table widget with hover card # The most important part of this widget that hasn't been covered in the previous widget is the hover card is the "renderFn" key in the columnsDefs object # renderFn: Specifies a rendering function for cell data. -# Example: "titleCase"Possible values: +# Example: "titleCase"Possible values: # - greenRed: Applies a green or red color based on conditions # - titleCase: Converts text to title case # - hoverCard: Displays additional information when hovering over a cell @@ -762,104 +741,88 @@ def table_widget_with_column_definitions(): # Example: # - if renderFn is "columnColor", then renderFnParams is required and must be a "colorRules" dictionary with the following keys: condition, color, range, fill. # - if renderFn is "hoverCard", then renderFnParams is required and must be a "hoverCard" dictionary with the following keys: cellField, title, markdown. -@register_widget({ - "name": "Table Widget with Render Functions", - "description": "A table widget with render functions", - "type": "table", - "endpoint": "table_widget_with_render_functions", - "gridData": {"w": 20, "h": 6}, - "data": { - "table": { - "columnsDefs": [ - { - "field": "name", - "headerName": "Asset", - "cellDataType": "text", - "formatterFn": "none", - "renderFn": "titleCase", - "width": 120, - "pinned": "left" - }, - { - "field": "tvl", - "headerName": "TVL (USD)", - "headerTooltip": "Total Value Locked", - "cellDataType": "number", - "formatterFn": "int", - "width": 150, - "renderFn": "columnColor", - "renderFnParams": { - "colorRules": [ - { - "condition": "between", - "range": { - "min": 30000000000, - "max": 40000000000 +@register_widget( + { + "name": "Table Widget with Render Functions", + "description": "A table widget with render functions", + "type": "table", + "endpoint": "table_widget_with_render_functions", + "gridData": {"w": 20, "h": 6}, + "data": { + "table": { + "columnsDefs": [ + { + "field": "name", + "headerName": "Asset", + "cellDataType": "text", + "formatterFn": "none", + "renderFn": "titleCase", + "width": 120, + "pinned": "left", + }, + { + "field": "tvl", + "headerName": "TVL (USD)", + "headerTooltip": "Total Value Locked", + "cellDataType": "number", + "formatterFn": "int", + "width": 150, + "renderFn": "columnColor", + "renderFnParams": { + "colorRules": [ + { + "condition": "between", + "range": {"min": 30000000000, "max": 40000000000}, + "color": "blue", + "fill": False, }, - "color": "blue", - "fill": False - }, - { - "condition": "lt", - "value": 10000000000, - "color": "#FFA500", - "fill": False - }, - { - "condition": "gt", - "value": 40000000000, - "color": "green", - "fill": True - } - ] - } - }, - { - "field": "change_1d", - "headerName": "24h Change", - "cellDataType": "number", - "formatterFn": "percent", - "renderFn": "greenRed", - "width": 120, - "maxWidth": 150, - "minWidth": 70, - }, - { - "field": "change_7d", - "headerName": "7d Change", - "cellDataType": "number", - "formatterFn": "percent", - "renderFn": "greenRed", - "width": 120, - "maxWidth": 150, - "minWidth": 70, - } - ] - } - }, -}) + { + "condition": "lt", + "value": 10000000000, + "color": "#FFA500", + "fill": False, + }, + { + "condition": "gt", + "value": 40000000000, + "color": "green", + "fill": True, + }, + ] + }, + }, + { + "field": "change_1d", + "headerName": "24h Change", + "cellDataType": "number", + "formatterFn": "percent", + "renderFn": "greenRed", + "width": 120, + "maxWidth": 150, + "minWidth": 70, + }, + { + "field": "change_7d", + "headerName": "7d Change", + "cellDataType": "number", + "formatterFn": "percent", + "renderFn": "greenRed", + "width": 120, + "maxWidth": 150, + "minWidth": 70, + }, + ] + } + }, + } +) @app.get("/table_widget_with_render_functions") def table_widget_with_render_functions(): """Returns a mock table data for demonstration""" mock_data = [ - { - "name": "Ethereum", - "tvl": 45000000000, - "change_1d": 2.5, - "change_7d": 5.2 - }, - { - "name": "Bitcoin", - "tvl": 35000000000, - "change_1d": 1.2, - "change_7d": 4.8 - }, - { - "name": "Solana", - "tvl": 8000000000, - "change_1d": -0.5, - "change_7d": 2.1 - } + {"name": "Ethereum", "tvl": 45000000000, "change_1d": 2.5, "change_7d": 5.2}, + {"name": "Bitcoin", "tvl": 35000000000, "change_1d": 1.2, "change_7d": 4.8}, + {"name": "Solana", "tvl": 8000000000, "change_1d": -0.5, "change_7d": 2.1}, ] return mock_data @@ -867,64 +830,66 @@ def table_widget_with_render_functions(): # Simple table widget with hover card # The most important part of this widget that hasn't been covered in the previous widgets is the hover card # which is a feature that allows you to display additional information when hovering over a cell -@register_widget({ - "name": "Table Widget with Hover Card", - "description": "A table widget with hover card", - "type": "table", - "endpoint": "table_widget_with_hover_card", - "gridData": {"w": 20, "h": 6}, - "data": { - "table": { - "columnsDefs": [ - { - "field": "name", - "headerName": "Asset", - "cellDataType": "text", - "formatterFn": "none", - "width": 120, - "pinned": "left", - "renderFn": "hoverCard", - "renderFnParams": { - "hoverCard": { - "cellField": "value", - "title": "Project Details", - "markdown": "### {value} (since {foundedDate})\n**Description:** {description}" - } - } - }, - { - "field": "tvl", - "headerName": "TVL (USD)", - "headerTooltip": "Total Value Locked", - "cellDataType": "number", - "formatterFn": "int", - "width": 150, - "renderFn": "columnColor", - }, - { - "field": "change_1d", - "headerName": "24h Change", - "cellDataType": "number", - "formatterFn": "percent", - "renderFn": "greenRed", - "width": 120, - "maxWidth": 150, - "minWidth": 70, - }, - { - "field": "change_7d", - "headerName": "7d Change", - "cellDataType": "number", - "formatterFn": "percent", - "renderFn": "greenRed", - "width": 120, - "maxWidth": 150, - "minWidth": 70, - } - ] - } - }, -}) +@register_widget( + { + "name": "Table Widget with Hover Card", + "description": "A table widget with hover card", + "type": "table", + "endpoint": "table_widget_with_hover_card", + "gridData": {"w": 20, "h": 6}, + "data": { + "table": { + "columnsDefs": [ + { + "field": "name", + "headerName": "Asset", + "cellDataType": "text", + "formatterFn": "none", + "width": 120, + "pinned": "left", + "renderFn": "hoverCard", + "renderFnParams": { + "hoverCard": { + "cellField": "value", + "title": "Project Details", + "markdown": "### {value} (since {foundedDate})\n**Description:** {description}", + } + }, + }, + { + "field": "tvl", + "headerName": "TVL (USD)", + "headerTooltip": "Total Value Locked", + "cellDataType": "number", + "formatterFn": "int", + "width": 150, + "renderFn": "columnColor", + }, + { + "field": "change_1d", + "headerName": "24h Change", + "cellDataType": "number", + "formatterFn": "percent", + "renderFn": "greenRed", + "width": 120, + "maxWidth": 150, + "minWidth": 70, + }, + { + "field": "change_7d", + "headerName": "7d Change", + "cellDataType": "number", + "formatterFn": "percent", + "renderFn": "greenRed", + "width": 120, + "maxWidth": 150, + "minWidth": 70, + }, + ] + } + }, + } +) @app.get("/table_widget_with_hover_card") def table_widget_with_hover_card(): """Returns a mock table data for demonstration""" @@ -933,32 +898,32 @@ def table_widget_with_hover_card(): "name": { "value": "Ethereum", "description": "A decentralized, open-source blockchain with smart contract functionality", - "foundedDate": "2015-07-30" + "foundedDate": "2015-07-30", }, "tvl": 45000000000, "change_1d": 2.5, - "change_7d": 5.2 + "change_7d": 5.2, }, { "name": { "value": "Bitcoin", "description": "The first decentralized cryptocurrency", - "foundedDate": "2009-01-03" + "foundedDate": "2009-01-03", }, "tvl": 35000000000, "change_1d": 1.2, - "change_7d": 4.8 + "change_7d": 4.8, }, { "name": { "value": "Solana", "description": "A high-performance blockchain supporting builders around the world", - "foundedDate": "2020-03-16" + "foundedDate": "2020-03-16", }, "tvl": 8000000000, "change_1d": -0.5, - "change_7d": 2.1 - } + "change_7d": 2.1, + }, ] return mock_data @@ -968,151 +933,111 @@ def table_widget_with_hover_card(): # chartDataType: Specifies how data is treated in a chart. # Example: "category" # Possible values: "category", "series", "time", "excluded" -@register_widget({ - "name": "Table to Chart Widget", - "description": "A table widget", - "type": "table", - "endpoint": "table_to_chart_widget", - "gridData": {"w": 20, "h": 12}, - "data": { - "table": { - "enableCharts": True, - "showAll": False, - "chartView": { - "enabled": True, - "chartType": "column" - }, - "columnsDefs": [ - { - "field": "name", - "headerName": "Asset", - "chartDataType": "category", - }, - { - "field": "tvl", - "headerName": "TVL (USD)", - "chartDataType": "series", - }, - ] - } - }, -}) +@register_widget( + { + "name": "Table to Chart Widget", + "description": "A table widget", + "type": "table", + "endpoint": "table_to_chart_widget", + "gridData": {"w": 20, "h": 12}, + "data": { + "table": { + "enableCharts": True, + "showAll": False, + "chartView": {"enabled": True, "chartType": "column"}, + "columnsDefs": [ + { + "field": "name", + "headerName": "Asset", + "chartDataType": "category", + }, + { + "field": "tvl", + "headerName": "TVL (USD)", + "chartDataType": "series", + }, + ], + } + }, + } +) @app.get("/table_to_chart_widget") def table_to_chart_widget(): """Returns a mock table data for demonstration""" mock_data = [ - { - "name": "Ethereum", - "tvl": 45000000000, - "change_1d": 2.5, - "change_7d": 5.2 - }, - { - "name": "Bitcoin", - "tvl": 35000000000, - "change_1d": 1.2, - "change_7d": 4.8 - }, - { - "name": "Solana", - "tvl": 8000000000, - "change_1d": -0.5, - "change_7d": 2.1 - } + {"name": "Ethereum", "tvl": 45000000000, "change_1d": 2.5, "change_7d": 5.2}, + {"name": "Bitcoin", "tvl": 35000000000, "change_1d": 1.2, "change_7d": 4.8}, + {"name": "Solana", "tvl": 8000000000, "change_1d": -0.5, "change_7d": 2.1}, ] return mock_data - # Table to time series Widget # In here we will see how to use a table widget to display a time series chart -@register_widget({ - "name": "Table to Time Series Widget", - "description": "A table widget", - "type": "table", - "endpoint": "table_to_time_series_widget", - "gridData": {"w": 20, "h": 12}, - "data": { - "table": { - "enableCharts": True, - "showAll": False, - "chartView": { - "enabled": True, - "chartType": "line" - }, - "columnsDefs": [ - { - "field": "date", - "headerName": "Date", - "chartDataType": "time", - }, - { - "field": "Ethereum", - "headerName": "Ethereum", - "chartDataType": "series", - }, - { - "field": "Bitcoin", - "headerName": "Bitcoin", - "chartDataType": "series", - }, - { - "field": "Solana", - "headerName": "Solana", - "chartDataType": "series", - } - ] - } - }, -}) +@register_widget( + { + "name": "Table to Time Series Widget", + "description": "A table widget", + "type": "table", + "endpoint": "table_to_time_series_widget", + "gridData": {"w": 20, "h": 12}, + "data": { + "table": { + "enableCharts": True, + "showAll": False, + "chartView": {"enabled": True, "chartType": "line"}, + "columnsDefs": [ + { + "field": "date", + "headerName": "Date", + "chartDataType": "time", + }, + { + "field": "Ethereum", + "headerName": "Ethereum", + "chartDataType": "series", + }, + { + "field": "Bitcoin", + "headerName": "Bitcoin", + "chartDataType": "series", + }, + { + "field": "Solana", + "headerName": "Solana", + "chartDataType": "series", + }, + ], + } + }, + } +) @app.get("/table_to_time_series_widget") def table_to_time_series_widget(): """Returns a mock table data for demonstration""" mock_data = [ - { - "date": "2024-06-06", - "Ethereum": 1.0000, - "Bitcoin": 1.0000, - "Solana": 1.0000 - }, - { - "date": "2024-06-07", - "Ethereum": 1.0235, - "Bitcoin": 0.9822, - "Solana": 1.0148 - }, - { - "date": "2024-06-08", - "Ethereum": 0.9945, - "Bitcoin": 1.0072, - "Solana": 0.9764 - }, - { - "date": "2024-06-09", - "Ethereum": 1.0205, - "Bitcoin": 0.9856, - "Solana": 1.0300 - }, - { - "date": "2024-06-10", - "Ethereum": 0.9847, - "Bitcoin": 1.0195, - "Solana": 0.9897 - } + {"date": "2024-06-06", "Ethereum": 1.0000, "Bitcoin": 1.0000, "Solana": 1.0000}, + {"date": "2024-06-07", "Ethereum": 1.0235, "Bitcoin": 0.9822, "Solana": 1.0148}, + {"date": "2024-06-08", "Ethereum": 0.9945, "Bitcoin": 1.0072, "Solana": 0.9764}, + {"date": "2024-06-09", "Ethereum": 1.0205, "Bitcoin": 0.9856, "Solana": 1.0300}, + {"date": "2024-06-10", "Ethereum": 0.9847, "Bitcoin": 1.0195, "Solana": 0.9897}, ] return mock_data + # Simple table widget from an API endpoint # This is a simple widget that demonstrates how to use a table widget from an API endpoint # Note that the endpoint is the endpoint of the API that will be used to fetch the data # and the data is returned in the JSON format -@register_widget({ - "name": "Table Widget from API Endpoint", - "description": "A table widget from an API endpoint", - "type": "table", - "endpoint": "table_widget_from_api_endpoint", - "gridData": {"w": 12, "h": 4}, -}) +@register_widget( + { + "name": "Table Widget from API Endpoint", + "description": "A table widget from an API endpoint", + "type": "table", + "endpoint": "table_widget_from_api_endpoint", + "gridData": {"w": 12, "h": 4}, + } +) @app.get("/table_widget_from_api_endpoint") def table_widget_from_api_endpoint(): """Get current TVL of all chains using Defi LLama""" @@ -1122,23 +1047,18 @@ def table_widget_from_api_endpoint(): return response.json() print(f"Request error {response.status_code}: {response.text}") - raise HTTPException( - status_code=response.status_code, - detail=response.text - ) - + raise HTTPException(status_code=response.status_code, detail=response.text) -@register_widget({ - "name": "PDF Widget with Base64", - "description": "Display a PDF file with base64 encoding", - "endpoint": "pdf_widget_base64", - "gridData": { - "w": 20, - "h": 20 - }, - "type": "pdf", -}) +@register_widget( + { + "name": "PDF Widget with Base64", + "description": "Display a PDF file with base64 encoding", + "endpoint": "pdf_widget_base64", + "gridData": {"w": 20, "h": 20}, + "type": "pdf", + } +) @app.get("/pdf_widget_base64") def get_pdf_widget_base64(): """Serve a file through base64 encoding.""" @@ -1148,13 +1068,10 @@ def get_pdf_widget_base64(): file_data = file.read() encoded_data = base64.b64encode(file_data) content = encoded_data.decode("utf-8") - + except FileNotFoundError as exc: - raise HTTPException( - status_code=404, - detail="File not found" - ) from exc - + raise HTTPException(status_code=404, detail="File not found") from exc + return JSONResponse( headers={"Content-Type": "application/json"}, content={ @@ -1167,20 +1084,21 @@ def get_pdf_widget_base64(): ) -@register_widget({ - "name": "PDF Widget with URL", - "description": "Display a PDF file", - "type": "pdf", - "endpoint": "pdf_widget_url", - "gridData": { - "w": 20, - "h": 20 - }, -}) +@register_widget( + { + "name": "PDF Widget with URL", + "description": "Display a PDF file", + "type": "pdf", + "endpoint": "pdf_widget_url", + "gridData": {"w": 20, "h": 20}, + } +) @app.get("/pdf_widget_url") def get_pdf_widget_url(): """Serve a file through URL.""" - file_reference = "https://openbb-assets.s3.us-east-1.amazonaws.com/testing/sample.pdf" + file_reference = ( + "https://openbb-assets.s3.us-east-1.amazonaws.com/testing/sample.pdf" + ) if not file_reference: raise HTTPException(status_code=404, detail="File not found") return JSONResponse( @@ -1194,6 +1112,7 @@ def get_pdf_widget_url(): }, ) + # Sample PDF files data SAMPLE_PDFS = [ { @@ -1202,12 +1121,13 @@ def get_pdf_widget_url(): "url": "https://openbb-assets.s3.us-east-1.amazonaws.com/testing/sample.pdf", }, { - "name": "Bitcoin Whitepaper", + "name": "Bitcoin Whitepaper", "location": "bitcoin.pdf", "url": "https://openbb-assets.s3.us-east-1.amazonaws.com/testing/bitcoin.pdf", - } + }, ] + # Sample PDF options endpoint # This is a simple endpoint to get the list of available PDFs # and return it in the JSON format. The reason why we need this endpoint is because the multi_file_viewer widget @@ -1215,36 +1135,31 @@ def get_pdf_widget_url(): @app.get("/get_pdf_options") async def get_pdf_options(): """Get list of available PDFs""" - return [ - { - "label": pdf["name"], - "value": pdf["name"] - } for pdf in SAMPLE_PDFS - ] + return [{"label": pdf["name"], "value": pdf["name"]} for pdf in SAMPLE_PDFS] -@register_widget({ - "name": "Multi PDF Viewer - Base64", - "description": "View multiple PDF files using base64 encoding", - "type": "multi_file_viewer", - "endpoint": "multi_pdf_base64", - "gridData": { - "w": 20, - "h": 10 - }, - "params": [ - { - "paramName": "pdf_name", - "description": "PDF file to display", - "type": "endpoint", - "label": "PDF File", - "optionsEndpoint": "/get_pdf_options", - "show": False, - "value": ["Bitcoin Whitepaper"], - "multiSelect": True, - "roles": ["fileSelector"] - } - ] -}) + +@register_widget( + { + "name": "Multi PDF Viewer - Base64", + "description": "View multiple PDF files using base64 encoding", + "type": "multi_file_viewer", + "endpoint": "multi_pdf_base64", + "gridData": {"w": 20, "h": 10}, + "params": [ + { + "paramName": "pdf_name", + "description": "PDF file to display", + "type": "endpoint", + "label": "PDF File", + "optionsEndpoint": "/get_pdf_options", + "show": False, + "value": ["Bitcoin Whitepaper"], + "multiSelect": True, + "roles": ["fileSelector"], + } + ], + } +) @app.get("/multi_pdf_base64") async def get_multi_pdf_base64(pdf_name: str): """Get PDF content in base64 format""" @@ -1262,37 +1177,34 @@ async def get_multi_pdf_base64(pdf_name: str): return JSONResponse( headers={"Content-Type": "application/json"}, content={ - "data_format": { - "data_type": "pdf", - "filename": f"{pdf['name']}.pdf" - }, + "data_format": {"data_type": "pdf", "filename": f"{pdf['name']}.pdf"}, "content": base64_content, }, ) -@register_widget({ - "name": "Multi PDF Viewer - URL", - "description": "View multiple PDF files using URLs", - "type": "multi_file_viewer", - "endpoint": "multi_pdf_url", - "gridData": { - "w": 20, - "h": 10 - }, - "params": [ - { - "paramName": "pdf_name", - "description": "PDF file to display", - "type": "endpoint", - "label": "PDF File", - "optionsEndpoint": "/get_pdf_options", - "value": ["Sample"], - "show": False, - "multiSelect": True, - "roles": ["fileSelector"] - } - ] -}) + +@register_widget( + { + "name": "Multi PDF Viewer - URL", + "description": "View multiple PDF files using URLs", + "type": "multi_file_viewer", + "endpoint": "multi_pdf_url", + "gridData": {"w": 20, "h": 10}, + "params": [ + { + "paramName": "pdf_name", + "description": "PDF file to display", + "type": "endpoint", + "label": "PDF File", + "optionsEndpoint": "/get_pdf_options", + "value": ["Sample"], + "show": False, + "multiSelect": True, + "roles": ["fileSelector"], + } + ], + } +) @app.get("/multi_pdf_url") async def get_multi_pdf_url(pdf_name: str): """Get PDF URL""" @@ -1308,108 +1220,119 @@ async def get_multi_pdf_url(pdf_name: str): }, ) + # This is a simple markdown widget with a date picker parameter # The date picker parameter is a date picker that allows users to select a specific date -# and we pass this parameter to the widget as the date_picker parameter -@register_widget({ - "name": "Markdown Widget with Date Picker", - "description": "A markdown widget with a date picker parameter", - "endpoint": "markdown_widget_with_date_picker", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - { - "paramName": "date_picker", - "description": "Choose a date to display", - "value": (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d"), - "label": "Select Date", - "type": "date" - } - ] -}) +# and we pass this parameter to the widget as the date_picker parameter +@register_widget( + { + "name": "Markdown Widget with Date Picker", + "description": "A markdown widget with a date picker parameter", + "endpoint": "markdown_widget_with_date_picker", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + { + "paramName": "date_picker", + "description": "Choose a date to display", + "value": (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d"), + "label": "Select Date", + "type": "date", + } + ], + } +) @app.get("/markdown_widget_with_date_picker") def markdown_widget_with_date_picker( - date_picker: str = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") + date_picker: str = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d"), ): """Returns a markdown widget with date picker parameter""" return f"""# Date Picker Selected date: {date_picker} """ + # This is a simple markdown widget with a text input parameter # The text input parameter is a text input that allows users to enter a specific text # and we pass this parameter to the widget as the textBox1 parameter -@register_widget({ - "name": "Markdown Widget with Text Input", - "description": "A markdown widget with a text input parameter", - "endpoint": "markdown_widget_with_text_input", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - { - "paramName": "text_box", - "value": "hello", - "label": "Enter Text", - "description": "Type something to display", - "type": "text" - } - ] -}) +@register_widget( + { + "name": "Markdown Widget with Text Input", + "description": "A markdown widget with a text input parameter", + "endpoint": "markdown_widget_with_text_input", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + { + "paramName": "text_box", + "value": "hello", + "label": "Enter Text", + "description": "Type something to display", + "type": "text", + } + ], + } +) @app.get("/markdown_widget_with_text_input") def markdown_widget_with_text_input(text_box: str): """Returns a markdown widget with text input parameter""" return f"""# Text Input Entered text: {text_box} -""" +""" # This is a simple markdown widget with an editable text input parameter # The editable text input parameter is a text input that allows users to enter a specific text # and we pass all of them as a list to the widget as the text_box parameter # The user is able to add more by just typing a new variable and pressing enter -@register_widget({ - "name": "Markdown Widget with Editable Text Input", - "description": "A markdown widget with an editable text input parameter", - "endpoint": "markdown_widget_with_editable_text_input", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - { - "paramName": "text_box", - "value": "var1,var2,var3", - "label": "Variables", - "description": "Type the variables to display, separated by commas", - "multiple": True, - "type": "text" - } - ] -}) +@register_widget( + { + "name": "Markdown Widget with Editable Text Input", + "description": "A markdown widget with an editable text input parameter", + "endpoint": "markdown_widget_with_editable_text_input", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + { + "paramName": "text_box", + "value": "var1,var2,var3", + "label": "Variables", + "description": "Type the variables to display, separated by commas", + "multiple": True, + "type": "text", + } + ], + } +) @app.get("/markdown_widget_with_editable_text_input") def markdown_widget_with_editable_text_input(text_box: str): """Returns a markdown widget with text input parameter""" return f"""# Text Input Entered text: {text_box} -""" +""" + # This is a simple markdown widget with a boolean parameter # The boolean parameter is a boolean parameter that allows users to enable or disable a feature # and we pass this parameter to the widget as the condition parameter -@register_widget({ - "name": "Markdown Widget with Boolean Toggle", - "description": "A markdown widget with a boolean parameter", - "endpoint": "markdown_widget_with_boolean", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - { - "paramName": "condition", - "description": "Enable or disable this feature", - "label": "Toggle Option", - "type": "boolean", - "value": True, - } - ] -}) +@register_widget( + { + "name": "Markdown Widget with Boolean Toggle", + "description": "A markdown widget with a boolean parameter", + "endpoint": "markdown_widget_with_boolean", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + { + "paramName": "condition", + "description": "Enable or disable this feature", + "label": "Toggle Option", + "type": "boolean", + "value": True, + } + ], + } +) @app.get("/markdown_widget_with_boolean") def markdown_widget_with_boolean(condition: bool): """Returns a markdown widget with boolean parameter""" @@ -1417,25 +1340,28 @@ def markdown_widget_with_boolean(condition: bool): Current state: {'Enabled' if condition else 'Disabled'} """ + # This is a simple markdown widget with a text input parameter # The text input parameter is a text input that allows users to enter a specific text # and we pass this parameter to the widget as the textBox1 parameter -@register_widget({ - "name": "Markdown Widget with Number Input", - "description": "A markdown widget with a number input parameter", - "endpoint": "markdown_widget_with_number_input", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - { - "paramName": "number_box", - "description": "Enter a number", - "value": 20, - "label": "Enter Number", - "type": "number" - } - ] -}) +@register_widget( + { + "name": "Markdown Widget with Number Input", + "description": "A markdown widget with a number input parameter", + "endpoint": "markdown_widget_with_number_input", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + { + "paramName": "number_box", + "description": "Enter a number", + "value": 20, + "label": "Enter Number", + "type": "number", + } + ], + } +) @app.get("/markdown_widget_with_number_input") def markdown_widget_with_number_input(number_box: int): """Returns a markdown widget with number input parameter""" @@ -1443,49 +1369,37 @@ def markdown_widget_with_number_input(number_box: int): Entered number: {number_box} """ + # This is a simple markdown widget with a dropdown parameter # The dropdown parameter is a dropdown parameter that allows users to select a specific option # and we pass this parameter to the widget as the days_picker parameter # Note that the multiSelect parameter is set to True, so the user can select multiple options -@register_widget({ - "name": "Markdown Widget with Dropdown", - "description": "A markdown widget with a dropdown parameter", - "endpoint": "markdown_widget_with_dropdown", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - { - "paramName": "days_picker", - "description": "Number of days to look back", - "value": "1", - "label": "Select Days", - "type": "text", - "multiSelect": True, - "options": [ - { - "value": "1", - "label": "1" - }, - { - "value": "5", - "label": "5" - }, - { - "value": "10", - "label": "10" - }, - { - "value": "20", - "label": "20" - }, - { - "value": "30", - "label": "30" - } - ] - } - ] -}) +@register_widget( + { + "name": "Markdown Widget with Dropdown", + "description": "A markdown widget with a dropdown parameter", + "endpoint": "markdown_widget_with_dropdown", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + { + "paramName": "days_picker", + "description": "Number of days to look back", + "value": "1", + "label": "Select Days", + "type": "text", + "multiSelect": True, + "options": [ + {"value": "1", "label": "1"}, + {"value": "5", "label": "5"}, + {"value": "10", "label": "10"}, + {"value": "20", "label": "20"}, + {"value": "30", "label": "30"}, + ], + } + ], + } +) @app.get("/markdown_widget_with_dropdown") def markdown_widget_with_dropdown(days_picker: str): """Returns a markdown widget with dropdown parameter""" @@ -1504,55 +1418,55 @@ def advanced_dropdown_options(): return [ { "label": "Apple Inc.", - "value": "AAPL", + "value": "AAPL", "extraInfo": { "description": "Technology Company", - "rightOfDescription": "NASDAQ" - } + "rightOfDescription": "NASDAQ", + }, }, { "label": "Microsoft Corporation", "value": "MSFT", "extraInfo": { - "description": "Software Company", - "rightOfDescription": "NASDAQ" - } + "description": "Software Company", + "rightOfDescription": "NASDAQ", + }, }, { "label": "Google", "value": "GOOGL", "extraInfo": { "description": "Search Engine", - "rightOfDescription": "NASDAQ" - } - } + "rightOfDescription": "NASDAQ", + }, + }, ] # This is a simple markdown widget with an advanced drodpown with more information and allowed to select multiple options # It uses the optionsEndpoint to get the list of possible options as opposed to having them hardcoded in the widget # The style parameter is used to customize the dropdown widget, in this case we are setting the popupWidth to 450px -@register_widget({ - "name": "Markdown Widget with Multi Select Advanced Dropdown", - "description": "A markdown widget with a multi select advanced dropdown parameter", - "endpoint": "markdown_widget_with_multi_select_advanced_dropdown", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - { - "paramName": "stock_picker", - "description": "Select a stock to analyze", - "value": "AAPL", - "label": "Select Stock", - "type": "endpoint", - "multiSelect": True, - "optionsEndpoint": "/advanced_dropdown_options", - "style": { - "popupWidth": 450 +@register_widget( + { + "name": "Markdown Widget with Multi Select Advanced Dropdown", + "description": "A markdown widget with a multi select advanced dropdown parameter", + "endpoint": "markdown_widget_with_multi_select_advanced_dropdown", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + { + "paramName": "stock_picker", + "description": "Select a stock to analyze", + "value": "AAPL", + "label": "Select Stock", + "type": "endpoint", + "multiSelect": True, + "optionsEndpoint": "/advanced_dropdown_options", + "style": {"popupWidth": 450}, } - } - ] -}) + ], + } +) @app.get("/markdown_widget_with_multi_select_advanced_dropdown") def markdown_widget_with_multi_select_advanced_dropdown(stock_picker: str): """Returns a markdown widget with multi select advanced dropdown parameter""" @@ -1560,31 +1474,37 @@ def markdown_widget_with_multi_select_advanced_dropdown(stock_picker: str): Selected stocks: {stock_picker} """ + # Define more than one widget with the same endpoint # Note that the id is used to identify the widget in the OpenBB Workspace # and when more than one is defined we are utilizing it to differentiate between them # since the endpoint cannot be used for the id for both anymore -@register_widget({ - # If we don't specify the widgetId, it will be the same as the endpoint - "name": "Same Markdown Widget", - "description": "Same markdown widget", - "type": "markdown", - "endpoint": "same_markdown_widget", - "gridData": {"w": 12, "h": 4}, -}) -@register_widget({ - "widgetId": "same_markdown_widget_2", - "name": "Same Markdown Widget 2", - "description": "Same markdown widget 2", - "type": "markdown", - "endpoint": "same_markdown_widget", - "gridData": {"w": 12, "h": 4}, -}) +@register_widget( + { + # If we don't specify the widgetId, it will be the same as the endpoint + "name": "Same Markdown Widget", + "description": "Same markdown widget", + "type": "markdown", + "endpoint": "same_markdown_widget", + "gridData": {"w": 12, "h": 4}, + } +) +@register_widget( + { + "widgetId": "same_markdown_widget_2", + "name": "Same Markdown Widget 2", + "description": "Same markdown widget 2", + "type": "markdown", + "endpoint": "same_markdown_widget", + "gridData": {"w": 12, "h": 4}, + } +) @app.get("/same_markdown_widget") def same_markdown_widget(): """Returns a markdown widget""" return "# The very same widget" + # This endpoint provides the list of available documents # It takes a category parameter to filter the documents # The category parameter comes from the first dropdown in the widget @@ -1594,79 +1514,65 @@ def get_document_options(category: str = "all"): # Sample documents data - This is our mock database of documents # Each document has a name and belongs to a category SAMPLE_DOCUMENTS = [ - { - "name": "Q1 Report", - "category": "reports" - }, - { - "name": "Q2 Report", - "category": "reports" - }, - { - "name": "Investor Presentation", - "category": "presentations" - }, - { - "name": "Product Roadmap", - "category": "presentations" - } + {"name": "Q1 Report", "category": "reports"}, + {"name": "Q2 Report", "category": "reports"}, + {"name": "Investor Presentation", "category": "presentations"}, + {"name": "Product Roadmap", "category": "presentations"}, ] # Filter documents based on category filtered_docs = ( - SAMPLE_DOCUMENTS if category == "all" + SAMPLE_DOCUMENTS + if category == "all" else [doc for doc in SAMPLE_DOCUMENTS if doc["category"] == category] ) - + # Return the filtered documents in the format expected by the dropdown # Each document needs a label (what the user sees) and a value (what's passed to the backend) - return [ - { - "label": doc["name"], - "value": doc["name"] - } - for doc in filtered_docs - ] + return [{"label": doc["name"], "value": doc["name"]} for doc in filtered_docs] + # This widget demonstrates how to create dependent dropdowns # The first dropdown (category) controls what options are available in the second dropdown (document) -@register_widget({ - "name": "Dropdown Dependent Widget", - "description": "A simple widget with a dropdown depending on another dropdown", - "endpoint": "dropdown_dependent_widget", - "gridData": {"w": 16, "h": 6}, - "type": "markdown", - "params": [ - # First dropdown - Category selection - # This is a simple text dropdown with predefined options - { - "paramName": "category", - "description": "Category of documents to fetch", - "value": "all", # Default value - "label": "Category", - "type": "text", - "options": [ - {"label": "All", "value": "all"}, - {"label": "Reports", "value": "reports"}, - {"label": "Presentations", "value": "presentations"} - ] - }, - # Second dropdown - Document selection - # This is an endpoint-based dropdown that gets its options from /document_options - # The optionsParams property tells the endpoint to use the value from the category dropdown - # The $category syntax means "use the value of the parameter named 'category'" - { - "paramName": "document_type", - "description": "Document to display", - "label": "Select Document", - "type": "endpoint", - "optionsEndpoint": "/document_options", - "optionsParams": { - "category": "$category" # This passes the selected category to the endpoint - } - }, - ] -}) +@register_widget( + { + "name": "Dropdown Dependent Widget", + "description": "A simple widget with a dropdown depending on another dropdown", + "endpoint": "dropdown_dependent_widget", + "gridData": {"w": 16, "h": 6}, + "type": "markdown", + "params": [ + # First dropdown - Category selection + # This is a simple text dropdown with predefined options + { + "paramName": "category", + "description": "Category of documents to fetch", + "value": "all", # Default value + "label": "Category", + "type": "text", + "options": [ + {"label": "All", "value": "all"}, + {"label": "Reports", "value": "reports"}, + {"label": "Presentations", "value": "presentations"}, + ], + }, + # Second dropdown - Document selection + # This is an endpoint-based dropdown that gets its options from /document_options + # The optionsParams property tells the endpoint to use the value from the category dropdown + # The $category syntax means "use the value of the parameter named 'category'" + { + "paramName": "document_type", + "description": "Document to display", + "label": "Select Document", + "type": "endpoint", + "optionsEndpoint": "/document_options", + "optionsParams": { + "category": "$category" # This passes the selected category to the endpoint + }, + }, + ], + } +) @app.get("/dropdown_dependent_widget") def dropdown_dependent_widget(category: str = "all", document_type: str = "all"): """Returns a dropdown dependent widget""" @@ -1688,7 +1594,7 @@ def get_company_options(): {"label": "Volkswagen Group", "value": "VWAGY"}, {"label": "General Motors", "value": "GM"}, {"label": "Ford Motor Company", "value": "F"}, - {"label": "Tesla Inc.", "value": "TSLA"} + {"label": "Tesla Inc.", "value": "TSLA"}, ] @@ -1701,62 +1607,64 @@ def get_company_options(): # When grouped, changing a parameter in one widget will automatically update the other # Note that both widgets also share the same optionsEndpoint ("/company_options") # which ensures they have identical options in their dropdowns -@register_widget({ - "name": "Car Manufacturer Performance", - "description": "Displays performance metrics for the selected car manufacturer", - "type": "table", - "endpoint": "company_performance", - "gridData": {"w": 16, "h": 8}, - "params": [ - { - "paramName": "company", # Shared paramName with company_details widget - "description": "Select a car manufacturer to view performance", - "value": "TM", - "label": "Manufacturer", - "type": "endpoint", - "optionsEndpoint": "/company_options" # Shared endpoint with company_details widget +@register_widget( + { + "name": "Car Manufacturer Performance", + "description": "Displays performance metrics for the selected car manufacturer", + "type": "table", + "endpoint": "company_performance", + "gridData": {"w": 16, "h": 8}, + "params": [ + { + "paramName": "company", # Shared paramName with company_details widget + "description": "Select a car manufacturer to view performance", + "value": "TM", + "label": "Manufacturer", + "type": "endpoint", + "optionsEndpoint": "/company_options", # Shared endpoint with company_details widget + }, + { + "paramName": "year", # Shared paramName with company_details widget + "description": "Select model year to view performance", + "value": "2024", + "label": "Model Year", + "type": "text", + "options": [ + {"label": "2024", "value": "2024"}, + {"label": "2023", "value": "2023"}, + {"label": "2022", "value": "2022"}, + ], + }, + ], + "data": { + "table": { + "showAll": True, + "columnsDefs": [ + { + "field": "metric", + "headerName": "Metric", + "cellDataType": "text", + "width": 150, + }, + { + "field": "value", + "headerName": "Value", + "cellDataType": "text", + "width": 150, + }, + { + "field": "change", + "headerName": "Change", + "cellDataType": "number", + "formatterFn": "percent", + "renderFn": "greenRed", + "width": 150, + }, + ], + } }, - { - "paramName": "year", # Shared paramName with company_details widget - "description": "Select model year to view performance", - "value": "2024", - "label": "Model Year", - "type": "text", - "options": [ - {"label": "2024", "value": "2024"}, - {"label": "2023", "value": "2023"}, - {"label": "2022", "value": "2022"} - ] - } - ], - "data": { - "table": { - "showAll": True, - "columnsDefs": [ - { - "field": "metric", - "headerName": "Metric", - "cellDataType": "text", - "width": 150 - }, - { - "field": "value", - "headerName": "Value", - "cellDataType": "text", - "width": 150 - }, - { - "field": "change", - "headerName": "Change", - "cellDataType": "number", - "formatterFn": "percent", - "renderFn": "greenRed", - "width": 150 - } - ] - } } -}) +) @app.get("/company_performance") def get_company_performance(company: str, year: str = "2024"): """Returns car manufacturer performance metrics""" @@ -1766,106 +1674,107 @@ def get_company_performance(company: str, year: str = "2024"): {"metric": "Global Sales", "value": "10.5M", "change": 5.2}, {"metric": "EV Sales", "value": "1.2M", "change": 45.8}, {"metric": "Operating Margin", "value": "8.5%", "change": 1.2}, - {"metric": "R&D Investment", "value": "$12.5B", "change": 15.3} + {"metric": "R&D Investment", "value": "$12.5B", "change": 15.3}, ], "2023": [ {"metric": "Global Sales", "value": "9.98M", "change": 3.1}, {"metric": "EV Sales", "value": "0.82M", "change": 35.2}, {"metric": "Operating Margin", "value": "7.3%", "change": 0.8}, - {"metric": "R&D Investment", "value": "$10.8B", "change": 12.5} + {"metric": "R&D Investment", "value": "$10.8B", "change": 12.5}, ], "2022": [ {"metric": "Global Sales", "value": "9.67M", "change": 1.2}, {"metric": "EV Sales", "value": "0.61M", "change": 25.4}, {"metric": "Operating Margin", "value": "6.5%", "change": -0.5}, - {"metric": "R&D Investment", "value": "$9.6B", "change": 8.7} - ] + {"metric": "R&D Investment", "value": "$9.6B", "change": 8.7}, + ], }, "VWAGY": { "2024": [ {"metric": "Global Sales", "value": "9.2M", "change": 4.8}, {"metric": "EV Sales", "value": "1.5M", "change": 52.3}, {"metric": "Operating Margin", "value": "7.8%", "change": 1.5}, - {"metric": "R&D Investment", "value": "$15.2B", "change": 18.5} + {"metric": "R&D Investment", "value": "$15.2B", "change": 18.5}, ], "2023": [ {"metric": "Global Sales", "value": "8.78M", "change": 3.2}, {"metric": "EV Sales", "value": "0.98M", "change": 42.1}, {"metric": "Operating Margin", "value": "6.3%", "change": 0.9}, - {"metric": "R&D Investment", "value": "$12.8B", "change": 15.2} + {"metric": "R&D Investment", "value": "$12.8B", "change": 15.2}, ], "2022": [ {"metric": "Global Sales", "value": "8.5M", "change": 1.8}, {"metric": "EV Sales", "value": "0.69M", "change": 32.5}, {"metric": "Operating Margin", "value": "5.4%", "change": -0.7}, - {"metric": "R&D Investment", "value": "$11.1B", "change": 10.8} - ] + {"metric": "R&D Investment", "value": "$11.1B", "change": 10.8}, + ], }, "GM": { "2024": [ {"metric": "Global Sales", "value": "6.8M", "change": 3.5}, {"metric": "EV Sales", "value": "0.8M", "change": 48.2}, {"metric": "Operating Margin", "value": "8.2%", "change": 1.8}, - {"metric": "R&D Investment", "value": "$9.5B", "change": 16.5} + {"metric": "R&D Investment", "value": "$9.5B", "change": 16.5}, ], "2023": [ {"metric": "Global Sales", "value": "6.57M", "change": 2.1}, {"metric": "EV Sales", "value": "0.54M", "change": 38.5}, {"metric": "Operating Margin", "value": "6.4%", "change": 1.2}, - {"metric": "R&D Investment", "value": "$8.15B", "change": 14.2} + {"metric": "R&D Investment", "value": "$8.15B", "change": 14.2}, ], "2022": [ {"metric": "Global Sales", "value": "6.43M", "change": 0.8}, {"metric": "EV Sales", "value": "0.39M", "change": 28.7}, {"metric": "Operating Margin", "value": "5.2%", "change": -0.5}, - {"metric": "R&D Investment", "value": "$7.13B", "change": 9.8} - ] + {"metric": "R&D Investment", "value": "$7.13B", "change": 9.8}, + ], }, "F": { "2024": [ {"metric": "Global Sales", "value": "4.2M", "change": 2.8}, {"metric": "EV Sales", "value": "0.6M", "change": 42.5}, {"metric": "Operating Margin", "value": "7.5%", "change": 1.5}, - {"metric": "R&D Investment", "value": "$8.2B", "change": 15.8} + {"metric": "R&D Investment", "value": "$8.2B", "change": 15.8}, ], "2023": [ {"metric": "Global Sales", "value": "4.08M", "change": 1.5}, {"metric": "EV Sales", "value": "0.42M", "change": 35.2}, {"metric": "Operating Margin", "value": "6.0%", "change": 1.0}, - {"metric": "R&D Investment", "value": "$7.08B", "change": 13.5} + {"metric": "R&D Investment", "value": "$7.08B", "change": 13.5}, ], "2022": [ {"metric": "Global Sales", "value": "4.02M", "change": 0.5}, {"metric": "EV Sales", "value": "0.31M", "change": 25.8}, {"metric": "Operating Margin", "value": "5.0%", "change": -0.8}, - {"metric": "R&D Investment", "value": "$6.24B", "change": 8.9} - ] + {"metric": "R&D Investment", "value": "$6.24B", "change": 8.9}, + ], }, "TSLA": { "2024": [ {"metric": "Global Sales", "value": "2.1M", "change": 35.2}, {"metric": "EV Sales", "value": "2.1M", "change": 35.2}, {"metric": "Operating Margin", "value": "15.5%", "change": 3.7}, - {"metric": "R&D Investment", "value": "$4.5B", "change": 25.8} + {"metric": "R&D Investment", "value": "$4.5B", "change": 25.8}, ], "2023": [ {"metric": "Global Sales", "value": "1.55M", "change": 28.5}, {"metric": "EV Sales", "value": "1.55M", "change": 28.5}, {"metric": "Operating Margin", "value": "11.8%", "change": 2.5}, - {"metric": "R&D Investment", "value": "$3.58B", "change": 22.3} + {"metric": "R&D Investment", "value": "$3.58B", "change": 22.3}, ], "2022": [ {"metric": "Global Sales", "value": "1.21M", "change": 21.8}, {"metric": "EV Sales", "value": "1.21M", "change": 21.8}, {"metric": "Operating Margin", "value": "9.3%", "change": 1.8}, - {"metric": "R&D Investment", "value": "$2.93B", "change": 18.5} - ] - } + {"metric": "R&D Investment", "value": "$2.93B", "change": 18.5}, + ], + }, } - - return performance_data.get(company, {}).get(year, [ - {"metric": "No Data", "value": "N/A", "change": 0} - ]) + + return performance_data.get(company, {}).get( + year, [{"metric": "No Data", "value": "N/A", "change": 0}] + ) + # This widget is grouped with the company_performance widget above # They share the same paramNames ("company" and "year") @@ -1877,35 +1786,37 @@ def get_company_performance(company: str, year: str = "2024"): # 1. Using identical paramNames in both widgets # 2. Using the same optionsEndpoint for shared parameters # 3. Configuring the grouping in apps.json or through the UI -@register_widget({ - "name": "Car Manufacturer Details", - "description": "Displays detailed information about the selected car manufacturer", - "type": "markdown", - "endpoint": "company_details", - "gridData": {"w": 16, "h": 8}, - "params": [ - { - "paramName": "company", # Shared paramName with company_performance widget - "description": "Select a car manufacturer to view details", - "value": "TM", - "label": "Manufacturer", - "type": "endpoint", - "optionsEndpoint": "/company_options" # Shared endpoint with company_performance widget - }, - { - "paramName": "year", # Shared paramName with company_performance widget - "description": "Select model year to view details", - "value": "2024", - "label": "Model Year", - "type": "text", - "options": [ - {"label": "2024", "value": "2024"}, - {"label": "2023", "value": "2023"}, - {"label": "2022", "value": "2022"} - ] - } - ] -}) +@register_widget( + { + "name": "Car Manufacturer Details", + "description": "Displays detailed information about the selected car manufacturer", + "type": "markdown", + "endpoint": "company_details", + "gridData": {"w": 16, "h": 8}, + "params": [ + { + "paramName": "company", # Shared paramName with company_performance widget + "description": "Select a car manufacturer to view details", + "value": "TM", + "label": "Manufacturer", + "type": "endpoint", + "optionsEndpoint": "/company_options", # Shared endpoint with company_performance widget + }, + { + "paramName": "year", # Shared paramName with company_performance widget + "description": "Select model year to view details", + "value": "2024", + "label": "Model Year", + "type": "text", + "options": [ + {"label": "2024", "value": "2024"}, + {"label": "2023", "value": "2023"}, + {"label": "2022", "value": "2022"}, + ], + }, + ], + } +) @app.get("/company_details") def get_company_details(company: str, year: str = "2024"): """Returns car manufacturer details in markdown format""" @@ -1920,8 +1831,8 @@ def get_company_details(company: str, year: str = "2024"): "models": { "2024": ["Camry", "Corolla", "RAV4", "Highlander"], "2023": ["Camry", "Corolla", "RAV4", "Highlander"], - "2022": ["Camry", "Corolla", "RAV4", "Highlander"] - } + "2022": ["Camry", "Corolla", "RAV4", "Highlander"], + }, }, "VWAGY": { "name": "Volkswagen Group", @@ -1933,8 +1844,8 @@ def get_company_details(company: str, year: str = "2024"): "models": { "2024": ["Golf", "Passat", "Tiguan", "ID.4"], "2023": ["Golf", "Passat", "Tiguan", "ID.4"], - "2022": ["Golf", "Passat", "Tiguan", "ID.4"] - } + "2022": ["Golf", "Passat", "Tiguan", "ID.4"], + }, }, "GM": { "name": "General Motors", @@ -1946,8 +1857,8 @@ def get_company_details(company: str, year: str = "2024"): "models": { "2024": ["Silverado", "Equinox", "Malibu", "Corvette"], "2023": ["Silverado", "Equinox", "Malibu", "Corvette"], - "2022": ["Silverado", "Equinox", "Malibu", "Corvette"] - } + "2022": ["Silverado", "Equinox", "Malibu", "Corvette"], + }, }, "F": { "name": "Ford Motor Company", @@ -1959,8 +1870,8 @@ def get_company_details(company: str, year: str = "2024"): "models": { "2024": ["F-150", "Mustang", "Explorer", "Mach-E"], "2023": ["F-150", "Mustang", "Explorer", "Mach-E"], - "2022": ["F-150", "Mustang", "Explorer", "Mach-E"] - } + "2022": ["F-150", "Mustang", "Explorer", "Mach-E"], + }, }, "TSLA": { "name": "Tesla Inc.", @@ -1972,23 +1883,26 @@ def get_company_details(company: str, year: str = "2024"): "models": { "2024": ["Model 3", "Model Y", "Model S", "Model X"], "2023": ["Model 3", "Model Y", "Model S", "Model X"], - "2022": ["Model 3", "Model Y", "Model S", "Model X"] - } - } + "2022": ["Model 3", "Model Y", "Model S", "Model X"], + }, + }, } - - details = company_info.get(company, { - "name": "Unknown", - "sector": "Unknown", - "market_cap": "N/A", - "pe_ratio": 0, - "dividend_yield": 0, - "description": "No information available for this manufacturer.", - "models": {"2024": [], "2023": [], "2022": []} - }) - - models = details['models'].get(year, []) - + + details = company_info.get( + company, + { + "name": "Unknown", + "sector": "Unknown", + "market_cap": "N/A", + "pe_ratio": 0, + "dividend_yield": 0, + "description": "No information available for this manufacturer.", + "models": {"2024": [], "2023": [], "2022": []}, + }, + ) + + models = details["models"].get(year, []) + return f"""# {details['name']} ({company}) - {year} Models **Sector:** {details['sector']} **Market Cap:** ${details['market_cap']} @@ -2001,6 +1915,7 @@ def get_company_details(company: str, year: str = "2024"): {', '.join(models)} """ + # This endpoint provides a list of available stock symbols # This is used by both widgets to populate their dropdown menus @app.get("/get_tickers_list") @@ -2011,144 +1926,119 @@ def get_tickers_list(): {"label": "Microsoft Corporation", "value": "MSFT"}, {"label": "Google", "value": "GOOGL"}, {"label": "Amazon", "value": "AMZN"}, - {"label": "Tesla", "value": "TSLA"} + {"label": "Tesla", "value": "TSLA"}, ] + # This widget demonstrates how to use cellOnClick with grouping functionality # The key feature here is the cellOnClick renderFn in the symbol column # When a user clicks on a symbol cell, it triggers the groupBy action # This action updates all widgets that use the same parameter name (symbol) -@register_widget({ - "name": "Table widget with grouping by cell click", - "description": "A table widget that groups data when clicking on symbols. Click on a symbol to update all related widgets.", - "type": "table", - "endpoint": "table_widget_with_grouping_by_cell_click", - "params": [ - { - "paramName": "symbol", # This parameter name is crucial - it's used for grouping - "description": "Select stocks to display", - "value": "AAPL", - "label": "Symbol", - "type": "endpoint", - "optionsEndpoint": "/get_tickers_list", - "multiSelect": False, - "show": True - } - ], - "data": { - "table": { - "showAll": True, - "columnsDefs": [ - { - "field": "symbol", - "headerName": "Symbol", - "cellDataType": "text", - "width": 120, - "pinned": "left", - # The cellOnClick renderFn makes cells clickable - "renderFn": "cellOnClick", - "renderFnParams": { - # groupBy action type means clicking will update all widgets using this parameter - "actionType": "groupBy", - # This must match the paramName in both widgets for grouping to work - "groupByParamName": "symbol" - } - }, - { - "field": "price", - "headerName": "Price", - "cellDataType": "number", - "formatterFn": "none", - "width": 120 - }, - { - "field": "change", - "headerName": "Change", - "cellDataType": "number", - "formatterFn": "percent", - "renderFn": "greenRed", # Shows positive/negative changes in green/red - "width": 120 - }, - { - "field": "volume", - "headerName": "Volume", - "cellDataType": "number", - "formatterFn": "int", - "width": 150 - } - ] - } - }, - "gridData": { - "w": 20, - "h": 9 +@register_widget( + { + "name": "Table widget with grouping by cell click", + "description": "A table widget that groups data when clicking on symbols. Click on a symbol to update all related widgets.", + "type": "table", + "endpoint": "table_widget_with_grouping_by_cell_click", + "params": [ + { + "paramName": "symbol", # This parameter name is crucial - it's used for grouping + "description": "Select stocks to display", + "value": "AAPL", + "label": "Symbol", + "type": "endpoint", + "optionsEndpoint": "/get_tickers_list", + "multiSelect": False, + "show": True, + } + ], + "data": { + "table": { + "showAll": True, + "columnsDefs": [ + { + "field": "symbol", + "headerName": "Symbol", + "cellDataType": "text", + "width": 120, + "pinned": "left", + # The cellOnClick renderFn makes cells clickable + "renderFn": "cellOnClick", + "renderFnParams": { + # groupBy action type means clicking will update all widgets using this parameter + "actionType": "groupBy", + # This must match the paramName in both widgets for grouping to work + "groupByParamName": "symbol", + }, + }, + { + "field": "price", + "headerName": "Price", + "cellDataType": "number", + "formatterFn": "none", + "width": 120, + }, + { + "field": "change", + "headerName": "Change", + "cellDataType": "number", + "formatterFn": "percent", + "renderFn": "greenRed", # Shows positive/negative changes in green/red + "width": 120, + }, + { + "field": "volume", + "headerName": "Volume", + "cellDataType": "number", + "formatterFn": "int", + "width": 150, + }, + ], + } + }, + "gridData": {"w": 20, "h": 9}, } -}) +) @app.get("/table_widget_with_grouping_by_cell_click") def table_widget_with_grouping_by_cell_click(symbol: str = "AAPL"): """Returns stock data that can be grouped by symbol""" # Mock data - in a real application, this would come from a data source mock_data = [ - { - "symbol": "AAPL", - "price": 175.50, - "change": 0.015, - "volume": 50000000 - }, - { - "symbol": "MSFT", - "price": 380.25, - "change": -0.008, - "volume": 25000000 - }, - { - "symbol": "GOOGL", - "price": 140.75, - "change": 0.022, - "volume": 15000000 - }, - { - "symbol": "AMZN", - "price": 175.25, - "change": 0.005, - "volume": 30000000 - }, - { - "symbol": "TSLA", - "price": 175.50, - "change": -0.012, - "volume": 45000000 - } + {"symbol": "AAPL", "price": 175.50, "change": 0.015, "volume": 50000000}, + {"symbol": "MSFT", "price": 380.25, "change": -0.008, "volume": 25000000}, + {"symbol": "GOOGL", "price": 140.75, "change": 0.022, "volume": 15000000}, + {"symbol": "AMZN", "price": 175.25, "change": 0.005, "volume": 30000000}, + {"symbol": "TSLA", "price": 175.50, "change": -0.012, "volume": 45000000}, ] - + return mock_data + # This widget demonstrates how to use the grouped symbol parameter # It will update automatically when a symbol is clicked in the stock table # The key to making this work is using the same paramName ("symbol") as the table widget # When a user clicks a symbol in the table, this widget will automatically update # to show details for the selected symbol -@register_widget({ - "name": "Widget managed by parameter from cell click on table widget", - "description": "This widget demonstrates how to use the grouped symbol parameter from a table widget. When a symbol is clicked in the table, this widget will automatically update to show details for the selected symbol.", - "type": "markdown", - "endpoint": "widget_managed_by_parameter_from_cell_click_on_table_widget", - "params": [ - { - "paramName": "symbol", # Must match the groupByParamName in the table widget - "description": "The symbol to get details for", - "value": "AAPL", - "label": "Symbol", - "type": "endpoint", - "optionsEndpoint": "/get_tickers_list", - "show": True - } - ], - "gridData": { - "w": 20, - "h": 6 +@register_widget( + { + "name": "Widget managed by parameter from cell click on table widget", + "description": "This widget demonstrates how to use the grouped symbol parameter from a table widget. When a symbol is clicked in the table, this widget will automatically update to show details for the selected symbol.", + "type": "markdown", + "endpoint": "widget_managed_by_parameter_from_cell_click_on_table_widget", + "params": [ + { + "paramName": "symbol", # Must match the groupByParamName in the table widget + "description": "The symbol to get details for", + "value": "AAPL", + "label": "Symbol", + "type": "endpoint", + "optionsEndpoint": "/get_tickers_list", + "show": True, + } + ], + "gridData": {"w": 20, "h": 6}, } -}) +) @app.get("/widget_managed_by_parameter_from_cell_click_on_table_widget") def widget_managed_by_parameter_from_cell_click_on_table_widget(symbol: str = "AAPL"): """Returns detailed information about the selected stock""" @@ -2160,7 +2050,7 @@ def widget_managed_by_parameter_from_cell_click_on_table_widget(symbol: str = "A "market_cap": "2.8T", "pe_ratio": 28.5, "dividend_yield": 0.5, - "description": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide." + "description": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide.", }, "MSFT": { "name": "Microsoft Corporation", @@ -2168,7 +2058,7 @@ def widget_managed_by_parameter_from_cell_click_on_table_widget(symbol: str = "A "market_cap": "2.5T", "pe_ratio": 35.2, "dividend_yield": 0.8, - "description": "Microsoft Corporation develops and supports software, services, devices, and solutions worldwide." + "description": "Microsoft Corporation develops and supports software, services, devices, and solutions worldwide.", }, "GOOGL": { "name": "Alphabet Inc.", @@ -2176,7 +2066,7 @@ def widget_managed_by_parameter_from_cell_click_on_table_widget(symbol: str = "A "market_cap": "1.8T", "pe_ratio": 25.8, "dividend_yield": 0.0, - "description": "Alphabet Inc. provides various products and platforms in the United States, Europe, the Middle East, Africa, the Asia-Pacific, Canada, and Latin America." + "description": "Alphabet Inc. provides various products and platforms in the United States, Europe, the Middle East, Africa, the Asia-Pacific, Canada, and Latin America.", }, "AMZN": { "name": "Amazon.com Inc.", @@ -2184,7 +2074,7 @@ def widget_managed_by_parameter_from_cell_click_on_table_widget(symbol: str = "A "market_cap": "1.6T", "pe_ratio": 45.2, "dividend_yield": 0.0, - "description": "Amazon.com Inc. engages in the retail sale of consumer products and subscriptions in North America and internationally." + "description": "Amazon.com Inc. engages in the retail sale of consumer products and subscriptions in North America and internationally.", }, "TSLA": { "name": "Tesla Inc.", @@ -2192,21 +2082,24 @@ def widget_managed_by_parameter_from_cell_click_on_table_widget(symbol: str = "A "market_cap": "800B", "pe_ratio": 65.3, "dividend_yield": 0.0, - "description": "Tesla Inc. designs, develops, manufactures, leases, and sells electric vehicles, and energy generation and storage systems in the United States, China, and internationally." - } + "description": "Tesla Inc. designs, develops, manufactures, leases, and sells electric vehicles, and energy generation and storage systems in the United States, China, and internationally.", + }, } - + # Get details for the selected symbol # If no symbol is selected or symbol doesn't exist, return default values - details = stock_details.get(symbol, { - "name": "Unknown", - "sector": "Unknown", - "market_cap": "N/A", - "pe_ratio": 0, - "dividend_yield": 0, - "description": "No information available for this symbol." - }) - + details = stock_details.get( + symbol, + { + "name": "Unknown", + "sector": "Unknown", + "market_cap": "N/A", + "pe_ratio": 0, + "dividend_yield": 0, + "description": "No information available for this symbol.", + }, + ) + return f"""# {details['name']} ({symbol}) **Sector:** {details['sector']}\n **Market Cap:** ${details['market_cap']}\n @@ -2216,16 +2109,19 @@ def widget_managed_by_parameter_from_cell_click_on_table_widget(symbol: str = "A {details['description']} """ + # Plotly chart # This widget demonstrates how to use the Plotly library to create a chart # this gives you the ability to create any interactive type of charts with unlimited flexibility -@register_widget({ - "name": "Plotly Chart", - "description": "Plotly chart", - "type": "chart", - "endpoint": "plotly_chart", - "gridData": {"w": 40, "h": 15} -}) +@register_widget( + { + "name": "Plotly Chart", + "description": "Plotly chart", + "type": "chart", + "endpoint": "plotly_chart", + "gridData": {"w": 40, "h": 15}, + } +) @app.get("/plotly_chart") def get_plotly_chart(): # Generate mock time series data @@ -2239,7 +2135,7 @@ def get_plotly_chart(): {"date": "2023-01-07", "return": 2.8, "transactions": 1780}, {"date": "2023-01-08", "return": -0.9, "transactions": 1620}, {"date": "2023-01-09", "return": 1.2, "transactions": 1480}, - {"date": "2023-01-10", "return": 3.5, "transactions": 1920} + {"date": "2023-01-10", "return": 3.5, "transactions": 1920}, ] dates = [datetime.strptime(d["date"], "%Y-%m-%d") for d in mock_data] @@ -2250,38 +2146,19 @@ def get_plotly_chart(): fig = go.Figure() # Add the line trace for returns - fig.add_trace(go.Scatter( - x=dates, - y=returns, - mode='lines', - name='Returns', - line=dict(width=2) - )) + fig.add_trace( + go.Scatter(x=dates, y=returns, mode="lines", name="Returns", line=dict(width=2)) + ) # Add the bar trace for transactions - fig.add_trace(go.Bar( - x=dates, - y=transactions, - name='Transactions', - opacity=0.5 - )) + fig.add_trace(go.Bar(x=dates, y=transactions, name="Transactions", opacity=0.5)) # Update layout with axis titles and secondary y-axis fig.update_layout( - xaxis_title='Date', - yaxis_title='Returns (%)', - yaxis2=dict( - title="Transactions", - overlaying="y", - side="right" - ), - legend=dict( - orientation="h", - yanchor="bottom", - y=1.02, - xanchor="right", - x=1 - ) + xaxis_title="Date", + yaxis_title="Returns (%)", + yaxis2=dict(title="Transactions", overlaying="y", side="right"), + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), ) # Update the bar trace to use secondary y-axis @@ -2294,14 +2171,16 @@ def get_plotly_chart(): # This endpoint extends the basic Plotly chart by adding the ability to return raw data # This is useful for widgets that have a more differentiated interface where it's not # as easy to analyze raw data. But also for this data to be the one sent to the AI. -@register_widget({ - "name": "Plotly Chart with Raw Data", - "description": "Plotly chart with raw data", - "type": "chart", - "endpoint": "plotly_chart_with_raw_data", - "gridData": {"w": 40, "h": 15}, - "raw": True -}) +@register_widget( + { + "name": "Plotly Chart with Raw Data", + "description": "Plotly chart with raw data", + "type": "chart", + "endpoint": "plotly_chart_with_raw_data", + "gridData": {"w": 40, "h": 15}, + "raw": True, + } +) @app.get("/plotly_chart_with_raw_data") def get_plotly_chart_with_raw_data(raw: bool = False): # Generate mock time series data @@ -2315,7 +2194,7 @@ def get_plotly_chart_with_raw_data(raw: bool = False): {"date": "2023-01-07", "return": 2.8, "transactions": 1780}, {"date": "2023-01-08", "return": -0.9, "transactions": 1620}, {"date": "2023-01-09", "return": 1.2, "transactions": 1480}, - {"date": "2023-01-10", "return": 3.5, "transactions": 1920} + {"date": "2023-01-10", "return": 3.5, "transactions": 1920}, ] if raw: @@ -2329,38 +2208,19 @@ def get_plotly_chart_with_raw_data(raw: bool = False): fig = go.Figure() # Add the line trace for returns - fig.add_trace(go.Scatter( - x=dates, - y=returns, - mode='lines', - name='Returns', - line=dict(width=2) - )) + fig.add_trace( + go.Scatter(x=dates, y=returns, mode="lines", name="Returns", line=dict(width=2)) + ) # Add the bar trace for transactions - fig.add_trace(go.Bar( - x=dates, - y=transactions, - name='Transactions', - opacity=0.5 - )) + fig.add_trace(go.Bar(x=dates, y=transactions, name="Transactions", opacity=0.5)) # Update layout with axis titles and secondary y-axis fig.update_layout( - xaxis_title='Date', - yaxis_title='Returns (%)', - yaxis2=dict( - title="Transactions", - overlaying="y", - side="right" - ), - legend=dict( - orientation="h", - yanchor="bottom", - y=1.02, - xanchor="right", - x=1 - ) + xaxis_title="Date", + yaxis_title="Returns (%)", + yaxis2=dict(title="Transactions", overlaying="y", side="right"), + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), ) # Update the bar trace to use secondary y-axis @@ -2368,6 +2228,7 @@ def get_plotly_chart_with_raw_data(raw: bool = False): return json.loads(fig.to_json()) + # Plotly chart with theme # This endpoint extends the basic Plotly chart by adding theme support. # The theme parameter is automatically provided by OpenBB Workspace based on the user's @@ -2376,14 +2237,15 @@ def get_plotly_chart_with_raw_data(raw: bool = False): # pass it but the endpoint will ignore it. # Note: OpenBB widget UI dark mode is #151518 and light mode is #FFFFFF, using these # background colors make the chart look consistent with the widgets in the OpenBB Workspace. -@register_widget({ - "name": "Plotly Chart with Theme", - "description": "Plotly chart with theme", - "type": "chart", - "endpoint": "plotly_chart_with_theme", - "gridData": {"w": 40, "h": 15} -}) - +@register_widget( + { + "name": "Plotly Chart with Theme", + "description": "Plotly chart with theme", + "type": "chart", + "endpoint": "plotly_chart_with_theme", + "gridData": {"w": 40, "h": 15}, + } +) @app.get("/plotly_chart_with_theme") def get_plotly_chart_with_theme(theme: str = "dark"): # Generate mock time series data @@ -2397,59 +2259,63 @@ def get_plotly_chart_with_theme(theme: str = "dark"): {"date": "2023-01-07", "return": 2.8, "transactions": 1780}, {"date": "2023-01-08", "return": -0.9, "transactions": 1620}, {"date": "2023-01-09", "return": 1.2, "transactions": 1480}, - {"date": "2023-01-10", "return": 3.5, "transactions": 1920} + {"date": "2023-01-10", "return": 3.5, "transactions": 1920}, ] - + dates = [datetime.strptime(d["date"], "%Y-%m-%d") for d in mock_data] returns = [d["return"] for d in mock_data] transactions = [d["transactions"] for d in mock_data] - + # Create the figure with secondary y-axis fig = go.Figure() - + if theme == "dark": # Dark theme colors and styling line_color = "#FF8000" # Orange - bar_color = "#2D9BF0" # Blue + bar_color = "#2D9BF0" # Blue text_color = "#FFFFFF" # White grid_color = "rgba(51, 51, 51, 0.3)" - bg_color = "#151518" # Dark background + bg_color = "#151518" # Dark background else: # Light theme colors and styling line_color = "#2E5090" # Navy blue - bar_color = "#00AA44" # Forest green + bar_color = "#00AA44" # Forest green text_color = "#333333" # Dark gray grid_color = "rgba(221, 221, 221, 0.3)" - bg_color = "#FFFFFF" # White background - + bg_color = "#FFFFFF" # White background + # Add the line trace for returns with theme-specific color - fig.add_trace(go.Scatter( - x=dates, - y=returns, - mode='lines', - name='Returns', - line=dict(width=2, color=line_color) - )) - + fig.add_trace( + go.Scatter( + x=dates, + y=returns, + mode="lines", + name="Returns", + line=dict(width=2, color=line_color), + ) + ) + # Add the bar trace for transactions with theme-specific color - fig.add_trace(go.Bar( - x=dates, - y=transactions, - name='Transactions', - opacity=0.5, - marker_color=bar_color - )) - + fig.add_trace( + go.Bar( + x=dates, + y=transactions, + name="Transactions", + opacity=0.5, + marker_color=bar_color, + ) + ) + # Update layout with theme-specific styling fig.update_layout( - xaxis_title='Date', - yaxis_title='Returns (%)', + xaxis_title="Date", + yaxis_title="Returns (%)", yaxis2=dict( title="Transactions", overlaying="y", side="right", gridcolor=grid_color, - tickfont=dict(color=text_color) + tickfont=dict(color=text_color), ), legend=dict( orientation="h", @@ -2457,26 +2323,19 @@ def get_plotly_chart_with_theme(theme: str = "dark"): y=1.02, xanchor="right", x=1, - font=dict(color=text_color) + font=dict(color=text_color), ), paper_bgcolor=bg_color, plot_bgcolor=bg_color, font=dict(color=text_color), - xaxis=dict( - gridcolor=grid_color, - tickfont=dict(color=text_color) - ), - yaxis=dict( - gridcolor=grid_color, - tickfont=dict(color=text_color) - ) + xaxis=dict(gridcolor=grid_color, tickfont=dict(color=text_color)), + yaxis=dict(gridcolor=grid_color, tickfont=dict(color=text_color)), ) - + # Update the bar trace to use secondary y-axis fig.data[1].update(yaxis="y2") - - return json.loads(fig.to_json()) + return json.loads(fig.to_json()) # Plotly chart with theme and toolbar @@ -2485,14 +2344,15 @@ def get_plotly_chart_with_theme(theme: str = "dark"): # Note: As you can see, all the settings and styling utilized by plotly can be too # much boilerplate code, so it is recommended to create a plotly_config.py file # and use the functions defined in that file to create the chart. -@register_widget({ - "name": "Plotly Chart with Theme and Toolbar", - "description": "Plotly chart with Theme and toolbar", - "type": "chart", - "endpoint": "plotly_chart_with_theme_and_toolbar", - "gridData": {"w": 40, "h": 15} -}) - +@register_widget( + { + "name": "Plotly Chart with Theme and Toolbar", + "description": "Plotly chart with Theme and toolbar", + "type": "chart", + "endpoint": "plotly_chart_with_theme_and_toolbar", + "gridData": {"w": 40, "h": 15}, + } +) @app.get("/plotly_chart_with_theme_and_toolbar") def get_plotly_chart_with_theme_and_toolbar(theme: str = "dark"): # Generate mock time series data @@ -2506,59 +2366,63 @@ def get_plotly_chart_with_theme_and_toolbar(theme: str = "dark"): {"date": "2023-01-07", "return": 2.8, "transactions": 1780}, {"date": "2023-01-08", "return": -0.9, "transactions": 1620}, {"date": "2023-01-09", "return": 1.2, "transactions": 1480}, - {"date": "2023-01-10", "return": 3.5, "transactions": 1920} + {"date": "2023-01-10", "return": 3.5, "transactions": 1920}, ] - + dates = [datetime.strptime(d["date"], "%Y-%m-%d") for d in mock_data] returns = [d["return"] for d in mock_data] transactions = [d["transactions"] for d in mock_data] - + # Create the figure with secondary y-axis fig = go.Figure() - + if theme == "dark": # Dark theme colors and styling line_color = "#FF8000" # Orange - bar_color = "#2D9BF0" # Blue + bar_color = "#2D9BF0" # Blue text_color = "#FFFFFF" # White grid_color = "rgba(51, 51, 51, 0.3)" - bg_color = "#151518" # Dark background + bg_color = "#151518" # Dark background else: # Light theme colors and styling line_color = "#2E5090" # Navy blue - bar_color = "#00AA44" # Forest green + bar_color = "#00AA44" # Forest green text_color = "#333333" # Dark gray grid_color = "rgba(221, 221, 221, 0.3)" - bg_color = "#FFFFFF" # White background - + bg_color = "#FFFFFF" # White background + # Add the line trace for returns with theme-specific color - fig.add_trace(go.Scatter( - x=dates, - y=returns, - mode='lines', - name='Returns', - line=dict(width=2, color=line_color) - )) - + fig.add_trace( + go.Scatter( + x=dates, + y=returns, + mode="lines", + name="Returns", + line=dict(width=2, color=line_color), + ) + ) + # Add the bar trace for transactions with theme-specific color - fig.add_trace(go.Bar( - x=dates, - y=transactions, - name='Transactions', - opacity=0.5, - marker_color=bar_color - )) - + fig.add_trace( + go.Bar( + x=dates, + y=transactions, + name="Transactions", + opacity=0.5, + marker_color=bar_color, + ) + ) + # Update layout with theme-specific styling fig.update_layout( - xaxis_title='Date', - yaxis_title='Returns (%)', + xaxis_title="Date", + yaxis_title="Returns (%)", yaxis2=dict( title="Transactions", overlaying="y", side="right", gridcolor=grid_color, - tickfont=dict(color=text_color) + tickfont=dict(color=text_color), ), legend=dict( orientation="h", @@ -2566,85 +2430,77 @@ def get_plotly_chart_with_theme_and_toolbar(theme: str = "dark"): y=1.02, xanchor="right", x=1, - font=dict(color=text_color) + font=dict(color=text_color), ), paper_bgcolor=bg_color, plot_bgcolor=bg_color, font=dict(color=text_color), - xaxis=dict( - gridcolor=grid_color, - tickfont=dict(color=text_color) - ), - yaxis=dict( - gridcolor=grid_color, - tickfont=dict(color=text_color) - ) + xaxis=dict(gridcolor=grid_color, tickfont=dict(color=text_color)), + yaxis=dict(gridcolor=grid_color, tickfont=dict(color=text_color)), ) - + # Update the bar trace to use secondary y-axis fig.data[1].update(yaxis="y2") - + # Configure the toolbar and other display settings toolbar_config = { - 'displayModeBar': True, - 'responsive': True, - 'scrollZoom': True, - 'modeBarButtonsToRemove': [ - 'lasso2d', - 'select2d', - 'autoScale2d', - 'toggleSpikelines', - 'hoverClosestCartesian', - 'hoverCompareCartesian' - ], - 'modeBarButtonsToAdd': [ - 'drawline', - 'drawcircle', - 'drawrect', - 'eraseshape' + "displayModeBar": True, + "responsive": True, + "scrollZoom": True, + "modeBarButtonsToRemove": [ + "lasso2d", + "select2d", + "autoScale2d", + "toggleSpikelines", + "hoverClosestCartesian", + "hoverCompareCartesian", ], - 'doubleClick': 'reset+autosize', - 'showTips': True, - 'watermark': False, - 'staticPlot': False, - 'locale': 'en', - 'showAxisDragHandles': True, - 'showAxisRangeEntryBoxes': True, - 'displaylogo': False, - 'modeBar': { - 'bgcolor': 'rgba(0, 0, 0, 0.1)' if theme == 'light' else 'rgba(255, 255, 255, 0.1)', - 'color': text_color, - 'activecolor': line_color, - 'orientation': 'v', - 'yanchor': 'top', - 'xanchor': 'right', - 'x': 1.05, # Increased spacing from chart - 'y': 1, - 'opacity': 0, # Start hidden - 'hovermode': True, # Show on hover - 'hoverdelay': 0, # No delay on hover - 'hoverduration': 0 # No delay on hover out - } + "modeBarButtonsToAdd": ["drawline", "drawcircle", "drawrect", "eraseshape"], + "doubleClick": "reset+autosize", + "showTips": True, + "watermark": False, + "staticPlot": False, + "locale": "en", + "showAxisDragHandles": True, + "showAxisRangeEntryBoxes": True, + "displaylogo": False, + "modeBar": { + "bgcolor": "rgba(0, 0, 0, 0.1)" + if theme == "light" + else "rgba(255, 255, 255, 0.1)", + "color": text_color, + "activecolor": line_color, + "orientation": "v", + "yanchor": "top", + "xanchor": "right", + "x": 1.05, # Increased spacing from chart + "y": 1, + "opacity": 0, # Start hidden + "hovermode": True, # Show on hover + "hoverdelay": 0, # No delay on hover + "hoverduration": 0, # No delay on hover out + }, } - + # Convert figure to JSON and add config figure_json = json.loads(fig.to_json()) - figure_json['config'] = toolbar_config - + figure_json["config"] = toolbar_config + return figure_json # Plotly chart with theme and config file # This widget demonstrates how to create a chart using the Plotly library # and use the config file to minimize the amount of code needed to create the chart. -@register_widget({ - "name": "Plotly Chart with Theme and Toolbar using Config File", - "description": "Plotly chart with theme and toolbar using config file", - "type": "chart", - "endpoint": "plotly_chart_with_theme_and_toolbar_using_config_file", - "gridData": {"w": 40, "h": 15} -}) - +@register_widget( + { + "name": "Plotly Chart with Theme and Toolbar using Config File", + "description": "Plotly chart with theme and toolbar using config file", + "type": "chart", + "endpoint": "plotly_chart_with_theme_and_toolbar_using_config_file", + "gridData": {"w": 40, "h": 15}, + } +) @app.get("/plotly_chart_with_theme_and_toolbar_using_config_file") def get_plotly_chart_with_theme_and_toolbar_using_config_file(theme: str = "dark"): # Generate mock time series data @@ -2658,39 +2514,43 @@ def get_plotly_chart_with_theme_and_toolbar_using_config_file(theme: str = "dark {"date": "2023-01-07", "return": 2.8, "transactions": 1780}, {"date": "2023-01-08", "return": -0.9, "transactions": 1620}, {"date": "2023-01-09", "return": 1.2, "transactions": 1480}, - {"date": "2023-01-10", "return": 3.5, "transactions": 1920} + {"date": "2023-01-10", "return": 3.5, "transactions": 1920}, ] - + dates = [datetime.strptime(d["date"], "%Y-%m-%d") for d in mock_data] returns = [d["return"] for d in mock_data] transactions = [d["transactions"] for d in mock_data] - + # Get theme colors colors = get_theme_colors(theme) - + # Create the figure fig = go.Figure() - + # Add the line trace for returns - fig.add_trace(go.Scatter( - x=dates, - y=returns, - mode='lines', - name='Returns', - line=dict(width=2, color=colors["main_line"]) - )) - + fig.add_trace( + go.Scatter( + x=dates, + y=returns, + mode="lines", + name="Returns", + line=dict(width=2, color=colors["main_line"]), + ) + ) + # Add the bar trace for transactions - fig.add_trace(go.Bar( - x=dates, - y=transactions, - name='Transactions', - opacity=0.5, - marker_color=colors["neutral"] - )) - + fig.add_trace( + go.Bar( + x=dates, + y=transactions, + name="Transactions", + opacity=0.5, + marker_color=colors["neutral"], + ) + ) + fig.update_layout(**base_layout(theme=theme)) - + # Add secondary y-axis for transactions fig.update_layout( yaxis2=dict( @@ -2698,16 +2558,16 @@ def get_plotly_chart_with_theme_and_toolbar_using_config_file(theme: str = "dark overlaying="y", side="right", gridcolor=colors["grid"], - tickfont=dict(color=colors["text"]) + tickfont=dict(color=colors["text"]), ) ) - + # Update the bar trace to use secondary y-axis fig.data[1].update(yaxis="y2") figure_json = json.loads(fig.to_json()) - figure_json['config'] = get_toolbar_config() - + figure_json["config"] = get_toolbar_config() + return figure_json @@ -2716,37 +2576,39 @@ def get_plotly_chart_with_theme_and_toolbar_using_config_file(theme: str = "dark # including heatmaps, scatter plots, line charts, 3d charts, etc. # and also demonstrates how parameters can influence the a plotly chart. # Note that the theme parameter always comes at the end of the function. -@register_widget({ - "name": "Plotly Heatmap", - "description": "Plotly heatmap", - "type": "chart", - "endpoint": "plotly_heatmap", - "gridData": {"w": 40, "h": 15}, - "params": [ - { - "paramName": "color_scale", - "description": "Select the color scale for the heatmap", - "value": "RdBu_r", - "label": "Color Scale", - "type": "text", - "show": True, - "options": [ - {"label": "Red-Blue (RdBu_r)", "value": "RdBu_r"}, - {"label": "Viridis", "value": "Viridis"}, - {"label": "Plasma", "value": "Plasma"}, - {"label": "Inferno", "value": "Inferno"}, - {"label": "Magma", "value": "Magma"}, - {"label": "Greens", "value": "Greens"}, - {"label": "Blues", "value": "Blues"}, - {"label": "Reds", "value": "Reds"} - ] - } - ] -}) +@register_widget( + { + "name": "Plotly Heatmap", + "description": "Plotly heatmap", + "type": "chart", + "endpoint": "plotly_heatmap", + "gridData": {"w": 40, "h": 15}, + "params": [ + { + "paramName": "color_scale", + "description": "Select the color scale for the heatmap", + "value": "RdBu_r", + "label": "Color Scale", + "type": "text", + "show": True, + "options": [ + {"label": "Red-Blue (RdBu_r)", "value": "RdBu_r"}, + {"label": "Viridis", "value": "Viridis"}, + {"label": "Plasma", "value": "Plasma"}, + {"label": "Inferno", "value": "Inferno"}, + {"label": "Magma", "value": "Magma"}, + {"label": "Greens", "value": "Greens"}, + {"label": "Blues", "value": "Blues"}, + {"label": "Reds", "value": "Reds"}, + ], + } + ], + } +) @app.get("/plotly_heatmap") def get_plotly_heatmap(color_scale: str = "RdBu_r", theme: str = "dark"): # Create mock stock symbols - symbols = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA'] + symbols = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA"] # Create mock correlation matrix directly corr_matrix = [ @@ -2754,7 +2616,7 @@ def get_plotly_heatmap(color_scale: str = "RdBu_r", theme: str = "dark"): [0.65, 1.00, 0.55, 0.40, 0.25], # MSFT correlations [0.45, 0.55, 1.00, 0.35, 0.15], # GOOGL correlations [0.30, 0.40, 0.35, 1.00, 0.10], # AMZN correlations - [0.20, 0.25, 0.15, 0.10, 1.00] # TSLA correlations + [0.20, 0.25, 0.15, 0.10, 1.00], # TSLA correlations ] # Get theme colors @@ -2767,38 +2629,40 @@ def get_plotly_heatmap(color_scale: str = "RdBu_r", theme: str = "dark"): # This allows users to modify the layout configuration further # in case they want to steer from the default settings. - layout_config['title'] = { - 'text': "Correlation Matrix", - 'x': 0.5, - 'y': 0.95, - 'xanchor': 'center', - 'yanchor': 'top', - 'font': {'size': 20} + layout_config["title"] = { + "text": "Correlation Matrix", + "x": 0.5, + "y": 0.95, + "xanchor": "center", + "yanchor": "top", + "font": {"size": 20}, } - layout_config['margin'] = {'t': 50, 'b': 50, 'l': 50, 'r': 50} - + layout_config["margin"] = {"t": 50, "b": 50, "l": 50, "r": 50} + # Update figure with complete layout fig.update_layout(layout_config) # Add the heatmap trace - fig.add_trace(go.Heatmap( - z=corr_matrix, - x=symbols, - y=symbols, - colorscale=color_scale, - zmid=colors["heatmap"]["zmid"], - text=[[f'{val:.2f}' for val in row] for row in corr_matrix], - texttemplate='%{text}', - textfont={"color": colors["heatmap"]["text_color"]}, - hoverongaps=False, - hovertemplate='%{x} - %{y}