@ux-aspects/ux-aspects
Version:
Open source user interface framework for building modern, responsive, mobile big data applications
757 lines • 115 kB
JavaScript
import { END, HOME, LEFT_ARROW, RIGHT_ARROW } from '@angular/cdk/keycodes';
import { NgModule } from '@angular/core';
import * as i0 from "@angular/core";
const timelineDefaultOptions = {
timeline: {
backgroundColor: '#f1f2f3',
selectionColor: 'rgba(198, 23, 157, 0.15)',
// eslint-disable-next-line @typescript-eslint/no-empty-function
onChange: function onChange() { },
keyboard: {
step: 2_592_000_000, // 30 days
},
handles: {
backgroundColor: '#000',
foregroundColor: '#dcdedf',
focusIndicatorColor: 'rgba(0, 115, 231, 0.5)',
},
range: {
lower: null,
upper: null,
minimum: 0,
maximum: Infinity,
},
state: {
lowerHandleFocus: false,
upperHandleFocus: false,
rangeHandleFocus: false,
},
},
};
export class TimelineChartPlugin {
constructor() {
this.id = 'timeline-chart-plugin';
}
/** We only want to register the plugin once per application */
static { this._isRegistered = false; }
/** Register this plugin */
static register() {
/**
* We have to register this plugin globally because
* ng2-charts doesn't support plugins on an invidual
* basis. We must check in all lifecycle hooks that
* it is an timeline chart before performing any actions.
*
* We also need to have it inside the class otherwise it
* will be included in every application by default.
* Having it here allows it to be tree-shaken.
*/
if (!this._isRegistered) {
// if pluginService exists then we are in v2
if (window.Chart?.pluginService) {
window.Chart.pluginService.register(new TimelineChartPlugin());
}
else {
import('chart.js').then(({ Chart }) => {
Chart.register(new TimelineChartPlugin());
});
}
this._isRegistered = true;
}
}
isVersion3() {
return window.Chart?.pluginService ? false : true;
}
/**
* When chart is initialised store the chart instance and context
* for use outside lifecycle hooks.
*
* We should also supply default options for any options that have
* not been specified by the consuming application.
*
* We also need to add some event listeners for events that Chart.js
* does not inform us of.
*/
beforeInit(chart) {
// provide the default options for any missing properties
if (this.getEnabled(chart)) {
// chart.config.options.timeline = { ...timelineDefaultOptions.timeline, ...this.getOptions(chart) };
chart.config.options.timeline = this.getOptionsWithDefaults(this.getOptions(chart));
// get the range
const { lower, upper } = this.getRange(chart);
// ensure we have an initial range set
if (lower === null || upper === null) {
throw new Error('Timeline Chart - Ensure that both an upper and lower range are initially provided.');
}
// setup the function
chart.config.options.timeline.state.onMouseDown = () => this.onMouseDown(chart);
chart.config.options.timeline.state.onMouseUp = () => this.onMouseUp(chart);
// add mouse down and mouseup event listeners
chart.canvas.addEventListener('mousedown', chart.config.options.timeline.state.onMouseDown);
document.addEventListener('mouseup', chart.config.options.timeline.state.onMouseUp);
}
}
/**
* We want to setup some additional functionality
* after the chart has initialized.
*/
afterInit(chart) {
if (this.getEnabled(chart)) {
// add accessibility attributes and elements to the chart
this.setupAccessibility(chart);
// intially call the onChange function
this.triggerOnChange(chart);
}
}
/**
* The timeline chart should have a subtle background
* color behind the main chart area (excluding the axis area).
* Suprisingly Chart.js does not support this out of the box
* so we need to add this functionality but it should be behind
* all chart elements.
*/
beforeDraw(chart) {
if (this.getEnabled(chart)) {
this.drawBackgroundColor(chart);
}
}
/**
* Once the Chart elements have been drawn we want to draw the drag
* handles and the overlay showing the selected region
*/
afterDraw(chart) {
if (this.getEnabled(chart)) {
this.drawSelection(chart);
this.drawHandles(chart);
}
}
/**
* We want to update the cursor whenever the mouse is over
* one of the drag handles. We have do calculate this manually
* as there are no DOM element to add CSS to.
*/
afterEvent(chart, parentEvent) {
const event = this.isVersion3() ? parentEvent.event : parentEvent;
if (parentEvent.replay === true) {
return;
}
// skip this if timeline is not enabled
if (!this.getEnabled(chart)) {
return;
}
switch (event.type) {
case 'mousemove':
this.setCursor(chart, event);
this.setRangeOnDrag(chart, event);
this.handleMouseMove(chart, event);
// store the latest mouse position
this.setState(chart, { mouseX: event.x });
break;
case 'mouseout':
this.resetCursor(chart);
this.hideTooltip(chart);
break;
}
}
/**
* Unbind from the event listeners we manually set up
*/
destroy(chart) {
if (this.getEnabled(chart)) {
document.removeEventListener('mouseup', chart.config.options.timeline.state.onMouseUp, true);
}
}
/** Get the timeline options from the chart instance */
getOptions(chart) {
return chart.config.options.timeline;
}
/** Determine if this chart is using the timeline */
getEnabled(chart) {
return !!this.getOptions(chart);
}
/** Get the timeline range from the chart instance */
getRange(chart) {
return this.getOptions(chart).range;
}
/** Get the chart area but include any padding */
getChartArea(chart) {
const { top, right, bottom, left } = chart.chartArea;
const padding = chart.config.options.layout && chart.config.options.layout.padding
? chart.config.options.layout.padding
: 0;
if (typeof padding === 'number') {
return {
top: top - padding,
right: right - padding,
left: left - padding,
bottom: bottom - padding,
};
}
else if (typeof padding === 'object') {
return {
top: top - padding.top,
right: right - padding.right,
left: left - padding.left,
bottom: bottom - padding.bottom,
};
}
return chart.chartArea;
}
/** Get stored state inside the chart options */
getState(chart) {
return this.getOptions(chart).state;
}
/** Store state inside the chart options */
setState(chart, state) {
// store the latest state
chart.config.options.timeline.state = { ...chart.config.options.timeline.state, ...state };
// trigger a chart re-render
chart.update();
}
/** Call the callback with the latest range */
triggerOnChange(chart) {
// get the current date range
const { lower, upper } = this.getRange(chart);
// get the callback function
const { onChange } = this.getOptions(chart);
// call the callback with the lower and upper values
requestAnimationFrame(() => onChange(lower, upper));
// get the handle elements
const { lowerHandleElement, upperHandleElement } = this.getState(chart);
// update the aria properties
lowerHandleElement.setAttribute('aria-valuemin', new Date(this.getHandleMinimum(chart, TimelineHandle.Lower)).toDateString());
lowerHandleElement.setAttribute('aria-valuenow', lower.toDateString());
lowerHandleElement.setAttribute('aria-valuemax', new Date(this.getHandleMaximum(chart, TimelineHandle.Lower)).toDateString());
upperHandleElement.setAttribute('aria-valuemin', new Date(this.getHandleMinimum(chart, TimelineHandle.Upper)).toDateString());
upperHandleElement.setAttribute('aria-valuenow', upper.toDateString());
upperHandleElement.setAttribute('aria-valuemax', new Date(this.getHandleMaximum(chart, TimelineHandle.Upper)).toDateString());
}
/** To make the chart accessible add some internal elements that can be focused */
setupAccessibility(chart) {
// create the invisible elements
const lowerHandle = document.createElement('div');
const upperHandle = document.createElement('div');
const rangeHandle = document.createElement('div');
// make the items focusable
lowerHandle.setAttribute('tabindex', '0');
upperHandle.setAttribute('tabindex', '0');
rangeHandle.setAttribute('tabindex', '0');
// insert the elements
chart.canvas.appendChild(lowerHandle);
chart.canvas.appendChild(upperHandle);
chart.canvas.appendChild(rangeHandle);
// add the event handlers
lowerHandle.addEventListener('focus', () => this.setState(chart, { lowerHandleFocus: true }));
lowerHandle.addEventListener('blur', () => this.setState(chart, { lowerHandleFocus: false }));
lowerHandle.addEventListener('keydown', (event) => this.onKeydown(chart, event, TimelineHandle.Lower));
upperHandle.addEventListener('focus', () => this.setState(chart, { upperHandleFocus: true }));
upperHandle.addEventListener('blur', () => this.setState(chart, { upperHandleFocus: false }));
upperHandle.addEventListener('keydown', (event) => this.onKeydown(chart, event, TimelineHandle.Upper));
rangeHandle.addEventListener('focus', () => this.setState(chart, { rangeHandleFocus: true }));
rangeHandle.addEventListener('blur', () => this.setState(chart, { rangeHandleFocus: false }));
rangeHandle.addEventListener('keydown', (event) => this.onRangeKeydown(chart, event));
// store the items in the state object
this.setState(chart, {
lowerHandleElement: lowerHandle,
upperHandleElement: upperHandle,
rangeHandleElement: rangeHandle,
});
}
/** Handle keyboard accessibility events */
onKeydown(chart, event, handle) {
// get the current value for the given handle
const value = this.getHandleValue(chart, handle).getTime();
const step = this.getOptions(chart).keyboard.step;
const [minimum, maximum] = this.getChartRange(chart);
switch (event.keyCode) {
case LEFT_ARROW:
this.setHandleValue(chart, handle, new Date(value - step));
event.preventDefault();
break;
case HOME:
this.setHandleValue(chart, handle, new Date(minimum));
event.preventDefault();
break;
case RIGHT_ARROW:
this.setHandleValue(chart, handle, new Date(value + step));
event.preventDefault();
break;
case END:
this.setHandleValue(chart, handle, new Date(maximum));
event.preventDefault();
break;
}
}
/**
* Handle range changes made with the keyboard as these are exempt from
* many of the validation checks that are required when dragging only one
* handle at a time.
*/
onRangeKeydown(chart, event) {
// get the current handle values
let lowerValue = this.getHandleValue(chart, TimelineHandle.Lower).getTime();
let upperValue = this.getHandleValue(chart, TimelineHandle.Upper).getTime();
const step = this.getOptions(chart).keyboard.step;
const difference = upperValue - lowerValue;
// get the chart boundaries
const [minimum, maximum] = this.getChartRange(chart);
switch (event.keyCode) {
case LEFT_ARROW:
lowerValue = Math.max(lowerValue - step, minimum);
upperValue = lowerValue + difference;
event.preventDefault();
break;
case RIGHT_ARROW:
upperValue = Math.min(upperValue + step, maximum);
lowerValue = upperValue - difference;
event.preventDefault();
break;
case HOME:
lowerValue = minimum;
upperValue = lowerValue + difference;
event.preventDefault();
break;
case END:
upperValue = maximum;
lowerValue = upperValue - difference;
event.preventDefault();
break;
}
// store the new values
chart.config.options.timeline.range[TimelineHandle.Lower] = new Date(lowerValue);
chart.config.options.timeline.range[TimelineHandle.Upper] = new Date(upperValue);
// update the chart
chart.update();
// emit the latest range
this.triggerOnChange(chart);
}
/**
* When the mouse is first pressed within a chart we should see if we are
* currently over a drag handle to start the dragging
*/
onMouseDown(chart) {
// ensure we only proceed when we have a chart context
if (!chart.ctx) {
return;
}
// get the position from the chart area
const { top } = this.getChartArea(chart);
// get the properties from the state
const { mouseX } = this.getState(chart);
// check if the event started within a drag handle
const handle = this.isWithinHandle(chart, { x: mouseX, y: top });
// if it did then we are now dragging the handle and should store it
this.setState(chart, { handle: handle !== null ? handle : null });
}
/** When the mouse is released we are no longer dragging */
onMouseUp(chart) {
if (chart.canvas) {
this.setState(chart, { handle: null });
}
}
handleMouseMove(chart, event) {
const mousePosition = this.isWithinHandle(chart, event);
const timelineOptions = (this.isVersion3() ? chart.config.options : chart.options);
// eslint-disable-next-line no-prototype-builtins
const hasTooltipOnRange = timelineOptions.timeline.range.hasOwnProperty('tooltip');
// eslint-disable-next-line no-prototype-builtins
const hasTooltipOnHandles = timelineOptions.timeline.handles.hasOwnProperty('tooltip');
let timelineTooltipText;
let handleTooltipText;
if (hasTooltipOnRange) {
timelineTooltipText = timelineOptions.timeline.range.tooltip.label();
}
if (hasTooltipOnHandles) {
handleTooltipText = timelineOptions.timeline.handles.tooltip.label();
}
if (mousePosition === TimelineHandle.Range && hasTooltipOnRange) {
this.externalTooltipHandler(chart, TimelineHandle.Range, timelineTooltipText);
}
else if (mousePosition === TimelineHandle.Lower && hasTooltipOnHandles) {
this.externalTooltipHandler(chart, TimelineHandle.Lower, handleTooltipText.rangeLower);
}
else if (mousePosition === TimelineHandle.Upper && hasTooltipOnHandles) {
this.externalTooltipHandler(chart, TimelineHandle.Upper, handleTooltipText.rangeUpper);
}
else {
this.hideTooltip(chart);
}
}
hideTooltip(chart) {
const tooltipEl = this.getOrCreateTooltip(chart);
tooltipEl.style.opacity = '0';
}
getOrCreateTooltip(chart) {
let tooltipEl = chart.canvas.parentNode.querySelector('.timeline-tooltip');
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.classList.add('timeline-tooltip');
tooltipEl.classList.add('tooltip');
const caret = document.createElement('div');
caret.classList.add('tooltip-caret');
const span = document.createElement('span');
tooltipEl.appendChild(span);
tooltipEl.appendChild(caret);
chart.canvas.parentNode.appendChild(tooltipEl);
}
return tooltipEl;
}
externalTooltipHandler(chart, position, tooltipText) {
// Tooltip Element
const tooltipEl = this.getOrCreateTooltip(chart);
const span = tooltipEl.querySelector('span');
span.innerText = tooltipText;
const { x, y } = this.tooltipPositioner(chart, position);
tooltipEl.style.left = x + 'px';
tooltipEl.style.top = y + 'px';
tooltipEl.style.opacity = '1';
}
tooltipPositioner(chart, position) {
const lower = this.getHandleArea(chart, TimelineHandle.Lower).left;
const upper = this.getHandleArea(chart, TimelineHandle.Upper).left;
const tooltipEl = this.getOrCreateTooltip(chart);
const width = tooltipEl.getBoundingClientRect().width;
const caret = tooltipEl.querySelector('.tooltip-caret');
if (position === TimelineHandle.Range) {
caret.style.top = null;
caret.style.right = null;
caret.style.left = '50%';
caret.style.transform = 'rotate(0deg)';
const middle = (lower + upper) / 2;
return {
x: middle + 2,
y: -14,
};
}
else if (position === TimelineHandle.Lower) {
caret.style.top = '40%';
caret.style.right = 'auto';
caret.style.left = '-2px';
caret.style.transform = 'rotate(90deg)';
return {
x: lower + (width / 2 + 20),
y: 10,
};
}
else if (position === TimelineHandle.Upper) {
caret.style.top = '40%';
caret.style.right = '-7px';
caret.style.left = 'auto';
caret.style.transform = 'rotate(-90deg)';
return {
x: upper - (width / 2 + 20),
y: 10,
};
}
}
/** Update the range when dragged */
setRangeOnDrag(chart, event) {
const { handle, mouseX } = this.getState(chart);
// if we are not dragging then do nothing
if (!handle) {
return;
}
// get the chart area
const { left, right } = this.getChartArea(chart);
// get the current range
const { lower, upper } = this.getRange(chart);
// get the difference in x position since the last mouse position
const delta = event.x - mouseX;
// get the width of the chart area
const width = right - left;
// get the time range on the x-axis
const [minimum, maximum] = this.getChartRange(chart);
// determine how much of the time range was spanned in the move
const percentageDiff = (delta / width) * 100;
// calculate the time difference in the movement
const valueDiff = ((maximum - minimum) / 100) * percentageDiff;
if (handle === TimelineHandle.Lower) {
this.setHandleValue(chart, TimelineHandle.Lower, new Date(lower.getTime() + valueDiff));
}
if (handle === TimelineHandle.Upper) {
this.setHandleValue(chart, TimelineHandle.Upper, new Date(upper.getTime() + valueDiff));
}
if (handle === TimelineHandle.Range) {
// get the current range
const range = upper.getTime() - lower.getTime();
// update the values
if (valueDiff < 0) {
this.setHandleValue(chart, TimelineHandle.Upper, new Date(upper.getTime() + valueDiff));
this.setHandleValue(chart, TimelineHandle.Lower, new Date(lower.getTime() + valueDiff));
}
else {
this.setHandleValue(chart, TimelineHandle.Lower, new Date(lower.getTime() + valueDiff));
this.setHandleValue(chart, TimelineHandle.Upper, new Date(upper.getTime() + valueDiff));
}
// calculate the new range
const currentRange = chart.config.options.timeline.range.upper.getTime() -
chart.config.options.timeline.range.lower.getTime();
// ensure the range is still the same
if (currentRange !== range) {
if (valueDiff < 0) {
this.setHandleValue(chart, TimelineHandle.Upper, new Date(chart.config.options.timeline.range.upper.getTime() + (range - currentRange)));
}
else {
this.setHandleValue(chart, TimelineHandle.Lower, new Date(chart.config.options.timeline.range.lower.getTime() + (currentRange - range)));
}
}
}
}
/**
* Draw the background color in the region that sits behind all the chart content
*/
drawBackgroundColor(chart) {
// get the region that the chart is drawn on (excluding axis)
const { top, right, bottom, left } = this.getChartArea(chart);
// fill the background color
chart.ctx.save();
chart.ctx.fillStyle = this.getOptions(chart).backgroundColor;
chart.ctx.fillRect(left, top, right - left, bottom - top);
chart.ctx.restore();
}
/** Draw the overlay that indicates the selected region */
drawSelection(chart) {
// get the region that the chart is drawn on (excluding axis)
const { top, bottom } = this.getChartArea(chart);
// get the fill color
const selectionColor = this.getOptions(chart).selectionColor;
// get the focus indicator color
const { focusIndicatorColor } = this.getOptions(chart).handles;
// get the lower and upper handle render regions
const lower = this.getHandleArea(chart, TimelineHandle.Lower);
const upper = this.getHandleArea(chart, TimelineHandle.Upper);
// draw selection region
chart.ctx.save();
chart.ctx.fillStyle = selectionColor;
chart.ctx.fillRect(lower.left, 0, upper.right - lower.left, bottom - top);
// check if we are focused on the range handle
if (this.isHandleFocused(chart, TimelineHandle.Range)) {
chart.ctx.strokeStyle = focusIndicatorColor;
const handleWidth = 4;
const lineWidth = 2;
chart.ctx.lineWidth = lineWidth;
chart.ctx.strokeRect(lower.left + handleWidth + lineWidth, lineWidth / 2, upper.right - lower.left - (handleWidth + lineWidth) * 2, bottom - top - lineWidth);
}
chart.ctx.restore();
}
/** Darw the drag handles */
drawHandles(chart) {
// get the region that the chart is drawn on (excluding axis)
const { top, bottom } = this.getChartArea(chart);
// get the handle colors
const { backgroundColor, foregroundColor, focusIndicatorColor } = this.getOptions(chart).handles;
// draw each handle
[TimelineHandle.Lower, TimelineHandle.Upper].forEach(handle => {
// get the area of the handle
const area = this.getHandleArea(chart, handle);
const handleWidth = 5;
const chartHeight = bottom - top;
chart.ctx.save();
// if the handle is focused draw an outline
if (this.isHandleFocused(chart, handle)) {
chart.ctx.fillStyle = focusIndicatorColor;
chart.ctx.fillRect(area.left - 2, 0, handleWidth + 4, chartHeight);
}
// draw the handle
chart.ctx.fillStyle = backgroundColor;
chart.ctx.fillRect(area.left, 0, handleWidth, chartHeight);
// draw the 3 drag handles within the drag handle
chart.ctx.fillStyle = foregroundColor;
// calculate size and position
const width = 3;
const height = 3;
const x = area.left + (handleWidth - width) / 2;
const midpoint = area.top + chartHeight / 2;
const topY = midpoint - height * 2.5;
const middleY = midpoint - height / 2;
const bottomY = midpoint + height * 1.5;
chart.ctx.fillRect(x, topY, width, height);
chart.ctx.fillRect(x, middleY, width, height);
chart.ctx.fillRect(x, bottomY, width, height);
chart.ctx.restore();
});
}
/**
* Update the CSS cursor on the canvas element if we are hovering over a drag handle
*/
setCursor(chart, event) {
// get the handle if we are hovering over one
const handle = this.getState(chart).handle || this.isWithinHandle(chart, event);
if (handle === TimelineHandle.Lower || handle === TimelineHandle.Upper) {
chart.canvas.style.cursor = 'ew-resize';
}
else if (handle === TimelineHandle.Range) {
chart.canvas.style.cursor = 'move';
}
else {
this.resetCursor(chart);
}
}
// restore the cursor to the default
resetCursor(chart) {
if (chart.canvas.style.cursor !== '') {
chart.canvas.style.cursor = '';
}
}
isHandleFocused(chart, handle) {
if (handle === TimelineHandle.Lower) {
return this.getState(chart).lowerHandleFocus;
}
if (handle === TimelineHandle.Upper) {
return this.getState(chart).upperHandleFocus;
}
if (handle === TimelineHandle.Range) {
return this.getState(chart).rangeHandleFocus;
}
return false;
}
/** Determine if a position is within one of the drag handles */
isWithinHandle(chart, event) {
// get the lower and upper handle render regions
const lower = this.getHandleArea(chart, TimelineHandle.Lower);
const upper = this.getHandleArea(chart, TimelineHandle.Upper);
// get the position co-ordinates
const { x, y } = event;
if (x >= lower.left && x <= lower.right && y >= lower.top && y <= lower.bottom) {
return TimelineHandle.Lower;
}
if (x >= upper.left && x <= upper.right && y >= upper.top && y <= upper.bottom) {
return TimelineHandle.Upper;
}
if (x > lower.right && x < upper.left && y >= lower.top && y <= lower.bottom) {
return TimelineHandle.Range;
}
return null;
}
/** Get the area a specific handle covers within the chart */
getHandleArea(chart, handle) {
// get the region that the chart is drawn on (excluding axis)
const { left, top, right, bottom } = this.getChartArea(chart);
// perform some calculations on the chart area
const width = right - left;
// get the minimum and maximum ticks on the chart
const [minimum, maximum] = this.getChartRange(chart);
// get the lower and upper range values
const { lower, upper } = this.getOptions(chart).range;
if (handle === TimelineHandle.Lower) {
const percentage = ((lower.getTime() - minimum) / (maximum - minimum)) * 100;
const position = left + (width / 100) * percentage;
return { top, left: position, right: position + 5, bottom };
}
if (handle === TimelineHandle.Upper) {
const percentage = ((upper.getTime() - minimum) / (maximum - minimum)) * 100;
const position = left + (width / 100) * percentage;
return { top, left: position - 5, right: position, bottom };
}
}
/**
* Get the minimum and maximum values on the x-axis
*/
getChartRange(chart) {
let minimum;
let maximum;
if (this.isVersion3()) {
// get the current data
const data = chart.scales;
// get the range on the x-axis
minimum = data.x.min;
maximum = data.x.max;
}
else {
// get the current data
const { data } = chart.getDatasetMeta(0);
// get the range on the x-axis
minimum = data[0]._xScale.min;
maximum = data[0]._xScale.max;
}
return [minimum, maximum];
}
/** Get the value for a given handle */
getHandleValue(chart, handle) {
const { lower, upper } = this.getOptions(chart).range;
return handle === TimelineHandle.Lower ? lower : upper;
}
setHandleValue(chart, handle, value) {
// perform lower handle validation
if (handle === TimelineHandle.Lower) {
value = new Date(Math.min(Math.max(this.getHandleMinimum(chart, handle), value.getTime()), this.getHandleMaximum(chart, handle)));
}
// perform upper handle validation
if (handle === TimelineHandle.Upper) {
value = new Date(Math.max(Math.min(this.getHandleMaximum(chart, handle), value.getTime()), this.getHandleMinimum(chart, handle)));
}
// store the new value
chart.config.options.timeline.range[handle] = value;
// update the chart
chart.update();
// emit the latest range
this.triggerOnChange(chart);
}
getHandleMinimum(chart, handle) {
// get the minimum distance
const minDistance = this.getOptions(chart).range.minimum || 0;
const maxDistance = this.getOptions(chart).range.maximum || Infinity;
// get the chart boundaries
const [minimum] = this.getChartRange(chart);
// get the current date range
const { lower, upper } = this.getRange(chart);
if (handle === TimelineHandle.Lower) {
return Math.max(upper.getTime() - maxDistance, minimum);
}
if (handle === TimelineHandle.Upper) {
return lower.getTime() + minDistance;
}
}
getHandleMaximum(chart, handle) {
// get the minimum distance
const minDistance = this.getOptions(chart).range.minimum || 0;
const maxDistance = this.getOptions(chart).range.maximum || Infinity;
// get the chart boundaries
const [, maximum] = this.getChartRange(chart);
// get the current date range
const { lower, upper } = this.getRange(chart);
if (handle === TimelineHandle.Lower) {
return upper.getTime() - minDistance;
}
if (handle === TimelineHandle.Upper) {
return Math.min(lower.getTime() + maxDistance, maximum);
}
}
getOptionsWithDefaults(options) {
const merge = (target, source) => {
for (const key of Object.keys(source)) {
if (source[key] instanceof Object &&
!(source[key] instanceof Date) &&
typeof source[key] !== 'function') {
Object.assign(source[key], merge(target[key], source[key]));
}
}
return Object.assign(target || {}, source);
};
return merge({ ...timelineDefaultOptions.timeline }, options);
}
}
/**
* Directly exporting a file that is not an Angular component, module, etc..
* can cause build issues. We can use a module that instantiates the plugin
* instead of directly exporting the Chart.js plugin.
*/
export class TimelineChartModule {
constructor() {
TimelineChartPlugin.register();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: TimelineChartModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.12", ngImport: i0, type: TimelineChartModule }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: TimelineChartModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: TimelineChartModule, decorators: [{
type: NgModule,
args: [{}]
}], ctorParameters: () => [] });
export var TimelineHandle;
(function (TimelineHandle) {
TimelineHandle["Lower"] = "lower";
TimelineHandle["Upper"] = "upper";
TimelineHandle["Range"] = "range";
})(TimelineHandle || (TimelineHandle = {}));
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZWxpbmUtY2hhcnQubW9kdWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL3BsdWdpbnMvY2hhcnRqcy90aW1lbGluZS90aW1lbGluZS1jaGFydC5tb2R1bGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQzNFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxlQUFlLENBQUM7O0FBR3pDLE1BQU0sc0JBQXNCLEdBQXFEO0lBQy9FLFFBQVEsRUFBRTtRQUNSLGVBQWUsRUFBRSxTQUFTO1FBQzFCLGNBQWMsRUFBRSwwQkFBMEI7UUFDMUMsZ0VBQWdFO1FBQ2hFLFFBQVEsRUFBRSxTQUFTLFFBQVEsS0FBSSxDQUFDO1FBQ2hDLFFBQVEsRUFBRTtZQUNSLElBQUksRUFBRSxhQUFhLEVBQUUsVUFBVTtTQUNoQztRQUNELE9BQU8sRUFBRTtZQUNQLGVBQWUsRUFBRSxNQUFNO1lBQ3ZCLGVBQWUsRUFBRSxTQUFTO1lBQzFCLG1CQUFtQixFQUFFLHdCQUF3QjtTQUM5QztRQUNELEtBQUssRUFBRTtZQUNMLEtBQUssRUFBRSxJQUFJO1lBQ1gsS0FBSyxFQUFFLElBQUk7WUFDWCxPQUFPLEVBQUUsQ0FBQztZQUNWLE9BQU8sRUFBRSxRQUFRO1NBQ2xCO1FBQ0QsS0FBSyxFQUFFO1lBQ0wsZ0JBQWdCLEVBQUUsS0FBSztZQUN2QixnQkFBZ0IsRUFBRSxLQUFLO1lBQ3ZCLGdCQUFnQixFQUFFLEtBQUs7U0FDeEI7S0FDRjtDQUNGLENBQUM7QUFFRixNQUFNLE9BQU8sbUJBQW1CO0lBQWhDO1FBQ0UsT0FBRSxHQUFXLHVCQUF1QixDQUFDO0lBMDRCdkMsQ0FBQztJQXg0QkMsK0RBQStEO2FBQ2hELGtCQUFhLEdBQVksS0FBSyxBQUFqQixDQUFrQjtJQUU5QywyQkFBMkI7SUFDM0IsTUFB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