diff --git a/agents/coder_agent/MobileChartShell.tsx b/agents/coder_agent/MobileChartShell.tsx new file mode 100644 index 0000000..50ee54f --- /dev/null +++ b/agents/coder_agent/MobileChartShell.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { + Bar, + BarChart, + CartesianGrid, + Cell, + ComposedChart, + Legend, + Line, + LineChart, + Pie, + PieChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +// [AGENT_ACTION]: INSERT DATA HERE +// Expected format: array of objects +const DATA = [ + { name: "A", value: 400 }, + { name: "B", value: 300 }, + { name: "C", value: 300 }, + { name: "D", value: 200 }, +]; + +// [AGENT_ACTION]: CONFIGURE HEIGHT LOGIC +// If the chart needs to scroll (e.g., many bars), set a threshold and calculate height. +// Otherwise, use a fixed height (e.g., 300 or 400). +const ROW_HEIGHT = 50; +const SCROLL_THRESHOLD = 10; +const isScrollable = DATA.length > SCROLL_THRESHOLD; +const CHART_HEIGHT = isScrollable ? DATA.length * ROW_HEIGHT : 400; + +// [AGENT_ACTION]: DEFINE CUSTOM TOOLTIP IF NEEDED +// interface CustomTooltipProps { ... } +// const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => { ... } + +export default function MobileChartShell() { + return ( + // [AGENT_ACTION]: PRESERVE UI SHELL STYLING +
+ {/* Header Section */} +
+ {/* [AGENT_ACTION]: INSERT TITLE */} +

+ Chart Title +

+ {/* [AGENT_ACTION]: INSERT DESCRIPTION */} +

+ Chart description goes here. Explain what the user is seeing. +

+
+ + {/* Chart Container */} + {/* [AGENT_ACTION]: CONFIGURE CHART LAYOUT */} +
+ + {/* [AGENT_ACTION]: REPLACE WITH SPECIFIC CHART TYPE (BarChart, LineChart, PieChart, etc.) */} + + + + {/* [AGENT_ACTION]: CONFIGURE AXES */} + + + + } + /> + + {/* [AGENT_ACTION]: INSERT CHART ELEMENTS (Bar, Line, Pie, etc.) */} + + + +
+ + {/* Footer / Legend / Scroll Hint */} +
+ {/* [AGENT_ACTION]: INSERT LEGEND OR SCROLL HINT */} + {isScrollable ? ( + Scroll for more data + ) : ( + Data source: Source Name + )} +
+
+ ); +} diff --git a/agents/coder_agent/action_space.md b/agents/coder_agent/action_space.md new file mode 100644 index 0000000..350d55f --- /dev/null +++ b/agents/coder_agent/action_space.md @@ -0,0 +1,396 @@ +# Mobile Visualization Action Space (Agent Optimized) + +## Part 1: Component Taxonomy & DOM Heuristics +| Component ID | Description | DOM Identification Strategies (Selector Hints) | +| --- | --- | --- | +| **L0_Container** | Root visualization container | `svg`, `div[role="graphics-document"]`, `.chart-container`, `.vis-wrapper` | +| **L1_DataModel** | Data Transformation Layer (Aggregation, Filtering) | N/A (Internal Logic / Data State) | +| **L1_Chart** | Individual chart in a multi-chart layout | `g.panel`, `g.facet`, `svg.small-multiple` | +| **L1_Interaction** | Interaction controls and state | `div.tooltip`, `g.brush`, Mouse/Touch Event Listeners | +| **L2_TitleBlock** | Block containing main title and subtitle | `g.title-group`, `text.chart-title`, `g[aria-label="title"]` | +| **L2_CoordSys** | Coordinate system wrapper | `g.grid-layer`, `g.cartesian-layer` | +| **L2_DataMarks** | The primary data visualization elements | `g.mark-layer`, `g.series-group`, `.bars`, `.lines`, `.scatter` | +| **L2_Annotations** | Text annotations overlaid on chart | `g.annotation-group`, `text.annotation` | +| **L3_Axes** | X or Y Axis container | `g.axis`, `g.x-axis`, `g.y-axis`, `g[role="graphics-axis"]` | +| **L3_Legend** | Legend block container | `g.legend`, `.legend-wrapper`, `div.legend`, `g[role="complementary"]` | +| **L4_Ticks** | Group of tick marks and labels | `g.tick`, `.tick` elements inside axis | +| **L4_MarkInstance** | Individual geometric shapes (bars, points) | `rect.bar`, `circle.dot`, `path.line-segment` | +| **L4_MarkLabel** | Labels attached to specific data marks | `text.bar-label`, `g.label-group` | +| **L5_TickLabel** | Text labels on axes | `text` inside `g.tick`, `text.tick-label` | + +## Part 2: Planner Action Rules (Decision Matrix) + +### Level 0/1: Container & Data Strategies +* **Component: L0_Container (Viewport)** + * **Condition**: `viewport_overflow == true` OR `content_width > screen_width` `(Source: DOM/Visual)` + * **Action**: `RESCALE_VIEWBOX` + * **Param Schema**: `{ "fit": "contain" | "cover", "width": string }` + * **Requires Data**: `[FALSE]` + +* **Component: L0_Container (Margins)** + * **Condition**: `whitespace_ratio > 0.3` `(Source: Visual)` + * **Action**: `REPOSITION_MARGINS` + * **Param Schema**: `{ "margin": number | string }` + * **Requires Data**: `[FALSE]` + +* **Component: L1_DataModel (Density)** + * **Condition**: `data_density > 30 points` AND `screen_width < 400px` `(Source: Data)` + * **Action**: `AGGREGATE_DATA` + * **Param Schema**: `{ "method": "avg" | "sum" | "count", "interval": "monthly" | "weekly" }` + * **Requires Data**: `[REQUIRES_DATA]` + +* **Component: L1_DataModel (Series)** + * **Condition**: `series_count > 5` AND `chart_type == "line"` `(Source: Data)` + * **Action**: `FILTER_SERIES` + * **Param Schema**: `{ "keep": "top_k", "k": number, "others": "gray" | "remove" }` + * **Requires Data**: `[REQUIRES_DATA]` + +* **Component: L1_Interaction** + * **Condition**: `device_type == "touch"` `(Source: Environment)` + * **Action**: `DISABLE_HOVER` + * **Param Schema**: `{ "fallback": "click" | "tap" }` + * **Requires Data**: `[FALSE]` + +* **Component: L1_Interaction (Widgets)** + * **Condition**: `screen_width < 600px` AND `has_widget == true` `(Source: DOM)` + * **Action**: `REMOVE_WIDGETS` + * **Param Schema**: `{ "target": "search" | "filter_dropdown", "fallback": "simple_toggle" }` + * **Requires Data**: `[FALSE]` + +* **Component: L1_Chart (Small Multiples)** + * **Condition**: `facet_layout == "grid"` AND `columns > 1` `(Source: DOM)` + * **Action**: `SERIALIZE_LAYOUT` + * **Param Schema**: `{ "columns": 1 }` + * **Requires Data**: `[FALSE]` + +* **Component: L1_Chart (Pagination)** + * **Condition**: `panel_count > 3` AND `vertical_space_constrained == true` `(Source: DOM)` + * **Action**: `PAGINATE_PANELS` + * **Param Schema**: `{ "page_size": 1, "control": "dots" | "arrows" }` + * **Requires Data**: `[FALSE]` + +* **Component: L1_Chart (Type Switching)** + * **Condition**: `chart_type == "bar"` AND `mark_density > 0.8` `(Source: Visual)` + * **Action**: `SWITCH_CHART_TYPE` + * **Param Schema**: `{ "to_type": "line" | "heatmap" | "list" }` + * **Requires Data**: `[REQUIRES_DATA]` + +### Level 2/3: Components & Layout +* **Component: L2_TitleBlock (Title)** + * **Condition**: `title_width > screen_width - padding` `(Source: Visual)` + * **Action**: `RESCALE_FONT` + * **Param Schema**: `{ "target_size": string | number, "unit": "px" | "rem" }` + * **Requires Data**: `[FALSE]` + +* **Component: L2_TitleBlock (Subtitle)** + * **Condition**: `screen_width < 400px` `(Source: DOM)` + * **Action**: `REMOVE_SUBTITLE` + * **Param Schema**: `{}` + * **Requires Data**: `[FALSE]` + +* **Component: L2_Legend** + * **Condition**: `screen_width < 500px` AND `position == "right"` `(Source: DOM/Visual)` + * **Action**: `REPOSITION_LEGEND` + * **Param Schema**: `{ "target": "bottom" | "top", "layout": "flex_row" | "flex_col" }` + * **Requires Data**: `[FALSE]` + +* **Component: L2_Legend (Overflow)** + * **Condition**: `item_count > 10` AND `layout == "horizontal"` `(Source: DOM)` + * **Action**: `COMPENSATE_TOGGLE` + * **Param Schema**: `{ "widget": "dropdown" | "modal" | "accordion" }` + * **Requires Data**: `[FALSE]` + +* **Component: L2_Legend (Nuance)** + * **Condition**: `item_count > 5` AND `screen_width < 380px` `(Source: Data)` + * **Action**: `FILTER_LEGEND_ITEMS` + * **Param Schema**: `{ "keep": "top_k", "k": 3, "hide_corresponding_data": boolean }` + * **Requires Data**: `[REQUIRES_DATA]` + +* **Component: L2_DataMarks (Simplification)** + * **Condition**: `mark_type == "image"` AND `screen_width < 400px` `(Source: DOM)` + * **Action**: `SIMPLIFY_MARK` + * **Param Schema**: `{ "fallback_shape": "rect" | "circle", "keep_color": boolean }` + * **Requires Data**: `[FALSE]` + +* **Component: L2_DataMarks (Focus)** + * **Condition**: `data_density > 50` OR `user_intent == "focus"` `(Source: Visual/User)` + * **Action**: `GRAY_OUT_CONTEXT` + * **Param Schema**: `{ "trigger": "static" | "interactive", "opacity_ratio": 0.1 }` + * **Requires Data**: `[REQUIRES_DATA]` + +* **Component: L2_Annotations** + * **Condition**: `overlap_ratio > 0` `(Source: Visual)` + * **Action**: `EXTERNALIZE_ANNOTATIONS` + * **Param Schema**: `{ "position": "below" | "side", "link_style": "numbered" | "line" }` + * **Requires Data**: `[FALSE]` + +* **Component: L3_Axes (Structure)** + * **Condition**: `chart_type == "bar"` AND `x_axis_label_count > 10` AND `screen_width < 400px` `(Source: Code/DOM)` + * **Action**: `TRANSPOSE_CHART` + * **Param Schema**: `{ "swap_axes": boolean }` + * **Requires Data**: `[REQUIRES_DATA]` + +* **Component: L3_Axes (Noise Reduction)** + * **Condition**: `screen_width < 360px` `(Source: DOM)` + * **Action**: `REMOVE_AXIS_TITLE` + * **Param Schema**: `{ "axis": "y" | "x" | "both" }` + * **Requires Data**: `[FALSE]` + +* **Component: L3_Axes (Interaction)** + * **Condition**: `content_width > screen_width` AND `interaction_policy == "allow_scroll"` `(Source: Config)` + * **Action**: `ENABLE_PAN_ZOOM` + * **Param Schema**: `{ "axis": "x" | "y", "mode": "pan" | "zoom" }` + * **Requires Data**: `[FALSE]` + +* **Component: L3_Axes (Domain)** + * **Condition**: `has_outliers == true` AND `screen_width < 400px` `(Source: Data)` + * **Action**: `CROP_AXIS_DOMAIN` + * **Param Schema**: `{ "percentile": [5, 95], "max_crop_ratio": 0.2 }` + * **Requires Data**: `[REQUIRES_DATA]` + +* **Component: L3_Gridlines** + * **Condition**: `density > 0.5` OR `screen_width < 400px` `(Source: Visual)` + * **Action**: `REMOVE_GRIDLINES` + * **Param Schema**: `{ "axis": "x" | "both" }` + * **Requires Data**: `[FALSE]` + +* **Component: L3_Gridlines / L4_AxisLine / L5_TickLine (De-cluttering)** + * **Condition**: `ink_ratio > 0.1` AND `screen_width < 360px` `(Source: Visual)` + * **Action**: `REMOVE_DECORATIONS` + * **Param Schema**: `{ "targets": ["gridlines", "axis_line", "tick_line"] }` + * **Requires Data**: `[FALSE]` + +### Level 4/5: Marks & Ticks +* **Component: L4_MarkInstance** + * **Condition**: `mark_overlap > 0` OR `mark_width > available_space` `(Source: Visual)` + * **Action**: `RESCALE_MARK_WIDTH` + * **Param Schema**: `{ "scale_factor": number, "min_width": number }` + * **Requires Data**: `[REQUIRES_DATA]` + +* **Component: L4_MarkLabel (Priority 1)** + * **Condition**: `label_width > mark_width` OR `overlap > 0` `(Source: Visual)` + * **Action**: `EXTERNALIZE_LABEL` + * **Param Schema**: `{ "position": "top" | "side" }` + * **Requires Data**: `[FALSE]` + +* **Component: L4_MarkLabel (Priority 2)** + * **Condition**: `external_space_available == false` `(Source: Visual)` + * **Action**: `REMOVE_LABEL` + * **Param Schema**: `{ "fallback": "tooltip" }` + * **Requires Data**: `[FALSE]` + +* **Component: L4_Ticks** + * **Condition**: `tick_overlap_predicted == true` `(Source: Visual)` + * **Action**: `DECIMATE_TICKS` + * **Param Schema**: `{ "target_count": number | "auto", "strategy": "uniform" | "extents_only" }` + * **Requires Data**: `[FALSE]` + +* **Component: L5_TickLabel** + * **Condition**: `text_overflow == true` OR `label_length > 8 chars` `(Source: Visual/DOM)` + * **Action**: `SIMPLIFY_LABEL` + * **Param Schema**: `{ "format": "abbreviate" | "truncate" | "rotate" | "date_format" }` + * **Requires Data**: `[FALSE]` + +* **Component: L5_LegendSymbol** + * **Condition**: `horizontal_space_constrained == true` `(Source: Visual)` + * **Action**: `RESCALE_LEGEND_SYMBOL` + * **Param Schema**: `{ "scale": 0.5 }` + * **Requires Data**: `[FALSE]` + +## Part 3: Coder Implementation Logic + +### 3.1 Structural Changes (SVG/XML Attributes) +* **Action**: `TRANSPOSE_AXES` + * **Strategy**: Swap layout definitions. Map X-scale to Y-range and Y-scale to X-range. + * **Logic**: + $$ + \text{Op}_{Transpose}(S_{LS}) \Rightarrow S_{SS} = \begin{cases} + \text{Axis}_{x} \leftarrow S_{LS}.\text{Axis}_{y} \\ + \text{Axis}_{y} \leftarrow S_{LS}.\text{Axis}_{x} \\ + \text{Range}_{x} \leftarrow [0, W_{screen}] \\ + \text{Range}_{y} \leftarrow [0, \infty) \quad (\text{Scrollable}) + \end{cases} + $$ + * **Target**: SVG Attributes (`transform`, `d`, `x`, `y`) + +* **Action**: `SERIALIZE_LAYOUT` + * **Strategy**: Stack multiple chart panels vertically using a cumulative height offset. + * **Logic**: + $$ + \text{Pos}_{SS}(i) = \left( 0, \sum_{k=0}^{i-1} (H_{\text{mark}} + H_{\text{padding}}) \right) + $$ + * **Target**: SVG transforms (``) + +* **Action**: `DECIMATE_TICKS` + * **Strategy**: Calculate maximum possible ticks based on tick label width and screen width, then filter. + * **Logic**: + $$ + N_{ticks} = \left\lfloor \frac{W_{screen}}{\text{Width}_{\text{label}} \times \alpha} \right\rfloor + $$ + If `strategy == "extents_only"`, return `[min, max]`. + * **Target**: DOM Structure (Removes `` elements) + +* **Action**: `RESCALE_MARK_WIDTH` + * **Strategy**: Reduce width of geometrical marks (rects) while ensuring minimum touch target. + * **Constraint**: If $W_{new} < 40px$, create a transparent overlay rect ($W=40px$, opacity=0) to maintain interaction. + * **Target**: Attribute `width` or `r` + New Overlay DOM Element. + +* **Action**: `REMOVE_DECORATIONS` + * **Strategy**: Minimize "Data-Ink" ratio by hiding non-essential structural lines. + * **Logic**: + Set `stroke: none`, `display: none` or `opacity: 0`. + * **Target**: CSS/SVG Attributes on `.grid-line`, `.domain`, `.tick-line`. + +* **Action**: `REMOVE_WIDGETS` + * **Strategy**: Detect and hide complex HTML widgets that consume vertical space. + * **Logic**: `display: none`. + * **Target**: DOM containers (`.search-bar`, `.filter-panel`). + +### 3.2 Behavioral Changes (JS/Events Injection) +* **Action**: `FIX_TOOLTIP_POSITION` + * **Strategy**: Inject script to override default mouse-follow behavior. Force tooltip to fixed coordinates on touch devices. + * **Logic**: + $$ + P_{\text{tooltip}}(x, y) = \begin{cases} + (x_{\text{cursor}} + \delta, y_{\text{cursor}} + \delta) & \text{if Desktop (Hover)} \\ + (W_{screen}/2, H_{screen} - H_{\text{tooltip}}) & \text{if Mobile (Touch)} + \end{cases} + $$ + * **Target**: `