Skip to content

Line overlay (with area) #13

Description

@ze2j

Proposal

Here is a sketch of what it could look like:

class_name TauLineConfig extends TauPaneOverlayConfig

enum LineMode
{
	INDEPENDENT,    ## Each series is drawn independently. Lines at the same X may overlap.
	STACKED         ## Values at the same X are summed across series.
}
@export var mode: LineMode = LineMode.INDEPENDENT

enum InterpolationMode
{
	LINEAR,             ## Straight segment between consecutive samples.
	STEP_BEFORE,        ## Vertical jump at the previous sample's X, then horizontal.
	STEP_AFTER,         ## Horizontal first, then vertical jump at the next sample's X.
	STEP_MIDDLE,        ## Jump at the pixel midpoint between the two X positions.
	SMOOTH_MONOTONE     ## Fritsch-Carlson monotone cubic, no overshoot between samples.
}
## Per-series cycle, read as interpolation_modes[series_index % size].
## An empty array means LINEAR everywhere.
@export var interpolation_modes: Array[InterpolationMode] = [InterpolationMode.LINEAR]

enum GapPolicy
{
	SKIP,      ## Break the polyline at the invalid sample.
	BRIDGE     ## Drop the invalid sample and connect the surrounding valid ones.
}
## Per-series cycle. Applies to NaN, infinite, and values forbidden by the
## active axis scale, such as a non-positive value on a logarithmic axis.
@export var gap_policies: Array[GapPolicy] = [GapPolicy.SKIP]

## Normalization applied to the stacked sums when mode is STACKED.
@export var stacked_normalization: StackedNormalization = StackedNormalization.NONE

## How negative values enter the cumulative when mode is STACKED.
## SIGNED_SUM dips below the previous layer, DIVERGING splits each X into an
## upper and a lower stack anchored at zero, SKIP_NEGATIVES drops them.
@export var stacked_negative_policy: StackedNegativePolicy = StackedNegativePolicy.SIGNED_SUM

## Per-series cycle of hover pixel gates. A 2D distance gate in NEAREST mode,
## an x-only gate in X_ALIGNED mode.
@export var hover_max_distances_px: Array[int] = [10]

## Visual style applied to this overlay. Never null.
@export var style: TauLineStyle = TauLineStyle.new()

And the style:

class_name TauLineStyle extends Resource

## Per-series cycle of line widths in pixels in the normal state.
@export var line_widths_px: Array[float] = [2.0]

## Per-series cycle of line widths for the two segments adjacent to the hovered
## sample. Clamped at draw time to at least the series base width.
@export var hovered_line_widths_px: Array[float] = [3.0]

## Per-series cycle of dash lengths in pixels. Zero means solid.
@export var dash_lengths_px: Array[int] = [0]

## Per-series cycle of fills. Unlike the arrays above, this one merges with the
## theme entry by entry and field by field. A null entry leaves that position
## to the theme.
@export var fills: Array[TauLineFill] = []

The fill is its own resource, so a series can be filled while the next one is not:

class_name TauLineFill extends Resource

enum FillMode
{
	NONE,           ## Leave the area unfilled.
	TO_BASELINE,    ## Fill between the line and the constant level fill_baseline.
	STACKED         ## Fill down to the top of the layer below. Needs LineMode.STACKED.
}
@export var fill_mode: FillMode = FillMode.NONE

## Reference Y level for TO_BASELINE, in data units on the series y axis.
@export var fill_baseline: float = 0.0

## Sentinel meaning "derive from TauXYStyle.series_colors".
const NO_COLOR: Color = Color(0, 0, 0, 0)
@export var color: Color = NO_COLOR

## Multiplier applied to the alpha of the resolved fill.
@export var alpha: float = 0.5

## Painted over the fill area, taking the place of color when set.
@export var texture: Texture2D = null

enum FillTextureMode
{
	STRETCH,   ## Fit the texture across the fill once, as a gradient or a band.
	TILE       ## Repeat the texture at its native pixel size, for a motif.
}
@export var texture_mode: FillTextureMode = FillTextureMode.STRETCH

enum FillStretchSpan
{
	LINE,        ## Position inside the band, from the line to the baseline.
	VALUE_Y,     ## The Y value of the point, so a color stays tied to a value.
	VALUE_X,     ## The X value of the point.
	MAGNITUDE    ## Distance from fill_baseline, either side. Not available with STACKED.
}
## What a stretched texture's color stands for. The texture is read as a color
## scale between its two edges, so a gradient can be a single pixel wide.
@export var stretch_span: FillStretchSpan = FillStretchSpan.LINE

enum StretchRangePolicy
{
	DOMAIN,   ## Span the whole series, from its lowest value to its highest.
	CUSTOM    ## Use the fixed window set in stretch_range.
}
## Where VALUE_Y, VALUE_X and MAGNITUDE read their low and high ends.
## LINE never reads them.
@export var stretch_range_policy: StretchRangePolicy = StretchRangePolicy.DOMAIN
@export var stretch_range: Vector2 = Vector2.ZERO

## Uniform scale of the tile grid in TILE mode. 1.0 is the native pixel size.
@export var tile_scale: float = 1.0

## Rotation of the tile grid in TILE mode, turned around the pane center.
@export var tile_rotation_deg: float = 0.0

## Screen-space translation of the tile grid in TILE mode, applied after
## rotation. Animate it to scroll the pattern.
@export var tile_offset_px: Vector2 = Vector2.ZERO

LineVisualAttributes carries only the inherited buffers color_buffer and alpha_buffer.

At draw time the final fill color of a series is resolved in this order:

  • nothing is painted when fill_mode is NONE.
  • texture if non-null.
  • color if different from NO_COLOR.
  • else the series color from TauXYStyle.series_colors.

Whatever survives has its alpha channel multiplied by alpha before rendering.

Behavior:

  • Fill only is supported.
  • STACKED mode requires a SHARED_X dataset, all line series of the pane on the same y axis, and a non-logarithmic y axis.
  • FillMode.STACKED needs the overlay to be in LineMode.STACKED, otherwise the fill is dropped.
  • Line overlays prefer X_ALIGNED when TauHoverConfig.hover_mode is AUTO, since a line chart is read along the x axis.
  • Hover reports both the plotted Y (the cumulative top, normalized when asked) and the raw dataset value.
  • Everything in TauLineStyle and TauLineFill goes through the usual three-layer cascade, with per-series and per-pane theme keys.

Edits:

  1. changed dash_px scalar to dash_lengths_px array (indexed by series index)
  2. changed line_width_px scalar to line_widths_px array (indexed by series index)
  3. realigned the sketch with the implementation: interpolation_mode, gap_policy and hover_max_distance_px became per-series cycles, hovered_line_width_px became hovered_line_widths_px, stacked_negative_policy was added, and every fill setting moved out of the config and the style into a per-series TauLineFill, where fill_anchor was replaced by texture_mode, stretch_span and the tile transform

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions