Classes in depth Chapter 21 of 35

The styling and behaviour every data set shares, from the legend label to the entry lookups and the cached value ranges.

A data set is one series: the entries that belong together plus the settings for drawing them. LineDataSet, BarDataSet, PieDataSet and the rest all inherit what this chapter describes, so anything here works on any of them.

Where it sits#

Three levels carry the shared behaviour.

  • IDataSet<T> is the interface. Renderers and highlighters only ever read this, never a concrete class. See Custom data sets.
  • BaseDataSet<T> implements the styling: colors, value labels, legend form, axis, visibility. It leaves entry storage open.
  • DataSet<T> adds the entry list, the cached ranges and the lookups.

Each chart type then has its own subclass with its own properties, described in DataSet subclasses.

Naming, visibility and axis#

val set = LineDataSet(entries, "Revenue")
set.label = "Revenue 2024"
set.isVisible = false
set.axisDependency = YAxis.AxisDependency.RIGHT
PropertyMeaningDefault
labelname in the legend, and the key for data.getDataSetByLabel(...)the constructor argument
isVisiblewhether the set is drawntrue
axisDependencywhich y axis the set is plotted againstLEFT
A hidden set is not drawn but still counts towards the axis ranges, so hiding a set does not rescale the chart.

Value labels#

The numbers drawn next to the entries are styled on the set.

set.isDrawValuesEnabled = true
set.valueTextSize = 10f
set.valueTextColor = Color.DKGRAY
set.valueTypeface = tfLight
set.valueFormatter = MyValueFormatter()
PropertyMeaningDefault
isDrawValuesEnableddraw the labels at alltrue
valueTextSizetext size in dp7f
valueTextColorfirst of valueTextColors when read, replaces the whole list when writtenblack
valueTextColorsone color per entry, cycled like colorsone black
valueTypefacetypeface, null for the defaultnull
valueFormatterturns a value into the label textthe chart's default

Until you assign a formatter, needsFormatter stays true and the chart hands the set its own default formatter, which picks a digit count from the value range. See Formatters.

To style every set at once, call the same things on the data object: data.setValueTextSize(10f), data.setValueTextColor(...), data.setDrawValues(false).

Values are also left out when more entries are visible than the chart's maxVisibleCount allows, no matter what the set says.

Icons#

An entry can carry a Drawable that is drawn at its position.

val star = ContextCompat.getDrawable(this, R.drawable.star)
val entries = listOf(Entry(0f, 4f, icon = star))
set.isDrawIconsEnabled = true
set.iconsOffset = MPPointF(0f, -12f)

iconsOffset is in dp and moves every icon of the set. On pie and radar charts x moves the icon along the value direction and y towards or away from the center. Like values, icons are skipped once maxVisibleCount is exceeded.

The legend form#

By default every set is drawn in the legend with the legend's own form. These four properties override it for one set only.

PropertyMeaningDefault
formshape of the formLegendForm.DEFAULT, the legend's form
formSizesize in dpFloat.NaN, the legend's size
formLineWidthstroke width in dp for a line formFloat.NaN, the legend's width
formLineDashEffectdash pattern for a line formnull, the legend's dash effect
set.form = Legend.LegendForm.LINE
set.formLineWidth = 2f
set.formLineDashEffect = DashPathEffect(floatArrayOf(10f, 5f), 0f)

More in The legend.

Highlighting#

isHighlightEnabled decides whether entries of this set can be selected, by touch or by chart.highlightValue(...). It defaults to true.

set.isHighlightEnabled = false

The look of the highlight is set per subclass: indicator lines on line, scatter, candle and radar sets, an overlay on bars, a shift on pie slices. See Highlighting.

Reading entries#

The entries live in entries, a MutableList. The list you hand to the constructor is kept by reference when it is an ArrayList and copied otherwise; a list you assign later is always kept by reference. Either way, assigning recomputes the ranges.

set.entries = newEntries
chart.notifyDataSetChanged()
CallReturns
entryCountnumber of entries
getEntryForIndex(index)the entry at that list position, throws when the index is out of range
getEntryForXValue(x, closestToY, rounding)the entry at or nearest to that x, or null when the set is empty
getEntryIndex(x, closestToY, rounding)the same as an index, or -1 when the set is empty
getEntriesForXValue(x)every entry whose x matches exactly, empty when there is none
getEntryIndex(entry)the list position of that exact entry object, or -1
getIndexInEntries(xIndex)the first entry with exactly that x, by scanning, or -1
contains(entry)true if that exact entry object is in the set

contains and getIndexInEntries walk the whole list, so keep them out of tight loops. The x value lookups use binary search instead, which is fast but requires the entries to be sorted by x ascending.

Rounding when there is no exact match#

getEntryForXValue and getEntryIndex almost never get an x that an entry has exactly, so they take two extra arguments, both with defaults.

rounding decides which neighbour wins:

DataSet.RoundingPicks
CLOSEST (the default)the entry whose x is nearest; on a tie the larger x
UPthe same, but moves one entry up when the nearest x is below the requested one, unless it is already the last
DOWNthe same, but moves one entry down when the nearest x is above the requested one, unless it is already the first

closestToY only matters when several entries share the found x value. Pass a y and the one closest to it is returned; leave it at Float.NaN and the first of them wins.

val entry = set.getEntryForXValue(12.4f)                                   // nearest
val below = set.getEntryForXValue(12.4f, Float.NaN, DataSet.Rounding.DOWN)

Adding and removing entries#

set.addEntry(Entry(10f, 42f))         // appends, no order check
set.addEntryOrdered(Entry(4.5f, 18f)) // inserts so the entries stay sorted by x
set.removeFirst()
set.removeLast()
set.removeEntry(entry)
set.removeEntry(index)
set.removeEntryByXValue(12.4f)
set.clear()

addEntry is the fast one: it appends and only widens the cached ranges. Use it when your data arrives in x order. addEntryOrdered appends too when the new x is not smaller than the last one, and otherwise binary searches for the insert position, so the lookups keep working.

Removing an entry recomputes the whole range, which costs a pass over the entries. removeEntryByXValue removes the entry nearest to the x you pass, not only an exact match.

removeEntry(index) throws IndexOutOfBoundsException for an index outside the list rather than returning false. Check the index yourself.

The cached ranges#

A set caches xMin, xMax, yMin and yMax so the chart does not walk the entries on every frame. An empty set reports Float.MAX_VALUE for the minimums and -Float.MAX_VALUE for the maximums.

The cache is refreshed for you when you assign entries, add an entry or remove one. It is not refreshed when you change something inside the list, for example the y of an entry you are holding. Then call it yourself:

entry.y = 17f
set.notifyDataSetChanged()   // recomputes this set's ranges
chart.notifyDataSetChanged() // recomputes the axes and redraws

chart.notifyDataSetChanged() builds the axis ranges from the sets' cached ranges, so the set has to be up to date first. calcMinMax() does the same work as notifyDataSetChanged() on the set; calcMinMaxY(fromX, toX) recomputes only the y range over a slice of the x range, which is what charts with isAutoScaleMinMaxEnabled use.

Copying a set#

copy() returns a new set of the same class.

val faded = set.copy()
faded.color = Color.LTGRAY

The entries are copied one by one with Entry.copy(), so changing an entry in the copy does not change the original. The icon and the entry payload are shared, not duplicated. The label and the styling are carried over, and colors, valueTextColors and the value formatter are shared rather than duplicated. Only PieDataSet leaves its own properties behind, noted in DataSet subclasses.