java.lang.Object
org.rrd4j.graph.MonotoneSpline

final class MonotoneSplineextends Object
Monotone cubic Hermite interpolation (Fritsch-Carlson / Fritsch-Butland) used to smooth graph plot lines and area boundaries.

The unsmoothed renderer emits a staircase: runs of identical samples are collapsed to a single horizontal segment and each value change becomes a near-vertical segment. This class replaces the straight segments with cubic Bezier segments whose tangents are chosen so that:

  • Nothing overshoots. The limiting step bounds the normalized tangent magnitudes (alpha, beta) by 3, which is exactly the condition for the interpolant on an interval to stay within the two endpoint values. A curve therefore never leaves the vertical band of the data it passes through, so it cannot escape the plot area.
  • Genuinely flat data stays flat. Where the secants on both sides of a vertex are equal, have opposite signs, or one of them is zero, the tangent is forced to zero. A plateau is bracketed by two horizontal tangents and so is drawn as an exact straight horizontal line; only the transitions between plateaus are curved.
  • Spacing is honored. The interpolation is x-aware, so it stays correct when a DownSampler has produced a non-uniform sample spacing.

The interpolation is not linear in the y values, because zeroing the tangent at a local extremum or plateau is a sign test rather than a weighted sum. Two series therefore do not interpolate additively, which is why ImageWorker only smooths plots whose fill has a flat lower boundary; see fillPolygonSmooth for the reasoning.

All methods are static and side-effect free so the interpolation can be exercised without a graphics context or a router.

  • Method Summary

    All Methods
    Modifier and Type
    Method
    Description
    (package private) static void
    appendForward(GeneralPath path, int[] xv, int[] yv, double[] m)
    Appends the smoothed curve through the vertices, walking forward from xv[0] to xv[n - 1].
    (package private) static void
    appendReverse(GeneralPath path, int[] xv, int[] yv, double[] m)
    Appends the smoothed curve through the vertices, walking backward from xv[n - 1] to xv[0].
    (package private) static boolean
    isInteriorTransition(int[] xv, int[] vy, int i)
    Tells whether the interval at i is a zero-width value change that may be widened.
    (package private) static double[]
    tangents(double[] x, double[] y, int start, int end)
    Computes the slope (dy/dx) at each of the vertices x[start] ..
    (package private) static int
    widenTransitions(int[] xv, int[] vy, int maxWidth)
    Widens zero-width transitions so they have room to be drawn as curves.

    Methods inherited from class Object

    clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • tangents

      static double[] tangents(double[] x, double[] y, int start, int end)
      Computes the slope (dy/dx) at each of the vertices x[start] .. x[end - 1].
      Parameters:
      x - vertex x coordinates, strictly increasing within the range
      y - vertex y coordinates
      start - first index of the range, inclusive
      end - last index of the range, exclusive
      Returns:
      slopes in y-units per x-unit, one per vertex; never null, empty when end - start < 1
    • appendForward

      static void appendForward(GeneralPath path, int[] xv, int[] yv, double[] m)
      Appends the smoothed curve through the vertices, walking forward from xv[0] to xv[n - 1].

      The caller is responsible for having positioned the path at (xv[0], yv[0]). Vertices with a non-positive span between them are joined with a straight line, since no curve is defined there.

      Parameters:
      path - path to append to
      xv - vertex x coordinates
      yv - vertex y coordinates
      m - slopes as returned by tangents(double[], double[], int, int) for this vertex range
    • appendReverse

      static void appendReverse(GeneralPath path, int[] xv, int[] yv, double[] m)
      Appends the smoothed curve through the vertices, walking backward from xv[n - 1] to xv[0]. Used for the lower boundary of a filled area, which is traversed in reverse to close the outline.

      The caller is responsible for having positioned the path at (xv[n - 1], yv[n - 1]).

      Parameters:
      path - path to append to
      xv - vertex x coordinates
      yv - vertex y coordinates
      m - slopes as returned by tangents(double[], double[], int, int) for this vertex range
    • widenTransitions

      static int widenTransitions(int[] xv, int[] vy, int maxWidth)
      Widens zero-width transitions so they have room to be drawn as curves.

      When a graph plots more periods than it has pixels, several samples share one pixel column and a value change lands inside that column. There is then no horizontal distance across which to draw a curve, so the transition stays a vertical line and the plot keeps its staircase look however good the interpolation is. This borrows the missing width from the flat runs on either side of the transition, spreading it into an S-shaped ramp.

      Only the x position of the two vertices bounding a transition moves; their y values, and every vertex of the surrounding plateaus, are left alone. A flat stretch therefore stays exactly flat and at exactly the value the data holds, it is just a little shorter. The curve still cannot overshoot, because it is bounded by the same two endpoint values as before.

      Only interior transitions are widened. The first and last intervals of a run are left at their own columns, because they are the run's anchors: the first is where the trace starts and the last is where it ends, and both sit on the plot area's boundary when the series fills the window. Widening either one moves the anchor, which would start the trace inside the plot instead of at its edge or run the tail past the far edge.

      Because the drawn curve is given width it does not literally pass through every sample once they are closer together than a pixel. That is the intended trade: this renders the series as a trend rather than reproducing sub-pixel steps exactly.

      Parameters:
      xv - vertex x coordinates, adjusted in place
      vy - vertex y coordinates, read only to tell a transition from a flat run
      maxWidth - the most total width, in pixels, to give one transition; must be at least 2
      Returns:
      the number of transitions widened
    • isInteriorTransition

      static boolean isInteriorTransition(int[] xv, int[] vy, int i)
      Tells whether the interval at i is a zero-width value change that may be widened.

      Three conditions, all required. The x coordinates must be equal, or the transition already spans pixels and there is nothing to widen. The y values must differ, or it is a flat run and there is no transition to draw. And i must be neither 0 nor xv.length - 2, because those intervals bound the run: moving either vertex would move the start or the end of the trace rather than a join in the middle of it.

      Parameters:
      xv - vertex x coordinates
      vy - vertex y coordinates
      i - index of the interval, whose two ends are i and i + 1
      Returns:
      true if the interval may be widened