How Cycle (Sprint) Management and Burn-Down Charts Work in Plane
Plane manages agile sprints through a MobX-powered CycleStore that retrieves time-boxed iteration data, normalizes mixed-version progress formats into unified chart structures, and renders interactive burn-down or burn-up visualizations comparing actual velocity against ideal completion lines.
Plane (makeplane/plane) implements cycle management as a core agile project management feature, enabling teams to track sprint progress through sophisticated burn-down analytics. The architecture separates data fetching, state normalization, and visualization concerns across dedicated services and utility layers. This design ensures real-time chart updates while supporting both issue-count and estimate-point based progress tracking.
Cycle State Management and Data Structure
In Plane, a cycle represents a time-boxed sprint container backed by ICycle objects stored in the backend. The frontend centralizes all cycle state within the CycleStore (apps/web/core/store/cycle.store.ts), a MobX store that maintains two critical data structures: cycleMap for entity storage and fetchedMap for tracking API request status. This caching strategy prevents redundant network calls and ensures instantaneous UI reactions when users switch between project views.
The store exposes specific fetch methods—fetchWorkspaceCycles, fetchAllCycles, and fetchActiveCycle—that orchestrate data retrieval through the CycleService (apps/web/core/services/cycle.service.ts). These methods populate the store while managing loading states and error handling across the application.
Normalizing Legacy and Modern Progress Formats
Plane's evolution introduced two distinct progress data schemas that the system normalizes for consistent chart rendering. Version 1 cycles provide distribution or estimate_distribution objects mapping dates to completion counts, while Version 2 cycles return a progress array containing daily total aggregates.
The utility function formatActiveCycle in packages/utils/src/cycle.ts handles this normalization, converting both schemas into a unified TProgressChartData structure. This function accepts configuration flags including isBurnDown (to determine whether actual represents pending or completed work) and isTypeIssue (to toggle between issue counts and estimate points). The normalization ensures that regardless of backend data age, the visualization components receive a consistent interface.
Calculating Ideal Velocity Lines
Beyond raw data formatting, the system calculates theoretical sprint velocity through an ideal function implemented in packages/utils/src/cycle.ts. This function computes the expected completion rate based on the total scope and the fraction of the sprint duration elapsed, generating a dotted reference line that appears alongside actual progress in charts.
For burn-down visualizations, the function returns actual = pending to show remaining work decreasing over time. For burn-up modes, it returns actual = completed to visualize accumulation toward scope completion. The CycleStore tracks user preferences for these display modes through getPlotTypeByCycleId, which defaults to "burndown" but supports toggling to "burnup" per cycle.
Rendering Interactive Charts
The ProgressChart component (apps/web/core/components/core/sidebar/progress-chart.tsx) consumes the normalized distribution data and renders the final visualization using an AreaChart from @plane/propel (packages/propel/src/charts/area-chart/root.tsx). This component accepts a distribution object mapping dates to work units, a totalIssues count for scale reference, and a plotTitle for labeling.
The resulting visualization displays dates on the X-axis and work units on the Y-axis, plotting both the calculated ideal line and the current actual progress as filled areas. Users can switch between burn-down and burn-up perspectives without refetching data, as the store's plotType state triggers local recalculation through formatActiveCycle.
Filtering and Numeric Progress Calculations
Plane supplements visual charts with numeric progress indicators used in status badges and summary tiles. The calculateCycleProgress function in packages/utils/src/cycle.ts computes completion percentages by comparing completed items against total scope, explicitly excluding cancelled issues from the calculation.
For list views, the cycleFilter store supplies criteria such as status and date ranges, while helper functions orderCycles and shouldFilterCycle (also in packages/utils/src/cycle.ts) apply these filters before rendering. This ensures that cycle management interfaces remain performant even when displaying large historical sprint datasets.
Integrating Cycle Charts in Custom Components
Developers can embed sprint analytics into custom views by combining the CycleStore with normalization utilities. The following pattern demonstrates retrieving the active cycle and preparing burn-down data:
import { observer } from "mobx-react-lite";
import { useRootStore } from "@/hooks/use-root-store";
import { formatActiveCycle } from "@plane/utils";
import ProgressChart from "@/components/core/sidebar/progress-chart";
const SprintBurnDown = observer(() => {
const { cycleStore } = useRootStore();
const activeCycle = cycleStore.currentProjectActiveCycle;
if (!activeCycle) return <div>No active sprint</div>;
const chartData = formatActiveCycle({
cycle: activeCycle,
isBurnDown: true,
isTypeIssue: true,
});
const distribution = Object.fromEntries(
chartData.map(d => [d.date, d.actual ?? 0])
);
return (
<ProgressChart
distribution={distribution}
totalIssues={activeCycle.total_issues ?? 0}
plotTitle="issues"
/>
);
});
This implementation leverages the store's cached data to avoid API calls, uses formatActiveCycle to handle version compatibility, and delegates rendering to the shared ProgressChart component.
Summary
- CycleStore (
apps/web/core/store/cycle.store.ts) centralizes sprint state using MobX, caching cycles incycleMapand tracking fetch status infetchedMapto optimize performance. - CycleService (
apps/web/core/services/cycle.service.ts) provides the API layer for retrieving workspace and project cycles via methods likefetchActiveCycle. - Data normalization occurs in
packages/utils/src/cycle.ts, whereformatActiveCycleunifies Version 1 and Version 2 progress formats intoTProgressChartData. - Ideal velocity calculation uses the total scope and elapsed time fraction to generate reference lines for comparison against actual progress.
- Visualization relies on
ProgressChartand the PropelAreaChartcomponent to render interactive burn-down or burn-up views based on the store'splotTypesetting. - Progress metrics such as completion percentages are calculated by
calculateCycleProgress, excluding cancelled items for accurate sprint health indicators.
Frequently Asked Questions
How does Plane handle different progress data formats in older versus newer cycles?
Plane supports two backend schemas through the formatActiveCycle utility in packages/utils/src/cycle.ts. Legacy cycles provide distribution objects while modern cycles use a progress array; the normalizer converts both into a consistent TProgressChartData structure that charts consume uniformly.
What determines whether Plane displays a burn-down or burn-up chart?
The CycleStore maintains a per-cycle plotType state accessed via getPlotTypeByCycleId, defaulting to "burndown". When users toggle views, the flag updates and formatActiveCycle adjusts its return values—returning pending counts for burn-down and completed counts for burn-up—without requiring additional API requests.
How does Plane calculate the ideal line shown in sprint charts?
The ideal function in packages/utils/src/cycle.ts calculates the expected completion rate by dividing the total sprint scope by the time elapsed since the cycle start date. This generates a linear projection that appears as a dotted reference line alongside actual progress in the AreaChart visualization.
Where does Plane store filter settings for cycle lists?
Filter criteria are managed by the cycleFilter store and applied through helper functions orderCycles and shouldFilterCycle located in packages/utils/src/cycle.ts. These utilities process user-selected filters—such as status or date ranges—before cycles render in list views, ensuring consistent sorting and visibility across the application.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →