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
190 changes: 190 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ Designed for developers using the Anedya Frontend SDK, the library provides drop
- [Animation configuration](#animation-configuration)
- [`onDataChange` — data-driven rendering](#ondatachange--data-driven-rendering-1)
- [How props are resolved](#how-props-are-resolved-1)
- [AnedyaLineChart](#anedyalinechart)
- [Required props](#required-props-2)
- [Time range & data fetching](#time-range--data-fetching)
- [The D3 passthrough model](#the-d3-passthrough-model)
- [Tooltip](#tooltip-1)
- [Area, points & grid](#area-points--grid)
- [`onDataChange` — data-driven rendering](#ondatachange--data-driven-rendering-2)
- [Common](#common)
- [Automatic theme detection](#automatic-theme-detection)
- [Formatting vs rendering](#formatting-vs-rendering)
Expand Down Expand Up @@ -665,6 +672,189 @@ Later layers win whenever two Tailwind utilities conflict, resolved with `twMerg

---

### AnedyaLineChart

A time-series line chart built on D3 — shows a variable's history as a line, with an optional gradient area fill, point markers, gridlines, a floating "latest value" badge, and a hover tooltip with a vertical crosshair.

```jsx
<AnedyaLineChart node={node} variable="humidity" title="Humidity" unit="%" />
```

#### Required props

| Prop | Type | Description |
|---|---|---|
| `node` | `any` | `anedya.newNode(client, nodeId)` |
| `variable` | `string` | Variable name to fetch |

---

#### Data fetching

Calls `node.getData({ variable, from, to, limit, order })` **and** `node.getLatestData(variable)` together, on mount and whenever `refresh` is clicked.

| Prop | Type | Default | Description |
|---|---|---|---|
| `from` | `number` | one year before `to` | Start of the range, in **milliseconds** |
| `to` | `number` | now | End of the range, in **milliseconds** |
| `limit` | `number` | `1000` | Max data points fetched |
| `order` | `"asc" \| "desc"` | `"asc"` | Fetch order — deliberately different from the SDK's own `"desc"` default, since a chart reads left-to-right chronologically |
| `refresh` | `boolean` | `true` | Show the refresh button, top-right of the toolbar |
| `onRefresh` | `() => void` | — | Called after a manual refresh completes |

**The "latest value" fetch always runs, even if the badge is hidden** — it also serves as a single-point fallback: if `getData` returns no points in the requested range but a live reading exists, the chart draws that one point instead of showing an empty state.

```jsx
<AnedyaLineChart
node={node}
variable="humidity"
from={Date.now() - 24 * 60 * 60 * 1000}
to={Date.now()}
/>
```

---

#### The D3 passthrough model

Chart-drawing customizations are **real D3 objects, passed through unmodified** — if you already know D3, you already know how to customize this chart.

```jsx
import * as d3 from "d3";

<AnedyaLineChart
node={node}
variable="temperature"
line={(line) => line.curve(d3.curveMonotoneX)}
xAxis={(axis) => axis.ticks(6).tickFormat(d3.timeFormat("%b %d") as any)}
/>
```

| Prop | Type | Description |
|---|---|---|
| `line` | `(line: d3.Line<LineChartDataPoint>) => d3.Line<LineChartDataPoint>` | Receives a `d3.line()` already bound to the data's x/y accessors — chain any D3 curve/method on it |
| `xScale` | `(scale: d3.ScaleTime<number, number>) => ...` | Customize the time scale directly |
| `yScale` | `(scale: d3.ScaleLinear<number, number>) => ...` | Customize the value scale directly |
| `xAxis` | `(axis: d3.Axis<...>) => ...` | Customize the x-axis generator directly |
| `yAxis` | `(axis: d3.Axis<...>) => ...` | Customize the y-axis generator directly |

**Note on data with long flat stretches** (e.g. a sensor reporting the same value repeatedly): the line can visually look "stepped" even with the default `curveLinear` — that's the data, not the curve. Test curve changes against data with genuine variation to see the effect clearly.

**X-axis labels auto-rotate** when they'd otherwise overlap at the current tick spacing — short default labels stay horizontal; longer custom formats rotate automatically. No configuration needed.

---

#### Tooltip

On by default, with built-in content, pointer-following positioning, and a vertical dashed crosshair line at the hovered point.

```jsx
<AnedyaLineChart
node={node}
variable="humidity"
tooltip={{ content: (d) => <span className="text-red-400">{d.value}% RH</span> }}
/>
```

| Prop | Type | Description |
|---|---|---|
| `show` | `boolean` | Default `true` |
| `content` | `(d: LineChartDataPoint) => ReactNode` | Custom content, keeps the widget's built-in positioning + crosshair |
| `onMouseOver` / `onMouseMove` / `onMouseOut` | Raw D3 event handlers `(event: MouseEvent, d: LineChartDataPoint) => void` | Full manual control — same signatures you'd pass to `.on("mouseover", ...)` on a real D3 selection. Providing any of these opts you out of the built-in tooltip **and** crosshair entirely. |

---

#### Area, points & grid

```jsx
<AnedyaLineChart
node={node}
variable="humidity"
area={{ show: true, opacity: 0.35 }}
point={{ show: true, radius: 4 }}
grid={{ ticksY: 4, ticksX: 6 }}
/>
```

| Prop | Type | Description |
|---|---|---|
| `area.show` | `boolean` | Gradient fill under the line, fading to transparent. Default `false` |
| `area.opacity` | `number` | Top-of-gradient opacity. Default `0.35` |
| `point.show` | `boolean` | Dot at each data point. Default `false` |
| `point.radius` | `number` | Dot radius in px. Default `3` |
| `grid.show` | `boolean` | Background gridlines (both axes). Default `true` |
| `grid.ticksX` / `grid.ticksY` | `number` | Approximate gridline count per axis. Default `5` each |

The area's gradient color follows `currentColor`, same as the line — set both together via `styles`:
```jsx
<AnedyaLineChart styles={{ line: "text-[#42a5f5]", area: "text-[#42a5f5]" }} area={{ show: true }} />
```

---

#### Latest value badge

A floating pill pinned to the top-right corner, showing the most recent reading — separate from the hover tooltip, always visible (when enabled) regardless of mouse position.

```jsx
<AnedyaLineChart node={node} variable="humidity" showLatestValue={false} />
```

| Prop | Type | Default | Description |
|---|---|---|---|
| `showLatestValue` | `boolean` | `true` | Whether the badge is shown. The `getLatestData` call still always runs regardless — this only controls visibility |

---

#### Min / Avg / Max summary

```jsx
<AnedyaLineChart node={node} variable="humidity" summary />
```

Shows a row below the chart with the minimum, average, and maximum values from the fetched range, including timestamps for min/max. Computed from the real range data only — not shown when the chart is displaying the single-point fallback.

> If you pass a fixed `height` alongside `summary`, add roughly 44px extra to leave room for the summary row — otherwise it may be clipped.

---

#### Sizing

Without an explicit `width`/`height`, the chart fills its container's width and derives a proportional height, capped at a sensible maximum (`320px` by default) so it doesn't grow unreasonably large inside a very wide container.

```jsx
<AnedyaLineChart node={node} variable="humidity" width={600} height={280} />
```

| Prop | Type | Description |
|---|---|---|
| `width` / `height` | `number` | Explicit size in px |
| `minWidth` / `maxWidth` | `number` | Width bounds |
| `minHeight` / `maxHeight` | `number` | Height bounds — `maxHeight` also caps the default auto-computed height |

---

#### `onDataChange` — data-driven rendering

```jsx
<AnedyaLineChart
node={node}
variable="humidity"
onDataChange={(data, meta) => {
if (meta.kind === "error") {
return { renderError: () => <span className="italic">Offline</span> };
}
if (!data) return;
const max = Math.max(...data.map((d) => d.value));
if (max > 90) return { title: "⚠ Peak humidity detected" };
}}
/>
```

Called whenever the fetched dataset changes; receives the full array of points (not a single value) plus a `meta` object (`{ kind: "success" | "error" | "empty", error? }`). Returns a partial set of props that temporarily override the chart's own props — same resolution model as `AnedyaCard`/`AnedyaGauge`.

---

## Common

The sections below apply to every widget in this SDK. Where behavior is identical, only the shared explanation is given once; each subsection includes an example for both `AnedyaCard` and `AnedyaGauge`.
Expand Down
31 changes: 31 additions & 0 deletions examples/application-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ To display live values from Anedya, provide a `node` and `variable`.
/>
```

### Line Chart

```tsx
<AnedyaLineChart
node={node}
variable="humidity"
/>
```

Unlike `AnedyaCard`/`AnedyaGauge`, `AnedyaLineChart` doesn't support a manual `value` mode — it always fetches a time-series range via `node`/`variable`.

---

## Manual values
Expand Down Expand Up @@ -122,6 +133,7 @@ When both `value` and `node`/`variable` are provided, the manual value is used i
# Required props

The widgets support two operating modes.
Both props are required when displaying live data — this applies to `AnedyaLineChart` as well, which has no manual `value` mode.

## Live data mode

Expand Down Expand Up @@ -188,6 +200,25 @@ The example application demonstrates many of these options and can be used as a

---

# Line chart–specific props

`AnedyaLineChart` also supports additional configuration:

- `from` / `to` — time range in milliseconds (default: last year)
- `limit` / `order` — max points fetched / fetch order
- `refresh` / `onRefresh` — manual refresh button and callback
- `area` — gradient fill under the line (`show`, `opacity`)
- `point` — data-point markers (`show`, `radius`)
- `grid` — background gridlines (`show`, `ticksX`, `ticksY`)
- `tooltip` — hover tooltip (`show`, `content`, or raw `onMouseOver`/`onMouseMove`/`onMouseOut`)
- `summary` — Min/Avg/Max row below the chart
- `showLatestValue` — the floating "latest value" badge
- `line` / `xScale` / `yScale` / `xAxis` / `yAxis` — real D3 objects, passed through for direct customization

The example application demonstrates several of these and can be used as a reference when configuring your own charts.

---

# Project purpose

This application is intended as a reference implementation for the Anedya Widgets SDK. It demonstrates the recommended setup, authentication flow, and common widget configurations, making it a useful starting point for building custom dashboards and IoT applications.
46 changes: 46 additions & 0 deletions examples/application-example/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,52 @@ export default function App() {
formatValue={(v) => `${v} km/h`}
labelText={(ts) => `Refreshed at ${new Date(ts).toLocaleTimeString()}`}
className="bg-white p-4 rounded-xl"
/>

{/* ================================================================
* SECTION 3 — AnedyaLineChart
* ================================================================ */}

{/* ----------------------------------------------------------------
* 3.1 Default appearance — no width/height, no theme. Fills its
* container's width and derives a proportional height capped at
* ~320px (see maxHeight in Sizing).
* ---------------------------------------------------------------- */}
<AnedyaLineChart
{...commonProps}
title="Default Line Chart"
unit="%"
/>

{/* ----------------------------------------------------------------
* 3.2 Explicit sizing + area fill + summary row. The area's
* gradient reads `currentColor`, so it's set via `styles` on the
* `line`/`area` slots together.
* ---------------------------------------------------------------- */}
<AnedyaLineChart
{...commonProps}
title="Humidity — Last Year"
unit="%"
width={600}
height={300}
area={{ show: true }}
summary
styles={{ line: "text-[#42a5f5]", area: "text-[#42a5f5]" }}
/>

{/* ----------------------------------------------------------------
* 3.3 The D3 passthrough model — real d3.line()/axis objects,
* chained exactly as you would with raw D3.
* ---------------------------------------------------------------- */}
<AnedyaLineChart
{...commonProps}
title="Custom Curve + Ticks"
unit="%"
width={600}
height={300}
point={{ show: true, radius: 4 }}
line={(line) => line.curve(d3.curveMonotoneX)}
xAxis={(axis) => axis.ticks(6)}
/>
</div>
);
Expand Down
Loading
Loading