Interactive Project Prioritization (ECharts Edition)

Inspired by a tweet by Fanilo I vibe coded the below Project Prioritization dashboard.

Code
"""
Interactive Value vs Complexity Dashboard for Project Prioritization (ECharts Edition)

This script creates an interactive visualization to help teams prioritize project tasks
based on their business value and complexity. Tasks are plotted on a scatter chart with
hover interactions to explore details about each task.

The visualization helps identify "quick wins" (high value, low complexity) and helps
teams make data-driven decisions about task prioritization.

🚀 Features:
- Interactive ECharts scatter plot with labeled points
- Hover to explore task details and get recommendations
- Visual quadrant system for prioritization
- Modern, clean design with emojis and styling
- Built with HoloViz Panel and ECharts

📊 Usage:
- Run with: panel serve script.py --show
- Or import and use: TaskPrioritizationDashboard(data=your_data)

Author: Marc Skov Madsen
License: MIT
Inspiration: https://x.com/andfanilo/status/1967841173822906830
"""

import pandas as pd
import panel as pn
import param
from typing import cast

# Enable Panel extensions for ECharts
pn.extension('echarts')


@pn.cache
def create_project_tasks() -> pd.DataFrame:
    """
    Create a DataFrame with business value vs complexity data for project tasks.
    
    This function generates sample data representing common software development
    tasks with their associated complexity (effort required) and business value
    (impact on business objectives).
    
    Returns
    -------
    pd.DataFrame
        DataFrame with columns:
        - task_name: str, descriptive name of the task
        - complexity: int, scale 1-10 (1=low complexity, 10=high complexity)
        - business_value: int, scale 1-10 (1=low value, 10=high value)
        - emoji: str, visual representation of the task type
        
    Examples
    --------
    >>> df = create_project_tasks()
    >>> print(df.head())
    """
    data = {
        "task_name": [
            "User Authentication",
            "Database Migration",
            "Mobile App UI",
            "API Documentation",
            "Performance Optimization",
            "Unit Test Coverage",
            "Dark Mode Theme",
        ],
        "complexity": [7, 9, 6, 3, 8, 4, 2],
        "business_value": [9, 6, 8, 5, 7, 6, 4],
        "emoji": ["🔐", "🗄️", "📱", "📚", "⚡", "✅", "🌙"],
    }

    return pd.DataFrame(data)


