Skip to content

Commit

Permalink
v1.0.0 add goal markers, and goal vectors, update README (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinShetty authored Feb 7, 2025
1 parent 3b3344d commit dbaf163
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 94 deletions.
75 changes: 58 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
# `JustinShetty/mapf-visualizer`

[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
[![ci](https://github.com/JustinShetty/mapf-visualizer/actions/workflows/ci.yml/badge.svg)](https://github.com/JustinShetty/mapf-visualizer/actions/workflows/ci.yml)

This repository hosts the web-based version of the MAPF (Multi-Agent Pathfinding) Visualizer, adapted from the original [Kei18/mapf-visualizer](https://github.com/Kei18/mapf-visualizer). The app provides an interactive and intuitive way to visualize MAPF solutions directly in your browser.

## Feature Roadmap
This is a work-in-progress and has not yet reached feature parity with [Kei18/mapf-visualizer](https://github.com/Kei18/mapf-visualizer).
- [x] Map plotting
- [x] Basic solution animation (with and without rotation)
- [x] Playback control (play, pause, reset, speed adjustment, etc.)
- [x] Timestep display
- [x] Playback control tooltips
- [x] Agent ID display
- [x] Agent coloring
- [x] Per-agent path drawing
- [x] Keybindings for animation control
- [x] Built-in screenshot capture with keybind
- [ ] Show goal location
- [ ] Render line between agent current location and goal
This project runs entirely client-side and built using [React](https://reactjs.org/) and [PixiJS](https://pixijs.com/).

## Features

- **Browser-Based Interface**: No installation required, simply access the app through the GitHub Pages site.
- **Customizable Input**: Upload your own MAPF maps and solutions.
- **Real-Time Visualization**: Observe agent movements step-by-step.

## Demo
![demo](./assets/demo.gif)

## Usage

1. **Upload a Map File**: Load your MAPF map file (.txt format).
2. **Upload a Solution File**: Load the corresponding solution file (.txt format).
3. **Visualize**: The solution will automatically play
4. **Controls**:
- **Play/Pause**: Start or pause the visualization.
- **Step Forward/Backward**: Navigate the simulation frame-by-frame.
- **Speed Adjustment**: Change the playback speed.
- **Play/Pause**: Start or pause the visualization.
- **Step Forward/Backward**: Navigate the simulation frame-by-frame.
- **Step Size Adjustment**: Change the playback speed.
- **Restart Animation**: Restart the visualization from the beginning.
- **Toggle Animation**: Enable or disable the animation.
- **Reset View**: Reset the visualization to the initial view.
- **Take a Screenshot**: Capture the current state of the visualization as an image.
- **Toggle Agent IDs**: Show or hide the IDs of the agents.
- **Toggle Cell IDs**: Show or hide the IDs of the cells.
- **Toggle Traveled Paths**: Show or hide the paths that agents have traveled.
- **Toggle Goal Markers**: Show or hide the goal markers for the agents.
- **Toggle Goal Vectors**: Show or hide the vectors pointing to the agents' goals.


## File Format

Expand Down Expand Up @@ -82,3 +85,41 @@ Special thanks to [Kei18](https://github.com/Kei18) for creating the original MA
## Contact

For questions or support, feel free to open an issue.

## Contributing
If you wish to contribute, please open a pull request and I'll review the changes as soon as practical.

<details>
<summary>Development Instructions</summary>

### Running the Development Server

To run the development server locally, follow these steps:

1. **Clone the Repository**:
```sh
git clone https://github.com/JustinShetty/mapf-visualizer.git
cd mapf-visualizer
```

2. **Install Dependencies**:
```sh
npm install
```

3. **Start the Development Server**:
```sh
npm run dev
```

### Linting the Codebase

To maintain code quality, lint the codebase using the following commands:

1. **Run Linter**:
```sh
npm run lint
```

</details>

Binary file added assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mapf-visualizer",
"private": true,
"version": "0.0.0",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
51 changes: 42 additions & 9 deletions src/AnimationControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import ScreenshotMonitorOutlinedIcon from '@mui/icons-material/ScreenshotMonitor
import StartIcon from '@mui/icons-material/Start';
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
import SmartToyIcon from '@mui/icons-material/SmartToy';
import FlagIcon from '@mui/icons-material/Flag';
import OutlinedFlagIcon from '@mui/icons-material/OutlinedFlag';
import PolylineIcon from '@mui/icons-material/Polyline';
import PolylineOutlinedIcon from '@mui/icons-material/PolylineOutlined';

const STEP_SIZE_INCREMENT = 0.2;
const STEP_SIZE_MAX = 10;
Expand All @@ -38,6 +42,8 @@ const STEP_SIZE_DOWN_KEY = 'ArrowDown';
const TRACE_PATHS_KEY = 'p';
const SCREENSHOT_KEY = 's';
const SHOW_CELL_ID_KEY = 'c';
const SHOW_GOALS_KEY = 'g';
const SHOW_GOAL_VECTORS_KEY = 'v';

interface AnimationControlProps {
playAnimation: boolean;
Expand All @@ -58,6 +64,10 @@ interface AnimationControlProps {
takeScreenshot: () => void;
showCellId: boolean;
setShowCellId: (showCellId: boolean) => void;
showGoals: boolean;
setShowGoals: (showGoals: boolean) => void;
showGoalVectors: boolean;
setShowGoalVectors: (showGoalVectors: boolean) => void;
}

function AnimationControl({
Expand All @@ -79,6 +89,10 @@ function AnimationControl({
takeScreenshot,
showCellId,
setShowCellId,
showGoals,
setShowGoals,
showGoalVectors,
setShowGoalVectors,
}: AnimationControlProps) {
const handleSliderChange = (event: Event, value: number | number[]) => {
event.preventDefault();
Expand Down Expand Up @@ -117,6 +131,10 @@ function AnimationControl({
takeScreenshot();
} else if (event.key === SHOW_CELL_ID_KEY) {
setShowCellId(!showCellId);
} else if (event.key === SHOW_GOALS_KEY) {
setShowGoals(!showGoals);
} else if (event.key === SHOW_GOAL_VECTORS_KEY) {
setShowGoalVectors(!showGoalVectors);
}
};
window.addEventListener('keydown', handleKeyDown);
Expand All @@ -126,10 +144,11 @@ function AnimationControl({
}, [playAnimation, onPlayAnimationChange, loopAnimation, onFitView,
onLoopAnimationChange, onRestart, onShowAgentIdChange, onSkipBackward,
onSkipForward, onStepSizeChange, showAgentId, stepSize, onTracePathsChange, tracePaths,
takeScreenshot, showCellId, setShowCellId]);
takeScreenshot, showCellId, setShowCellId, showGoals, setShowGoals, showGoalVectors,
setShowGoalVectors]);

return (
<Stack direction="column" spacing={2}>
<Stack direction="column" spacing={1}>
<Stack direction="row" spacing={2} justifyContent="center">
<Tooltip
title={
Expand All @@ -147,7 +166,7 @@ function AnimationControl({
max={STEP_SIZE_MAX}
valueLabelDisplay="auto"
onChange={handleSliderChange}
sx={{ width: '50%', height: "auto"}}
sx={{ width: '40%', height: "auto"}}
/>
</Tooltip>
<Tooltip title="Reset step size">
Expand Down Expand Up @@ -196,6 +215,13 @@ function AnimationControl({
<FilterCenterFocusOutlinedIcon />
</Button>
</Tooltip>
<Tooltip title={"Take screenshot" + ` (${SCREENSHOT_KEY})`}>
<span>
<Button disabled={!canScreenshot} onClick={takeScreenshot}>
<ScreenshotMonitorOutlinedIcon />
</Button>
</span>
</Tooltip>
</ButtonGroup>
</Box>
<Box display="flex" justifyContent="center">
Expand All @@ -221,12 +247,19 @@ function AnimationControl({
<DirectionsOutlinedIcon />}
</Button>
</Tooltip>
<Tooltip title={"Take screenshot" + ` (${SCREENSHOT_KEY})`}>
<span>
<Button disabled={!canScreenshot} onClick={takeScreenshot}>
<ScreenshotMonitorOutlinedIcon />
</Button>
</span>
<Tooltip title={(showGoals ? "Hide goals" : "Show goals") + ` (${SHOW_GOALS_KEY})`}>
<Button onClick={() => setShowGoals(!showGoals)}>
{showGoals ?
<FlagIcon />:
<OutlinedFlagIcon />}
</Button>
</Tooltip>
<Tooltip title={(showGoalVectors ? "Hide goal vectors" : "Show goal vectors") + ` (${SHOW_GOAL_VECTORS_KEY})`}>
<Button onClick={() => setShowGoalVectors(!showGoalVectors)}>
{showGoalVectors ?
<PolylineIcon />:
<PolylineOutlinedIcon />}
</Button>
</Tooltip>
</ButtonGroup>
</Box>
Expand Down
16 changes: 12 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ function App() {
const [tracePaths, setTracePaths] = React.useState<boolean>(true);
const [canScreenshot, setCanScreenshot] = React.useState<boolean>(true);
const [showCellId, setShowCellId] = React.useState<boolean>(false);
const [showGoals, setShowGoals] = React.useState<boolean>(true);
const [showGoalVectors, setShowGoalVectors] = React.useState<boolean>(false);

const handleSkipBackward = () => {
if (pixiAppRef.current?.skipBackward) {
Expand Down Expand Up @@ -72,6 +74,8 @@ function App() {
tracePaths={tracePaths}
setCanScreenshot={setCanScreenshot}
showCellId={showCellId}
showGoals={showGoals}
showGoalVectors={showGoalVectors}
/>
</Grid>
<Grid size={4}>
Expand All @@ -80,23 +84,27 @@ function App() {
onGraphChange={useCallback((graph: Graph | null) => setGraph(graph), [])}
onSolutionChange={useCallback((solution: Solution | null) => setSolution(solution), [])}
playAnimation={playAnimation}
onPlayAnimationChange={(playAnimation: boolean) => setPlayAnimation(playAnimation)}
onPlayAnimationChange={setPlayAnimation}
onSkipBackward={handleSkipBackward}
onSkipForward={handleSkipForward}
onRestart={handleRestart}
stepSize={stepSize}
onStepSizeChange={setStepSize}
loopAnimation={loopAnimation}
onLoopAnimationChange={(loopAnimation: boolean) => setLoopAnimation(loopAnimation)}
onLoopAnimationChange={setLoopAnimation}
onFitView={handleFitView}
showAgentId={showAgentId}
onShowAgentIdChange={(showAgentId: boolean) => setShowAgentId(showAgentId)}
onShowAgentIdChange={setShowAgentId}
tracePaths={tracePaths}
onTracePathsChange={(tracePaths: boolean) => setTracePaths(tracePaths)}
onTracePathsChange={setTracePaths}
canScreenshot={canScreenshot}
takeScreenshot={handleTakeScreenshot}
showCellId={showCellId}
setShowCellId={setShowCellId}
showGoals={showGoals}
setShowGoals={setShowGoals}
showGoalVectors={showGoalVectors}
setShowGoalVectors={setShowGoalVectors}
/>
</Grid>
</Grid>
Expand Down
16 changes: 14 additions & 2 deletions src/ConfigBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ interface ConfigBarProps {
takeScreenshot: () => void;
showCellId: boolean;
setShowCellId: (showCellId: boolean) => void;
showGoals: boolean;
setShowGoals: (showGoals: boolean) => void;
showGoalVectors: boolean;
setShowGoalVectors: (showGoalVectors: boolean) => void;
}

function ConfigBar({
Expand All @@ -54,6 +58,10 @@ function ConfigBar({
takeScreenshot,
showCellId,
setShowCellId,
showGoals,
setShowGoals,
showGoalVectors,
setShowGoalVectors,
}: ConfigBarProps) {
const repoName = "JustinShetty/mapf-visualizer";
const [mapFile, setMapFile] = React.useState<File | null>(null);
Expand Down Expand Up @@ -146,7 +154,7 @@ function ConfigBar({
}

return (
<Stack direction="column" spacing={2} sx={{padding: 2}} >
<Stack direction="column" spacing={1} sx={{padding: 2}} >
<Stack direction="column" spacing={1}>
<Button variant="outlined" onClick={() => handleLoadDemo("2x2")}>Load 2x2 demo</Button>
<Button variant="outlined" onClick={() => handleLoadDemo("random-32-32-20")}>Load 32x32 demo</Button>
Expand Down Expand Up @@ -219,8 +227,12 @@ function ConfigBar({
takeScreenshot={takeScreenshot}
showCellId={showCellId}
setShowCellId={setShowCellId}
showGoals={showGoals}
setShowGoals={setShowGoals}
showGoalVectors={showGoalVectors}
setShowGoalVectors={setShowGoalVectors}
/>
<Divider />
<Divider />
<a target="_blank" href={`https://github.com/${repoName}`} style={{ color: 'white', width: 'fit-content' }}>
{repoName}
</a>
Expand Down
Loading

0 comments on commit dbaf163

Please sign in to comment.