This guide is the architectural and operational reference for MPAndroidChart — for developers integrating it deeply, architects evaluating it, and contributors extending it. It covers the internal design, the rendering pipeline, the coordinate-transform system, extension points, performance characteristics, security considerations, and the contribution workflow.
For day-to-day usage (drawing each chart type, configuring axes, handling gestures), see the companion User Guide.
Table of Contents
- Executive Summary
- Architecture Overview
- Requirements & Build Configuration
- Repository Structure
- The Rendering Pipeline
- The Coordinate-Transform System
- Class Hierarchies (Module Reference)
- Extension Points
- Performance Engineering
- Security Analysis
- Testing Strategy
- Integration & Deployment
- Contributing
- Known Limitations & Improvement Opportunities
- Quick Reference
1. Executive Summary
MPAndroidChart is a self-contained Android charting library that renders charts directly onto a Canvas using the Android 2D graphics API. It ships nine chart types (line, bar, horizontal bar, pie, scatter, bubble, candlestick, radar, combined) as View subclasses that integrate into standard Android layouts.
| Attribute | Value |
|---|---|
| Library version | 3.1.0 |
| Group / artifact | com.github.philjay / MPAndroidChart (consumed via JitPack as com.github.PhilJay:MPAndroidChart:v3.1.0) |
| Language | Java |
| Min SDK / Target SDK | 14 / 28 |
| Compile SDK | 28 |
| Runtime dependencies | androidx.annotation:annotation only |
| Test dependencies | junit:junit:4.12 |
| License | Apache License 2.0 |
| Build system | Gradle (Android Gradle Plugin 7.0.4) |
| Distribution | JitPack (source-built AAR) |
Architectural essence: the library cleanly separates four concerns — data (what to draw), chart views (the public API and lifecycle), renderers (how to paint it), and coordinate transforms (where pixels go). This separation is what allows the same gesture, animation, and axis machinery to serve every chart type, and it is the seam along which the library is extended.
Note
There is no networking, persistence, reflection, or dynamic code loading anywhere in the library. It is a pure, offline rendering component. This sharply narrows its security and dependency surface (see Security Analysis).
2. Architecture Overview
The library is built around a Model–View–Renderer decomposition, with a dedicated coordinate-transform layer underneath.
┌─────────────────────────────────────────────┐
│ Chart (extends ViewGroup) │
YOUR DATA │ public API · lifecycle · onDraw() · touch │
┌──────────┐ setData │ │
│ ChartData │ ────────►│ ┌────────────┐ ┌──────────────────────┐ │
│ ▲ │ │ │ Animator │ │ ChartTouchListener │ │
│ │ holds │ │ │ (Easing) │ │ (drag/zoom/highlight)│ │
│ DataSet[] │ │ └────────────┘ └──────────────────────┘ │
│ ▲ │ │ │ onDraw() orchestrates: │
│ │ holds │ │ ▼ │
│ Entry[] │ │ ┌────────────┐ ┌────────────┐ ┌────────┐ │
└──────────┘ │ │DataRenderer│ │AxisRenderer│ │Legend │ │
│ └─────┬──────┘ └─────┬──────┘ │Renderer│ │
│ │ │ └────────┘ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Transformer + ViewPortHandler │ │
│ │ (value↔pixel matrices, zoom/pan) │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Android Canvas │
└─────────────────────────────────────────────┘
The four layers
| Layer | Responsibility | Key classes |
|---|---|---|
| Data model | Holds values and per-series styling; computes min/max. | ChartData, BaseDataSet/DataSet, Entry/BaseEntry |
| Chart views | Public API, Android View lifecycle, dispatching draw, handling touch. |
Chart, BarLineChartBase, PieRadarChartBase, the 9 concrete charts |
| Renderers | Paint data, axes, legend, grid, limit lines, markers onto the Canvas. |
DataRenderer (+ subclasses), AxisRenderer, LegendRenderer |
| Transform | Convert data values to screen pixels and back; manage zoom/pan state. | Transformer, ViewPortHandler |
Two parallel chart families
The chart hierarchy splits early into two families with fundamentally different geometry:
- Cartesian charts (
BarLineChartBase): line, bar, horizontal bar, scatter, bubble, candlestick, combined. They have X and dual Y axes, support drag/pinch-zoom, and useTransformermatrices. - Radial charts (
PieRadarChartBase): pie and radar. They have a center, a rotation angle, and angular/distance math instead of axis transforms.
This bifurcation is the single most important fact for understanding why some features (zoom, dual axes) exist only on some charts.
3. Requirements & Build Configuration
Library module (MPChartLib/build.gradle)
apply plugin: 'com.android.library'
group = 'com.github.philjay'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 3
versionName '3.1.0'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
testOptions {
unitTests.returnDefaultValues = true // allows JVM unit tests without mocking Android
}
}
dependencies {
implementation 'androidx.annotation:annotation:1.0.0'
testImplementation 'junit:junit:4.12'
}
The build also defines sourcesJar, javadoc, and javadocJar tasks, publishing sources and Javadoc alongside the AAR — which is how JitPack serves the -sources and -javadoc artifacts.
Project requirements (gradle.properties)
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048M
Important
The library is AndroidX-based. Consuming projects must have
android.useAndroidX=true. Theproguard-rules.profile ships empty — the library has no special keep-rules, because it uses no reflection. Standard R8/ProGuard shrinking is safe without custom configuration.
4. Repository Structure
MPAndroidChart/
├── MPChartLib/ # the published library module
│ └── src/main/java/com/github/mikephil/charting/
│ ├── charts/ (12 files) # public View classes + 2 base classes
│ ├── data/ (29 files) # ChartData, DataSet, Entry families
│ ├── components/ (11 files) # XAxis, YAxis, Legend, LimitLine, Description, MarkerView
│ ├── interfaces/ # data-provider + dataset interfaces (decoupling)
│ │ ├── dataprovider/ (8)
│ │ └── datasets/ (11)
│ ├── renderer/ (22 + 8) # all painting logic (+ scatter shape renderers)
│ ├── highlight/ (10 files) # touch-to-value hit testing
│ ├── formatter/ (11 files) # value & axis label formatting
│ ├── listener/ (7 files) # touch & gesture listeners
│ ├── animation/ (2 files) # ChartAnimator, Easing
│ ├── jobs/ (6 files) # pooled viewport-animation jobs
│ ├── buffer/ (3 files) # reusable draw buffers
│ ├── utils/ (13 files) # transforms, pools, color templates, geometry
│ ├── matrix/, model/, exception/
│ └── (test) src/test/... # 7 JUnit test classes
├── MPChartExample/ # demo app (not published) — 30+ example activities
├── design/, screenshots/ # marketing assets
├── README.md, CONTRIBUTING.md, LICENSE
└── build.gradle, settings.gradle, gradle/
The MPChartExample app is the de-facto integration test and living documentation: each activity (e.g. LineChartActivity1, PieChartActivity, CombinedChartActivity) demonstrates one chart type with sliders that mutate the data live.
5. The Rendering Pipeline
All drawing happens inside the chart's overridden onDraw(Canvas). The base Chart.onDraw() handles the empty-data placeholder and one-time offset calculation; each family then orchestrates its renderers in a fixed order.
Cartesian render order (BarLineChartBase.onDraw)
drawGridBackground()— optional grey backdrop + borders.- Auto-scale Y axis if
setAutoScaleMinMaxEnabled(true). - Compute axis intervals (
AxisRenderer.computeAxis) for X, left Y, right Y. - Render axis lines, then grid lines (order depends on
setDrawGridLinesBehindData). - Render limit lines behind data (if configured).
- Clip to content rect (unless disabled) and
mRenderer.drawData(canvas)— the actual series. - Draw highlighted values (
drawHighlighted) if anything is selected. drawExtras(), then limit lines on top, then axis labels.drawValues()— per-point value text.LegendRenderer.renderLegend(), then description, then markers.
Note
The library tracks per-frame draw time when
setLogEnabled(true)is set, logging average draw time and cycle count. This is the built-in profiling hook for diagnosing slow charts.
Renderer class family
Renderer (base, holds ViewPortHandler)
├── DataRenderer (abstract: drawData/drawValues/drawHighlighted/drawExtras + paints)
│ ├── LineRadarRenderer ──► LineChartRenderer, RadarChartRenderer
│ ├── BarLineScatterCandleBubbleRenderer
│ │ ├── BarChartRenderer ──► HorizontalBarChartRenderer
│ │ ├── ScatterChartRenderer, CandleStickChartRenderer, BubbleChartRenderer
│ ├── PieChartRenderer
│ └── CombinedChartRenderer (delegates to a list of sub-renderers)
├── AxisRenderer ──► XAxisRenderer, YAxisRenderer (+ HorizontalBarChart / RadarChart variants)
└── LegendRenderer
Scatter shapes are themselves pluggable: IShapeRenderer has implementations CircleShapeRenderer, SquareShapeRenderer, TriangleShapeRenderer, CrossShapeRenderer, XShapeRenderer, ChevronUpShapeRenderer, ChevronDownShapeRenderer. You can supply your own via ScatterDataSet.setShapeRenderer().
6. The Coordinate-Transform System
The most architecturally significant subsystem. Two cooperating classes convert between data space (your Entry x/y values) and pixel space (screen coordinates), and manage zoom/pan.
ViewPortHandler
Owns a Matrix (mMatrixTouch) representing the current zoom and translation, plus the content rectangle (the drawable area inside the axis margins). It enforces zoom/scroll limits.
| Concern | Methods |
|---|---|
| Dimensions | setChartDimens(), restrainViewPort() |
| Zoom | zoomIn/Out, zoom, setZoom, fitScreen, resetZoom |
| Limits | setMinimumScaleX/Y, setMaximumScaleX/Y, setMinMaxScaleX/Y |
| Translate | translate, centerViewPort, limitTransAndScale |
| Drag bounds | setDragOffsetX/Y |
Transformer
Holds two matrices — mMatrixValueToPx (data→pixels) and mMatrixOffset (applies the content offset) — combined with the ViewPortHandler's touch matrix. This is what chart.getPixelForValues() and chart.getValuesByTouchPoint() use.
data value (x, y)
│ mMatrixValueToPx (scale to content size, flip Y)
▼
│ ViewPortHandler.mMatrixTouch (apply user zoom + pan)
▼
│ mMatrixOffset (shift into the content rect)
▼
screen pixel
Cartesian charts keep two transformers — mLeftAxisTransformer and mRightAxisTransformer — so the left and right Y axes can have independent scales. HorizontalBarChart swaps in a TransformerHorizontalBarChart and HorizontalViewPortHandler to achieve the 90° rotation without changing any data code.
Tip
When debugging "my point is in the wrong place," the issue is almost always in the transform chain: check which
AxisDependencythe dataset uses, and whether the axis min/max were set manually (which fixes the value-to-pixel scale).
7. Class Hierarchies (Module Reference)
Chart views
View → ViewGroup → Chart<T extends ChartData> (implements ChartInterface)
├── BarLineChartBase<T> (implements BarLineScatterCandleBubbleDataProvider)
│ ├── LineChart, BarChart → HorizontalBarChart
│ ├── ScatterChart, BubbleChart, CandleStickChart
│ └── CombinedChart
└── PieRadarChartBase<T>
├── PieChart
└── RadarChart
Chart provides the cross-cutting API: setData, invalidate, highlighting, markers, animation, description/legend access, image export, and offset management. BarLineChartBase adds axes, zoom/pan, and viewport jobs. PieRadarChartBase adds rotation and angular geometry.
Data model
BaseEntry (y, data, icon)
└── Entry (adds x) → BarEntry, BubbleEntry, CandleEntry, PieEntry, RadarEntry
IDataSet → BaseDataSet → DataSet
└── LineScatterCandleRadarDataSet → LineRadarDataSet → LineDataSet / RadarDataSet
└── BarLineScatterCandleBubbleDataSet → BarDataSet, ScatterDataSet, CandleDataSet, BubbleDataSet
└── PieDataSet
ChartData → BarLineScatterCandleBubbleData → {Bar,Line,Scatter,Candle,Bubble}Data
└── PieData, RadarData, CombinedData
Note
Charts depend on interfaces (
ILineDataSet,LineDataProvider, etc.), not concrete classes. Thisinterfaces/dataprovider+interfaces/datasetsindirection is what letsCombinedChartreuse the line/bar/scatter renderers, and lets you substitute custom dataset implementations.
Supporting subsystems
| Subsystem | Classes | Purpose |
|---|---|---|
| Highlighting | ChartHighlighter + per-type (Bar, Pie, Radar, Combined, …), Highlight, Range |
Map a touch point to the nearest entry. |
| Touch | ChartTouchListener → BarLineChartTouchListener, PieRadarChartTouchListener |
Gesture state machines. |
| Animation | ChartAnimator, Easing (30+ EasingFunctions) |
Phase-based value animation. |
| Viewport jobs | ViewPortJob → MoveViewJob, ZoomJob, AnimatedMoveViewJob, AnimatedZoomJob |
Deferred/animated viewport changes, object-pooled. |
| Formatters | IValueFormatter/IAxisValueFormatter + LargeValueFormatter, PercentFormatter, IndexAxisValueFormatter, StackedValueFormatter |
Label text generation. |
| Utilities | ColorTemplate, Fill, MPPointF, MPPointD, ObjectPool, Utils, FSize, EntryXComparator |
Colors, pooled geometry, dp conversion. |
8. Extension Points
The library is designed to be extended without forking. The principal seams:
| To customize… | Override / supply | Set via |
|---|---|---|
| How a series is painted | A DataRenderer subclass |
chart.setRenderer(...) |
| Axis painting | XAxisRenderer / YAxisRenderer subclass |
chart.setXAxisRenderer(...), setRendererLeftYAxis(...) |
| Scatter point shape | IShapeRenderer implementation |
scatterDataSet.setShapeRenderer(...) |
| Value / axis label text | IValueFormatter / IAxisValueFormatter |
dataSet.setValueFormatter(...), axis.setValueFormatter(...) |
| Fill position under a line | IFillFormatter |
lineDataSet.setFillFormatter(...) |
| Hit-testing logic | IHighlighter implementation |
chart.setHighlighter(...) |
| Tooltip content/layout | MarkerView subclass (or raw IMarker) |
chart.setMarker(...) |
| A custom dataset backing store | implement I*DataSet |
pass to the matching *Data |
This interface-first design (every collaborator is referenced through an interface) is the architectural property that makes the library so extensible despite being a closed AAR.
9. Performance Engineering
Charting redraws the entire view every frame during gestures, so the library invests heavily in allocation avoidance.
- Reusable draw buffers (
buffer/AbstractBufferand subclasses): the bar/line renderers pre-fill a flatfloat[]ofx,y,x,y,…coordinates and replace rather than recreate it each frame. The class comment is explicit: "Concept: Replace instead of recreate … This needs to run FAST." - Object pooling (
utils/ObjectPool,MPPointF,MPPointD): geometry points are borrowed from pools (MPPointF.getInstance(...)) and returned (MPPointF.recycleInstance(...)), keeping per-frame allocation near zero to avoid GC pauses. - Viewport clipping: data is clipped to the content rect (
setClipDataToContent, default on) so off-screen geometry isn't painted. - Visible-range windowing:
setVisibleXRangeMaximum()+moveViewToX()let large datasets render only a slice. - Value-label suppression:
mMaxVisibleCount(default 100) automatically stops drawing value labels when too many entries are visible.
Performance trade-offs to document for operators
| Setting | Cost |
|---|---|
setDrawBarShadow(true) |
~50% slower bar rendering |
enableDashedLine(...) |
Requires hardware acceleration off (slower overall) |
TRIANGLE scatter shape |
Slowest shape; prefer CIRCLE/SQUARE |
| Drawing value labels | Per-point text layout each frame; disable for dense data |
setHardwareAccelerationEnabled(false) |
Needed for some effects, but disables GPU compositing |
Warning
All values are stored as 32-bit
float. For large magnitudes or high precision (currency, epoch-millis), precision is lost. Normalize/scale data into a friendly range and use a formatter to display real magnitudes. There is nodouble/BigDecimalpath.
10. Security Analysis
MPAndroidChart has an exceptionally small attack surface for an Android library.
| Concern | Finding |
|---|---|
| Network access | None. No HTTP, sockets, or URL usage in the library. |
| Reflection / dynamic loading | None. Hence the empty ProGuard rules — safe to shrink/obfuscate. |
| Permissions required | None for rendering. Only saveToGallery()/saveToPath() need WRITE_EXTERNAL_STORAGE. |
| File system writes | Only via the explicit image-export methods, into external storage. |
| Untrusted input | Charts render numeric data only; there is no parser/eval path to exploit. |
| Dependencies | One runtime dep (androidx.annotation) — minimal supply-chain exposure. |
Hardening guidance
- Storage permission:
saveToGallery()writes toEnvironment.getExternalStorageDirectory()using the legacy storage model. On Android 10+ (scoped storage), this path is restricted; if you export images, prefer capturinggetChartBitmap()and writing throughMediaStore/Storage Access Frameworkyourself, and request the runtime permission on API 23+. - No data leaves the device through this library — any analytics/telemetry would be in your own code, not here.
- Obfuscation is fully supported; do not add reflective keep-rules (none are needed).
Important
The only OS-version-sensitive code path is image export (legacy external-storage API). Treat it as the single integration point requiring permission handling and scoped-storage awareness on modern Android.
11. Testing Strategy
The library ships 7 JVM JUnit 4 tests under MPChartLib/src/test/:
| Test | Covers |
|---|---|
ApproximatorTest |
Data reduction / Douglas-Peucker approximation. |
AxisRendererTest |
Axis interval computation. |
BarDataTest |
Bar data min/max and grouping math. |
ChartDataTest |
Multi-dataset aggregation, min/max across sets. |
DataSetTest |
Entry insertion/ordering, binary search by x. |
LargeValueFormatterTest |
1.2k/3.4m formatting. |
ObjectPoolTest |
Pool borrow/recycle correctness. |
Tests run on the JVM (no instrumented device) thanks to testOptions.unitTests.returnDefaultValues = true, which stubs Android framework calls. Coverage is concentrated on pure-logic components (data math, formatters, pools); rendering and gesture code is validated manually through the example app.
Note
The contribution guidelines require testing changes by actually running the example app (or adding a new example activity), reflecting the reality that visual/gesture behavior is not unit-testable here.
12. Integration & Deployment
Consuming the library
The library is not on Maven Central; JitPack builds the AAR from the tagged source on first request. Consumers must add the JitPack repository (see User Guide → Installation). Pinning a release tag (v3.1.0) ensures a cached, reproducible build rather than a freshly compiled -SNAPSHOT.
Embedding a chart
A chart is a plain View. Two integration modes:
XML layout
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Programmatic
LineChart chart = new LineChart(context);
container.addView(chart);
Jetpack Compose
AndroidView(factory = { ctx -> LineChart(ctx) }, update = { it.data = lineData; it.invalidate() })
Lifecycle considerations
- Charts hold bitmaps in some renderers;
LineChart/PieChartrelease them inonDetachedFromWindow(). - For memory-tight screens, enable
chart.setUnbindEnabled(true)to unbind drawables on detach, and callchart.clear()when discarding data. setKeepPositionOnRotation(true)preserves zoom/scroll across configuration changes.
13. Contributing
The project accepts community PRs under a defined workflow (CONTRIBUTING.md):
- [ ] Search existing issues and PRs before starting.
- [ ] Fork; branch from
master, named for the change. - [ ] Make commits of logical units; conform to the surrounding code style.
- [ ] Commit titles ≤ 50 characters; body after a blank line for non-obvious changes.
- [ ] Test by running the example app, or add a new example activity demonstrating the change.
- [ ] Open a PR following the auto-generated template.
Issue templates exist for bugs and feature requests — the maintainers ask that "how do I…" questions go to Stack Overflow (mpandroidchart tag), not the issue tracker.
Note
The repository also documents that this is the Android library; the iOS sibling is danielgindi/Charts, which mirrors the same architecture in Swift.
14. Known Limitations & Improvement Opportunities
Derived from the source and the community issue tracker:
float-only precision. Nodouble/BigDecimalsupport. Repeatedly reported (huge values rendering wrong, lost precision inCandleEntry, requests forBigDecimalinBarEntry). The fix would be invasive — the entire data model and transform matrices arefloat-based.- No Jetpack Compose API. Compose users must wrap charts in
AndroidView. A first-class Compose surface is a long-standing feature request. - Legacy external-storage image export.
saveToGallery()predates scoped storage; modernizing it toMediaStorewould remove theWRITE_EXTERNAL_STORAGErequirement on API 29+. - Accessibility gaps. TalkBack/screen-reader support for selecting individual bars/slices is limited — a noted issue for a11y-sensitive apps.
- Targets API 28.
compileSdkVersion/targetSdkVersion 28predate current Play Store target-API requirements; consuming apps set their own target SDK, but the library's own modernization (AGP, SDK levels) lags. - Dashed lines vs. hardware acceleration. The requirement to disable HW acceleration for dashes is a usability sharp edge inherent to the Android
CanvasAPI. - Manual rendering tests. No instrumented/screenshot tests; visual regressions can only be caught by running the example app.
15. Quick Reference
Package map
| Package | Role |
|---|---|
charts |
Public View classes + 2 base classes |
data |
ChartData, DataSet, Entry families |
components |
Axes, legend, limit lines, description, markers |
interfaces |
Dataset + data-provider interfaces (decoupling) |
renderer |
All Canvas painting (+ scatter/ shape renderers) |
highlight |
Touch-to-value hit testing |
formatter |
Value & axis label formatting |
listener |
Touch / gesture listeners |
animation |
ChartAnimator, Easing |
jobs |
Pooled viewport-animation jobs |
buffer |
Reusable draw buffers |
utils |
Transforms, pools, color templates, geometry |
Extension cheat sheet
| Override | Interface/class | Setter |
|---|---|---|
| Series painting | DataRenderer |
setRenderer |
| Axis painting | AxisRenderer |
setXAxisRenderer / setRendererLeftYAxis |
| Scatter shape | IShapeRenderer |
setShapeRenderer |
| Value text | IValueFormatter |
setValueFormatter |
| Axis text | IAxisValueFormatter |
setValueFormatter (on axis) |
| Hit testing | IHighlighter |
setHighlighter |
| Tooltip | MarkerView |
setMarker |
Key facts
| Fact | Value |
|---|---|
| Min SDK | 14 |
| Animations require | API 11+ |
| Runtime deps | androidx.annotation only |
| Value type | 32-bit float |
| Permissions | none (except image export) |
| Reflection | none (empty ProGuard rules) |
| License | Apache 2.0 |