From 073993d663bba86f0379d3599aa581a05d10228b Mon Sep 17 00:00:00 2001 From: vargburz Date: Sat, 21 May 2022 15:12:23 +0300 Subject: [PATCH 1/7] series model --- src/index.ts | 135 +++++++++++++++++++----------------------- src/models/options.ts | 0 src/models/series.ts | 71 ++++++++++++++++++++++ src/state.ts | 4 +- src/types.ts | 3 +- 5 files changed, 137 insertions(+), 76 deletions(-) create mode 100644 src/models/options.ts create mode 100644 src/models/series.ts diff --git a/src/index.ts b/src/index.ts index ac9ec2e..f53584c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,13 @@ import VueChartwerkPodMixin from './VueChartwerkPodMixin'; import { PodState } from './state'; import { Grid } from './components/grid'; +import { CoreSeries } from 'models/series'; import styles from './css/style.css'; import { Margin, - TimeSerie, + CoreSerie, Options, TickOrientation, TimeFormat, @@ -109,7 +110,11 @@ const DEFAULT_OPTIONS: Options = { renderLegend: true, } -abstract class ChartwerkPod { +abstract class ChartwerkPod { + + protected coreSeries: CoreSeries; + protected options: O; + protected d3Node?: d3.Selection; protected customOverlay?: d3.Selection; protected crosshair?: d3.Selection; @@ -130,8 +135,7 @@ abstract class ChartwerkPod { protected y1AxisElement?: d3.Selection; protected yAxisTicksColors?: string[] = []; private _clipPathUID = ''; - protected series: T[]; - protected options: O; + protected readonly d3: typeof d3; protected deltaYTransform = 0; // TODO: forceRerender is a hack, it will be remove someday. But we need to update state on resize @@ -154,7 +158,8 @@ abstract class ChartwerkPod { let options = cloneDeep(_options); defaultsDeep(options, DEFAULT_OPTIONS); this.options = options; - this.series = cloneDeep(_series); + + this.coreSeries = new CoreSeries(_series); this.d3Node = d3.select(this.el); this.addEventListeners(); @@ -227,8 +232,7 @@ abstract class ChartwerkPod { if(newSeries === undefined) { return; } - let series = cloneDeep(newSeries); - this.series = series; + this.coreSeries.updateSeries(newSeries); } protected abstract renderMetrics(): void; @@ -243,7 +247,7 @@ abstract class ChartwerkPod { height: this.height, width: this.width, } - this.state = new PodState(boxPararms, this.series, this.options); + this.state = new PodState(boxPararms, this.coreSeries.visibleSeries, this.options); } protected initComponents(): void { @@ -521,46 +525,48 @@ abstract class ChartwerkPod { if(this.options.renderLegend === false) { return; } - if(this.series.length > 0) { - let legendRow = this.chartContainer - .append('g') - .attr('class', 'legend-row'); - for(let idx = 0; idx < this.series.length; idx++) { - if(includes(this.seriesTargetsWithBounds, this.series[idx].target)) { - continue; - } - let node = legendRow.selectAll('text').node(); - let rowWidth = 0; - if(node !== null) { - 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(`
`) - .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); - } - }); + if(!this.coreSeries.isSeriesAvailable) { + return; + } + let legendRow = this.chartContainer + .append('g') + .attr('class', 'legend-row'); + const series = this.coreSeries.allSeries; + for(let idx = 0; idx < series.length; idx++) { + if(includes(this.seriesTargetsWithBounds, series[idx].target)) { + continue; } + let node = legendRow.selectAll('text').node(); + let rowWidth = 0; + if(node !== null) { + rowWidth = legendRow.node().getBBox().width + 25; + } + + const isChecked = series[idx].visible !== false; + legendRow.append('foreignObject') + .attr('x', rowWidth) + .attr('y', this.legendRowPositionY - 12) + .attr('width', 13) + .attr('height', 15) + .html(`
`) + .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', series[idx].color) + .text(series[idx].target) + .on('click', () => { + if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendLabelClick !== undefined) { + this.options.eventsCallbacks.onLegendLabelClick(idx); + } + }); } } @@ -585,7 +591,7 @@ abstract class ChartwerkPod { return; } let yPosition = this.height + this.margin.top + this.margin.bottom - 35; - if(this.series.length === 0) { + if(this.coreSeries.isSeriesAvailable) { yPosition += 20; } this.chartContainer.append('text') @@ -943,11 +949,11 @@ abstract class ChartwerkPod { } get serieTimestampRange(): number | undefined { - if(this.series.length === 0) { + if(!this.coreSeries.isSeriesAvailable) { return undefined; } - const startTimestamp = first(this.series[0].datapoints)[0]; - const endTimestamp = last(this.series[0].datapoints)[0]; + const startTimestamp = first(this.coreSeries.visibleSeries[0].datapoints)[0]; + const endTimestamp = last(this.coreSeries.visibleSeries[0].datapoints)[0]; return (endTimestamp - startTimestamp) / 1000; } @@ -982,8 +988,8 @@ abstract class ChartwerkPod { } get timeInterval(): number { - if(this.series !== undefined && this.series.length > 0 && this.series[0].datapoints.length > 1) { - const interval = this.series[0].datapoints[1][0] - this.series[0].datapoints[0][0]; + if(this.coreSeries.isSeriesAvailable && this.coreSeries.visibleSeries[0].datapoints.length > 1) { + const interval = this.coreSeries.visibleSeries[0].datapoints[1][0] - this.coreSeries.visibleSeries[0].datapoints[0][0]; return interval; } if(this.options.timeInterval !== undefined && this.options.timeInterval.count !== undefined) { @@ -1033,7 +1039,7 @@ abstract class ChartwerkPod { optionalMargin.left += 20; } } - if(this.series.length > 0) { + if(this.coreSeries.isSeriesAvailable) { optionalMargin.bottom += 25; } return optionalMargin; @@ -1067,19 +1073,6 @@ abstract class ChartwerkPod { 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 || @@ -1089,17 +1082,13 @@ abstract class ChartwerkPod { return []; } let series = []; - this.series.forEach(serie => { + this.coreSeries.allSeries.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 { if(this._clipPathUID.length === 0) { this._clipPathUID = uid(); @@ -1123,7 +1112,7 @@ abstract class ChartwerkPod { export { ChartwerkPod, VueChartwerkPodMixin, - Margin, TimeSerie, Options, TickOrientation, TimeFormat, BrushOrientation, PanOrientation, + Margin, CoreSerie, Options, TickOrientation, TimeFormat, BrushOrientation, PanOrientation, AxisFormat, yAxisOrientation, CrosshairOrientation, ScrollPanOrientation, ScrollPanDirection, KeyEvent, palette }; diff --git a/src/models/options.ts b/src/models/options.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/models/series.ts b/src/models/series.ts new file mode 100644 index 0000000..9c2c37c --- /dev/null +++ b/src/models/series.ts @@ -0,0 +1,71 @@ +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: '', + visible: true, + yOrientation: yAxisOrientation.LEFT, + datapoints: [], + // fields below will be set in "fillDefaults" method + idx: undefined, + color: undefined, +}; + +export class CoreSeries { + _series: Array = []; + + 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(SERIE_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 { + return this._series.filter(serie => serie.visible); + } + + get allSeries(): Array { + return this._series; + } + + get leftYRelatedSeries(): Array { + return this.visibleSeries.filter(serie => serie.yOrientation = yAxisOrientation.LEFT); + } + + get rightYRelatedSeries(): Array { + return this.visibleSeries.filter(serie => serie.yOrientation = yAxisOrientation.RIGHT); + } +} diff --git a/src/state.ts b/src/state.ts index aec86b8..6d82239 100755 --- a/src/state.ts +++ b/src/state.ts @@ -1,4 +1,4 @@ -import { TimeSerie, Options, yAxisOrientation } from './types'; +import { CoreSerie, Options, yAxisOrientation } from './types'; import * as d3 from 'd3'; @@ -22,7 +22,7 @@ const DEFAULT_TRANSFORM = { // TODO: remove duplicates in max/min values. // 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. -export class PodState { +export class PodState { private _xValueRange: [number, number]; private _yValueRange: [number, number]; private _y1ValueRange: [number, number]; diff --git a/src/types.ts b/src/types.ts index 6e55391..f16fa25 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,9 +4,10 @@ export type Margin = { top: number, right: number, bottom: number, left: number export type Timestamp = number; // TODO: Pods can render not only "time" series -export type TimeSerie = { +export type CoreSerie = { target: string, datapoints: [Timestamp, number][], + idx?: number, alias?: string, visible?: boolean, color?: string, From d1e4496181828fc9dd762840618fcb93865f60b8 Mon Sep 17 00:00:00 2001 From: vargburz Date: Sat, 21 May 2022 21:27:10 +0300 Subject: [PATCH 2/7] add series and optioons models and use it --- src/index.ts | 244 +++++++----------------------------------- src/models/options.ts | 166 ++++++++++++++++++++++++++++ src/models/series.ts | 4 +- src/state.ts | 2 +- src/types.ts | 55 +++++----- 5 files changed, 233 insertions(+), 238 deletions(-) diff --git a/src/index.ts b/src/index.ts index f53584c..9f47c38 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,9 @@ import VueChartwerkPodMixin from './VueChartwerkPodMixin'; import { PodState } from './state'; import { Grid } from './components/grid'; + import { CoreSeries } from 'models/series'; +import { CoreOptions } from 'models/options'; import styles from './css/style.css'; @@ -9,7 +11,6 @@ import { Margin, CoreSerie, Options, - TickOrientation, TimeFormat, BrushOrientation, AxisFormat, @@ -21,6 +22,7 @@ import { ScrollPanOrientation, ScrollPanDirection, AxisOption, + AxesOptions, } from './types'; import { uid } from './utils'; import { palette } from './colors'; @@ -43,77 +45,11 @@ const DEFAULT_MARGIN: Margin = { top: 30, right: 20, bottom: 20, left: 30 }; const DEFAULT_TICK_COUNT = 4; const DEFAULT_TICK_SIZE = 2; 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 { protected coreSeries: CoreSeries; - protected options: O; + protected coreOptions: CoreOptions; protected d3Node?: d3.Selection; protected customOverlay?: d3.Selection; @@ -155,10 +91,7 @@ abstract class ChartwerkPod { // TODO: test if it's necessary styles.use(); - let options = cloneDeep(_options); - defaultsDeep(options, DEFAULT_OPTIONS); - this.options = options; - + this.coreOptions = new CoreOptions(_options); this.coreSeries = new CoreSeries(_series); this.d3Node = d3.select(this.el); @@ -178,9 +111,7 @@ abstract class ChartwerkPod { } public render(): void { - if(has(this.options.eventsCallbacks, 'renderStart')) { - this.options.eventsCallbacks.renderStart(); - } + this.coreOptions.callbackRenderStart(); this.renderClipPath(); this.addEvents(); @@ -196,9 +127,7 @@ abstract class ChartwerkPod { this.renderYLabel(); this.renderXLabel(); - if(has(this.options.eventsCallbacks, 'renderEnd')) { - this.options.eventsCallbacks.renderEnd(); - } + this.coreOptions.callbackRenderEnd(); } public updateData(series?: T[], options?: O, shouldRerender = true): void { @@ -222,10 +151,7 @@ abstract class ChartwerkPod { if(newOptions === undefined) { return; } - let options = cloneDeep(newOptions); - defaultsDeep(options, DEFAULT_OPTIONS); - this.options = options; - // TODO: update state if axis ranges were changed + this.coreOptions.updateOptions(newOptions); } protected updateSeries(newSeries: T[]): void { @@ -247,7 +173,7 @@ abstract class ChartwerkPod { height: this.height, width: this.width, } - this.state = new PodState(boxPararms, this.coreSeries.visibleSeries, this.options); + this.state = new PodState(boxPararms, this.coreSeries.visibleSeries, this.coreOptions.allOptions); } protected initComponents(): void { @@ -260,7 +186,7 @@ abstract class ChartwerkPod { 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 { @@ -303,7 +229,7 @@ abstract class ChartwerkPod { } protected renderXAxis(): void { - if(this.options.axis.x.isActive === false) { + if(this.coreOptions.axis.x.isActive === false) { return; } this.chartContainer.select('#x-axis-container').remove(); @@ -314,16 +240,14 @@ abstract class ChartwerkPod { .style('pointer-events', 'none') .call( d3.axisBottom(this.xScale) - .ticks(this.options.axis.x.ticksCount) + .ticks(this.coreOptions.axis.x.ticksCount) .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 { - if(this.options.axis.y.isActive === false) { + if(this.coreOptions.axis.y.isActive === false) { return; } this.chartContainer.select('#y-axis-container').remove(); @@ -335,9 +259,9 @@ abstract class ChartwerkPod { // TODO: number of ticks shouldn't be hardcoded .call( d3.axisLeft(this.yScale) - .ticks(this.options.axis.y.ticksCount) + .ticks(this.coreOptions.axis.y.ticksCount) .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(); this.yAxisTicksColors.map((color, index) => { @@ -349,7 +273,7 @@ abstract class ChartwerkPod { } protected renderY1Axis(): void { - if(this.options.axis.y1.isActive === false) { + if(this.coreOptions.axis.y1.isActive === false) { return; } this.chartContainer.select('#y1-axis-container').remove(); @@ -363,7 +287,7 @@ abstract class ChartwerkPod { d3.axisRight(this.y1Scale) .ticks(DEFAULT_TICK_COUNT) .tickSize(DEFAULT_TICK_SIZE) - .tickFormat(this.getAxisTicksFormatter(this.options.axis.y1)) + .tickFormat(this.getAxisTicksFormatter(this.coreOptions.axis.y1)) ); } @@ -375,28 +299,28 @@ abstract class ChartwerkPod { .style('display', 'none'); if( - this.options.crosshair.orientation === CrosshairOrientation.VERTICAL || - this.options.crosshair.orientation === CrosshairOrientation.BOTH + this.coreOptions.crosshair.orientation === CrosshairOrientation.VERTICAL || + this.coreOptions.crosshair.orientation === CrosshairOrientation.BOTH ) { this.crosshair.append('line') .attr('class', 'crosshair-line') .attr('id', 'crosshair-line-x') - .attr('fill', this.options.crosshair.color) - .attr('stroke', this.options.crosshair.color) + .attr('fill', this.coreOptions.crosshair.color) + .attr('stroke', this.coreOptions.crosshair.color) .attr('stroke-width', '1px') .attr('y1', 0) .attr('y2', this.height) .style('pointer-events', 'none'); } if( - this.options.crosshair.orientation === CrosshairOrientation.HORIZONTAL || - this.options.crosshair.orientation === CrosshairOrientation.BOTH + this.coreOptions.crosshair.orientation === CrosshairOrientation.HORIZONTAL || + this.coreOptions.crosshair.orientation === CrosshairOrientation.BOTH ) { this.crosshair.append('line') .attr('class', 'crosshair-line') .attr('id', 'crosshair-line-y') - .attr('fill', this.options.crosshair.color) - .attr('stroke', this.options.crosshair.color) + .attr('fill', this.coreOptions.crosshair.color) + .attr('stroke', this.coreOptions.crosshair.color) .attr('stroke-width', '1px') .attr('x1', 0) .attr('x2', this.width) @@ -501,7 +425,7 @@ abstract class ChartwerkPod { this.initScaleX = this.xScale.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.pan = d3.zoom() @@ -533,9 +457,6 @@ abstract class ChartwerkPod { .attr('class', 'legend-row'); const series = this.coreSeries.allSeries; for(let idx = 0; idx < series.length; idx++) { - if(includes(this.seriesTargetsWithBounds, series[idx].target)) { - continue; - } let node = legendRow.selectAll('text').node(); let rowWidth = 0; if(node !== null) { @@ -549,11 +470,7 @@ abstract class ChartwerkPod { .attr('width', 13) .attr('height', 15) .html(`
`) - .on('click', () => { - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendClick !== undefined) { - this.options.eventsCallbacks.onLegendClick(idx); - } - }); + .on('click', () => this.coreOptions.callbackLegendClick(idx)); legendRow.append('text') .attr('x', rowWidth + 20) @@ -562,16 +479,12 @@ abstract class ChartwerkPod { .style('font-size', '12px') .style('fill', series[idx].color) .text(series[idx].target) - .on('click', () => { - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.onLegendLabelClick !== undefined) { - this.options.eventsCallbacks.onLegendLabelClick(idx); - } - }); + .on('click', () => this.coreOptions.callbackLegendLabelClick(idx)); } } protected renderYLabel(): void { - if(this.options.labelFormat === undefined || this.options.labelFormat.yAxis === undefined) { + if(this.coreOptions.axis.y.label === undefined) { return; } this.chartContainer.append('text') @@ -583,11 +496,11 @@ abstract class ChartwerkPod { .style('text-anchor', 'middle') .style('font-size', '14px') .style('fill', 'currentColor') - .text(this.options.labelFormat.yAxis); + .text(this.coreOptions.axis.y.label); } protected renderXLabel(): void { - if(this.options.labelFormat === undefined || this.options.labelFormat.xAxis === undefined) { + if(this.coreOptions.axis.x.label === undefined) { return; } let yPosition = this.height + this.margin.top + this.margin.bottom - 35; @@ -601,7 +514,7 @@ abstract class ChartwerkPod { .style('text-anchor', 'middle') .style('font-size', '14px') .style('fill', 'currentColor') - .text(this.options.labelFormat.xAxis); + .text(this.coreOptions.axis.x.label); } protected renderNoDataPointsMessage(): void { @@ -883,9 +796,6 @@ abstract class ChartwerkPod { } protected zoomOut(): void { - if(this.isOutOfChart() === true) { - return; - } let xAxisMiddleValue: number = this.xScale.invert(this.width / 2); let yAxisMiddleValue: number = this.yScale.invert(this.height / 2); const centers = { @@ -999,52 +909,6 @@ abstract class ChartwerkPod { 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.coreSeries.isSeriesAvailable) { - optionalMargin.bottom += 25; - } - return optionalMargin; - } - get width(): number { return this.d3Node.node().clientWidth - this.margin.left - this.margin.right; } @@ -1058,61 +922,25 @@ abstract class ChartwerkPod { } get margin(): Margin { - if(this.options.margin !== undefined) { - 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; + return this.coreOptions.margin; } protected clearState(): void { this.state.clearState(); } - protected get seriesTargetsWithBounds(): any[] { - if( - this.options.bounds === undefined || - this.options.bounds.upper === undefined || - this.options.bounds.lower === undefined - ) { - return []; - } - let series = []; - this.coreSeries.allSeries.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 rectClipId(): string { if(this._clipPathUID.length === 0) { this._clipPathUID = uid(); } 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 { ChartwerkPod, VueChartwerkPodMixin, - Margin, CoreSerie, Options, TickOrientation, TimeFormat, BrushOrientation, PanOrientation, + Margin, CoreSerie, Options, TimeFormat, BrushOrientation, PanOrientation, + AxesOptions, AxisOption, AxisFormat, yAxisOrientation, CrosshairOrientation, ScrollPanOrientation, ScrollPanDirection, KeyEvent, palette }; diff --git a/src/models/options.ts b/src/models/options.ts index e69de29..4bb935a 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -0,0 +1,166 @@ +import { + Options, + TickOrientation, + TimeFormat, + BrushOrientation, + AxisFormat, + CrosshairOrientation, + KeyEvent, + PanOrientation, + ScrollPanOrientation, + ScrollPanDirection, + GridOptions, + AxesOptions, + CrosshairOptions, + Margin +} 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_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, + // remove options below + renderTicksfromTimestamps: false, + timeInterval: { + timeFormat: TimeFormat.MINUTE + }, +} + +export class CoreOptions { + _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; + } + + // 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); + } + } +} diff --git a/src/models/series.ts b/src/models/series.ts index 9c2c37c..86dde81 100644 --- a/src/models/series.ts +++ b/src/models/series.ts @@ -9,6 +9,7 @@ import lodashUniq from 'lodash/uniq'; const SERIE_DEFAULTS = { alias: '', + target: '', visible: true, yOrientation: yAxisOrientation.LEFT, datapoints: [], @@ -19,6 +20,7 @@ const SERIE_DEFAULTS = { export class CoreSeries { _series: Array = []; + _defaults: CoreSerie = SERIE_DEFAULTS; constructor(series: T[]) { this.setSeries(series); @@ -42,7 +44,7 @@ export class CoreSeries { } protected fillDefaults(serie: T, idx: number): T { - let defaults = lodashCloneDeep(SERIE_DEFAULTS); + let defaults = lodashCloneDeep(this._defaults); defaults.color = palette[idx % palette.length]; defaults.idx = idx; lodashDefaultsDeep(serie, defaults); diff --git a/src/state.ts b/src/state.ts index 6d82239..75ee733 100755 --- a/src/state.ts +++ b/src/state.ts @@ -245,7 +245,7 @@ export class PodState { } protected filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean { - if(serie.yOrientation === undefined || serie.yOrientation === yAxisOrientation.BOTH) { + if(serie.yOrientation === undefined) { return true; } return serie.yOrientation === orientation; diff --git a/src/types.ts b/src/types.ts index f16fa25..feaa196 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,7 +16,6 @@ export type CoreSerie = { // TODO: move some options to line-chart export type Options = { margin?: Margin; - confidence?: number; eventsCallbacks?: { zoomIn?: (range: AxisRange[]) => void, panning?: (event: { ranges: AxisRange[], d3Event: any }) => void, @@ -31,32 +30,13 @@ export type Options = { renderStart?: () => void, renderEnd?: () => void, }; - axis?: { - x?: AxisOption, - y?: AxisOption, - y1?: AxisOption - }; + axis?: AxesOptions; grid?: GridOptions; - crosshair?: { - orientation?: CrosshairOrientation; - color?: string; - } + crosshair?: CrosshairOptions; timeInterval?: { timeFormat?: TimeFormat; count?: number; }; - tickFormat?: { - xAxis?: string; - xTickOrientation?: TickOrientation; - }; - labelFormat?: { - xAxis?: string; - yAxis?: string; - }; - bounds?: { - upper: string; - lower: string; - }; timeRange?: { from: number, to: number @@ -96,6 +76,7 @@ export type Options = { renderTicksfromTimestamps?: boolean; renderLegend?: boolean; }; + export type GridOptions = { x?: { enabled?: boolean; @@ -106,22 +87,32 @@ export type GridOptions = { ticksCount?: number; }, } + +export type AxesOptions = { + x?: AxisOption, + y?: AxisOption, + y1?: AxisOption +} + export type AxisOption = { isActive?: boolean; ticksCount?: number; format?: AxisFormat; range?: [number, number]; invert?: boolean; + label?: string; valueFormatter?: (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 VueOptions = Omit; -export enum TickOrientation { - VERTICAL = 'vertical', - HORIZONTAL = 'horizontal', - DIAGONAL = 'diagonal' -} + export enum TimeFormat { SECOND = 'second', MINUTE = 'minute', @@ -130,47 +121,56 @@ export enum TimeFormat { MONTH = 'month', YEAR = 'year' } + export enum BrushOrientation { VERTICAL = 'vertical', HORIZONTAL = 'horizontal', RECTANGLE = 'rectangle', SQUARE = 'square' } + export enum PanOrientation { VERTICAL = 'vertical', HORIZONTAL = 'horizontal', BOTH = 'both', } + export enum ScrollPanOrientation { VERTICAL = 'vertical', HORIZONTAL = 'horizontal', } + export enum ScrollPanDirection { FORWARD = 'forward', BACKWARD = 'backward', BOTH = 'both', } + export enum AxisFormat { TIME = 'time', NUMERIC = 'numeric', STRING = 'string', CUSTOM = 'custom' } + export enum CrosshairOrientation { VERTICAL = 'vertical', HORIZONTAL = 'horizontal', BOTH = 'both' } + export type SvgElementAttributes = { x: number, y: number, width: number, height: number } + export enum KeyEvent { MAIN = 'main', SHIFT = 'shift' } + // allow series values to affect a specific axis export enum xAxisOrientation { TOP = 'top', @@ -180,7 +180,6 @@ export enum xAxisOrientation { export enum yAxisOrientation { LEFT = 'left', RIGHT = 'right', - BOTH = 'both' } export type SvgElParams = { height: number, From 21c8a6c986a7bf5b1c5ec3783b48b1b10b3c4c97 Mon Sep 17 00:00:00 2001 From: vargburz Date: Mon, 23 May 2022 19:28:22 +0300 Subject: [PATCH 3/7] types for zoom events --- src/models/options.ts | 43 ++++++++++++++++-------- src/types.ts | 76 +++++++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/models/options.ts b/src/models/options.ts index 4bb935a..0bdf129 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -1,18 +1,10 @@ import { Options, - TickOrientation, - TimeFormat, - BrushOrientation, - AxisFormat, - CrosshairOrientation, - KeyEvent, - PanOrientation, - ScrollPanOrientation, - ScrollPanDirection, - GridOptions, - AxesOptions, - CrosshairOptions, - Margin + GridOptions, AxesOptions, AxisFormat, + CrosshairOptions, CrosshairOrientation, + ZoomEvents, MouseZoomEvent, MousePanEvent, DoubleClickEvent, ScrollZoomEvent, ScrollPanEvent, + ScrollPanOrientation, ScrollPanDirection, PanOrientation, KeyEvent, BrushOrientation, + Margin, TimeFormat, } from '../types'; import lodashDefaultsDeep from 'lodash/defaultsDeep'; @@ -139,6 +131,31 @@ export class CoreOptions { 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')) { diff --git a/src/types.ts b/src/types.ts index feaa196..fc872f3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,38 +41,7 @@ export type Options = { 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; - }, - }, - } + zoomEvents?: ZoomEvents; renderTicksfromTimestamps?: boolean; renderLegend?: boolean; }; @@ -187,3 +156,46 @@ export type SvgElParams = { xScale: d3.ScaleLinear, yScale: d3.ScaleLinear, } + +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; +} From 935a1dfb5c29411ce51ec49d4a2d49b709c9d779 Mon Sep 17 00:00:00 2001 From: vargburz Date: Mon, 23 May 2022 19:45:10 +0300 Subject: [PATCH 4/7] use rest of options --- src/index.ts | 76 +++++++++++++++++-------------------------- src/models/options.ts | 26 ++++++++++++++- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9f47c38..722a729 100644 --- a/src/index.ts +++ b/src/index.ts @@ -330,8 +330,8 @@ abstract class ChartwerkPod { protected addEvents(): void { // TODO: refactor for a new mouse/scroll events - const panKeyEvent = this.options.zoomEvents.mouse.pan.keyEvent; - const isPanActive = this.options.zoomEvents.mouse.pan.isActive; + const panKeyEvent = this.coreOptions.mousePanEvent.keyEvent; + const isPanActive = this.coreOptions.mousePanEvent.isActive; if(isPanActive === true && panKeyEvent === KeyEvent.MAIN) { this.initPan(); this.initBrush(); @@ -348,11 +348,11 @@ abstract class ChartwerkPod { } protected initBrush(): void { - const isBrushActive = this.options.zoomEvents.mouse.zoom.isActive; + const isBrushActive = this.coreOptions.mouseZoomEvent.isActive; if(isBrushActive === false) { return; } - switch(this.options.zoomEvents.mouse.zoom.orientation) { + switch(this.coreOptions.mouseZoomEvent.orientation) { case BrushOrientation.VERTICAL: this.brush = d3.brushY(); break; @@ -366,7 +366,7 @@ abstract class ChartwerkPod { default: this.brush = d3.brushX(); } - const keyEvent = this.options.zoomEvents.mouse.zoom.keyEvent; + const keyEvent = this.coreOptions.mouseZoomEvent.keyEvent; this.brush.extent([ [0, 0], [this.width, this.height] @@ -404,13 +404,13 @@ abstract class ChartwerkPod { protected initPan(): void { if( - this.options.zoomEvents.mouse.pan.isActive === false && - this.options.zoomEvents.scroll.pan.isActive === false && - this.options.zoomEvents.scroll.zoom.isActive === false + this.coreOptions.mousePanEvent.isActive === false && + this.coreOptions.scrollPanEvent.isActive === false && + this.coreOptions.scrollZoomEvent.isActive === false ) { return; } - if(this.options.zoomEvents.mouse.zoom.isActive === false) { + if(this.coreOptions.mouseZoomEvent.isActive === false) { // init cumstom overlay to handle all events this.customOverlay = this.chartContainer.append('rect') .attr('class', 'custom-overlay') @@ -446,7 +446,7 @@ abstract class ChartwerkPod { } protected renderLegend(): void { - if(this.options.renderLegend === false) { + if(this.coreOptions.allOptions.renderLegend === false) { return; } if(!this.coreSeries.isSeriesAvailable) { @@ -530,12 +530,12 @@ abstract class ChartwerkPod { private disableScrollForward(event: any): boolean { return event.sourceEvent.wheelDelta > 0 - && this.options.zoomEvents.scroll.pan.direction === ScrollPanDirection.FORWARD; + && this.coreOptions.scrollPanEvent.direction === ScrollPanDirection.FORWARD; } private disableScrollBackward(event: any): boolean { return event.sourceEvent.wheelDelta < 0 - && this.options.zoomEvents.scroll.pan.direction === ScrollPanDirection.BACKWARD; + && this.coreOptions.scrollPanEvent.direction === ScrollPanDirection.BACKWARD; } protected onPanning(): void { @@ -549,14 +549,10 @@ abstract class ChartwerkPod { } this.rescaleMetricAndAxis(event); - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.panning !== undefined) { - this.options.eventsCallbacks.panning({ - ranges: [this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange], - d3Event: event - }); - } else { - console.log('on panning, but there is no callback'); - } + this.coreOptions.callbackPanning({ + ranges: [this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange], + d3Event: event + }); } public rescaleMetricAndAxis(event: d3.D3ZoomEvent): void { @@ -575,8 +571,8 @@ abstract class ChartwerkPod { protected onPanningRescale(event: d3.D3ZoomEvent): void { // rescale metrics and axis on mouse and scroll panning const eventType = event.sourceEvent.type; // 'wheel' or 'mousemove' - const scrollPanOptions = this.options.zoomEvents.scroll.pan; - const scrollZoomOptions = this.options.zoomEvents.scroll.zoom; + const scrollPanOptions = this.coreOptions.scrollPanEvent; + const scrollZoomOptions = this.coreOptions.scrollZoomEvent; // TODO: maybe use switch and move it to onPanning if(eventType === 'wheel') { if(scrollPanOptions.isActive === true && this.isD3EventKeyEqualOption(event, scrollPanOptions.keyEvent)) { @@ -609,7 +605,7 @@ abstract class ChartwerkPod { return; } - const panOrientation = this.options.zoomEvents.mouse.pan.orientation; + const panOrientation = this.coreOptions.mousePanEvent.orientation; if(panOrientation === PanOrientation.HORIZONTAL || panOrientation === PanOrientation.BOTH) { this.rescaleAxisX(event.transform.x); } @@ -642,7 +638,7 @@ abstract class ChartwerkPod { } protected onScrollPanningRescale(event: d3.D3ZoomEvent): 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 const transformStep = scrollPanOptions.panStep; const scrollPanOrientation = scrollPanOptions.orientation; @@ -659,7 +655,7 @@ abstract class ChartwerkPod { case ScrollPanOrientation.VERTICAL: // @ts-ignore let signY = Math.sign(event.transform.y); - if(this.options.axis.y.invert === true) { + if(this.coreOptions.axis.y.invert === true) { signY = -signY; } let rangeY = this.state.yValueRange; @@ -683,16 +679,12 @@ abstract class ChartwerkPod { this.isPanning = false; this.deltaYTransform = 0; this.onMouseOut(); - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.panningEnd !== undefined) { - this.options.eventsCallbacks.panningEnd([this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange]); - } else { - console.log('on panning end, but there is no callback'); - } + this.coreOptions.callbackPanningEnd([this.state.xValueRange, this.state.yValueRange, this.state.y1ValueRange]); } protected onBrush(): void { 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; } const selectionAtts = this.getSelectionAttrs(selection); @@ -748,7 +740,7 @@ abstract class ChartwerkPod { let xRange: [number, number]; let yRange: [number, number]; - switch(this.options.zoomEvents.mouse.zoom.orientation) { + switch(this.coreOptions.mouseZoomEvent.orientation) { case BrushOrientation.HORIZONTAL: const startTimestamp = this.xScale.invert(extent[0]); const endTimestamp = this.xScale.invert(extent[1]); @@ -788,11 +780,7 @@ abstract class ChartwerkPod { this.brushStartSelection = null; } - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.zoomIn !== undefined) { - this.options.eventsCallbacks.zoomIn([xRange, yRange]); - } else { - console.log('zoom in, but there is no callback'); - } + this.coreOptions.callbackZoomIn([xRange, yRange]); } protected zoomOut(): void { @@ -802,11 +790,7 @@ abstract class ChartwerkPod { x: xAxisMiddleValue, y: yAxisMiddleValue } - if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.zoomOut !== undefined) { - this.options.eventsCallbacks.zoomOut(centers); - } else { - console.log('zoom out, but there is no callback'); - } + this.coreOptions.callbackZoomOut(centers); } // TODO: move to State @@ -837,10 +821,10 @@ abstract class ChartwerkPod { } 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); } - switch(this.options.timeInterval.timeFormat) { + switch(this.coreOptions.allOptions.timeInterval.timeFormat) { case TimeFormat.SECOND: return d3.utcSecond.every(count); case TimeFormat.MINUTE: @@ -902,9 +886,9 @@ abstract class ChartwerkPod { const interval = this.coreSeries.visibleSeries[0].datapoints[1][0] - this.coreSeries.visibleSeries[0].datapoints[0][0]; return interval; } - if(this.options.timeInterval !== undefined && this.options.timeInterval.count !== undefined) { + if(this.coreOptions.allOptions.timeInterval.count !== undefined) { //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; } diff --git a/src/models/options.ts b/src/models/options.ts index 0bdf129..299fed0 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -4,7 +4,7 @@ import { CrosshairOptions, CrosshairOrientation, ZoomEvents, MouseZoomEvent, MousePanEvent, DoubleClickEvent, ScrollZoomEvent, ScrollPanEvent, ScrollPanOrientation, ScrollPanDirection, PanOrientation, KeyEvent, BrushOrientation, - Margin, TimeFormat, + Margin, TimeFormat, AxisRange, } from '../types'; import lodashDefaultsDeep from 'lodash/defaultsDeep'; @@ -180,4 +180,28 @@ export class CoreOptions { 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); + } + } } From 1e2f6b9f520cc6964d147c5112c74b4892c44b7a Mon Sep 17 00:00:00 2001 From: vargburz Date: Mon, 23 May 2022 19:50:00 +0300 Subject: [PATCH 5/7] fix --- src/index.ts | 9 +-------- src/models/options.ts | 3 +++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index 722a729..535fff9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,19 +29,11 @@ import { palette } from './colors'; import * as d3 from 'd3'; -import defaultsDeep from 'lodash/defaultsDeep'; -import includes from 'lodash/includes'; import first from 'lodash/first'; 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 has from 'lodash/has'; -const DEFAULT_MARGIN: Margin = { top: 30, right: 20, bottom: 20, left: 30 }; const DEFAULT_TICK_COUNT = 4; const DEFAULT_TICK_SIZE = 2; const MILISECONDS_IN_MINUTE = 60 * 1000; @@ -173,6 +165,7 @@ abstract class ChartwerkPod { height: this.height, width: this.width, } + // TODO: use instanses instead of oblects this.state = new PodState(boxPararms, this.coreSeries.visibleSeries, this.coreOptions.allOptions); } diff --git a/src/models/options.ts b/src/models/options.ts index 299fed0..6627586 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -51,6 +51,8 @@ const DEFAULT_CROSSHAIR_OPTIONS = { color: 'gray', } +const DEFAULT_MARGIN: Margin = { top: 30, right: 20, bottom: 20, left: 30 }; + const DEFAULT_OPTIONS: Options = { zoomEvents: { mouse: { @@ -88,6 +90,7 @@ const DEFAULT_OPTIONS: Options = { grid: DEFAULT_GRID_OPTIONS, crosshair: DEFAULT_CROSSHAIR_OPTIONS, renderLegend: true, + margin: DEFAULT_MARGIN, // remove options below renderTicksfromTimestamps: false, timeInterval: { From 3cec0f80ca9cc9f6eff170830da832b70565ee4e Mon Sep 17 00:00:00 2001 From: vargburz Date: Mon, 23 May 2022 20:14:12 +0300 Subject: [PATCH 6/7] fix path --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 535fff9..256cc25 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,8 @@ import VueChartwerkPodMixin from './VueChartwerkPodMixin'; import { PodState } from './state'; import { Grid } from './components/grid'; -import { CoreSeries } from 'models/series'; -import { CoreOptions } from 'models/options'; +import { CoreSeries } from './models/series'; +import { CoreOptions } from './models/options'; import styles from './css/style.css'; From aaedd4988085bd4c1c0a3fe23a99451f3d6d98cb Mon Sep 17 00:00:00 2001 From: vargburz Date: Mon, 23 May 2022 20:35:37 +0300 Subject: [PATCH 7/7] fix --- src/types.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/types.ts b/src/types.ts index fc872f3..e156fb8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,10 +37,6 @@ export type Options = { timeFormat?: TimeFormat; count?: number; }; - timeRange?: { - from: number, - to: number - }; zoomEvents?: ZoomEvents; renderTicksfromTimestamps?: boolean; renderLegend?: boolean;