class TaskPrioritizationDashboard(pn.viewable.Viewer):
    """
    Interactive dashboard for visualizing and exploring project task prioritization.
    
    This component creates an ECharts scatter plot where tasks are positioned based on their
    complexity (x-axis) and business value (y-axis). Users can hover over points
    to see detailed information about each task.
    
    The dashboard includes quadrant guidelines to help identify:
    - Quick Wins: High value, low complexity (top-left)
    - Major Projects: High value, high complexity (top-right)
    - Fill-ins: Low value, low complexity (bottom-left)  
    - Questionable: Low value, high complexity (bottom-right)
    
    Parameters
    ----------
    data : pd.DataFrame
        DataFrame containing task information with columns:
        task_name, complexity, business_value, emoji
    selected_row : pd.Series or None
        Currently selected task row, updated on hover
    """
    
    data = cast(pd.DataFrame, param.DataFrame(constant=True, allow_None=False))
    selected_row = param.Parameter(default=None)

    # Configuration constants
    _points_color = "#FF6B9D"  # Modern pink color
    _plot_size = 600
    _quadrant_line_position = 5

    def __init__(self, **params):
        """
        Initialize the Task Prioritization Dashboard.
        
        Parameters
        ----------
        **params
            Additional parameters passed to the parent Viewer class
        """
        super().__init__(**params)
        
        # Create the interactive plot
        self._setup_plot()
        
        # Create the UI layout
        self._create_layout()

    def _setup_plot(self) -> None:
        """Set up the interactive ECharts scatter plot with hover functionality."""
        data = self.data

        # Prepare data for ECharts scatter plot
        scatter_data = []
        for idx, row in data.iterrows():
            scatter_data.append({
                "value": [row["complexity"], row["business_value"]],
                "name": f"{row['emoji']} {row['task_name']}",
                "task_index": idx  # Store the original index for hover handling
            })

        # Create ECharts option dictionary
        echart_option = {
            "title": {
                "text": "Project Task Prioritization Matrix",
                "left": "center",
                "textStyle": {"fontSize": 16}
            },
            "tooltip": {
                "trigger": "item",
                "formatter": "{b}<br/>Complexity: {c0}<br/>Business Value: {c1}"
            },
            "xAxis": {
                "type": "value",
                "name": "Complexity →",
                "nameLocation": "middle",
                "nameGap": 25,
                "min": 0,
                "max": 10,
                "splitLine": {"show": True, "lineStyle": {"color": "#f0f0f0"}},
                "axisLine": {"lineStyle": {"color": "#666"}}
            },
            "yAxis": {
                "type": "value", 
                "name": "Business Value →",
                "nameLocation": "middle",
                "nameGap": 40,
                "min": 0,
                "max": 10,
                "splitLine": {"show": True, "lineStyle": {"color": "#f0f0f0"}},
                "axisLine": {"lineStyle": {"color": "#666"}}
            },
            "series": [
                {
                    "type": "scatter",
                    "data": scatter_data,
                    "symbolSize": 40,
                    "itemStyle": {"color": self._points_color},
                    "label": {
                        "show": True,
                        "formatter": "{b}",
                        "position": "top",
                        "fontSize": 10,
                        "color": "#333"
                    },
                    "emphasis": {
                        "itemStyle": {"color": "#FF3366"},
                        "label": {"fontSize": 12}
                    },
                    "markLine": {
                        "silent": True,
                        "data": [
                            {"xAxis": self._quadrant_line_position, "lineStyle": {"type": "dashed", "color": "#999"}},
                            {"yAxis": self._quadrant_line_position, "lineStyle": {"type": "dashed", "color": "#999"}}
                        ]
                    }
                }
            ]
        }

        # Create ECharts pane
        self._echart_pane = pn.pane.ECharts(
            echart_option, 
            height=self._plot_size, 
            width=self._plot_size
        )
        
        self._echart_pane.on_event('mouseover', self.handle_mouse_over)
        self._echart_pane.on_event('mouseout', self.handle_mouse_out)

    # Add hover event handler
    def handle_mouse_over(self, event):
        # Extract task index from the hovered data point
        task_idx = event.data["dataIndex"]
        self.selected_row = self.data.iloc[task_idx]

    def handle_mouse_out(self, event):
        self.selected_row = None

    def _create_layout(self) -> None:
        """Create the dashboard layout with ECharts plot and task details."""
        self._view = pn.Column(
            "# 📊 Task Prioritization Dashboard (ECharts)",
            pn.pane.Markdown(
                """
                **How to use this dashboard:**
                - Hover over any point to see task details and recommendations
                - **Top-left quadrant**: Quick wins (high value, low complexity)
                - **Top-right quadrant**: Major projects (high value, high complexity)
                - **Bottom-left quadrant**: Fill-ins (low value, low complexity)
                - **Bottom-right quadrant**: Questionable tasks (low value, high complexity)
                """,
                margin=(0, 0, 20, 0)
            ),
            self._echart_pane,
            pn.pane.Markdown(
                self.task_details, 
                styles={"font-size": "18px", "padding": "20px", "background": "#f8f9fa"}, 
                width=self._plot_size + 100,
                margin=(20, 0, 0, 0)
            ),
            width=self._plot_size + 100,
            styles={"margin": "auto"}
        )

    @param.depends("selected_row")
    def task_details(self) -> str:
        """
        Generate formatted task details for the selected task.
        
        Returns
        -------
        str
            Markdown-formatted string with task information
        """
        if self.selected_row is None:
            return """
            ### 👆 Hover over a task to see details
            
            Hover over any point on the chart above to view detailed information about that task.
            """
        
        row = self.selected_row
        if not isinstance(row, pd.Series):
            return "Error: Invalid task data"
            
        value_stars = "⭐" * int(row['business_value'])
        complexity_gears = "⚙️" * int(row['complexity'])

        return f"""
        ### {row['emoji']} {row['task_name']}

        **Business Value:** {value_stars} ({row['business_value']}/10)

        **Complexity:** {complexity_gears} ({row['complexity']}/10)
        
        **Recommendation:** {self._get_priority_recommendation(row)}
        """

    def _get_priority_recommendation(self, task_row: pd.Series) -> str:
        """
        Get priority recommendation based on task position in the matrix.
        
        Parameters
        ----------
        task_row : pd.Series
            Task data containing complexity and business_value
            
        Returns
        -------
        str
            Priority recommendation text
        """
        value = int(task_row['business_value'])
        complexity = int(task_row['complexity'])
        
        if value >= self._quadrant_line_position and complexity < self._quadrant_line_position:
            return "🚀 **Quick Win** - High priority, implement soon!"
        elif value >= self._quadrant_line_position and complexity >= self._quadrant_line_position:
            return "🎯 **Major Project** - Plan carefully and allocate sufficient resources"
        elif value < self._quadrant_line_position and complexity < self._quadrant_line_position:
            return "📝 **Fill-in Task** - Good for spare time or junior developers"
        else:
            return "❓ **Questionable** - Consider if this task is really necessary"

    def __panel__(self) -> pn.viewable.Viewable:
        """Return the Panel view for display."""
        return self._view

# Create and serve the dashboard
if pn.state.served:
    # Create the dashboard with sample data
    dashboard = TaskPrioritizationDashboard(data=create_project_tasks())
    
    # Make it servable for Panel applications
    dashboard.servable(title="Task Prioritization Dashboard (ECharts)")

Install with pip install panel pandas.
Serve the app with panel serve app.py.