Browse Source

Merge branch 'oop-core' into 'main'

OOP core part1

See merge request chartwerk/core!18
merge-requests/19/head
Alexander Velikiy 3 years ago
parent
commit
dacac198bd
  1. 426
      src/index.ts
  2. 210
      src/models/options.ts
  3. 73
      src/models/series.ts
  4. 6
      src/state.ts
  5. 138
      src/types.ts

426
src/index.ts

@ -2,13 +2,15 @@ import VueChartwerkPodMixin from './VueChartwerkPodMixin';
import { PodState } from './state'; import { PodState } from './state';
import { Grid } from './components/grid'; import { Grid } from './components/grid';
import { CoreSeries } from './models/series';
import { CoreOptions } from './models/options';
import styles from './css/style.css'; import styles from './css/style.css';
import { import {
Margin, Margin,
TimeSerie, CoreSerie,
Options, Options,
TickOrientation,
TimeFormat, TimeFormat,
BrushOrientation, BrushOrientation,
AxisFormat, AxisFormat,
@ -20,96 +22,27 @@ import {
ScrollPanOrientation, ScrollPanOrientation,
ScrollPanDirection, ScrollPanDirection,
AxisOption, AxisOption,
AxesOptions,
} from './types'; } from './types';
import { uid } from './utils'; import { uid } from './utils';
import { palette } from './colors'; import { palette } from './colors';
import * as d3 from 'd3'; import * as d3 from 'd3';
import defaultsDeep from 'lodash/defaultsDeep';
import includes from 'lodash/includes';
import first from 'lodash/first'; import first from 'lodash/first';
import last from 'lodash/last'; import last from 'lodash/last';
import mergeWith from 'lodash/mergeWith';
import add from 'lodash/add';
import replace from 'lodash/replace';
import cloneDeep from 'lodash/cloneDeep';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import has from 'lodash/has';
const DEFAULT_MARGIN: Margin = { top: 30, right: 20, bottom: 20, left: 30 };
const DEFAULT_TICK_COUNT = 4; const DEFAULT_TICK_COUNT = 4;
const DEFAULT_TICK_SIZE = 2; const DEFAULT_TICK_SIZE = 2;
const MILISECONDS_IN_MINUTE = 60 * 1000; const MILISECONDS_IN_MINUTE = 60 * 1000;
const DEFAULT_SCROLL_PAN_STEP = 50;
const DEFAULT_OPTIONS: Options = {
confidence: 0,
timeInterval: {
timeFormat: TimeFormat.MINUTE
},
tickFormat: {
xAxis: '%H:%M',
xTickOrientation: TickOrientation.HORIZONTAL
},
zoomEvents: {
mouse: {
zoom: {
isActive: true,
keyEvent: KeyEvent.MAIN,
orientation: BrushOrientation.HORIZONTAL
},
pan: {
isActive: true,
keyEvent: KeyEvent.SHIFT,
orientation: PanOrientation.HORIZONTAL
},
doubleClick: {
isActive: true,
keyEvent: KeyEvent.MAIN,
},
},
scroll: {
zoom: {
isActive: true,
keyEvent: KeyEvent.MAIN,
orientation: PanOrientation.BOTH,
},
pan: {
isActive: false,
keyEvent: KeyEvent.SHIFT,
panStep: DEFAULT_SCROLL_PAN_STEP,
orientation: ScrollPanOrientation.HORIZONTAL,
direction: ScrollPanDirection.BOTH,
},
},
},
axis: {
x: {
isActive: true,
ticksCount: DEFAULT_TICK_COUNT,
format: AxisFormat.TIME
},
y: {
isActive: true,
ticksCount: DEFAULT_TICK_COUNT,
format: AxisFormat.NUMERIC
},
y1: {
isActive: false,
ticksCount: DEFAULT_TICK_COUNT,
format: AxisFormat.NUMERIC
}
},
crosshair: {
orientation: CrosshairOrientation.VERTICAL,
color: 'red'
},
renderTicksfromTimestamps: false,
renderLegend: true,
}
abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { abstract class ChartwerkPod<T extends CoreSerie, O extends Options> {
protected coreSeries: CoreSeries<T>;
protected coreOptions: CoreOptions<O>;
protected d3Node?: d3.Selection<HTMLElement, unknown, null, undefined>; protected d3Node?: d3.Selection<HTMLElement, unknown, null, undefined>;
protected customOverlay?: d3.Selection<SVGRectElement, unknown, null, undefined>; protected customOverlay?: d3.Selection<SVGRectElement, unknown, null, undefined>;
protected crosshair?: d3.Selection<SVGGElement, unknown, null, undefined>; protected crosshair?: d3.Selection<SVGGElement, unknown, null, undefined>;
@ -130,8 +63,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
protected y1AxisElement?: d3.Selection<SVGGElement, unknown, null, undefined>; protected y1AxisElement?: d3.Selection<SVGGElement, unknown, null, undefined>;
protected yAxisTicksColors?: string[] = []; protected yAxisTicksColors?: string[] = [];
private _clipPathUID = ''; private _clipPathUID = '';
protected series: T[];
protected options: O;
protected readonly d3: typeof d3; protected readonly d3: typeof d3;
protected deltaYTransform = 0; protected deltaYTransform = 0;
// TODO: forceRerender is a hack, it will be remove someday. But we need to update state on resize // TODO: forceRerender is a hack, it will be remove someday. But we need to update state on resize
@ -151,10 +83,8 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
// TODO: test if it's necessary // TODO: test if it's necessary
styles.use(); styles.use();
let options = cloneDeep(_options); this.coreOptions = new CoreOptions(_options);
defaultsDeep(options, DEFAULT_OPTIONS); this.coreSeries = new CoreSeries(_series);
this.options = options;
this.series = cloneDeep(_series);
this.d3Node = d3.select(this.el); this.d3Node = d3.select(this.el);
this.addEventListeners(); this.addEventListeners();
@ -173,9 +103,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
public render(): void { public render(): void {
if(has(this.options.eventsCallbacks, 'renderStart')) { this.coreOptions.callbackRenderStart();
this.options.eventsCallbacks.renderStart();
}
this.renderClipPath(); this.renderClipPath();
this.addEvents(); this.addEvents();
@ -191,9 +119,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
this.renderYLabel(); this.renderYLabel();
this.renderXLabel(); this.renderXLabel();
if(has(this.options.eventsCallbacks, 'renderEnd')) { this.coreOptions.callbackRenderEnd();
this.options.eventsCallbacks.renderEnd();
}
} }
public updateData(series?: T[], options?: O, shouldRerender = true): void { public updateData(series?: T[], options?: O, shouldRerender = true): void {
@ -217,18 +143,14 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
if(newOptions === undefined) { if(newOptions === undefined) {
return; return;
} }
let options = cloneDeep(newOptions); this.coreOptions.updateOptions(newOptions);
defaultsDeep(options, DEFAULT_OPTIONS);
this.options = options;
// TODO: update state if axis ranges were changed
} }
protected updateSeries(newSeries: T[]): void { protected updateSeries(newSeries: T[]): void {
if(newSeries === undefined) { if(newSeries === undefined) {
return; return;
} }
let series = cloneDeep(newSeries); this.coreSeries.updateSeries(newSeries);
this.series = series;
} }
protected abstract renderMetrics(): void; protected abstract renderMetrics(): void;
@ -243,7 +165,8 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
height: this.height, height: this.height,
width: this.width, width: this.width,
} }
this.state = new PodState(boxPararms, this.series, this.options); // TODO: use instanses instead of oblects
this.state = new PodState(boxPararms, this.coreSeries.visibleSeries, this.coreOptions.allOptions);
} }
protected initComponents(): void { protected initComponents(): void {
@ -256,7 +179,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
yScale: this.state.yScale, yScale: this.state.yScale,
} }
this.grid = new Grid(this.chartContainer, svgElParams, this.options.grid); this.grid = new Grid(this.chartContainer, svgElParams, this.coreOptions.grid);
} }
protected renderMetricsContainer(): void { protected renderMetricsContainer(): void {
@ -299,7 +222,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
protected renderXAxis(): void { protected renderXAxis(): void {
if(this.options.axis.x.isActive === false) { if(this.coreOptions.axis.x.isActive === false) {
return; return;
} }
this.chartContainer.select('#x-axis-container').remove(); this.chartContainer.select('#x-axis-container').remove();
@ -310,16 +233,14 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
.style('pointer-events', 'none') .style('pointer-events', 'none')
.call( .call(
d3.axisBottom(this.xScale) d3.axisBottom(this.xScale)
.ticks(this.options.axis.x.ticksCount) .ticks(this.coreOptions.axis.x.ticksCount)
.tickSize(DEFAULT_TICK_SIZE) .tickSize(DEFAULT_TICK_SIZE)
.tickFormat(this.getAxisTicksFormatter(this.options.axis.x)) .tickFormat(this.getAxisTicksFormatter(this.coreOptions.axis.x))
); );
this.chartContainer.select('#x-axis-container').selectAll('.tick').selectAll('text')
.style('transform', this.xTickTransform);
} }
protected renderYAxis(): void { protected renderYAxis(): void {
if(this.options.axis.y.isActive === false) { if(this.coreOptions.axis.y.isActive === false) {
return; return;
} }
this.chartContainer.select('#y-axis-container').remove(); this.chartContainer.select('#y-axis-container').remove();
@ -331,9 +252,9 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
// TODO: number of ticks shouldn't be hardcoded // TODO: number of ticks shouldn't be hardcoded
.call( .call(
d3.axisLeft(this.yScale) d3.axisLeft(this.yScale)
.ticks(this.options.axis.y.ticksCount) .ticks(this.coreOptions.axis.y.ticksCount)
.tickSize(DEFAULT_TICK_SIZE) .tickSize(DEFAULT_TICK_SIZE)
.tickFormat(this.getAxisTicksFormatter(this.options.axis.y)) .tickFormat(this.getAxisTicksFormatter(this.coreOptions.axis.y))
); );
const ticks = this.yAxisElement.selectAll(`.tick`).select('text').nodes(); const ticks = this.yAxisElement.selectAll(`.tick`).select('text').nodes();
this.yAxisTicksColors.map((color, index) => { this.yAxisTicksColors.map((color, index) => {
@ -345,7 +266,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
protected renderY1Axis(): void { protected renderY1Axis(): void {
if(this.options.axis.y1.isActive === false) { if(this.coreOptions.axis.y1.isActive === false) {
return; return;
} }
this.chartContainer.select('#y1-axis-container').remove(); this.chartContainer.select('#y1-axis-container').remove();
@ -359,7 +280,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
d3.axisRight(this.y1Scale) d3.axisRight(this.y1Scale)
.ticks(DEFAULT_TICK_COUNT) .ticks(DEFAULT_TICK_COUNT)
.tickSize(DEFAULT_TICK_SIZE) .tickSize(DEFAULT_TICK_SIZE)
.tickFormat(this.getAxisTicksFormatter(this.options.axis.y1)) .tickFormat(this.getAxisTicksFormatter(this.coreOptions.axis.y1))
); );
} }
@ -371,28 +292,28 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
.style('display', 'none'); .style('display', 'none');
if( if(
this.options.crosshair.orientation === CrosshairOrientation.VERTICAL || this.coreOptions.crosshair.orientation === CrosshairOrientation.VERTICAL ||
this.options.crosshair.orientation === CrosshairOrientation.BOTH this.coreOptions.crosshair.orientation === CrosshairOrientation.BOTH
) { ) {
this.crosshair.append('line') this.crosshair.append('line')
.attr('class', 'crosshair-line') .attr('class', 'crosshair-line')
.attr('id', 'crosshair-line-x') .attr('id', 'crosshair-line-x')
.attr('fill', this.options.crosshair.color) .attr('fill', this.coreOptions.crosshair.color)
.attr('stroke', this.options.crosshair.color) .attr('stroke', this.coreOptions.crosshair.color)
.attr('stroke-width', '1px') .attr('stroke-width', '1px')
.attr('y1', 0) .attr('y1', 0)
.attr('y2', this.height) .attr('y2', this.height)
.style('pointer-events', 'none'); .style('pointer-events', 'none');
} }
if( if(
this.options.crosshair.orientation === CrosshairOrientation.HORIZONTAL || this.coreOptions.crosshair.orientation === CrosshairOrientation.HORIZONTAL ||
this.options.crosshair.orientation === CrosshairOrientation.BOTH this.coreOptions.crosshair.orientation === CrosshairOrientation.BOTH
) { ) {
this.crosshair.append('line') this.crosshair.append('line')
.attr('class', 'crosshair-line') .attr('class', 'crosshair-line')
.attr('id', 'crosshair-line-y') .attr('id', 'crosshair-line-y')
.attr('fill', this.options.crosshair.color) .attr('fill', this.coreOptions.crosshair.color)
.attr('stroke', this.options.crosshair.color) .attr('stroke', this.coreOptions.crosshair.color)
.attr('stroke-width', '1px') .attr('stroke-width', '1px')
.attr('x1', 0) .attr('x1', 0)
.attr('x2', this.width) .attr('x2', this.width)
@ -402,8 +323,8 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
protected addEvents(): void { protected addEvents(): void {
// TODO: refactor for a new mouse/scroll events // TODO: refactor for a new mouse/scroll events
const panKeyEvent = this.options.zoomEvents.mouse.pan.keyEvent; const panKeyEvent = this.coreOptions.mousePanEvent.keyEvent;
const isPanActive = this.options.zoomEvents.mouse.pan.isActive; const isPanActive = this.coreOptions.mousePanEvent.isActive;
if(isPanActive === true && panKeyEvent === KeyEvent.MAIN) { if(isPanActive === true && panKeyEvent === KeyEvent.MAIN) {
this.initPan(); this.initPan();
this.initBrush(); this.initBrush();
@ -420,11 +341,11 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
protected initBrush(): void { protected initBrush(): void {
const isBrushActive = this.options.zoomEvents.mouse.zoom.isActive; const isBrushActive = this.coreOptions.mouseZoomEvent.isActive;
if(isBrushActive === false) { if(isBrushActive === false) {
return; return;
} }
switch(this.options.zoomEvents.mouse.zoom.orientation) { switch(this.coreOptions.mouseZoomEvent.orientation) {
case BrushOrientation.VERTICAL: case BrushOrientation.VERTICAL:
this.brush = d3.brushY(); this.brush = d3.brushY();
break; break;
@ -438,7 +359,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
default: default:
this.brush = d3.brushX(); this.brush = d3.brushX();
} }
const keyEvent = this.options.zoomEvents.mouse.zoom.keyEvent; const keyEvent = this.coreOptions.mouseZoomEvent.keyEvent;
this.brush.extent([ this.brush.extent([
[0, 0], [0, 0],
[this.width, this.height] [this.width, this.height]
@ -476,13 +397,13 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
protected initPan(): void { protected initPan(): void {
if( if(
this.options.zoomEvents.mouse.pan.isActive === false && this.coreOptions.mousePanEvent.isActive === false &&
this.options.zoomEvents.scroll.pan.isActive === false && this.coreOptions.scrollPanEvent.isActive === false &&
this.options.zoomEvents.scroll.zoom.isActive === false this.coreOptions.scrollZoomEvent.isActive === false
) { ) {
return; return;
} }
if(this.options.zoomEvents.mouse.zoom.isActive === false) { if(this.coreOptions.mouseZoomEvent.isActive === false) {
// init cumstom overlay to handle all events // init cumstom overlay to handle all events
this.customOverlay = this.chartContainer.append('rect') this.customOverlay = this.chartContainer.append('rect')
.attr('class', 'custom-overlay') .attr('class', 'custom-overlay')
@ -497,7 +418,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
this.initScaleX = this.xScale.copy(); this.initScaleX = this.xScale.copy();
this.initScaleY = this.yScale.copy(); this.initScaleY = this.yScale.copy();
if(this.options.axis.y1.isActive === true) { if(this.coreOptions.axis.y1.isActive) {
this.initScaleY1 = this.y1Scale.copy(); this.initScaleY1 = this.y1Scale.copy();
} }
this.pan = d3.zoom() this.pan = d3.zoom()
@ -518,54 +439,45 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
protected renderLegend(): void { protected renderLegend(): void {
if(this.options.renderLegend === false) { if(this.coreOptions.allOptions.renderLegend === false) {
return; return;
} }
if(this.series.length > 0) { if(!this.coreSeries.isSeriesAvailable) {
let legendRow = this.chartContainer return;
.append('g') }
.attr('class', 'legend-row'); let legendRow = this.chartContainer
for(let idx = 0; idx < this.series.length; idx++) { .append('g')
if(includes(this.seriesTargetsWithBounds, this.series[idx].target)) { .attr('class', 'legend-row');
continue; const series = this.coreSeries.allSeries;
} for(let idx = 0; idx < series.length; idx++) {
let node = legendRow.selectAll('text').node(); let node = legendRow.selectAll('text').node();
let rowWidth = 0; let rowWidth = 0;
if(node !== null) { if(node !== null) {
rowWidth = legendRow.node().getBBox().width + 25; rowWidth = legendRow.node().getBBox().width + 25;
}
const isChecked = this.series[idx].visible !== false;
legendRow.append('foreignObject')
.attr('x', rowWidth)
.attr('y', this.legendRowPositionY - 12)
.attr('width', 13)
.attr('height', 15)
.html(`<form><input type=checkbox ${isChecked? 'checked' : ''} /></form>`)
.on('click', () => {
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendClick !== undefined) {
this.options.eventsCallbacks.onLegendClick(idx);
}
});
legendRow.append('text')
.attr('x', rowWidth + 20)
.attr('y', this.legendRowPositionY)
.attr('class', `metric-legend-${idx}`)
.style('font-size', '12px')
.style('fill', this.getSerieColor(idx))
.text(this.series[idx].target)
.on('click', () => {
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendLabelClick !== undefined) {
this.options.eventsCallbacks.onLegendLabelClick(idx);
}
});
} }
const isChecked = series[idx].visible !== false;
legendRow.append('foreignObject')
.attr('x', rowWidth)
.attr('y', this.legendRowPositionY - 12)
.attr('width', 13)
.attr('height', 15)
.html(`<form><input type=checkbox ${isChecked? 'checked' : ''} /></form>`)
.on('click', () => this.coreOptions.callbackLegendClick(idx));
legendRow.append('text')
.attr('x', rowWidth + 20)
.attr('y', this.legendRowPositionY)
.attr('class', `metric-legend-${idx}`)
.style('font-size', '12px')
.style('fill', series[idx].color)
.text(series[idx].target)
.on('click', () => this.coreOptions.callbackLegendLabelClick(idx));
} }
} }
protected renderYLabel(): void { protected renderYLabel(): void {
if(this.options.labelFormat === undefined || this.options.labelFormat.yAxis === undefined) { if(this.coreOptions.axis.y.label === undefined) {
return; return;
} }
this.chartContainer.append('text') this.chartContainer.append('text')
@ -577,15 +489,15 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
.style('text-anchor', 'middle') .style('text-anchor', 'middle')
.style('font-size', '14px') .style('font-size', '14px')
.style('fill', 'currentColor') .style('fill', 'currentColor')
.text(this.options.labelFormat.yAxis); .text(this.coreOptions.axis.y.label);
} }
protected renderXLabel(): void { protected renderXLabel(): void {
if(this.options.labelFormat === undefined || this.options.labelFormat.xAxis === undefined) { if(this.coreOptions.axis.x.label === undefined) {
return; return;
} }
let yPosition = this.height + this.margin.top + this.margin.bottom - 35; let yPosition = this.height + this.margin.top + this.margin.bottom - 35;
if(this.series.length === 0) { if(this.coreSeries.isSeriesAvailable) {
yPosition += 20; yPosition += 20;
} }
this.chartContainer.append('text') this.chartContainer.append('text')
@ -595,7 +507,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
.style('text-anchor', 'middle') .style('text-anchor', 'middle')
.style('font-size', '14px') .style('font-size', '14px')
.style('fill', 'currentColor') .style('fill', 'currentColor')
.text(this.options.labelFormat.xAxis); .text(this.coreOptions.axis.x.label);
} }
protected renderNoDataPointsMessage(): void { protected renderNoDataPointsMessage(): void {
@ -611,12 +523,12 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
private disableScrollForward(event: any): boolean { private disableScrollForward(event: any): boolean {
return event.sourceEvent.wheelDelta > 0 return event.sourceEvent.wheelDelta > 0
&& this.options.zoomEvents.scroll.pan.direction === ScrollPanDirection.FORWARD; && this.coreOptions.scrollPanEvent.direction === ScrollPanDirection.FORWARD;
} }
private disableScrollBackward(event: any): boolean { private disableScrollBackward(event: any): boolean {
return event.sourceEvent.wheelDelta < 0 return event.sourceEvent.wheelDelta < 0
&& this.options.zoomEvents.scroll.pan.direction === ScrollPanDirection.BACKWARD; && this.coreOptions.scrollPanEvent.direction === ScrollPanDirection.BACKWARD;
} }
protected onPanning(): void { protected onPanning(): void {
@ -630,14 +542,10 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
this.rescaleMetricAndAxis(event); this.rescaleMetricAndAxis(event);
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.panning !== undefined) { this.coreOptions.callbackPanning({
this.options.eventsCallbacks.panning({ ranges: [this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange],
ranges: [this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange], d3Event: event
d3Event: event });
});
} else {
console.log('on panning, but there is no callback');
}
} }
public rescaleMetricAndAxis(event: d3.D3ZoomEvent<any, any>): void { public rescaleMetricAndAxis(event: d3.D3ZoomEvent<any, any>): void {
@ -656,8 +564,8 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
protected onPanningRescale(event: d3.D3ZoomEvent<any, any>): void { protected onPanningRescale(event: d3.D3ZoomEvent<any, any>): void {
// rescale metrics and axis on mouse and scroll panning // rescale metrics and axis on mouse and scroll panning
const eventType = event.sourceEvent.type; // 'wheel' or 'mousemove' const eventType = event.sourceEvent.type; // 'wheel' or 'mousemove'
const scrollPanOptions = this.options.zoomEvents.scroll.pan; const scrollPanOptions = this.coreOptions.scrollPanEvent;
const scrollZoomOptions = this.options.zoomEvents.scroll.zoom; const scrollZoomOptions = this.coreOptions.scrollZoomEvent;
// TODO: maybe use switch and move it to onPanning // TODO: maybe use switch and move it to onPanning
if(eventType === 'wheel') { if(eventType === 'wheel') {
if(scrollPanOptions.isActive === true && this.isD3EventKeyEqualOption(event, scrollPanOptions.keyEvent)) { if(scrollPanOptions.isActive === true && this.isD3EventKeyEqualOption(event, scrollPanOptions.keyEvent)) {
@ -690,7 +598,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
return; return;
} }
const panOrientation = this.options.zoomEvents.mouse.pan.orientation; const panOrientation = this.coreOptions.mousePanEvent.orientation;
if(panOrientation === PanOrientation.HORIZONTAL || panOrientation === PanOrientation.BOTH) { if(panOrientation === PanOrientation.HORIZONTAL || panOrientation === PanOrientation.BOTH) {
this.rescaleAxisX(event.transform.x); this.rescaleAxisX(event.transform.x);
} }
@ -723,7 +631,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
protected onScrollPanningRescale(event: d3.D3ZoomEvent<any, any>): void { protected onScrollPanningRescale(event: d3.D3ZoomEvent<any, any>): void {
const scrollPanOptions = this.options.zoomEvents.scroll.pan; const scrollPanOptions = this.coreOptions.scrollPanEvent;
// TODO: event.transform.y / x depends on mouse position, so we use hardcoded const, which should be removed // TODO: event.transform.y / x depends on mouse position, so we use hardcoded const, which should be removed
const transformStep = scrollPanOptions.panStep; const transformStep = scrollPanOptions.panStep;
const scrollPanOrientation = scrollPanOptions.orientation; const scrollPanOrientation = scrollPanOptions.orientation;
@ -740,7 +648,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
case ScrollPanOrientation.VERTICAL: case ScrollPanOrientation.VERTICAL:
// @ts-ignore // @ts-ignore
let signY = Math.sign(event.transform.y); let signY = Math.sign(event.transform.y);
if(this.options.axis.y.invert === true) { if(this.coreOptions.axis.y.invert === true) {
signY = -signY; signY = -signY;
} }
let rangeY = this.state.yValueRange; let rangeY = this.state.yValueRange;
@ -764,16 +672,12 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
this.isPanning = false; this.isPanning = false;
this.deltaYTransform = 0; this.deltaYTransform = 0;
this.onMouseOut(); this.onMouseOut();
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.panningEnd !== undefined) { this.coreOptions.callbackPanningEnd([this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange]);
this.options.eventsCallbacks.panningEnd([this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange]);
} else {
console.log('on panning end, but there is no callback');
}
} }
protected onBrush(): void { protected onBrush(): void {
const selection = d3.event.selection; const selection = d3.event.selection;
if(this.options.zoomEvents.mouse.zoom.orientation !== BrushOrientation.SQUARE || selection === null) { if(this.coreOptions.mouseZoomEvent.orientation !== BrushOrientation.SQUARE || selection === null) {
return; return;
} }
const selectionAtts = this.getSelectionAttrs(selection); const selectionAtts = this.getSelectionAttrs(selection);
@ -829,7 +733,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
let xRange: [number, number]; let xRange: [number, number];
let yRange: [number, number]; let yRange: [number, number];
switch(this.options.zoomEvents.mouse.zoom.orientation) { switch(this.coreOptions.mouseZoomEvent.orientation) {
case BrushOrientation.HORIZONTAL: case BrushOrientation.HORIZONTAL:
const startTimestamp = this.xScale.invert(extent[0]); const startTimestamp = this.xScale.invert(extent[0]);
const endTimestamp = this.xScale.invert(extent[1]); const endTimestamp = this.xScale.invert(extent[1]);
@ -869,28 +773,17 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
this.brushStartSelection = null; this.brushStartSelection = null;
} }
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.zoomIn !== undefined) { this.coreOptions.callbackZoomIn([xRange, yRange]);
this.options.eventsCallbacks.zoomIn([xRange, yRange]);
} else {
console.log('zoom in, but there is no callback');
}
} }
protected zoomOut(): void { protected zoomOut(): void {
if(this.isOutOfChart() === true) {
return;
}
let xAxisMiddleValue: number = this.xScale.invert(this.width / 2); let xAxisMiddleValue: number = this.xScale.invert(this.width / 2);
let yAxisMiddleValue: number = this.yScale.invert(this.height / 2); let yAxisMiddleValue: number = this.yScale.invert(this.height / 2);
const centers = { const centers = {
x: xAxisMiddleValue, x: xAxisMiddleValue,
y: yAxisMiddleValue y: yAxisMiddleValue
} }
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.zoomOut !== undefined) { this.coreOptions.callbackZoomOut(centers);
this.options.eventsCallbacks.zoomOut(centers);
} else {
console.log('zoom out, but there is no callback');
}
} }
// TODO: move to State // TODO: move to State
@ -921,10 +814,10 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
getd3TimeRangeEvery(count: number): d3.TimeInterval { getd3TimeRangeEvery(count: number): d3.TimeInterval {
if(this.options.timeInterval === undefined || this.options.timeInterval.timeFormat === undefined) { if(this.coreOptions.allOptions.timeInterval.timeFormat === undefined) {
return d3.timeMinute.every(count); return d3.timeMinute.every(count);
} }
switch(this.options.timeInterval.timeFormat) { switch(this.coreOptions.allOptions.timeInterval.timeFormat) {
case TimeFormat.SECOND: case TimeFormat.SECOND:
return d3.utcSecond.every(count); return d3.utcSecond.every(count);
case TimeFormat.MINUTE: case TimeFormat.MINUTE:
@ -943,11 +836,11 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
get serieTimestampRange(): number | undefined { get serieTimestampRange(): number | undefined {
if(this.series.length === 0) { if(!this.coreSeries.isSeriesAvailable) {
return undefined; return undefined;
} }
const startTimestamp = first(this.series[0].datapoints)[0]; const startTimestamp = first(this.coreSeries.visibleSeries[0].datapoints)[0];
const endTimestamp = last(this.series[0].datapoints)[0]; const endTimestamp = last(this.coreSeries.visibleSeries[0].datapoints)[0];
return (endTimestamp - startTimestamp) / 1000; return (endTimestamp - startTimestamp) / 1000;
} }
@ -982,63 +875,17 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
get timeInterval(): number { get timeInterval(): number {
if(this.series !== undefined && this.series.length > 0 && this.series[0].datapoints.length > 1) { if(this.coreSeries.isSeriesAvailable && this.coreSeries.visibleSeries[0].datapoints.length > 1) {
const interval = this.series[0].datapoints[1][0] - this.series[0].datapoints[0][0]; const interval = this.coreSeries.visibleSeries[0].datapoints[1][0] - this.coreSeries.visibleSeries[0].datapoints[0][0];
return interval; return interval;
} }
if(this.options.timeInterval !== undefined && this.options.timeInterval.count !== undefined) { if(this.coreOptions.allOptions.timeInterval.count !== undefined) {
//TODO: timeFormat to timestamp //TODO: timeFormat to timestamp
return this.options.timeInterval.count * MILISECONDS_IN_MINUTE; return this.coreOptions.allOptions.timeInterval.count * MILISECONDS_IN_MINUTE;
} }
return MILISECONDS_IN_MINUTE; return MILISECONDS_IN_MINUTE;
} }
get xTickTransform(): string {
if(this.options.tickFormat === undefined || this.options.tickFormat.xTickOrientation === undefined) {
return '';
}
switch (this.options.tickFormat.xTickOrientation) {
case TickOrientation.VERTICAL:
return 'translate(-10px, 50px) rotate(-90deg)';
case TickOrientation.HORIZONTAL:
return '';
case TickOrientation.DIAGONAL:
return 'translate(-30px, 30px) rotate(-45deg)';
default:
return '';
}
}
get extraMargin(): Margin {
let optionalMargin = { top: 0, right: 0, bottom: 0, left: 0 };
if(this.options.tickFormat !== undefined && this.options.tickFormat.xTickOrientation !== undefined) {
switch (this.options.tickFormat.xTickOrientation) {
case TickOrientation.VERTICAL:
optionalMargin.bottom += 80;
break;
case TickOrientation.HORIZONTAL:
break;
case TickOrientation.DIAGONAL:
optionalMargin.left += 15;
optionalMargin.bottom += 50;
optionalMargin.right += 10;
break;
}
}
if(this.options.labelFormat !== undefined) {
if(this.options.labelFormat.xAxis !== undefined && this.options.labelFormat.xAxis.length > 0) {
optionalMargin.bottom += 20;
}
if(this.options.labelFormat.yAxis !== undefined && this.options.labelFormat.yAxis.length > 0) {
optionalMargin.left += 20;
}
}
if(this.series.length > 0) {
optionalMargin.bottom += 25;
}
return optionalMargin;
}
get width(): number { get width(): number {
return this.d3Node.node().clientWidth - this.margin.left - this.margin.right; return this.d3Node.node().clientWidth - this.margin.left - this.margin.right;
} }
@ -1052,78 +899,25 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
get margin(): Margin { get margin(): Margin {
if(this.options.margin !== undefined) { return this.coreOptions.margin;
return this.options.margin;
}
return mergeWith({}, DEFAULT_MARGIN, this.extraMargin, add);
}
formattedBound(alias: string, target: string): string {
const confidenceMetric = replace(alias, '$__metric_name', target);
return confidenceMetric;
} }
protected clearState(): void { protected clearState(): void {
this.state.clearState(); this.state.clearState();
} }
protected getSerieColor(idx: number): string {
if(this.series[idx] === undefined) {
throw new Error(
`Can't get color for unexisting serie: ${idx}, there are only ${this.series.length} series`
);
}
let serieColor = this.series[idx].color;
if(serieColor === undefined) {
serieColor = palette[idx % palette.length];
}
return serieColor;
}
protected get seriesTargetsWithBounds(): any[] {
if(
this.options.bounds === undefined ||
this.options.bounds.upper === undefined ||
this.options.bounds.lower === undefined
) {
return [];
}
let series = [];
this.series.forEach(serie => {
series.push(this.formattedBound(this.options.bounds.upper, serie.target));
series.push(this.formattedBound(this.options.bounds.lower, serie.target));
});
return series;
}
protected get visibleSeries(): any[] {
return this.series.filter(serie => serie.visible !== false);
}
protected get rectClipId(): string { protected get rectClipId(): string {
if(this._clipPathUID.length === 0) { if(this._clipPathUID.length === 0) {
this._clipPathUID = uid(); this._clipPathUID = uid();
} }
return this._clipPathUID; return this._clipPathUID;
} }
isOutOfChart(): boolean {
const event = d3.mouse(this.chartContainer.node());
const eventX = event[0];
const eventY = event[1];
if(
eventY > this.height + 1 || eventY < -1 ||
eventX > this.width || eventX < 0
) {
return true;
}
return false;
}
} }
export { export {
ChartwerkPod, VueChartwerkPodMixin, ChartwerkPod, VueChartwerkPodMixin,
Margin, TimeSerie, Options, TickOrientation, TimeFormat, BrushOrientation, PanOrientation, Margin, CoreSerie, Options, TimeFormat, BrushOrientation, PanOrientation,
AxesOptions, AxisOption,
AxisFormat, yAxisOrientation, CrosshairOrientation, ScrollPanOrientation, ScrollPanDirection, KeyEvent, AxisFormat, yAxisOrientation, CrosshairOrientation, ScrollPanOrientation, ScrollPanDirection, KeyEvent,
palette palette
}; };

210
src/models/options.ts

@ -0,0 +1,210 @@
import {
Options,
GridOptions, AxesOptions, AxisFormat,
CrosshairOptions, CrosshairOrientation,
ZoomEvents, MouseZoomEvent, MousePanEvent, DoubleClickEvent, ScrollZoomEvent, ScrollPanEvent,
ScrollPanOrientation, ScrollPanDirection, PanOrientation, KeyEvent, BrushOrientation,
Margin, TimeFormat, AxisRange,
} from '../types';
import lodashDefaultsDeep from 'lodash/defaultsDeep';
import lodashMap from 'lodash/map';
import lodashCloneDeep from 'lodash/cloneDeep';
import has from 'lodash/has';
const DEFAULT_TICK_COUNT = 4;
const DEFAULT_SCROLL_PAN_STEP = 50;
const DEFAULT_GRID_TICK_COUNT = 5;
const DEFAULT_GRID_OPTIONS: GridOptions = {
x: {
enabled: true,
ticksCount: DEFAULT_GRID_TICK_COUNT,
},
y: {
enabled: true,
ticksCount: DEFAULT_GRID_TICK_COUNT,
},
}
const DEFAULT_AXES_OPTIONS: AxesOptions = {
x: {
isActive: true,
ticksCount: DEFAULT_TICK_COUNT,
format: AxisFormat.TIME
},
y: {
isActive: true,
ticksCount: DEFAULT_TICK_COUNT,
format: AxisFormat.NUMERIC
},
y1: {
isActive: false,
ticksCount: DEFAULT_TICK_COUNT,
format: AxisFormat.NUMERIC
}
}
const DEFAULT_CROSSHAIR_OPTIONS = {
orientation: CrosshairOrientation.VERTICAL,
color: 'gray',
}
const DEFAULT_MARGIN: Margin = { top: 30, right: 20, bottom: 20, left: 30 };
const DEFAULT_OPTIONS: Options = {
zoomEvents: {
mouse: {
zoom: {
isActive: true,
keyEvent: KeyEvent.MAIN,
orientation: BrushOrientation.HORIZONTAL
},
pan: {
isActive: true,
keyEvent: KeyEvent.SHIFT,
orientation: PanOrientation.HORIZONTAL
},
doubleClick: {
isActive: true,
keyEvent: KeyEvent.MAIN,
},
},
scroll: {
zoom: {
isActive: true,
keyEvent: KeyEvent.MAIN,
orientation: PanOrientation.BOTH,
},
pan: {
isActive: false,
keyEvent: KeyEvent.SHIFT,
panStep: DEFAULT_SCROLL_PAN_STEP,
orientation: ScrollPanOrientation.HORIZONTAL,
direction: ScrollPanDirection.BOTH,
},
},
},
axis: DEFAULT_AXES_OPTIONS,
grid: DEFAULT_GRID_OPTIONS,
crosshair: DEFAULT_CROSSHAIR_OPTIONS,
renderLegend: true,
margin: DEFAULT_MARGIN,
// remove options below
renderTicksfromTimestamps: false,
timeInterval: {
timeFormat: TimeFormat.MINUTE
},
}
export class CoreOptions<O extends Options> {
_options: O;
_defaults: Options = DEFAULT_OPTIONS;
constructor(options: O) {
this.setOptions(options);
}
public updateOptions(options: O): void {
this.setOptions(options);
}
protected setOptions(options: O): void {
this._options = lodashDefaultsDeep(lodashCloneDeep(options), this._defaults);
}
get allOptions(): O {
return this._options;
}
get grid(): GridOptions {
return this._options.grid;
}
get axis(): AxesOptions {
return this._options.axis;
}
get crosshair(): CrosshairOptions {
return this._options.crosshair;
}
get margin(): Margin {
return this._options.margin;
}
// events
get allEvents(): ZoomEvents {
return this._options.zoomEvents;
}
get mouseZoomEvent(): MouseZoomEvent {
return this._options.zoomEvents.mouse.zoom;
}
get mousePanEvent(): MousePanEvent {
return this._options.zoomEvents.mouse.pan;
}
get doubleClickEvent(): DoubleClickEvent {
return this._options.zoomEvents.mouse.doubleClick;
}
get scrollZoomEvent(): ScrollZoomEvent {
return this._options.zoomEvents.scroll.zoom;
}
get scrollPanEvent(): ScrollPanEvent {
return this._options.zoomEvents.scroll.pan;
}
// event callbacks
callbackRenderStart(): void {
if(has(this._options.eventsCallbacks, 'renderStart')) {
this._options.eventsCallbacks.renderStart();
}
}
callbackRenderEnd(): void {
if(has(this._options.eventsCallbacks, 'renderEnd')) {
this._options.eventsCallbacks.renderStart();
}
}
callbackLegendClick(idx: number): void {
if(has(this._options.eventsCallbacks, 'onLegendClick')) {
this._options.eventsCallbacks.onLegendClick(idx);
}
}
callbackLegendLabelClick(idx: number): void {
if(has(this._options.eventsCallbacks, 'onLegendLabelClick')) {
this._options.eventsCallbacks.onLegendLabelClick(idx);
}
}
callbackPanning(event: { ranges: AxisRange[], d3Event: any }): void {
if(has(this._options.eventsCallbacks, 'panning')) {
this._options.eventsCallbacks.panning(event);
}
}
callbackPanningEnd(ranges: AxisRange[]): void {
if(has(this._options.eventsCallbacks, 'panningEnd')) {
this._options.eventsCallbacks.panningEnd(ranges);
}
}
callbackZoomIn(ranges: AxisRange[]): void {
if(has(this._options.eventsCallbacks, 'zoomIn')) {
this._options.eventsCallbacks.zoomIn(ranges);
}
}
callbackZoomOut(centers: { x: number, y: number }): void {
if(has(this._options.eventsCallbacks, 'zoomOut')) {
this._options.eventsCallbacks.zoomOut(centers);
}
}
}

73
src/models/series.ts

@ -0,0 +1,73 @@
import { CoreSerie, yAxisOrientation } from '../types';
import { palette } from '../colors';
import lodashDefaultsDeep from 'lodash/defaultsDeep';
import lodashMap from 'lodash/map';
import lodashCloneDeep from 'lodash/cloneDeep';
import lodashUniq from 'lodash/uniq';
const SERIE_DEFAULTS = {
alias: '',
target: '',
visible: true,
yOrientation: yAxisOrientation.LEFT,
datapoints: [],
// fields below will be set in "fillDefaults" method
idx: undefined,
color: undefined,
};
export class CoreSeries<T extends CoreSerie> {
_series: Array<T> = [];
_defaults: CoreSerie = SERIE_DEFAULTS;
constructor(series: T[]) {
this.setSeries(series);
}
public updateSeries(series: T[]): void {
this.setSeries(series);
}
protected setSeries(series: T[]): void {
this._series = lodashMap(series, (serie, serieIdx) => this.fillDefaults(lodashCloneDeep(serie), serieIdx));
this.ensureSeriesValid(this._series);
}
ensureSeriesValid(series: T[]): void {
const targets = lodashMap(series, serie => serie.target);
const uniqTargets = lodashUniq(targets);
if(uniqTargets.length !== series.length) {
throw new Error(`All serie.target should be uniq`);
}
}
protected fillDefaults(serie: T, idx: number): T {
let defaults = lodashCloneDeep(this._defaults);
defaults.color = palette[idx % palette.length];
defaults.idx = idx;
lodashDefaultsDeep(serie, defaults);
return serie;
}
get isSeriesAvailable(): boolean {
return this.visibleSeries.length > 0;
}
get visibleSeries(): Array<T> {
return this._series.filter(serie => serie.visible);
}
get allSeries(): Array<T> {
return this._series;
}
get leftYRelatedSeries(): Array<T> {
return this.visibleSeries.filter(serie => serie.yOrientation = yAxisOrientation.LEFT);
}
get rightYRelatedSeries(): Array<T> {
return this.visibleSeries.filter(serie => serie.yOrientation = yAxisOrientation.RIGHT);
}
}

6
src/state.ts

@ -1,4 +1,4 @@
import { TimeSerie, Options, yAxisOrientation } from './types'; import { CoreSerie, Options, yAxisOrientation } from './types';
import * as d3 from 'd3'; import * as d3 from 'd3';
@ -22,7 +22,7 @@ const DEFAULT_TRANSFORM = {
// TODO: remove duplicates in max/min values. // TODO: remove duplicates in max/min values.
// TODO: PodState can be divided in two classes, but it is hard now. // TODO: PodState can be divided in two classes, but it is hard now.
// TODO: PodState.transform has conflicts with d3.zoom.event.transform. It should be synchronized. // TODO: PodState.transform has conflicts with d3.zoom.event.transform. It should be synchronized.
export class PodState<T extends TimeSerie, O extends Options> { export class PodState<T extends CoreSerie, O extends Options> {
private _xValueRange: [number, number]; private _xValueRange: [number, number];
private _yValueRange: [number, number]; private _yValueRange: [number, number];
private _y1ValueRange: [number, number]; private _y1ValueRange: [number, number];
@ -245,7 +245,7 @@ export class PodState<T extends TimeSerie, O extends Options> {
} }
protected filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean { protected filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean {
if(serie.yOrientation === undefined || serie.yOrientation === yAxisOrientation.BOTH) { if(serie.yOrientation === undefined) {
return true; return true;
} }
return serie.yOrientation === orientation; return serie.yOrientation === orientation;

138
src/types.ts

@ -4,9 +4,10 @@ export type Margin = { top: number, right: number, bottom: number, left: number
export type Timestamp = number; export type Timestamp = number;
// TODO: Pods can render not only "time" series // TODO: Pods can render not only "time" series
export type TimeSerie = { export type CoreSerie = {
target: string, target: string,
datapoints: [Timestamp, number][], datapoints: [Timestamp, number][],
idx?: number,
alias?: string, alias?: string,
visible?: boolean, visible?: boolean,
color?: string, color?: string,
@ -15,7 +16,6 @@ export type TimeSerie = {
// TODO: move some options to line-chart // TODO: move some options to line-chart
export type Options = { export type Options = {
margin?: Margin; margin?: Margin;
confidence?: number;
eventsCallbacks?: { eventsCallbacks?: {
zoomIn?: (range: AxisRange[]) => void, zoomIn?: (range: AxisRange[]) => void,
panning?: (event: { ranges: AxisRange[], d3Event: any }) => void, panning?: (event: { ranges: AxisRange[], d3Event: any }) => void,
@ -30,71 +30,18 @@ export type Options = {
renderStart?: () => void, renderStart?: () => void,
renderEnd?: () => void, renderEnd?: () => void,
}; };
axis?: { axis?: AxesOptions;
x?: AxisOption,
y?: AxisOption,
y1?: AxisOption
};
grid?: GridOptions; grid?: GridOptions;
crosshair?: { crosshair?: CrosshairOptions;
orientation?: CrosshairOrientation;
color?: string;
}
timeInterval?: { timeInterval?: {
timeFormat?: TimeFormat; timeFormat?: TimeFormat;
count?: number; count?: number;
}; };
tickFormat?: { zoomEvents?: ZoomEvents;
xAxis?: string;
xTickOrientation?: TickOrientation;
};
labelFormat?: {
xAxis?: string;
yAxis?: string;
};
bounds?: {
upper: string;
lower: string;
};
timeRange?: {
from: number,
to: number
};
zoomEvents?: {
mouse?: {
zoom?: { // same as brush
isActive: boolean;
keyEvent?: KeyEvent; // main(or base, or smth) / shift / alt / etc
orientation?: BrushOrientation; // to BrushOrientation: vertical, horizaontal, square, rectange
},
pan?: {
isActive: boolean;
keyEvent?: KeyEvent; // main(or base, or smth) / shift / alt / etc
orientation?: PanOrientation;
},
doubleClick?: {
isActive: boolean;
keyEvent?: KeyEvent;
},
},
scroll?: {
zoom?: {
isActive: boolean;
keyEvent?: KeyEvent;
orientation?: PanOrientation; // TODO: rename
},
pan?: {
isActive: boolean;
keyEvent?: KeyEvent;
panStep?: number;
orientation?: ScrollPanOrientation;
direction?: ScrollPanDirection;
},
},
}
renderTicksfromTimestamps?: boolean; renderTicksfromTimestamps?: boolean;
renderLegend?: boolean; renderLegend?: boolean;
}; };
export type GridOptions = { export type GridOptions = {
x?: { x?: {
enabled?: boolean; enabled?: boolean;
@ -105,22 +52,32 @@ export type GridOptions = {
ticksCount?: number; ticksCount?: number;
}, },
} }
export type AxesOptions = {
x?: AxisOption,
y?: AxisOption,
y1?: AxisOption
}
export type AxisOption = { export type AxisOption = {
isActive?: boolean; isActive?: boolean;
ticksCount?: number; ticksCount?: number;
format?: AxisFormat; format?: AxisFormat;
range?: [number, number]; range?: [number, number];
invert?: boolean; invert?: boolean;
label?: string;
valueFormatter?: (value: number, i: number) => string; valueFormatter?: (value: number, i: number) => string;
colorFormatter?: (value: number, i: number) => string; colorFormatter?: (value: number, i: number) => string;
} }
export type CrosshairOptions = {
orientation?: CrosshairOrientation;
color?: string;
}
export type AxisRange = [number, number] | undefined; export type AxisRange = [number, number] | undefined;
export type VueOptions = Omit<Options, 'eventsCallbacks'>; export type VueOptions = Omit<Options, 'eventsCallbacks'>;
export enum TickOrientation {
VERTICAL = 'vertical',
HORIZONTAL = 'horizontal',
DIAGONAL = 'diagonal'
}
export enum TimeFormat { export enum TimeFormat {
SECOND = 'second', SECOND = 'second',
MINUTE = 'minute', MINUTE = 'minute',
@ -129,47 +86,56 @@ export enum TimeFormat {
MONTH = 'month', MONTH = 'month',
YEAR = 'year' YEAR = 'year'
} }
export enum BrushOrientation { export enum BrushOrientation {
VERTICAL = 'vertical', VERTICAL = 'vertical',
HORIZONTAL = 'horizontal', HORIZONTAL = 'horizontal',
RECTANGLE = 'rectangle', RECTANGLE = 'rectangle',
SQUARE = 'square' SQUARE = 'square'
} }
export enum PanOrientation { export enum PanOrientation {
VERTICAL = 'vertical', VERTICAL = 'vertical',
HORIZONTAL = 'horizontal', HORIZONTAL = 'horizontal',
BOTH = 'both', BOTH = 'both',
} }
export enum ScrollPanOrientation { export enum ScrollPanOrientation {
VERTICAL = 'vertical', VERTICAL = 'vertical',
HORIZONTAL = 'horizontal', HORIZONTAL = 'horizontal',
} }
export enum ScrollPanDirection { export enum ScrollPanDirection {
FORWARD = 'forward', FORWARD = 'forward',
BACKWARD = 'backward', BACKWARD = 'backward',
BOTH = 'both', BOTH = 'both',
} }
export enum AxisFormat { export enum AxisFormat {
TIME = 'time', TIME = 'time',
NUMERIC = 'numeric', NUMERIC = 'numeric',
STRING = 'string', STRING = 'string',
CUSTOM = 'custom' CUSTOM = 'custom'
} }
export enum CrosshairOrientation { export enum CrosshairOrientation {
VERTICAL = 'vertical', VERTICAL = 'vertical',
HORIZONTAL = 'horizontal', HORIZONTAL = 'horizontal',
BOTH = 'both' BOTH = 'both'
} }
export type SvgElementAttributes = { export type SvgElementAttributes = {
x: number, x: number,
y: number, y: number,
width: number, width: number,
height: number height: number
} }
export enum KeyEvent { export enum KeyEvent {
MAIN = 'main', MAIN = 'main',
SHIFT = 'shift' SHIFT = 'shift'
} }
// allow series values to affect a specific axis // allow series values to affect a specific axis
export enum xAxisOrientation { export enum xAxisOrientation {
TOP = 'top', TOP = 'top',
@ -179,7 +145,6 @@ export enum xAxisOrientation {
export enum yAxisOrientation { export enum yAxisOrientation {
LEFT = 'left', LEFT = 'left',
RIGHT = 'right', RIGHT = 'right',
BOTH = 'both'
} }
export type SvgElParams = { export type SvgElParams = {
height: number, height: number,
@ -187,3 +152,46 @@ export type SvgElParams = {
xScale: d3.ScaleLinear<number, number>, xScale: d3.ScaleLinear<number, number>,
yScale: d3.ScaleLinear<number, number>, yScale: d3.ScaleLinear<number, number>,
} }
export type ZoomEvents = {
mouse?: {
zoom?: MouseZoomEvent;
pan?: MousePanEvent;
doubleClick?: DoubleClickEvent;
},
scroll?: {
zoom?: ScrollZoomEvent;
pan?: ScrollPanEvent;
}
}
export type MouseZoomEvent = { // same as brush
isActive?: boolean;
keyEvent?: KeyEvent; // main(or base, or smth) / shift / alt / etc
orientation?: BrushOrientation; // to BrushOrientation: vertical, horizaontal, square, rectange
}
export type MousePanEvent = { // same as brush
isActive?: boolean;
keyEvent?: KeyEvent; // main(or base, or smth) / shift / alt / etc
orientation?: PanOrientation;
}
export type DoubleClickEvent = {
isActive: boolean;
keyEvent?: KeyEvent;
}
export type ScrollZoomEvent = {
isActive?: boolean;
keyEvent?: KeyEvent;
orientation?: PanOrientation; // TODO: rename
}
export type ScrollPanEvent = {
isActive?: boolean;
keyEvent?: KeyEvent;
panStep?: number;
orientation?: ScrollPanOrientation;
direction?: ScrollPanDirection;
}

Loading…
Cancel
Save