Skip to content

Commit 3933f96

Browse files
Merge pull request #6 from mitigate-dev/develop
feat: Add error-segment feature
2 parents 1030ed1 + 66cbdcf commit 3933f96

9 files changed

Lines changed: 721 additions & 85 deletions

File tree

README.md

Lines changed: 146 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,25 @@ export default function App() {
105105

106106
### Chart Props
107107

108-
| Prop | Type | Required | Description |
109-
| ------------------ | ----------------- | -------- | ---------------------------------------------------------------------------------- |
110-
| `width` | `number` || Chart width in pixels |
111-
| `height` | `number` || Chart height in pixels |
112-
| `datasets` | `Dataset[]` || Array of data series to display |
113-
| `colors` | `ChartColors` || Color configuration for chart elements |
114-
| `timeDomain` | `TimeDomain` || Control intial zoom level / scale of X-axis, doesn't have to fit the whole dataset |
115-
| `noDataString` | `string` || Message to show when no data is available |
116-
| `zoomEnabled` | `boolean` || Enable zoom guesture |
117-
| `locale` | `string` || Locale for date/time formatting (default: 'en') |
118-
| `marginHorizontal` | `number` || Horizontal margin in pixels |
119-
| `calendarStrings` | `CalendarStrings` || Custom calendar strings for localization |
120-
| `onZoomStarted` | `() => void` || Callback when zoom interaction starts |
121-
| `onZoomEnded` | `() => void` || Callback when zoom interaction ends |
108+
| Prop | Type | Required | Description |
109+
| ------------------------ | ------------------------------------- | -------- | ---------------------------------------------------------------------------------- |
110+
| `width` | `number` || Chart width in pixels |
111+
| `height` | `number` || Chart height in pixels |
112+
| `datasets` | `Dataset[]` || Array of data series to display |
113+
| `colors` | `ChartColors` || Color configuration for chart elements |
114+
| `timeDomain` | `TimeDomain` || Control intial zoom level / scale of X-axis, doesn't have to fit the whole dataset |
115+
| `noDataString` | `string` || Message to show when no data is available |
116+
| `zoomEnabled` | `boolean` || Enable zoom guesture |
117+
| `locale` | `string` || Locale for date/time formatting (default: 'en') |
118+
| `marginHorizontal` | `number` || Horizontal margin in pixels |
119+
| `highlightPosition` | `number` || Position of highlight line (0-1, default: 0.5 for center) |
120+
| `highlightValuePosition` | `'top' \| 'tooltip' \| 'none'` || Where to show values: header, floating tooltip, or hidden (default: 'top') |
121+
| `xDividerConfig` | `XDividerConfig` || Style for vertical dividers on X axis (ticks or segments) |
122+
| `errorSegments` | `ErrorSegment[]` || Time ranges with error messages to display |
123+
| `calendarStrings` | `CalendarStrings` || Custom calendar strings for localization |
124+
| `onZoomStarted` | `() => void` || Callback when zoom interaction starts |
125+
| `onZoomEnded` | `() => void` || Callback when zoom interaction ends |
126+
| `onHighlightChanged` | `(payload: HighlightPayload) => void` || Callback when highlight position changes with current values |
122127

123128
### Types
124129

@@ -207,8 +212,130 @@ type CalendarStrings = {
207212
}
208213
```
209214
215+
#### ErrorSegment
216+
217+
```typescript
218+
type ErrorSegment = {
219+
message: string // Error message to display
220+
messageColor: string // Color for the error message
221+
start: number // Start timestamp (ms)
222+
end: number // End timestamp (ms)
223+
}
224+
```
225+
226+
#### XDividerConfig
227+
228+
```typescript
229+
// Option 1: Tick style (lines extending from labels)
230+
type XDividerTick = {
231+
type: 'tick'
232+
color?: string // Defaults to ChartColors.border
233+
strokeWidth?: number // Defaults to 0.5
234+
strokeDasharray?: string // Defaults to '2,2'
235+
}
236+
237+
// Option 2: Segment style (alternating full-height segments)
238+
type XDividerSegment = {
239+
type: 'segment'
240+
variant?: 'hour' | 'day' | { dynamicThreshold: number } // Defaults to dynamic
241+
color?: string // Defaults to '#FBFBFC' with gradient
242+
}
243+
244+
type XDividerConfig = XDividerTick | XDividerSegment
245+
```
246+
247+
#### HighlightPayload
248+
249+
```typescript
250+
type HighlightPayload = {
251+
timestamp: number // Exact timestamp at highlight position
252+
values: Array<{
253+
value: number | null // Data value at this point
254+
timestamp: number // Point timestamp
255+
color: string // Dataset color
256+
errorMessage: string | null // Error message if in error segment
257+
measurementName: string // Dataset name
258+
} | null> // null if dataset has no data at this position
259+
}
260+
```
261+
210262
## Advanced Usage
211263
264+
### Highlight Position and Value Display
265+
266+
Control where the vertical highlight line appears and how values are displayed:
267+
268+
```tsx
269+
<Chart
270+
// ... other props
271+
highlightPosition={0.7} // Position from 0 (left) to 1 (right), default: 0.5 (center)
272+
highlightValuePosition="tooltip" // 'top' (header), 'tooltip' (floating box), or 'none'
273+
onHighlightChanged={useCallback((payload) => {
274+
console.log('Current timestamp:', payload.timestamp)
275+
console.log('Values:', payload.values)
276+
}, [])}
277+
/>
278+
```
279+
280+
**Highlight value position modes:**
281+
282+
- `'top'` (default): Shows values in the chart header area
283+
- `'tooltip'`: Displays a floating tooltip box near the highlight line
284+
- `'none'`: Hides value display, useful with `onHighlightChanged` for custom UI
285+
286+
### X-Axis Dividers
287+
288+
Customize the vertical grid lines on the X-axis:
289+
290+
```tsx
291+
// Dashed tick lines (default style)
292+
<Chart
293+
xDividerConfig={{
294+
type: 'tick',
295+
color: '#999',
296+
strokeWidth: 1,
297+
strokeDasharray: '4,4',
298+
}}
299+
/>
300+
301+
// Alternating background segments
302+
<Chart
303+
xDividerConfig={{
304+
type: 'segment',
305+
variant: 'hour', // or 'day' or { dynamicThreshold: 95040000 }
306+
color: '#F5F5F5',
307+
}}
308+
/>
309+
```
310+
311+
**Segment variants:**
312+
313+
- `'hour'`: Every other hour has a background segment
314+
- `'day'`: Every other day has a background segment
315+
- `{ dynamicThreshold: number }`: Auto-switches between hour/day based on visible time range
316+
317+
### Error Segments
318+
319+
Display error messages and highlight problematic time ranges:
320+
321+
```tsx
322+
const errorSegments = [
323+
{
324+
message: 'Sensor offline',
325+
messageColor: '#FF0000',
326+
start: Date.now() - 3600000,
327+
end: Date.now() - 1800000,
328+
},
329+
]
330+
331+
<Chart
332+
// ... other props
333+
errorSegments={errorSegments}
334+
/>
335+
```
336+
337+
Data points within error segments will show the error message instead of values, and the background will be highlighted.
338+
212339
### Multiple Datasets
213340

214341
```tsx
@@ -328,6 +455,11 @@ const datasetWithSlices = {
328455
- react-native-webview >= 11.0.0
329456
- iOS 11.0+ / Android API 21+
330457

458+
## Known Limitations
459+
460+
- **Error segment gradient**: The gradient style for error segments is currently hardcoded and cannot be customized
461+
- **Highlight snap behavior**: The snap-to-data-point logic (distance thresholds of 8 pixels and 10 minutes) is hardcoded and not configurable via props
462+
331463
## Contributing
332464

333465
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.

android/src/main/assets/chart.html

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
-moz-user-select: none;
1818
-ms-user-select: none;
1919
user-select: none;
20+
font-family: 'Helvetica';
2021
margin: 0;
2122
}
2223
#my_dataviz {
2324
position: relative;
2425
font-size: 0;
2526
}
26-
#highlight_holder {
27+
#top_highlight_holder {
2728
display: flex;
2829
flex-direction: row;
2930
align-items: center;
@@ -61,12 +62,50 @@
6162
font-weight: 700;
6263
margin-left: 8px;
6364
}
65+
#tooltip_holder {
66+
position: absolute;
67+
width: fit-content;
68+
top: 0;
69+
bottom: 0;
70+
pointer-events: none;
71+
z-index: 10;
72+
}
73+
#tooltip_box {
74+
background-color: white;
75+
display: flex;
76+
flex-direction: column;
77+
width: fit-content;
78+
height: fit-content;
79+
pointer-events: all;
80+
border-radius: 6px;
81+
padding: 6px;
82+
margin-top: 4px;
83+
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
84+
transform: translateX(-50%);
85+
}
86+
#tooltip_holder span {
87+
font-family: Arial, Helvetica, sans-serif;
88+
font-size: medium;
89+
text-align: center;
90+
}
91+
#tooltip_values_holder {
92+
display: flex;
93+
flex-direction: column;
94+
gap: 4px;
95+
}
6496
</style>
6597

6698
<!-- Create a div where the graph will take place -->
6799
<body>
68100
<div id="my_dataviz">
69-
<div id="highlight_holder">
101+
<div id="tooltip_holder" style="display: none">
102+
<div id="tooltip_box">
103+
<div id="tooltip_values_holder"></div>
104+
<span id="tooltip_time"></span>
105+
<span id="tooltip_date"></span>
106+
</div>
107+
</div>
108+
<div id="top_highlight_holder" style="display: none">
70109
<div id="labels_holder" class="row_holder"></div>
71110
<div id="values_holder" class="row_holder"></div>
72111
<div id="timeholder">

0 commit comments

Comments
 (0)