# Plane Cycle (Sprint) Architecture and Burndown Chart Calculation

> Discover the Plane Cycle (Sprint) architecture, from React frontend to Django backend. Learn how burndown charts calculate progress by tracking completed issues against total scope daily.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: architecture
- Published: 2026-06-22

---

**Plane implements its Cycle feature through a three-layer architecture spanning React frontend components, MobX state management, and Django backend models, with burndown charts calculated by iterating daily through the sprint date range and subtracting cumulative completed issues or estimate points from the total scope.**

Plane is an open-source project management platform that organizes work into **Cycles**—sprint-like containers for issues. Understanding how the Cycle feature is architected and how burndown analytics are computed helps developers customize the platform or integrate its API. This analysis examines the complete stack from the React UI components in `apps/web/core/components/cycles` to the Python analytics utilities in [`apps/api/plane/utils/analytics_plot.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/analytics_plot.py) that generate sprint progress visualizations.

## Three-Layer Architecture Overview

Plane separates the Cycle feature into distinct frontend and backend layers. The **frontend** uses React with MobX for state management, while the **backend** relies on Django models and specialized analytics utilities to compute burndown data.

### Frontend UI Components

The user interface lives in `apps/web/core/components/cycles/` and renders cycle lists, detail pages, and analytics sidebars.

- [`cycles-view.tsx`](https://github.com/makeplane/plane/blob/main/cycles-view.tsx) serves as the main page component, pulling data from the MobX store.
- [`analytics-sidebar/issue-progress.tsx`](https://github.com/makeplane/plane/blob/main/analytics-sidebar/issue-progress.tsx) and [`progress-stats.tsx`](https://github.com/makeplane/plane/blob/main/progress-stats.tsx) display the burndown chart and related statistics.

### State Management and Services

The MobX store in [`apps/web/core/store/cycle.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/cycle.store.ts) maintains UI state including the selected cycle, filters, and plot type preferences.

```typescript
// apps/web/core/store/cycle.store.ts (excerpt)
@computed
getPlotTypeByCycleId = computedFn((cycleId: string) => this.plotType[cycleId] || "burndown");

```

The service layer wraps REST API calls. [`apps/web/core/services/cycle.service.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/services/cycle.service.ts) provides methods to fetch cycle data:

```typescript
// apps/web/core/services/cycle.service.ts (excerpt)
export class CycleService extends APIService {
  async list(slug: string, projectId: string) {
    return this.get(`/api/workspaces/${slug}/projects/${projectId}/cycles/`);
  }
}

```

### Backend Models and ViewSets

The Django model in [`apps/api/plane/db/models/cycle.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/cycle.py) defines the `Cycle` entity with standard fields (`name`, `start_date`, `end_date`) and a `progress_snapshot` JSON field that stores pre-computed analytics for fast retrieval.

The `CycleViewSet` in [`apps/api/plane/app/views/cycle/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/cycle/base.py) handles CRUD operations and provides enriched querysets with annotated counts:

```python

# apps/api/plane/app/views/cycle/base.py (excerpt)

.annotate(
    total_issues=Count(
        "issue_cycle__issue__id",
        distinct=True,
        filter=Q(
            issue_cycle__issue__archived_at__isnull=True,
            issue_cycle__issue__is_draft=False,
            issue_cycle__deleted_at__isnull=True,
            issue_cycle__issue__deleted_at__isnull=True,
        ),
    )
)

```

## Burndown Chart Calculation Logic

The burndown chart computation occurs server-side and is delivered through the `CycleAnalyticsEndpoint` at `GET /api/workspaces/:slug/projects/:projectId/cycles/:cycleId/analytics`.

### The Analytics Endpoint

This endpoint returns three data structures: assignee distribution, label distribution, and the **completion chart** (burndown). It invokes the `burndown_plot` function with a specified plot type:

```python

# apps/api/plane/app/views/cycle/base.py (excerpt)

completion_chart = burndown_plot(
    queryset=cycle,
    slug=slug,
    project_id=project_id,
    plot_type="issues",   # or "points"

    cycle_id=cycle_id,
)

```

### The Burndown Algorithm

The core logic resides in [`apps/api/plane/utils/analytics_plot.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/analytics_plot.py). The function builds a daily timeline of remaining work by subtracting completed items from the total scope.

The calculation follows these steps:

1. **Determine the date range** spanning from `start_date` to `end_date`.
2. **Collect completed work** grouped by day—either issue counts or sum of `estimate_point__value` fields.
3. **Initialize** the chart with the total scope (`total_issues` or total estimate points).
4. **Iterate chronologically** through each date:
   - Calculate cumulative completed work up to that date.
   - Subtract from the total scope to get remaining work.
   - Store `null` for future dates (rendered as gaps in the UI).

```python

# apps/api/plane/utils/analytics_plot.py (excerpt)

for date in date_range:
    cumulative_pending_issues = total_issues          # or total_estimate_points

    total_completed = sum(
        item["total_completed"] or float(item["estimate_point__value"])
        for item in completed_issues_distribution
        if item["date"] is not None and item["date"] <= date
    )
    cumulative_pending_issues -= total_completed
    chart_data[str(date)] = None if date > timezone.now().date() else cumulative_pending_issues

```

The resulting dictionary maps `YYYY-MM-DD` strings to remaining work values (or `null`), which the frontend renders as a line chart.

## Frontend Integration and Data Flow

To retrieve burndown data in a client application, use the `CycleAnalyticsService` to call the analytics endpoint:

```typescript
import { CycleAnalyticsService } from "@/services/cycle-analytics.service";

const analytics = new CycleAnalyticsService();
analytics.workspaceActiveCyclesAnalytics('my-workspace', 'project-123', 'cycle-456', 'issues')
  .then(data => {
    console.log('Assignees:', data.assignees);
    console.log('Labels:', data.labels);
    console.log('Burndown chart:', data.completion_chart);
  });

```

The `completion_chart` object can be fed directly to charting libraries. Here is an example using Recharts to render the sprint burndown:

```tsx
import { LineChart, Line, XAxis, YAxis, Tooltip } from "recharts";

function Burndown({ data }: { data: Record<string, number | null> }) {
  const chartData = Object.entries(data).map(([date, value]) => ({
    date,
    remaining: value,
  }));

  return (
    <LineChart data={chartData} width={500} height={300}>
      <XAxis dataKey="date" />
      <YAxis />
      <Tooltip />
      <Line type="monotone" dataKey="remaining" stroke="#ff6600" />
    </LineChart>
  );
}

```

## Summary

- Plane's **Cycle architecture** consists of three layers: React UI components (`apps/web/core/components/cycles`), MobX stores and services ([`cycle.store.ts`](https://github.com/makeplane/plane/blob/main/cycle.store.ts), [`cycle.service.ts`](https://github.com/makeplane/plane/blob/main/cycle.service.ts)), and Django backend models ([`cycle.py`](https://github.com/makeplane/plane/blob/main/cycle.py)) with analytics utilities.
- The **burndown calculation** happens in [`apps/api/plane/utils/analytics_plot.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/analytics_plot.py) via the `burndown_plot` function, which iterates through the sprint date range and subtracts cumulative completed work from the total scope.
- The algorithm supports both **issue count** and **estimate points** as metrics, controlled by the `plot_type` parameter.
- Future dates in the sprint return `null` values, creating natural gaps in the visualization until those dates occur.
- Pre-computed analytics are stored in the `progress_snapshot` JSON field on the `Cycle` model for optimized read performance.

## Frequently Asked Questions

### How does Plane store pre-computed cycle analytics?

Plane stores pre-computed analytics in a `progress_snapshot` JSON field defined in [`apps/api/plane/db/models/cycle.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/cycle.py). This field caches calculated metrics to avoid expensive aggregations on every read, while the `burndown_plot` function in [`apps/api/plane/utils/analytics_plot.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/analytics_plot.py) generates time-series data dynamically based on current issue states.

### What data sources feed the burndown chart calculation?

The burndown chart pulls from the `issue_cycle` relationships joined with the `issue` table. The calculation aggregates `completed_at` timestamps and optionally `estimate_point__value` fields from issues that are not archived, not drafts, and not soft-deleted, as filtered in the `CycleViewSet` annotations in [`apps/api/plane/app/views/cycle/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/cycle/base.py).

### Can burndown charts track estimate points instead of issue counts?

Yes. The `burndown_plot` function accepts a `plot_type` parameter that accepts either `"issues"` or `"points"`. When set to `"points"`, the algorithm sums the `estimate_point__value` field for completed issues each day instead of counting issue records, allowing teams to track sprint progress by effort rather than item count.

### How does the frontend handle future dates in burndown visualizations?

The backend returns `null` for any date beyond the current day (`timezone.now().date()`), and the frontend renders these as gaps in the line chart. This creates a clear visual distinction between historical actuals and projected future progress, preventing flat-line extensions into dates that have not yet occurred.