Skip to content

Commit 2803c22

Browse files
committed
feat: Support slices
1 parent 304bf37 commit 2803c22

5 files changed

Lines changed: 309 additions & 22 deletions

File tree

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,9 @@ type Dataset = {
132132
unit: string // Unit symbol (e.g., '°C', 'kg', 'm/s')
133133
decimals: number // Number of decimal places to show
134134
minDeltaY?: number // Minimum Y-axis change to show, limit Y-zoom
135-
areaColor?: string // Optional area fill color (defaults to base color)
135+
areaColor?: string | null // Area fill color (null to disable, defaults to base color)
136136
axisColor?: string // Optional Y-axis text color (defaults to base color)
137+
slices?: Slices // Optional background regions/zones
137138
decimalSeparator?: '.' | ',' // Decimal separator
138139
domain?: {
139140
// Custom Y-axis range
@@ -142,6 +143,16 @@ type Dataset = {
142143
}
143144
}
144145

146+
type Slices = {
147+
start: number // Start timestamp for the slices
148+
end: number // End timestamp for the slices
149+
items: Array<{
150+
color: string // Background color (use alpha for transparency)
151+
start: { top: number; bottom: number } // Y-values at start time
152+
end: { top: number; bottom: number } // Y-values at end time
153+
}>
154+
}
155+
145156
type ThresholdColor = {
146157
type: 'thresholds'
147158
baseColor: string // Default color for values below all thresholds
@@ -258,6 +269,48 @@ const datasetWithThresholds = {
258269
- **Battery levels**: Red (critical) → Orange (low) → Green (healthy)
259270
- **Network latency**: Green (fast) → Yellow (moderate) → Red (slow)
260271

272+
### Background Slices/Zones
273+
274+
Add colored background regions to highlight acceptable ranges, warning zones, or targets:
275+
276+
```tsx
277+
const datasetWithSlices = {
278+
measurementName: 'CPU Usage',
279+
color: '#333',
280+
unit: '%',
281+
decimals: 1,
282+
points: cpuData,
283+
slices: {
284+
start: startTimestamp,
285+
end: endTimestamp,
286+
items: [
287+
{
288+
color: '#00FF0020', // Green with transparency (healthy zone)
289+
start: { bottom: 0, top: 50 },
290+
end: { bottom: 0, top: 50 },
291+
},
292+
{
293+
color: '#FFA50020', // Orange with transparency (warning zone)
294+
start: { bottom: 50, top: 80 },
295+
end: { bottom: 50, top: 80 },
296+
},
297+
{
298+
color: '#FF000020', // Red with transparency (critical zone)
299+
start: { bottom: 80, top: 100 },
300+
end: { bottom: 80, top: 100 },
301+
},
302+
],
303+
},
304+
}
305+
```
306+
307+
**Features:**
308+
309+
- **Horizontal zones**: Use same top/bottom values for start and end
310+
- **Diagonal zones**: Use different values to create slanted regions
311+
- **Transparency**: Use alpha channel (e.g., `#FF000020`) for subtle backgrounds
312+
- **Multiple regions**: Stack different colored zones for complex visualizations
313+
261314
### Zoom Callbacks
262315

263316
```tsx

example/src/App.tsx

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, { useMemo, useState } from 'react'
22
import { View, StyleSheet, TouchableOpacity, Text, Switch } from 'react-native'
33

4-
import Chart, { ChartProps, Dataset } from 'react-native-d3-chart'
4+
import Chart, { ChartProps, Dataset, Point } from 'react-native-d3-chart'
5+
6+
import { generateRandomPulses } from './helpers/generateRandomPulses'
57

68
type TimeDomainType = 'hour' | 'day' | 'week' | 'month'
79

@@ -50,7 +52,18 @@ enum Measurement {
5052
Blue = 'Blue',
5153
Green = 'Green',
5254
Pink = 'Pink',
55+
Pulses = 'Pulses',
56+
PulseRate = 'Pulse Rate',
5357
}
58+
59+
const averagePulseRatePerHour = 120
60+
const pulsePoints = generateRandomPulses({
61+
maxAgeDays: 60,
62+
burstFactor: 10,
63+
burstProbability: 0.05,
64+
averageRatePerHour: averagePulseRatePerHour,
65+
})
66+
5467
const measurementKeys = Object.values(Measurement)
5568
const measurementsRecords: Record<Measurement, Dataset> = {
5669
[Measurement.Temperature]: {
@@ -62,7 +75,7 @@ const measurementsRecords: Record<Measurement, Dataset> = {
6275
startingValue: -8,
6376
}),
6477
decimals: 0,
65-
areaColor: '#83cba8',
78+
areaColor: '#c4deff',
6679
color: {
6780
type: 'thresholds',
6881
baseColor: '#3d91ff',
@@ -105,6 +118,110 @@ const measurementsRecords: Record<Measurement, Dataset> = {
105118
color: '#e0e',
106119
measurementName: Measurement.Pink,
107120
},
121+
122+
[Measurement.PulseRate]: {
123+
unit: 'pulses/h',
124+
points: pulsePoints.map(({ timestamp }, index, array) => {
125+
const slice = [...array.slice(Math.max(0, index - 60 + 1), index + 1)] // this and previous 59 minutes
126+
const hourSum = slice.reduce((sum, point) => sum + (point.value || 0), 0)
127+
128+
return {
129+
timestamp,
130+
value: hourSum,
131+
}
132+
}),
133+
slices: {
134+
start: pulsePoints[0]?.timestamp ?? 0,
135+
end: pulsePoints[pulsePoints.length - 1]?.timestamp ?? 0,
136+
items: [
137+
{
138+
color: '#08985115',
139+
start: { bottom: 0, top: averagePulseRatePerHour },
140+
end: { bottom: 0, top: averagePulseRatePerHour },
141+
},
142+
{
143+
color: '#ffc40015',
144+
start: {
145+
bottom: averagePulseRatePerHour,
146+
top: averagePulseRatePerHour * 1.1,
147+
},
148+
end: {
149+
bottom: averagePulseRatePerHour,
150+
top: averagePulseRatePerHour * 1.1,
151+
},
152+
},
153+
{
154+
color: '#bb222215',
155+
start: {
156+
bottom: averagePulseRatePerHour * 1.1,
157+
top: averagePulseRatePerHour * 10,
158+
},
159+
end: {
160+
bottom: averagePulseRatePerHour * 1.1,
161+
top: averagePulseRatePerHour * 10,
162+
},
163+
},
164+
],
165+
},
166+
decimals: 0,
167+
color: '#000',
168+
areaColor: null,
169+
measurementName: Measurement.PulseRate,
170+
},
171+
[Measurement.Pulses]: {
172+
unit: 'pulses',
173+
points: pulsePoints.reduce(
174+
({ array, sum }, { timestamp, value }) => {
175+
const newSum = sum + (value || 0)
176+
array.push({ timestamp, value: newSum })
177+
return {
178+
array,
179+
sum: newSum,
180+
}
181+
},
182+
{ array: [] as Point[], sum: 0 }
183+
).array,
184+
decimals: 0,
185+
color: '#000',
186+
areaColor: null,
187+
measurementName: 'Pulses cumulative',
188+
slices: (() => {
189+
const start = pulsePoints[0]?.timestamp ?? 0
190+
const end = pulsePoints[pulsePoints.length - 1]?.timestamp ?? 0
191+
const dataDurationMs = end - start
192+
const dataDurationHours = dataDurationMs / (60 * 60 * 1000)
193+
194+
const warningRate = averagePulseRatePerHour
195+
const dangerRate = warningRate * 1.1
196+
197+
const warningEnd = dataDurationHours * warningRate
198+
const dangerEnd = dataDurationHours * dangerRate
199+
200+
const topEdge = dangerRate * 2 * dataDurationHours
201+
202+
return {
203+
end,
204+
start,
205+
items: [
206+
{
207+
color: '#08985115',
208+
start: { bottom: 0, top: 0 },
209+
end: { bottom: 0, top: warningEnd },
210+
},
211+
{
212+
color: '#ffc40015',
213+
start: { bottom: 0, top: 0 },
214+
end: { bottom: warningEnd, top: dangerEnd },
215+
},
216+
{
217+
color: '#bb222215',
218+
start: { bottom: 0, top: topEdge },
219+
end: { bottom: dangerEnd, top: topEdge },
220+
},
221+
],
222+
}
223+
})(),
224+
},
108225
}
109226

110227
export default function App() {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
export function generateRandomPulses({
2+
maxAgeDays = 30,
3+
intervalMs = 60 * 1000,
4+
averageRatePerHour = 120,
5+
burstFactor = 3,
6+
burstProbability = 0.1,
7+
} = {}) {
8+
const points = []
9+
const now = Date.now()
10+
const oldestTimestamp = now - maxAgeDays * 24 * 60 * 60 * 1000 // maxAgeDays ago
11+
const averageRate = averageRatePerHour / 60 // Convert to per minute
12+
13+
for (
14+
let timestamp = oldestTimestamp;
15+
timestamp <= now;
16+
timestamp += intervalMs
17+
) {
18+
// Generate clustered/bursty Poisson data with higher short-term variance
19+
// Calculate rates to maintain target average: baseRate * (1-p) + burstRate * p = averageRate
20+
const burstRate = averageRate * burstFactor
21+
const baseRate =
22+
(averageRate - burstRate * burstProbability) / (1 - burstProbability)
23+
24+
let pulses = 0
25+
26+
// Determine if this is a burst period
27+
const isBurst = Math.random() < burstProbability
28+
const lambda = isBurst ? burstRate : baseRate
29+
30+
if (lambda < 30) {
31+
// Knuth's algorithm for small λ
32+
const L = Math.exp(-lambda)
33+
let k = 0
34+
let p = 1
35+
36+
do {
37+
k++
38+
p *= Math.random()
39+
} while (p > L)
40+
41+
pulses = k - 1
42+
} else {
43+
// Normal approximation for large λ
44+
const u1 = Math.random()
45+
const u2 = Math.random()
46+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2)
47+
pulses = Math.max(0, Math.round(lambda + Math.sqrt(lambda) * z))
48+
}
49+
50+
points.push({ timestamp, value: pulses })
51+
}
52+
53+
return points
54+
}

0 commit comments

Comments
 (0)