--- title: "How to Visualize Data Stories with AI: Lessons" date: "2025-04-14T08:21:24Z" lastmod: "2025-04-14T08:21:27Z" categories: - coding - llms wp_id: 4036 description: "LLMs speed up ambitious visual storytelling less by replacing expertise than by reducing procrastination, lowering friction, and making iteration and ideation radically easier." keywords: [visual storytelling, AI coding, Copilot, data stories, workflow lessons, visualization design] --- ![How to Visualize Data Stories with AI: Lessons](/blog/assets/ChatGPT-Image-Apr-14-2025-04_18_33-PM.webp) I tried 2 experiments. 1. **Can I code a visual data story **only** using LLMs?** Does this make me faster? How much? 2. **Has GitHub Copilot caught up with Cursor?** How far behind is it? Can I recommend it? So I built a [visual story](https://sanand0.github.io/eliminationgame/) for [Lech Mazur](https://x.com/lechmazur)'s [elimination game benchmark](https://github.com/lechmazur/elimination_game/) (it's like LLMs playing Survivor) using only the free [GitHub Copilot](https://github.com/copilot) as the AI code editor. **SUMMARY**: using LLMs and AI code editors make me a bit faster. It took me 7 hours instead of 10-12. But more importantly: 1. I procrastinate less. ("Oh, LLMs will make it easy.") 2. I get stuck less. ("Oh, LLMs will know that.") 3. I avoid ambitious designs less. ("Oh, LLMs will figure something out.") Also: [GitHub Copilot](https://github.com/copilot) is almost as good as Cursor at editing code, but slower at applying the edits. I'm perfectly happy recommending **the free tier** for beginners. Here’s a breakdown of the process I followed, along with the most insightful lessons I learned. ## Research usefulness I usually visualize data for fun. But [Naveen](https://www.linkedin.com/in/naveengattu)'s pops into my head, asking, "But Anand, what's the **use** of all this?" So, I asked O1-Pro: "What are ways in which this can help Straive push its AI business?" Turns out it [**can** help Straive's business](https://chatgpt.com/share/67f4b6a6-cd2c-800c-952a-9cce8cd8a768) by pitching multi-agent capabilities that can be useful in: - Understanding AI safety and alignment - Teaching material on group dynamics and negotiation - Scenario-based data-driven decision making to avoid groupthink - Model interactions across reviewers, authors, editors to model bias, integrity, review best practices - Research tool for simulating interactions Learnings: - πŸ’‘ **Ask LLMs why something is useful**. You'll invariably find plausible uses, even if you're doing it just for fun. ## Ideate visual representations To expore visualization options, I created the prompt by: - Copying a part of the [README.md](https://github.com/lechmazur/elimination_game/blob/main/README.md) - Copying part of [a log file](https://github.com/lechmazur/elimination_game/blob/main/logs/game_1739787734085223_20250217_061432.jsonl) Then I added my requirements (which took 10-15 minutes to think of.) > I would like to visualize each game interactively. The authors have created a visualization that looks like the image attached. I would like to do better. Specifically, I'd like to: > > - Allow the user to step through each stage or play each step in sequence, jumping to any step. (They should be able to link to any step as well.) > - Show the game, round, sub-round prominently > - Show what the model is saying or thinking NEXT to the model, making it easy to read > - Show alliance proposals and rejections as they form, ideally moving the models around as they seek to pair up. Rejections and replacements should be clearly visible > - Once alliances are formed, group models together > - Clearly show the voting process: who voted to eliminate which which model, how many elimination votes has each model received > - Clicking on each model should show all the model's thoughts and messages up to that point > > Keeping these in mind, suggest diverse ways to visualize each step of the game. The primary goal is to make the game easy to follow and understand and tell a GRIPPING, ENGAGING story about the politics of LLMs. Like a Survivor reality show. I asked both [O1 Pro](https://chatgpt.com/share/67f4bbf9-5084-800c-b42b-95abf8ab9e52) **and** [Gemini 2.5 Pro (exp)](https://g.co/gemini/share/52ad507ea19e) for visualization ideas. I liked Gemini's better. For example, Gemini said, - "Private Conversations: Dim the main stage slightly. Highlight the currently conversing pair. - "Voting Booth Visualization: As each private_vote_reason appears, briefly show the voter's avatar and their reason text (maybe in a "thought bubble" style) next to the target they intend to vote for." But O1 Pro gave me a few powerful ideas. The best was an **alliance table**: - β€œCreate a table with columns representing each model, rows representing rounds. Each cell shows the ID of the ally that model allied with in that round. If it’s 3+ consecutive alliances, collapse them with a vertical line. If the model was eliminated or had no alliance, leave it blank or use a placeholder icon.” Learnings: - πŸ’‘ **Ask LLMs for visualization ideas**. They'll suggest things you didn't think of. - πŸ’‘ **Ask **multiple** LLMs**. Each has a different style of thinking. ## Prototype the visual I stiched together pieces of the UI description and asked GPT 4o to create an image. This took 10-15 minutes. [Private chat](https://chatgpt.com/c/67f4baea-56c0-800c-bf8e-ab6bc5c6db6a): > Here's how I plan to visualize this. > > ### Overall Interface & Navigation > > - **Timeline Scrubber:** A prominent timeline at the bottom or top, showing rounds and sub-rounds (conversations, pairing, voting, elimination). Users can click, drag, or use next/prev buttons to navigate. Each step should be linkable (e.g., using URL hashes). Add play/pause controls for auto-stepping. > - **Game State Dashboard:** Always visible area showing: `Game ID`, `Round`, `Sub-round`, `Players Remaining`, `Players Eliminated (Jury)`. > - **Central Stage Layout:** Models represented as avatars (could be simple circles/icons or more thematic representations) arranged in a central area. Their positions and connections change based on game events. > > ### 1. Public Conversation (Round Start) > > - **Talking Heads Circle:** Arrange player avatars in a circle. When a player "speaks" (their message appears in the log): > - Highlight their avatar. > - Display their message in a speech bubble next to them. > - Fade previous messages slightly or stack them briefly. > - **Engaging Element:** Animate the avatar slightly (e.g., subtle pulse or glow) when they speak. > - **Chat Feed Style:** A more traditional chat interface on one side, linked to avatars on the main stage. Clicking a message highlights the avatar and vice-versa. > - **Engaging Element:** Use distinct colors or icons for each player avatar and their corresponding messages. > > ### 2. Alliance Formation (Preference Proposals/Outcomes/Results) > > - **Dynamic Pairing Dance:** > - **Proposal:** An animated arrow or beam shoots from the proposer's avatar to the target's avatar. Display text like "P1 proposes to P6 (Rank 0)". > - **Acceptance:** The arrow solidifies, perhaps pulsing gently. A "Matched" icon appears. > - **Rejection:** The arrow bounces off or shatters. A "Rejected" icon appears briefly. > - **Replacement:** Show the existing accepted proposal being visually "bumped" or overridden by the new accepted one. Clearly label it "Replaced Px". > - **Engaging Element:** Physically move the avatars closer when a proposal is made, snapping them together when accepted, and pushing them apart on rejection. Use distinct sounds for proposal, acceptance, rejection, replacement. > - **Preference List Display:** When hovering or clicking a player, show their ranked preference list **as they build it** during this phase. Highlight the status (proposed, accepted, rejected). > - **Final Pairs:** Once `preference_result` occurs, rearrange the avatars so matched pairs are visually grouped together on the stage, perhaps connected by a clear line or within a shared bounding box. > > ### 3. Private Conversations (Paired Chats) > > - **Private Chat Rooms:** Dim the main stage slightly. Highlight the currently conversing pair. Display their private messages in separate chat windows or adjacent speech bubbles clearly linked to the pair. > - **Engaging Element:** Use a "spotlight" effect on the active pair. Allow users to click other pairs to view their simultaneous conversations. > - **Connection Lines:** Draw lines between the paired avatars during this phase. Clicking a line could bring up the conversation history for that pair in that round. > - **Engaging Element:** Make the line pulse or glow when new messages are exchanged between the pair. > > ### 4. Voting (Reasons & Votes) > > - **Voting Booth Visualization:** > - As each `private_vote_reason` appears, briefly show the voter's avatar and their reason text (maybe in a "thought bubble" style) next to the target they intend to vote for. > - As each `vote` occurs, draw a clear, perhaps slightly dramatic, animated arrow from the voter to the target avatar. > - **Vote Tally:** Display a running count of votes received next to each player's avatar (e.g., a red badge with the number). Increment this visibly as each vote comes in. > - **Engaging Element:** Use a distinct color (e.g., red) for voting arrows. Add a subtle "target lock" animation on the player receiving a vote. Show if the vote was public or private (maybe different arrow styles). > > ### 5. Elimination > > - **Spotlight & Fade:** When the `elimination` event occurs: > - Put a dramatic spotlight on the eliminated player. > - Display the reason (tie-break, random pick if applicable). > - Visually "grey out" or fade the eliminated player's avatar and move them to a designated "Jury Box" area. > - **Engaging Element:** A brief, dramatic animation or sound effect for elimination. Update the "Players Remaining/Eliminated" dashboard instantly. > > ### 6. Jury Speeches & Voting (Final Round) > > - **Finalist Stage:** Place the two finalists prominently center stage. Move the Jury avatars to a visible "Jury Box". > - **Speech Display:** As each finalist gives their speech (`subround: 900`), display it clearly next to their avatar, perhaps like a closing statement. > - **Jury Deliberation:** > - As each `private_jury_reason` appears, briefly highlight the juror and show their reasoning (maybe visible only on hover/click to avoid clutter). > - Show jury votes accumulating for each finalist, similar to the elimination voting tally, but perhaps with a different visual style (e.g., gold stars). > - **Engaging Element:** Build suspense by revealing jury votes one by one or after a short delay. > > ### 7. Final Results > > - **Winner Announcement:** A clear "Winner" banner or crown appears over the winning avatar. > - **Rank Display:** Show the final ranks clearly, perhaps arranging avatars on a podium or listing them with their rank and partial points. > - **Game Summary:** Offer a summary view showing key stats or moments from the game. > > ### Interactivity (Clicking on Models) > > - **Player Dossier:** Clicking any avatar (active or jury) should open a panel or overlay showing: > - Player ID & Model Type. > - Their full message history (public and private, filterable by round/type). > - Their voting history (who they voted for, who voted for them). > - Their alliance history (proposals made/received, final pairs). > - Their final rank/status. > - **Engaging Element:** Use this panel to show hidden information like `private_vote_reason` after the vote has occurred. > > Draw the user interface for this EXACTLY as it would appear on the screen. Here's the prototype it created. ![Prototype](https://github.com/sanand0/eliminationgame/raw/main/img/chatgpt-prototype.webp) Based on this, I drew out my own, revised, visual: ![Design Sketch](https://github.com/sanand0/eliminationgame/raw/main/img/design-sketch.webp) Learnings: - πŸ’‘ **LLMs can create visual prototypes**. ChatGPT's new 4o image generation converted the description into an **acceptable** image. Needs to improve, but enough to ideate. - πŸ’‘ **Improving is less work than creating**. I rarely sketch visualizations. (Too lazy.) But since this prototype was **there**, and had some parts that were \***\*WRONG\*\***, I just **had** to fix it! πŸ™‚ ## Break down the task I then described the application to O1 Pro break down this task. [Private chat](https://chatgpt.com/c/67f4baea-56c0-800c-bf8e-ab6bc5c6db6a) > The URL looks like /#?game=286&line=4 indicating that game 286.jsonl must be loaded and line 4 is the current step we're in. > > The navbar has: > > - An app title > - A game state dashboard with the game number (dropdown), round (number), stage (e.g. voting, alliances, etc.), players (number of players still active) > - A timeline scrubber (a range slider) allowing users to jump to the specific line. This changes the URL which then triggers a change in app state. > - A light/dark theme picker > > The stage on the left as a set of models arranged in a circle. Each model/player has a unique and distinct color, a number inside it indicating the player number, a label above it indicating the model name (e.g. GPT-4o), a set of red dots below it when it receives an elimination vote, an arrow pointing from one model to another when a model talks to, plans an alliance with, or votes to eliminate another model. The arrow color depends on the action. When a model performs any action (speaking publicly / privately, voting, allying, etc.) the conversation or action summary is displayed in a semi-transparent overlay at the center of the circle. Eliminated models are shown with a 10% opacity. Allied models are redrawn next to each other with a line connecting them. > > The sidebar on the right has 3 collapsible sections. > > 1. CHAT: Shows the history of each model's public or private conversations. Each conversation begins with a circle with the model/player number in the same color as on the stage, followed by the model name. For private conversations, this is followed by an arrow pointing to the target model/player's numbered & colored circle. This is followed by the text of the conversation. Private conversations are in a light red background. > 2. ALLIANCES. This is a tabular representation where columns are models that form an alliance. Each row represents a round. The cells contain the model the model in the column header allied with. All models are numbered and colored. If a model in the column header was eliminated, the cell is empty. If it did not ally, the cell shows an empty grey circle. If 3+ consecutive cells show the same model in a column (i.e. the model did not switch alliances), we can replace the middle cells with a vertical line. > 3. ELIMINATIONS. This tabular representation works exactly the same way as alliances, except it captures eliminations. Each column represents a model that voted for elimination. Each row represents a round. The cell contains the model the model in the column header voted to eliminate. All models are numbered and colored. If a model in the column header was eliminated, the cell is empty. If 3+ consecutive cells show the same model in a column (i.e. a model consistently voted against the same model), we can replace the middle cells with a vertical line. > > I'd like to use an AI code generator to generate this visualization. Giving the entire spec at once is too much for it to build. I'd like to see the output step by step as well and propose changes. How might I break up this task? Here's its (rather good) breakdown: 1. Plan Your Data Structures 2. Basic Backend/Server Setup (If Needed) 3. Minimal Front-End with a Single View 4. Implement the Timeline Scrubber & URL Sync 5. Core Stage Visualization (Circle of Players) 6. Arrow/Line Visualization for Actions 7. Sidebar 1: CHAT Section 8. Sidebar 2: ALLIANCES Section 9. Sidebar 3: ELIMINATIONS Section 10. Refinements, Theming, & Final Touches Learnings: - πŸ’‘ **Ask LLMs to break down your task**. Its breakdown was better than mine. To document my workflow, I decided to commit each stage of progress. At this point, I made the first commit to the [repo](https://github.com/sanand0/eliminationgame/) documenting the process so far. [πŸ”— Commit](https://github.com/sanand0/eliminationgame/commit/05a9aab) ## Minimal Front-End with a Single View I skipped Step 1 (my mistake - I was [forced to do it](#plan-your-data-structures) later) and didn't need Step 2 (Backend/Server). So I began scaffolding, i.e. Step 3: Minimal Front-End with a Single View. At this point, I switched over to [GitHub Copilot](https://github.com/copilot) in [Edit mode](https://code.visualstudio.com/docs/copilot/chat/copilot-edits) using [Claude 3.5 Sonnet](https://code.visualstudio.com/docs/copilot/language-models). This is what I used for the rest of the session. I ran this prompt: > Create an index.html using Bootstrap via CDN. Scaffold it with a navbar > > The navbar has: > > - An app title (Elimination Game) > - A game state dashboard with the Game (dropdown), Round (number), Stage (e.g. voting, alliances, etc.), and Players (number of players still active) > - A timeline scrubber (a range slider) allowing users to jump to the specific line. This changes the URL which then triggers a change in app state. > - A light/dark theme picker. Here is the code for the theme picker. Use the same CDN links overall > > ```html > > > > > > > ``` > > Below the navbar is a section with a stage on the left and sidebar on the right. The stageon the left will contain a large responsive square SVG. The sidebar on the right contains 3 collapsible cards: Chat, Alliances, Eliminations. It generated this scaffolding. ![Scaffolding 1](https://github.com/sanand0/eliminationgame/raw/main/img/scaffolding-1.webp) Learnings: - πŸ’‘ **Claude 3.5 Sonnet remains an **excellent** model to generate UI**. Claude 3.7 Sonnet is even better, but is not currently available in the free Copilot subscription. - πŸ’‘ **Coders micro-manage LLMs**. I think a novice will be more efficient and get better results than me. For example: - Did I **need** to give it the code snippet? Could I have given it a link? - Did I **need** to say "a range slider" or specify that Round must be a "number", etc? Could it have inferred? [πŸ”— Commit](https://github.com/sanand0/eliminationgame/commit/50a377b) ## Improve the scaffolding I gave some feedback on the scaffolding and asked for improvements. > - Make the navbar always dark > - The sidebar cards must be independently collapsible > - For the Game, Round, Stage, and Players, show the label above the value. The label must be small and the value must be large. > - Use only me-\* margins on the navbar to ensure that there is no left margin mis-aligning the elements at low width. Also place the elements inside a collapsible navbar section at low widths > - The stage must have a bottom margin to avoid touching the sidebar's top on low-width screens This was the result: ![Scaffolding 2](https://github.com/sanand0/eliminationgame/raw/main/img/scaffolding-2.webp) That prompted more feedback from me: > - Prefer Bootstrap classes over `