Skip to content

limel-chart: axis values cannot be formatted, so date-based Gantt charts are unusable #4264

Description

@Kiarokh

New feature motivation

limel-chart can already draw everything a Gantt needs — an item whose value is a [start, end] pair renders as a span that begins away from zero, which is exactly what the limel-example-chart-type-gantt example shows. What it cannot do is label such a chart when the two numbers represent points in time, and time is the most natural thing to put on a Gantt.

The concrete case: we are adding configurable chart widgets to Lime CRM (see Lundalogik/hack-tuesday#361). An admin picks a filter set, a property to group records by, and one or two aggregates. That gives us Gantt-shaped data for free — "first and last activity date per project", "earliest and latest close date per pipeline stage", "shortest and longest handling time per ticket type". We had to leave all of them out, because there is no way to make the axis readable.

The problem

Two things combine:

  1. ChartItem.value is number | [number, number]. A date has to be converted to a number by the consumer — epoch milliseconds is the only sensible choice.
  2. renderAxisLines prints the raw number. It walks the range and emits <limel-badge label={value} /> with no formatting hook:
for (let value = minValue; value <= maxValue; value += increment) {
    lines.push(
        <div class={{ 'axis-line': true, 'zero-line': value === 0 }} role="presentation">
            <limel-badge label={value} />
        </div>
    );
}

So a chart of dates gets axis badges reading 1735689600000, 1738368000000, … There is no prop to intercept them. ChartItem.formattedValue fixes the item label (the tooltip reads 2026-01-01 — 2026-02-01 if the consumer formats it), but the axis stays raw, and the axis is what makes a Gantt legible.

axisIncrement has the same problem from the other side: to get one tick per month a consumer has to pass a millisecond count, and months are not a fixed number of milliseconds, so the ticks drift.

New feature description

Some way for a consumer to control how axis values are rendered. A formatter callback is the smallest version:

@Prop() public formatAxisValue?: (value: number) => string;

…used in renderAxisLines in place of the bare label={value}. That alone unblocks the date case: the consumer converts to epoch, formats back with Intl.DateTimeFormat, and the chart stays agnostic about what the numbers mean.

A richer alternative would be first-class temporal support — letting value hold dates and having the chart choose sensible period ticks (day/week/month/quarter) itself. That is a much bigger change, and it would also solve the axisIncrement drift, but the formatter is enough to make date Gantts usable and does not commit the component to understanding calendars.

Worth noting a formatter prop would also help non-date cases that are awkward today: currency axes (1.2M rather than 1200000), percentages, and units.

New feature implementation

The change is contained to renderAxisLines plus one new @Prop. Both call sites already pass through a single place — renderAxisLines(range, direction?) is used for the single value axis and, twice, for scatter's two axes — so a per-axis variant would need a little more thought if x and y should ever format differently (a date-vs-count scatter would want exactly that).

Environment

  • lime-elements version: 39.45.3 (also current on main)
  • Framework used: StencilJS, consumed via @limetech/lime-crm-building-blocks

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions