# How Plane Calculates Analytics and Burndown Charts: Server-Side Architecture Explained

> Discover how Plane calculates analytics and burndown charts. Explore the server-side architecture and Python utilities that aggregate issue data and track remaining work over time.

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

---

**Plane generates analytics and burndown charts using two core Python utilities in [`apps/api/plane/utils/analytics_plot.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/analytics_plot.py) that aggregate issue data by dimensions and calculate remaining work over time, returning dictionaries consumed by MobX stores in the React frontend.**

The open-source project management platform Plane (makeplane/plane) handles its analytics visualizations through a centralized server-side calculation layer. These **analytics and burndown chart calculations** transform raw issue data into plot-ready structures using Django ORM aggregations, enabling both count-based and point-based progress tracking across Cycles and Modules.

## Core Aggregation Utilities

Plane’s analytics engine relies on two primary helper functions that process QuerySets and return serialized data structures for chart rendering.

### Dimension-Based Graphs via `build_graph_plot`

The `build_graph_plot` function generates bar charts and line graphs by grouping issues across configurable dimensions. It validates inputs against **`VALID_ANALYTICS_FIELDS`** for the x-axis (accepting values like `state_id`, `labels__id`, or `created_at`) and **`VALID_YAXIS`** for the y-axis (either `issue_count` or `estimate`).

The function normalizes date fields into string dimensions using **`annotate_with_monthly_dimension`**, creating `"YYYY-MM"` tags for temporal grouping. When a **segment** parameter is provided (such as `priority`), the data splits further into sub-categories. The aggregation layer then applies either `Count("*")` for issue tallies or `Sum(Cast(...))` for estimate point totals, grouping results by dimension and applying priority-aware sorting. The output follows a nested dictionary structure: `{dimension: [{segment?, count|estimate}, …]}`.

```python
from plane.utils.analytics_plot import build_graph_plot

distribution = build_graph_plot(
    queryset=my_queryset,
    x_axis="created_at",          # month-grouped dimension

    y_axis="issue_count",
    segment="priority",           # optional split

)

```

### Burndown Calculations via `burndown_plot`

The `burndown_plot` function computes remaining work over the lifespan of a Cycle or Module. It first inspects **`Project.estimate.type`** to determine whether to use point-based or issue-count calculations, fetching `total_estimate_points` when `"points"` is configured; otherwise it defaults to `total_issues`.

The function constructs a date range from the entity’s `start_date` to `end_date` (or `target_date` for Modules). For each day, it queries completed items using **`TruncDate("completed_at")`** annotations—fetching point values when `plot_type == "points"` or counts otherwise. A running total tracks **`cumulative_pending_issues`**, subtracting daily completions from the initial total. Future dates beyond `timezone.now()` receive `null` values to terminate the chart at the current date. The result maps dates to remaining work: `{ "YYYY-MM-DD": remaining_work_or_None, … }`, stored in the `completion_chart` field.

```python
from plane.utils.analytics_plot import burndown_plot

chart = burndown_plot(
    queryset=my_cycle,            # Cycle or Module instance

    slug="my-workspace",
    project_id=42,
    plot_type="points",           # or "issues"

    cycle_id="c123",
)

```

## API Layer Integration Points

These utilities are invoked across multiple view modules to serve different analytics contexts:

- **Module analytics**: Called in [`apps/api/plane/app/views/module/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/module/base.py) (line 530) and [`archive.py`](https://github.com/makeplane/plane/blob/main/archive.py) (line 423), returning both `distribution` for bar charts and `completion_chart` for burndown visualization.
- **Cycle analytics**: Implemented in [`apps/api/plane/app/views/cycle/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/cycle/base.py) (line 932) and [`archive.py`](https://github.com/makeplane/plane/blob/main/archive.py) (line 462), providing the same data structures scoped to Cycle boundaries.
- **Project-wide analytics**: Utilized in [`apps/api/plane/app/views/analytic/base.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/analytic/base.py) (lines 68 and 214) for arbitrary dimension grouping across entire projects.
- **Export jobs**: Background tasks in [`apps/api/plane/bgtasks/analytic_plot_export.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/bgtasks/analytic_plot_export.py) (line 359) serialize these dictionaries for CSV/Excel generation.

## Frontend Data Consumption

The React frontend consumes these pre-calculated structures through MobX state management. The stores **[`apps/web/core/store/module.store.ts`](https://github.com/makeplane/plane/blob/main/apps/web/core/store/module.store.ts)** and **[`cycle.store.ts`](https://github.com/makeplane/plane/blob/main/cycle.store.ts)** expose a `plotType` property (defaulting to `"burndown"`) and maintain chart data in the `completion_chart` observable.

UI components such as **[`apps/web/core/components/modules/analytics-sidebar/issue-progress.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/modules/analytics-sidebar/issue-progress.tsx)** and the corresponding cycles component render the `completion_chart` dictionaries as line charts without additional client-side calculation.

```tsx
// Rendering a burndown chart for a cycle in the UI
import { observer } from "mobx-react";
import { useStore } from "hooks/use-store";

const CycleBurndown = observer(({ cycleId }: { cycleId: string }) => {
  const { cycleStore } = useStore();
  const cycle = cycleStore.cycleMap[cycleId];
  const chart = cycle?.estimate_distribution?.completion_chart ?? {};

  return (
    <LineChart
      data={Object.entries(chart).map(([date, value]) => ({
        date,
        remaining: value,
      }))}
    />
  );
});

```

## Practical Server-Side Example

To expose burndown data via a custom endpoint:

```python
from plane.utils.analytics_plot import burndown_plot
from plane.db.models import Cycle
from django.http import JsonResponse

def get_cycle_burndown(request, cycle_id):
    cycle = Cycle.objects.get(pk=cycle_id)
    chart = burndown_plot(
        queryset=cycle,
        slug=cycle.project.workspace.slug,
        project_id=cycle.project_id,
        plot_type="issues",
        cycle_id=cycle_id,
    )
    return JsonResponse(chart, safe=False)

```

## Summary

- **Plane** calculates analytics server-side in [`apps/api/plane/utils/analytics_plot.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/analytics_plot.py) using `build_graph_plot` for dimension aggregations and `burndown_plot` for time-series remaining work.
- **Validation constants** `VALID_ANALYTICS_FIELDS` and `VALID_YAXIS` enforce supported grouping and metric types, while `annotate_with_monthly_dimension` handles temporal normalization.
- **Burndown logic** detects point-based estimation via `Project.estimate.type`, iterates date ranges using `TruncDate`, and stores results in `completion_chart` fields.
- **API views** in Module, Cycle, and Analytic base modules invoke these utilities at specific line numbers (530, 932, 68, 214) to populate responses.
- **Frontend stores** in [`module.store.ts`](https://github.com/makeplane/plane/blob/main/module.store.ts) and [`cycle.store.ts`](https://github.com/makeplane/plane/blob/main/cycle.store.ts) consume these dictionaries directly, rendering them in [`issue-progress.tsx`](https://github.com/makeplane/plane/blob/main/issue-progress.tsx) components without transformation.

## Frequently Asked Questions

### How does Plane handle date grouping in analytics charts?

Plane uses the **`annotate_with_monthly_dimension`** helper to transform datetime fields into `"YYYY-MM"` string tags when `build_graph_plot` receives date-related x-axis parameters. This enables monthly aggregation bars while preserving the ability to sort chronologically.

### What is the difference between issue count and estimate points in burndown calculations?

When `plot_type="points"`, the system checks `Project.estimate.type` and aggregates the sum of estimate values using `Sum(Cast(...))`. For `plot_type="issues"` (or when points are disabled), it uses `Count("*")` on the issue QuerySet. The burndown logic subtracts daily completed points or counts from the initial `total_estimate_points` or `total_issues` respectively.

### Where does Plane store the calculated burndown chart data?

The `burndown_plot` function returns a dictionary mapping dates to remaining work values, which the API layer assigns to the **`completion_chart`** field on Cycle or Module model instances. This serialized structure persists in the database and transmits directly to the frontend MobX stores.

### How does the frontend determine whether to display points or issues in the chart?

The MobX stores expose a **`plotType`** observable (defaulting to `"burndown"`) that the UI components reference. When the user toggles between views, the store serves the appropriate pre-calculated data from `completion_chart`, ensuring the React components render the correct metric without recalculation.