Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions homedocs/src/examples/charts/LineGraphExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ interface Model {
showArea: boolean;
showLine: boolean;
smooth: boolean;
smoothingRatio: number;
line1: boolean;
line2: boolean;
}
Expand All @@ -40,7 +39,6 @@ class PageController extends Controller {
this.store.init(m.showArea, true);
this.store.init(m.showLine, true);
this.store.init(m.smooth, true);
this.store.init(m.smoothingRatio, 0.07);

this.addTrigger(
"on-count-change",
Expand Down Expand Up @@ -88,7 +86,6 @@ export default (
line={false}
area={m.showArea}
smooth={m.smooth}
smoothingRatio={m.smoothingRatio}
/>
<LineGraph
name="Series 1"
Expand All @@ -97,7 +94,6 @@ export default (
area={m.showArea}
active={bind(m.line1, true)}
smooth={m.smooth}
smoothingRatio={m.smoothingRatio}
line={m.showLine}
/>
<LineGraph
Expand All @@ -107,7 +103,6 @@ export default (
yField="y2"
active={bind(m.line2, true)}
smooth={m.smooth}
smoothingRatio={m.smoothingRatio}
line={m.showLine}
/>
</Chart>
Expand All @@ -128,15 +123,6 @@ export default (
<Switch label="Line" value={m.showLine} />

<Switch label="Smooth" value={m.smooth} style="margin-right: 20px" />
<Slider
label="Smoothing ratio"
enabled={m.smooth}
value={bind(m.smoothingRatio, 0.07)}
maxValue={0.4}
minValue={0}
step={0.01}
help={expr(m.smoothingRatio, (v) => v?.toFixed(2) ?? "")}
/>
</LabelsTopLayout>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const LineChart = createFunctionalComponent(
area={areaStyle != null}
areaStyle={areaStyle}
smooth
smoothingRatio={0.2}
/>
</Chart>
</Svg>
Expand Down
11 changes: 6 additions & 5 deletions homedocs/src/pages/docs/charts/line-graph.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Line charts are commonly used for data trends visualization. The `LineGraph` wid
<LineGraphExample client:load />
</CodeExample>

The example above demonstrates multiple line series with area fill, smooth curves, and a range band (using `y0Field` and `yField` to define upper and lower bounds). Use the controls to adjust the number of data points, toggle area/line visibility, and modify the smoothing effect.
The example above demonstrates multiple line series with area fill, smooth curves, and a range band (using `y0Field` and `yField` to define upper and lower bounds). Use the controls to adjust the number of data points, toggle area/line visibility, and toggle smoothing.

## Configuration

Expand Down Expand Up @@ -52,10 +52,11 @@ The example above demonstrates multiple line series with area fill, smooth curve

### Smoothing

| Property | Type | Default | Description |
| ---------------- | --------- | ------- | ---------------------------------------------------------------------------------------------- |
| `smooth` | `boolean` | `false` | Set to `true` to draw smoothed lines using cubic Bézier curves |
| `smoothingRatio` | `number` | `0.05` | Controls the intensity of smoothing (0 to 0.4). Higher values create more curved lines |
| Property | Type | Default | Description |
| -------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `smooth` | `boolean` | `false` | Set to `true` to draw smoothed lines. Uses monotone cubic interpolation, so the curve never overshoots the vertical range of the data |

> The `smoothingRatio` property is deprecated and ignored — the monotone interpolation used for smoothing does not have a configurable curvature.

### Stacking

Expand Down
13 changes: 0 additions & 13 deletions litmus/features/charts/line-graph/LineGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ class PageController extends Controller {
this.store.init("$page.showArea", true);
this.store.init("$page.showLine", true);
this.store.init("$page.smooth", true);
this.store.init("$page.smoothingRatio", 0.1);

this.addTrigger(
"on-count-change",
Expand Down Expand Up @@ -54,7 +53,6 @@ export default (
area-bind="$page.showArea"
line={false}
smooth-bind="$page.smooth"
smoothingRatio-bind="$page.smoothingRatio"
name="Line 1"
/>
<LineGraph
Expand All @@ -65,7 +63,6 @@ export default (
line
visible-bind="$page.showLine"
smooth-bind="$page.smooth"
smoothingRatio-bind="$page.smoothingRatio"
name="Line 1"
/>

Expand All @@ -76,7 +73,6 @@ export default (
area-bind="$page.showArea"
line-bind="$page.showLine"
smooth-bind="$page.smooth"
smoothingRatio-bind="$page.smoothingRatio"
name="Line 2"
/>
<PureContainer visible-bind="$page.showMarkers">
Expand Down Expand Up @@ -107,15 +103,6 @@ export default (
<Switch label="Line" value-bind="$page.showLine" />

<Switch label="Smooth" value-bind="$page.smooth" />
<Slider
label="Smoothing ratio"
enabled-bind="$page.smooth"
value={{ bind: "$page.smoothingRatio", debounce: 100 }}
maxValue={0.4}
minValue={0}
step={0.01}
help-tpl="{$page.smoothingRatio:n;0;2}"
/>
<Switch label="Show markers" value-bind="$page.showMarkers" />
</div>
</div>
Expand Down
146 changes: 146 additions & 0 deletions litmus/features/charts/line-graph/SmoothingOvershoot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Chart, Gridlines, LineGraph, Marker, MarkerLine, NumericAxis } from "cx/charts";
import { Svg } from "cx/svg";
import { Controller, LabelsLeftLayout, Repeater } from "cx/ui";
import { Select, Switch } from "cx/widgets";

// Verifies that smooth line rendering (monotone cubic interpolation) never
// overshoots the actual data range, i.e. the curve stays within the vertical
// bounds of the data. Most sensitive datasets are those with steep slopes next
// to flat segments (steps, spikes, zeros).

const datasets = {
step: {
label: "Step (flat → jump → flat)",
points: [0, 0, 0, 0, 0, 100, 100, 100, 100, 100].map((y, i) => ({ x: i * 10, y })),
},
spike: {
label: "Single spike",
points: [10, 10, 10, 10, 200, 10, 10, 10, 10].map((y, i) => ({ x: i * 10, y })),
},
zeros: {
label: "Sparse data with zeros",
points: [0, 0, 45, 0, 0, 0, 120, 80, 0, 0, 30, 0].map((y, i) => ({ x: i * 10, y })),
},
plateau: {
label: "Plateaus with steep transitions",
points: [10, 12, 11, 13, 200, 210, 205, 208, 12, 10, 11].map((y, i) => ({ x: i * 10, y })),
},
unevenX: {
label: "Uneven x spacing + steep slope",
points: [
{ x: 0, y: 20 },
{ x: 5, y: 22 },
{ x: 10, y: 21 },
{ x: 12, y: 180 },
{ x: 60, y: 185 },
{ x: 62, y: 20 },
{ x: 100, y: 22 },
],
},
};

class PageController extends Controller {
onInit() {
this.store.init("$page.dataset", "step");
this.store.init("$page.smooth", true);
this.store.init("$page.showArea", false);
this.store.init("$page.showRawLine", true);
this.store.init("$page.showMarkers", true);
this.store.init("$page.showBounds", true);

this.addTrigger(
"on-dataset-change",
["$page.dataset"],
(name) => {
const points = datasets[name].points;
this.store.set("$page.points", points);
this.store.set("$page.yMin", Math.min(...points.map((p) => p.y)));
this.store.set("$page.yMax", Math.max(...points.map((p) => p.y)));
},
true,
);
}
}

export default (
<cx>
<div class="widgets" style="padding-left: 30px" controller={PageController}>
<Svg style="width:560px; height:500px;">
<Chart
offset="100 -10 -100 50"
axes={{
x: { type: NumericAxis, lineStyle: "stroke: transparent" },
y: { type: NumericAxis, vertical: true },
}}
>
<Gridlines />

<MarkerLine
y-bind="$page.yMax"
visible-bind="$page.showBounds"
style="stroke: red; stroke-dasharray: 4 4"
/>
<MarkerLine
y-bind="$page.yMin"
visible-bind="$page.showBounds"
style="stroke: red; stroke-dasharray: 4 4"
/>

<LineGraph
data-bind="$page.points"
lineStyle="stroke: #0074eb; stroke-width: 2.5"
areaStyle="fill: rgba(0, 116, 235, 0.15)"
area-bind="$page.showArea"
smooth-bind="$page.smooth"
legend={false}
/>
<LineGraph
data-bind="$page.points"
visible-bind="$page.showRawLine"
lineStyle="stroke: #333; stroke-dasharray: 3 3; stroke-width: 1.5"
legend={false}
/>

<Repeater records-bind="$page.points">
<Marker
visible-bind="$page.showMarkers"
x-bind="$record.x"
y-bind="$record.y"
size={5}
shape="circle"
style="fill: #333; stroke: #333"
legend={false}
/>
</Repeater>
</Chart>
</Svg>

<div
style="display: flex; flex-direction: column; gap: 10px; margin-top: 20px; width: 500px"
layout={LabelsLeftLayout}
>
<Select label="Dataset" value-bind="$page.dataset">
<option value="step" text={datasets.step.label} />
<option value="spike" text={datasets.spike.label} />
<option value="zeros" text={datasets.zeros.label} />
<option value="plateau" text={datasets.plateau.label} />
<option value="unevenX" text={datasets.unevenX.label} />
</Select>

<Switch label="Smooth" value-bind="$page.smooth" />
<Switch label="Area" value-bind="$page.showArea" />
<Switch label="Show actual data (dashed)" value-bind="$page.showRawLine" />
<Switch label="Show markers" value-bind="$page.showMarkers" />
<Switch label="Show data min/max bounds" value-bind="$page.showBounds" />
</div>

<div style="margin-top: 20px; max-width: 700px; color: #666">
<p>
The smoothed curve should never cross the dashed red lines — those mark the actual minimum and maximum
of the data. Smoothing is based on monotone cubic interpolation, which stays within the data bounds, so
the graph never suggests values that don't exist in the data.
</p>
</div>
</div>
</cx>
);
3 changes: 2 additions & 1 deletion litmus/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ import "./index.scss";
//import Demo from "./features/grid/CellEditing";
// import Demo from "./features/charts/PointReducer";
// import Demo from "./features/charts/line-graph/LineGraph";
import Demo from "./features/charts/line-graph/SmoothingOvershoot";
// import Demo from "./bugs/GridDefaultSortFieldClearableSortIssue";
// import Demo from "./bugs/GridFixedColumnsFixedHeaderColumnsPosition";
// import Demo from "./bugs/GridOnFetchRecords";
// import Demo from "./performance/GridMemoryLeak";
import Demo from "./bugs/grid-sorting-without-field";
// import Demo from "./bugs/grid-sorting-without-field";
// import Demo from "./features/charts/axis/ComplexAxisLabels";
// import Demo from "./bugs/pie-chart-active-bind";
let store = (window.store = new Store());
Expand Down
Loading
Loading