React graphical component to display metric over time with a time selection feature.
npm install @spider-analyzer/timeline@3.4.0
React graphical component to display metric over time with a time selection feature.
Live example: https://spider-analyzer.io/home/components/timeline/
domain
metricsDefinition
prop.metricsDefinition.legend[]
metricsDefinition.colors[]
metricsDefinition.legend[]
, metricsDefinition.colors[]
and histo.items[].metrics[]
metricsDefinition.legend[]
, and displayed left to the chartA cursor allows to select time in the viewed domain.
Limits:
smallestResolution
Time domain can be dragged forward or backward by pressing ctrl and dragging the mouse on the chart.
biggestVisibleDomain
duration is defined, once the visible domain reach this maximum visible duration, it is shifted forward or backward rather than widened further.maxDomain
limit is defined, the timeline cannot display dates outside this domainTimeLine is design for integration in time series or operational data reporting and display. It is perfectly suited for integration aside a grid of records to define the time range of records to display.
When integrating the TimeLine in your project, you have to define:
metricsDefinition
domains[0]
loaded by onLoadDefaultDomain
maxDomain
biggestVisibleDomain
smallestResolution
onResetTime
.
onLoadDefaultDomain
and reset the domains.onFormatTimeLegend
onFormatTimeToolTips
onFormatMetricLegend
Default resolution is millisecond, the time may rounded outside TimeLine component to second, minute or so.
onCustomRange
, onLoadDefaultDomain
, onLoadHisto
functions, as shown in demo.smallestResolution
and onFormatTimeToolTips
should be adjusted in consequence.You may also redefine labels displayed: labels
Install using npm or your favorite tool.
npm install --save @spider-analyzer/timeline
Include in your react application.
Warning: Current version requires some props to be Moment.js objects. So you would need Moment in your own application. You can reduce webpack bundle when using webpack, as timeline lib is not using any locale of Moment.js. See: https://github.com/jmblog/how-to-optimize-momentjs-with-webpack.
<TimeLine
style={{
width: '100%',
height: '100%',
backgroundColor: 'white',
}} //merged into main DOM node style
timeSpan = {this.state.timeSpan} //start and stop of selection
histo = {{
items: this.state.items, // table of histo columns: [{time: moment, metrics: [metric1 value, metric2 value...], total: sum of metrics}, ...]
intervalMs: this.state.intervalMs //interval duration of column in ms
}}
quality = {{
items: this.state.quality, //table of quality indicators [{time: moment, quality: float [0,1], tip: string}]
intervalMin: this.state.intervalMin //interval duration of column in Min
}}
qualityScale = {
scaleLinear()
.domain([0,1])
.range(['red','green'])
.clamp(true)
} //color scale (optional)
domains = {this.state.domains} //array of zoom levels
maxDomain = {this.state.maxDomain} //max zoom level allowed
metricsDefinition = {metricsDefinition}
biggestVisibleDomain = {moment.duration('P1M')} //maximum visible duration, cannot zoom out further
biggestTimeSpan = {moment.duration('P1D')} //maximum duration that can be selected
smallestResolution = {moment.duration('PT1M')} //max zoom level: 15pixels = duration
labels={{
backwardButtonTips: {
slideBackward: 'Slide into the past',
}
}}
tools={{
gotoNow: false
}}
fetchWhileSliding
onLoadDefaultDomain = {this.onLoadDefaultDomain} //called on mount to get the default domain
onLoadHisto = {this.onLoadHisto} //called to load items. give the needed interval, computed from props.width, props.domains[0]
onCustomRange = {this.onCustomRange} //called when user has drawn or resized the cursor
onShowMessage = {console.log} //called to display an error message
onUpdateDomains = {this.onUpdateDomains} //called to save domains
onResetTime = {this.onResetTime} //called when user want to reset timeline
onFormatTimeToolTips = {this.onFormatTimeToolTips} //called to display time in tooltips
onFormatTimeLegend = {multiFormat} //called to format x axis legend
onFormatMetricLegend = {formatNumber} //called to format y axis metric legend
/>
TimeLine component is a controlled component.
CSS style object, merged in top level <div/>
encapsulating the svg graphic.
The width and height of the chart should be defined there. In % or strict dimensions.
When resizing the window, the chart will adapt.
Defines the start and stop of the selected time window.
timeSpan: PropTypes.shape({
start: PropTypes.instanceOf(moment).isRequired,
stop: PropTypes.instanceOf(moment).isRequired
}).isRequired
Ex:
timeSpan = {
start: moment().subtract(1, 'HOUR'),
stop: moment().add(1, 'HOUR'),
}
Provides the data to display.
histo: PropTypes.shape({
items: PropTypes.arrayOf(PropTypes.shape({
time: PropTypes.instanceOf(moment).isRequired, //time of histogram bar
metrics: PropTypes.arrayOf(PropTypes.number).isRequired, //array of values
total: PropTypes.number.isRequired, //total of values of the array
})),
intervalMs: PropTypes.number //interval of each bar
}).isRequired
Provides the data to display on the quality line.
quality: PropTypes.shape({
items: PropTypes.arrayOf(PropTypes.shape({
time: PropTypes.instanceOf(moment).isRequired, //time of quality slot
quality: PropTypes.number.isRequired, //quality of the slot
tip: PropTypes.node, //text to display in tooltip - optional
})),
intervalMin: PropTypes.number //duration of each slot (in minutes)
})
Allows to override the color scale for the quality line. Expects a function converting a quality number into a CSS color.
qualityScale: PropTypes.func
Stores/defines the actual zooms levels of the timeline.
domains: PropTypes.arrayOf(PropTypes.shape({
min: PropTypes.instanceOf(moment).isRequired,
max: PropTypes.instanceOf(moment).isRequired
})).isRequired
onUpdateDomains
is called with an update of the domains.onLoadDefaultDomain
that should be used to define the initial domain.Ex:
domains = [{
min: moment().subtract(1, 'WEEK').startOf('DAY'),
max: moment().endOf('DAY')
}]
May/should specify a maximum domain that will set min and max bounds when shifting the TimeLine.
maxDomain: PropTypes.shape({
min: PropTypes.instanceOf(moment).isRequired,
max: PropTypes.instanceOf(moment).isRequired
})
Ex:
maxDomain = {
min: moment().subtract(2, 'MONTHS').startOf('DAY'),
max: moment().add(1, 'WEEK').endOf('DAY')
}
Defines the metrics that will be displayed on the chart: count, legend, formatting
metricsDefinition: PropTypes.shape({
count: PropTypes.number.isRequired,
legends: PropTypes.arrayOf(PropTypes.string).isRequired,
colors: PropTypes.arrayOf(PropTypes.shape({
fill: PropTypes.string.isRequired,
stroke: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
})).isRequired
}).isRequired
Ex:
metricsDefinition = {
count: 3, //Count of metric in the graphic
legends: ['Info', 'Warn', 'Fail'], //Name of the metrics, in order. Will be displayed left of the chart
colors: [{ //Colors of the metrics, in order: fill of bar, stroke of bar, text in legend
fill: '#9be18c',
stroke: '#5db352',
text: '#5db352'
},
{
fill: '#f6bc62',
stroke: '#e69825',
text: '#e69825'
},{
fill: '#ff5d5a',
stroke: '#f6251e',
text: '#f6251e'
}]
}
Defines the maximum visible duration of a domain, if any. For instance, allows set a maxDomain of 1 year, but limit visible histogram to a window of 1 month. Limits the overloading of the aggregation API.
biggestVisibleDomain: PropTypes.object //expects a Duration created by moment.duration() object
Ex:
biggestVisibleDomain = moment.duration('P1M')
Defines the maximum duration that can be selected, if any.
biggestTimeSpan: PropTypes.object //expects a Duration created by moment.duration() object
Ex:
biggestTimeSpan = moment.duration('P1D')
Defines the smallest zoom resolution to display (for 15 pixels).
smallestResolution: PropTypes.object.isRequired //expects a Duration created by moment.duration() object
Ex:
smallestResolution = moment.duration('PT1M')
Overrides labels to display for ToolTips and onShowMessage calls. Provided for translation.
labels: PropTypes.shape({
forwardButtonTips: PropTypes.shape({
extendForward: PropTypes.string,
slideForward: PropTypes.string,
}),
backwardButtonTips: PropTypes.shape({
extendBackward: PropTypes.string,
slideBackward: PropTypes.string,
}),
resetButtonTip: PropTypes.string,
gotoNowButtonTip: PropTypes.string,
doubleClickMaxZoomMsg: PropTypes.string,
scrollMaxZoomMsg: PropTypes.string,
zoomInWithoutChangingSelectionMsg: PropTypes.string,
zoomSelectionResolutionExtended: PropTypes.string,
maxSelectionMsg: PropTypes.string,
})
Default:
const defaultLabels = {
forwardButtonTips: {
extendForward: 'Extend forward',
slideForward: 'Slide forward',
},
backwardButtonTips: {
extendBackward: 'Extend backward',
slideBackward: 'Slide backward',
},
resetButtonTip: 'Reset time span',
gotoNowButtonTip: 'Goto Now',
doubleClickMaxZoomMsg: 'Cannot zoom anymore!',
scrollMaxZoomMsg: 'Cannot zoom anymore!',
zoomInWithoutChangingSelectionMsg: 'Please change time selection before clicking on zoom ;)',
zoomSelectionResolutionExtended: 'You reached maximum zoom level',
maxSelectionMsg: 'You reached maximum selection',
gotoCursor: 'Goto Cursor',
zoomInLabel: 'Zoom in',
zoomOutLabel: 'Zoom out',
};
Allow disactivating tools. All are present by default.
tools: PropTypes.shape({
slideForward: PropTypes.bool,
slideBackward: PropTypes.bool,
resetTimeline: PropTypes.bool,
gotoNow: PropTypes.bool,
cursor: PropTypes.bool,
})
Defines if timeline should try to refresh data when sliding domain. May overload the aggregation API.
fetchWhileSliding: PropTypes.bool
Called on mount to get the default domain.
domains
prop.[{min: moment, max: moment}]
.Called to load items. Give the needed interval, computed from props.width and props.domains[0].
histo
prop.Called on:
fetchWhileSliding
prop is setParameters:
Called when user has drawn or resized the cursor.
timeSpan
prop.Called to display an error message.
Called to save domains.
domains
prop.Called when user want to reset timeline.
domains
prop. Usually to a new array with only default domain.domains[0]
is expected to be changed (new object) to trigger a new onLoadHisto
call.Called to display time in tooltips.
Ex:
onFormatTimeToolTips = (time) => {
return moment(time).second(0).millisecond(0).format(TIME_FORMAT_TOOLTIP);
};
Called to format the x axis legend. Depending of zoom resolution, the input will be a date rounded to:
millisecond
second
minute
hour
day
month
year
Must return a formatted date as string.
Result should be different for each rounded date.
Example:
import {timeFormat, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3';
const formatMillisecond = timeFormat('.%L'), // .456
formatSecond = timeFormat(':%S'), // :43
formatMinute = timeFormat('%H:%M'), // 13:12
formatHour = timeFormat('%H:00'), // 13:00
formatDay = timeFormat('%b %d'), // Nov 02
formatMonth = timeFormat('%b %d'), // Nov 01
formatYear = timeFormat('%Y %b %d') // 2017 Nov 01
;
const onFormatTimeLegend = (date) => {
return (timeSecond(date) < date ? formatMillisecond
: timeMinute(date) < date ? formatSecond
: timeHour(date) < date ? formatMinute
: timeDay(date) < date ? formatHour
: timeMonth(date) < date ? formatDay
: timeYear(date) < date ? formatMonth
: formatYear)(date);
};
Called to format metric amount value to display on the top of y axis.
Example:
import {formatLocale } from 'd3';
const locale = formatLocale({
decimal: '.',
thousands: ' ',
grouping: [3],
});
const onFormatMetricLegend = (number) => {
return locale.format(`,d`)(number);
};
You may run the demo in hot reloading mode:
#clone the repo
git clone https://gitlab.com/TincaTibo/timeline.git
cd timeline/test
#make a docker image with a demo app
make image (requires docker and npm)
Then, run the demo in prod mode
# run the demo in prod mode
make demo
Or in dev mode
# run the demo in dev mode (volume mount the dev files for hot reloading)
make start
To access it, go to http://localhost:5000 in your browser