Class Chart<W extends Chart<W>>

java.lang.Object
limn.scene.Widget<W>
limn.components.chart.Chart<W>
Direct Known Subclasses:
CartesianChart, DonutChart

public abstract class Chart<W extends Chart<W>> extends Widget<W>
What every chart in this package has in common: the data (labels and series), the colors, the title, the legend, the hover tooltip, the click callback and the animation. BarChart, LineChart and DonutChart add the marks and the axes.

A chart is usable with nothing configured: colors come from the palette that matches the active Theme, the scale rounds itself, the legend appears when there is more than one thing to name, and values animate in. Everything past that is a setter.


 BarChart chart = new BarChart();
 chart.setLabels("Mon", "Tue", "Wed", "Thu", "Fri");
 chart.addSeries(ChartSeries.of("Signups", 12, 19, 3, 5, 22));
 chart.addSeries(ChartSeries.of("Churn",    2,  3, 1, 4,  2));
 chart.setStacked(true);
 chart.onPointClick(point -> open(point.label()));
 

Animation. Values interpolate, not pixels: on the first appearance a series grows out of the axis baseline, and afterwards it eases from the number on screen to the number it was just given, so a live chart re-pushed every second reads as one moving shape rather than as a slideshow. setAnimationDuration(double) with 0 turns it off, which is also what a chart with no scene does: a headless test or a screenshot sees final values, never an in-between frame.

Interaction. Hovering reports the datum under the pointer; the tooltip follows it and stays inside the chart. A chart with more than one series reports every series at the hovered category at once (Chart.TooltipMode.INDEX), which is what makes two lines comparable at a glance. Clicking calls onPointClick(Consumer) with the same datum. Clicking a legend entry hides and shows its series, animating the scale and any stack around it.

Charts are painted, not composed: a chart has no child widgets, with one exception: DonutChart.setCenter(Widget) puts a real widget in the hole, so the middle of a donut can hold a label, an icon or a button.

On chaining. The setters here return Chart, so a chain that starts on a subclass cannot continue into one of that subclass's own setters: new BarChart().setTitle("x").setStacked(true) does not compile. Configure a chart in statements (which is how the demo scenes read anyway) or start from BarChart.of(java.util.List<java.lang.String>, limn.components.chart.ChartSeries...), LineChart.of(java.util.List<java.lang.String>, limn.components.chart.ChartSeries...) and DonutChart.of(java.util.List<java.lang.String>, double...), which take the labels and the series together. The alternative, redeclaring every setter in all three subclasses purely to narrow its return type, buys one expression shape at the price of forty methods whose documentation could only repeat their signatures.

  • Field Details

    • UNSET

      public static final float UNSET
      Hands a dimension back to the default size, the ProgressBar.UNSET idiom.
      See Also:
  • Constructor Details

    • Chart

      public Chart()
  • Method Details

    • labels

      public List<String> labels()
      The category labels as they currently read, in order.
    • label

      public String label(int index)
      The label for index, or "" when there are fewer labels than values.
    • labelSource

      public I18nString labelSource(int index)
      The localizable value behind label(int), or I18nString.EMPTY past the end: what a language change re-resolves.
    • setLabels

      public W setLabels(String... values)
      Sets the category labels: the x axis of a bar or line chart, the slices of a donut.
    • setLabels

      public W setLabels(I18nString... values)
      Sets category labels that follow the UI language. There is no List form of it (List<String> and List<I18nString> erase to the same signature), so hand a list over as list.toArray(new I18nString[0]).
    • setLabels

      public W setLabels(List<String> values)
      setLabels(String...) from a list; the list is copied.
    • series

      public List<ChartSeries> series()
      The series, in the order they were added (paint and palette order).
    • addSeries

      public W addSeries(ChartSeries newSeries)
      Adds a series. It takes the next palette slot unless it names its own color.
      Throws:
      IllegalStateException - if the series already belongs to a chart
    • onSeriesAdded

      protected void onSeriesAdded(ChartSeries added)
      Called with a series that has just joined the chart, before anything is animated. Charts with per-series defaults of their own apply them here, so a series added after the default was set is styled like the ones that were already there.
    • setSeries

      public W setSeries(ChartSeries... newSeries)
      Replaces every series at once.
    • removeSeries

      public W removeSeries(ChartSeries victim)
      Removes a series; a no-op when it is not in this chart.
    • clearSeries

      public W clearSeries()
      Removes every series.
    • series

      protected final ChartSeries series(int index)
      The series at index, without copying the list, the accessor the painting and hit-testing paths use, since series() allocates and both run per frame.
    • seriesCount

      protected final int seriesCount()
      How many series the chart holds, visible or not.
    • dataGeneration

      protected final int dataGeneration()
      Bumped by every change to the data or to what is visible in it. Derived structures that are expensive to rebuild (a stack-key list walked once per bar) cache against it instead of rebuilding per frame.
    • contentLeft

      protected final float contentLeft()
      Left edge of the region left after the title and the legend.
    • contentTop

      protected final float contentTop()
      Top edge of the region left after the title and the legend.
    • contentBoxWidth

      protected final float contentBoxWidth()
      Width of the region left after the title and the legend.
    • contentBoxHeight

      protected final float contentBoxHeight()
      Height of the region left after the title and the legend.
    • categoryCount

      public final int categoryCount()
      How many categories the chart spans: the labels, or the longest series.
    • palette

      public ChartPalette palette()
      The palette in force: the one that was set, or the built-in one for this theme.
    • setPalette

      public W setPalette(@Nullable ChartPalette value)
      Pins the palette; null hands it back to ChartPalette.defaultFor(limn.components.Theme), which follows the active theme's light/dark mode.
    • seriesColor

      public final Color seriesColor(int index)
      The color of series index: its own, or its palette slot.
    • title

      public @Nullable String title()
      The chart title as it currently reads, or null when there is none.
    • titleSource

      public @Nullable I18nString titleSource()
      The localizable value behind title(), or null.
    • setTitle

      public W setTitle(@Nullable String value)
      Sets a title drawn above the plot (null for none).
    • setTitle

      public W setTitle(@Nullable I18nString value)
      Sets a title that follows the UI language (null for none).
    • clearTitle

      public W clearTitle()
      Takes the title away, as setTitle(null) would: that call does not compile, because null fits more than one of the overloads.
      Returns:
      this chart
    • background

      public @Nullable Color background()
      The panel color behind the chart, or null when it paints on what is below.
    • setBackground

      public W setBackground(@Nullable Color color)
      Fills the chart's box with color before anything else, rounded like a card; null (the default) paints nothing, letting the chart sit on the surface it was placed on.
    • legendPosition

      public Chart.LegendPosition legendPosition()
      Where the legend sits.
    • setLegendPosition

      public W setLegendPosition(Chart.LegendPosition position)
      Moves the legend, or takes it away with Chart.LegendPosition.NONE.
    • isLegendInteractive

      public boolean isLegendInteractive()
      Whether clicking a legend entry hides and shows its data.
    • setLegendInteractive

      public W setLegendInteractive(boolean value)
      Enables or disables hide/show on legend clicks (on by default).
    • setPreferredSize

      public W setPreferredSize(float width, float height)
      Overrides the natural size (360.0f x 220.0f); UNSET on either axis restores it. A chart in an Expanded or a stretched Column takes the space it is given regardless; this is the fallback for an unconstrained parent.
    • valueFormat

      public DoubleFunction<String> valueFormat()
      How values are written in tooltips and in the names a screen reader hears for each mark: grouped digits by default (1,234.5), and scientific notation with up to three decimals from 1e15 up (1.235E20).
    • setValueFormat

      public W setValueFormat(DoubleFunction<String> format)
      Sets the tooltip value format; see NumberFormats for ready-made ones.
    • isTooltipEnabled

      public boolean isTooltipEnabled()
      Whether hovering shows a tooltip.
    • setTooltipEnabled

      public W setTooltipEnabled(boolean value)
      Turns the hover tooltip on or off (on by default).
    • tooltipMode

      public Chart.TooltipMode tooltipMode()
      How much of the data a hover reports.
    • setTooltipMode

      public W setTooltipMode(Chart.TooltipMode mode)
      Sets whether a hover reports the whole category or only the mark under the pointer.
    • setTooltipFormat

      public W setTooltipFormat(@Nullable Function<ChartPoint,String> format)
      Replaces the text of a tooltip row. The default lays each row out in two columns (the series name where reading starts, the formatted value where it ends, so both columns swap in a chart that reads right to left), which is what makes a multi-series tooltip scannable; a formatter set here produces the whole row as one string instead. null restores the default.
    • animationDuration

      public double animationDuration()
      Length of the entry and value-change animation, in seconds.
    • setAnimationDuration

      public W setAnimationDuration(double seconds)
      Sets the animation length in seconds; 0 draws every change immediately.
    • setAnimationEasing

      public W setAnimationEasing(Easing easing)
      Sets the animation curve (default Easing.EASE_OUT).
    • replayAnimation

      public W replayAnimation()
      Replays the entry animation from the axis baseline.
    • onPointClick

      public W onPointClick(@Nullable Consumer<ChartPoint> listener)
      Called with the datum under the pointer on a left click. In Chart.TooltipMode.INDEX a click anywhere in a category reports that category's nearest mark, so a thin line is as clickable as a fat bar.

      One handler at a time: null clears it, and setting a second while one is set throws IllegalStateException.

    • onPointHover

      public W onPointHover(@Nullable Consumer<@Nullable ChartPoint> listener)
      Called whenever the hovered datum changes, with null when the pointer leaves the marks. Fires on changes only, not on every pointer move.

      One handler at a time: null clears it, and setting a second while one is set throws IllegalStateException.

    • handleUserChange

      protected void handleUserChange(Change.Aspect aspect)
      Both handlers read hoveredPoint(), which is assigned before either announcement, so neither needs a payload the channel does not carry.
      Overrides:
      handleUserChange in class Widget<W extends Chart<W>>
      Parameters:
      aspect - what changed; the origin is USER or this is not called
    • hoveredPoint

      public @Nullable ChartPoint hoveredPoint()
      The datum under the pointer, or null.
    • paintContent

      protected abstract void paintContent(Canvas canvas, float x, float y, float w, float h)
      Paints the marks inside the region left after the title and the legend, in the chart's own coordinates. Grid, axes and everything data-shaped belong here.
    • pickAt

      protected abstract @Nullable ChartPoint pickAt(float localX, float localY)
      The datum under a point in the chart's local coordinates, or null when the pointer is not over the marks. Called for hover and for clicks, so it decides both.
    • tooltipRows

      protected List<ChartPoint> tooltipRows(ChartPoint picked)
      The rows a tooltip shows for picked. The default is Chart.TooltipMode.INDEX-aware: every visible series at the same index, or just the picked datum in Chart.TooltipMode.POINT.
    • tooltipTitle

      protected String tooltipTitle(ChartPoint picked)
      The heading over the tooltip rows; the category label by default.
    • tooltipRowName

      protected String tooltipRowName(ChartPoint row)
      The name a tooltip row carries where reading starts, the series name by default.
    • tooltipRowValue

      protected String tooltipRowValue(ChartPoint row)
      The value a tooltip row carries where reading ends.
    • tooltipRowColor

      protected Color tooltipRowColor(ChartPoint row)
      The swatch color for a tooltip row.
    • legendEntries

      protected List<Chart.LegendEntry> legendEntries()
      One entry per series, in palette order. Overridden by charts whose legend is not that.
    • toggleLegendEntry

      protected void toggleLegendEntry(int index)
      Hides or shows what legend entry index stands for.
    • legendChanged

      protected final void legendChanged()
      Drops the legend's cached entries, for a chart whose legend is not the series list.
    • onHoverChanged

      protected void onHoverChanged(@Nullable ChartPoint picked)
      Called when the datum under the pointer changes, null when there is none: the hook for a chart that animates something on hover. The application-facing callback is onPointHover(Consumer); this is for subclasses.
    • updateRegions

      protected final void updateRegions()
      Recomputes the title/legend/plot split. For a subclass laying out in onLayout.
    • legendEntryCount

      protected final int legendEntryCount()
      How many legend entries are drawn right now, with their boxes brought up to date first: 0 whenever the legend is not drawn, which folds in Chart.LegendPosition.NONE, Chart.LegendPosition.AUTO with fewer than two entries and a chart with nothing to name.

      For the describe hook, which runs after the layout pass and before the paint. The boxes are written by the region pass, and that pass runs before every paint and every pointer test but not before the tree is published, so a hook that read them as they stood would publish the previous frame's rectangles — or none at all on the first frame. The pass is re-run here only when the legend's own cache says its entries changed since the last one, or the UI language moved, because under a ruler that does not memoize its shaping the pass allocates, and a quiet damaged frame must not.

    • legendEntryBounds

      protected final void legendEntryBounds(int index, Accessibility a)
      Places the node being described at legend entry index's box, in this chart's own coordinates: the same rectangle a click on the legend is tested against, so the published box and the operable box are one array.
      Parameters:
      index - an entry below legendEntryCount(), which must have been asked first in the same pass so that the boxes are current
      a - the builder with the entry's synthetic child open
      Throws:
      IndexOutOfBoundsException - if there is no such entry
    • animationBaseline

      protected double animationBaseline()
      The value a series grows out of when it first appears, and collapses to when it is hidden: where the axis crosses the plot, which is zero on any scale that contains it and the nearer end otherwise.
    • staggerFraction

      protected float staggerFraction()
      How far apart consecutive elements start, as a fraction of the animation: a wipe rather than a single pop. 0 (the default) starts everything at once, which is what a line wants: staggering the points of a line animates a wave through it.
    • progress

      protected final float progress()
      Overall animation progress in [0,1]; 1 means the data as it stands.
    • elementProgress

      protected final float elementProgress(int index, int count)
      Animation progress for element index of count, with staggerFraction() applied. Elements past the first start later and finish later, and every one of them reaches 1 by the time progress() does.
    • seriesAlpha

      protected final float seriesAlpha(int index)
      The alpha a series is currently drawn at, folding in its hide/show fade.
    • drawnValue

      protected final double drawnValue(int seriesIndex, int index)
      The value series seriesIndex is drawn at right now, animation included.
    • pointFor

      protected final ChartPoint pointFor(int seriesIndex, int index, double share, float x, float y)
      Builds a ChartPoint for a datum, with the anchor the caller measured.
    • tokens

      protected final SizeTokens tokens()
      The tokens for the step resolved on this chart; resolve once per pass, never in a field.
    • measure

      protected final TextMetrics measure(String text, Font font)
      Measures a single line with the layout ruler (agrees with what drawText draws).
    • ellipsize

      protected final String ellipsize(String text, Font font, float maxWidth)
      text shortened with an ellipsis until it fits maxWidth, or "" when not even the ellipsis fits. Category labels are application data: they are as long as they are, and a chart that lets them collide is unreadable.

      Shaped once and cut where ShapedText.fitEnd(int, float) says, then re-shaped once or twice, the way Label does it. The obvious loop (drop a character, measure, repeat) made a label that overflows by forty characters cost forty shapings a paint, and a chart paints every frame of a value transition; worse, each candidate was a distinct string in the ruler's memo, so a few long labels evicted every other widget's text from it and everything on screen re-shaped each frame.

    • onAttached

      protected void onAttached()
      Description copied from class: Widget
      Called when this widget enters a scene (attached to the tree). Widget.scene() is the scene it just joined. Fires top-down, so a parent runs before its children. Default no-op.
      Overrides:
      onAttached in class Widget<W extends Chart<W>>
    • onMeasure

      protected Size onMeasure(Constraints constraints)
      Description copied from class: Widget
      Reports the size this widget wants within constraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.

      Resolve the ControlSize and the LayoutDirection once each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, and I18n.locale() answers it wherever text is resolved.

      Specified by:
      onMeasure in class Widget<W extends Chart<W>>
    • onLayout

      protected void onLayout()
      Recomputes the regions whenever the box is laid out, so that the legend's boxes are current for the tree published between this pass and the paint. A subclass that lays out a child of its own calls this first.
      Overrides:
      onLayout in class Widget<W extends Chart<W>>
    • onPaint

      protected void onPaint(Canvas canvas)
      Description copied from class: Widget
      Widget's own background/content, in local coordinates.
      Overrides:
      onPaint in class Widget<W extends Chart<W>>
    • onPaintOverlay

      protected void onPaintOverlay(Canvas canvas)
      Description copied from class: Widget
      Painted after children (scrollbars, focus rings…).
      Overrides:
      onPaintOverlay in class Widget<W extends Chart<W>>
    • hoverRegion

      protected @Nullable Rect hoverRegion(ChartPoint point)
      The region of this chart that changes when point is the datum under the pointer: lit marks, a crosshair, a band, a popped slice. null means "unknown", and the whole chart is repainted.

      A subclass owns this because only it knows what its hover draws, and the answers differ in kind: a cartesian chart lights a category across the plot, a donut pops one slice out of a ring. An answer that is too small leaves stale pixels, which is the one failure of partial rendering no assertion can see, so answer generously or answer null. The tooltip panel is not part of it — this class adds that.

      Parameters:
      point - the hovered datum, never null
      Returns:
      the region in widget coordinates, or null for "all of it"
    • damageHoverInPlace

      protected final void damageHoverInPlace()
      Damages whatever the hover draws right now, without erasing anywhere it has been.

      What a fade wants: the panel and the marks are where the last frame left them, so repainting them in place is the whole of it. A subclass whose own animation runs on the hover — a slice popping out of a ring — points its transition here.

    • onMouseEvent

      protected void onMouseEvent(MouseEvent event)
      Description copied from class: Widget
      Mouse events (bubbling); call event.consume() when handled.
      Overrides:
      onMouseEvent in class Widget<W extends Chart<W>>
    • stateFadeSeconds

      protected final double stateFadeSeconds()
      How long a state fade lasts (hiding a series, popping a hovered mark), in seconds. Short, because it is feedback rather than data movement, and 0 whenever setAnimationDuration(double) turned animation off, so "off" means all of it.