diff --git a/src/index.ts b/src/index.ts index fbcfa5e..acc4dd7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -119,7 +119,7 @@ abstract class ChartwerkPod { protected brush?: d3.BrushBehavior; protected zoom?: any; protected svg?: d3.Selection; - protected state?: PodState; + protected state?: PodState; protected clipPath?: any; protected isPanning = false; protected isBrushing = false; @@ -162,7 +162,6 @@ abstract class ChartwerkPod { this.series = cloneDeep(_series); this.d3 = _d3; - // TODO: mb move it to render(); this.initPodState(); this.d3Node = this.d3.select(this.el); @@ -181,8 +180,7 @@ abstract class ChartwerkPod { } public render(): void { - this.clearScaleCache(); - + console.log('render'); this.renderAxes(); this.renderGrid(); @@ -235,7 +233,7 @@ abstract class ChartwerkPod { public abstract hideSharedCrosshair(): void; protected initPodState(): void { - this.state = new PodState(this.options); + this.state = new PodState(this.series, this.options); } protected initComponents(): void { @@ -673,9 +671,6 @@ abstract class ChartwerkPod { const signX = Math.sign(event.transform.x); const transformX = this.absXScale.invert(Math.abs(transformStep)); let rangeX = this.state.xValueRange; - if(this.state.xValueRange === undefined) { - rangeX = [this.maxValueX, this.minValueX]; - } this.state.xValueRange = [rangeX[0] + signX * transformX, rangeX[1] + signX * transformX]; const translateX = this.state.transform.x + signX * transformStep; this.state.transform = { x: translateX }; @@ -687,7 +682,7 @@ abstract class ChartwerkPod { if(this.options.axis.y.invert === true) { signY = -signY; } - let rangeY = this.state.yValueRange || [this.maxValue, this.minValue]; + let rangeY = this.state.yValueRange; const transformY = this.absYScale.invert(deltaY); this.deltaYTransform = this.deltaYTransform + deltaY; // TODO: not hardcoded bounds @@ -840,22 +835,23 @@ abstract class ChartwerkPod { } get absXScale(): d3.ScaleLinear { - const domain = [0, Math.abs(this.maxValueX - this.minValueX)]; + const domain = [0, Math.abs(this.state.maxValueX - this.state.minValueX)]; return this.d3.scaleLinear() .domain(domain) .range([0, this.width]); } get absYScale(): d3.ScaleLinear { - const domain = [0, Math.abs(this.maxValue - this.minValue)]; + const domain = [0, Math.abs(this.state.maxValueY - this.state.minValueY)]; return this.d3.scaleLinear() .domain(domain) .range([0, this.height]); } + // TODO: scales should be moved to State. Because it dependes on changeable ranges get xScale(): d3.ScaleLinear { if(this._xScale === null) { - const domain = this.state.xValueRange || [this.minValueX, this.maxValueX]; + const domain = this.state.xValueRange; this._xScale = this.d3.scaleLinear() .domain(domain) .range([0, this.width]); @@ -865,7 +861,7 @@ abstract class ChartwerkPod { get yScale(): d3.ScaleLinear { if(this._yScale === null) { - let domain = this.state.yValueRange || [this.maxValue, this.minValue]; + let domain = this.state.yValueRange; domain = sortBy(domain) as [number, number]; if(this.options.axis.y.invert === true) { domain = reverse(domain); @@ -878,12 +874,12 @@ abstract class ChartwerkPod { } protected get y1Scale(): d3.ScaleLinear { - if(this.isSeriesUnavailable || this.options.axis.y1 === undefined || this.options.axis.y1.isActive === false) { + if(this.state.isSeriesUnavailable || this.options.axis.y1 === undefined || this.options.axis.y1.isActive === false) { return null; } // scale for y1 axis(right y axis) if(this._y1Scale === null) { - let domain = this.state.y1ValueRange || [this.y1MaxValue, this.y1MinValue]; + let domain = this.state.y1ValueRange; domain = sortBy(domain) as [number, number]; if(this.options.axis.y1.invert === true) { domain = reverse(domain); @@ -895,119 +891,6 @@ abstract class ChartwerkPod { return this._y1Scale; } - filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean { - if(serie.yOrientation === undefined || serie.yOrientation === yAxisOrientation.BOTH) { - return true; - } - return serie.yOrientation === orientation; - } - - get minValue(): number { - // y min value - if(this.isSeriesUnavailable) { - return DEFAULT_AXIS_RANGE[0]; - } - if(this.options.axis.y !== undefined && this.options.axis.y.range !== undefined) { - return min(this.options.axis.y.range); - } - const minValue = min( - this.series - .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.LEFT)) - .map( - serie => minBy(serie.datapoints, dp => dp[1])[1] - ) - ); - return minValue; - } - - get maxValue(): number { - // y max value - if(this.isSeriesUnavailable) { - return DEFAULT_AXIS_RANGE[1]; - } - if(this.options.axis.y !== undefined && this.options.axis.y.range !== undefined) { - return max(this.options.axis.y.range); - } - const maxValue = max( - this.series - .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.LEFT)) - .map( - serie => maxBy(serie.datapoints, dp => dp[1])[1] - ) - ); - return maxValue; - } - - get y1MinValue(): number { - // TODO: remove duplicates - if(this.isSeriesUnavailable || this.options.axis.y1 === undefined || this.options.axis.y1.isActive === false) { - return DEFAULT_AXIS_RANGE[0]; - } - if(this.options.axis.y1.range !== undefined) { - return min(this.options.axis.y1.range); - } - const minValue = min( - this.series - .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.RIGHT)) - .map( - serie => minBy(serie.datapoints, dp => dp[1])[1] - ) - ); - return minValue; - } - - get y1MaxValue(): number { - if(this.isSeriesUnavailable || this.options.axis.y1 === undefined || this.options.axis.y1.isActive === false) { - return DEFAULT_AXIS_RANGE[1]; - } - if(this.options.axis.y1 !== undefined && this.options.axis.y1.range !== undefined) { - return max(this.options.axis.y1.range); - } - const maxValue = max( - this.series - .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.RIGHT)) - .map( - serie => maxBy(serie.datapoints, dp => dp[1])[1] - ) - ); - return maxValue; - } - - get minValueX(): number { - if(this.isSeriesUnavailable) { - return DEFAULT_AXIS_RANGE[0]; - } - - if(this.options.axis.x !== undefined && this.options.axis.x.range !== undefined) { - return min(this.options.axis.x.range) - } - const minValue = min( - this.series - .filter(serie => serie.visible !== false) - .map( - serie => minBy(serie.datapoints, dp => dp[0])[0] - ) - ); - return minValue; - } - - get maxValueX(): number { - if(this.isSeriesUnavailable) { - return DEFAULT_AXIS_RANGE[1]; - } - if(this.options.axis.x !== undefined && this.options.axis.x.range !== undefined) { - return max(this.options.axis.x.range) - } - const maxValue = max( - this.series - .filter(serie => serie.visible !== false) - .map( - serie => maxBy(serie.datapoints, dp => dp[0])[0] - ) - ); - return maxValue; - } - getd3TimeRangeEvery(count: number): d3.TimeInterval { if(this.options.timeInterval === undefined || this.options.timeInterval.timeFormat === undefined) { return this.d3.timeMinute.every(count); @@ -1146,12 +1029,6 @@ abstract class ChartwerkPod { return mergeWith({}, DEFAULT_MARGIN, this.extraMargin, add); } - get isSeriesUnavailable(): boolean { - // TODO: Use one && throw error - return this.series === undefined || this.series.length === 0 || - max(this.series.map(serie => serie.datapoints.length)) === 0; - } - formatedBound(alias: string, target: string): string { const confidenceMetric = replace(alias, '$__metric_name', target); return confidenceMetric; diff --git a/src/state.ts b/src/state.ts index 6364ac7..e903ffb 100755 --- a/src/state.ts +++ b/src/state.ts @@ -1,27 +1,42 @@ -import { Options, TimeFormat, TickOrientation, AxisFormat } from './types'; +import { TimeSerie, Options, yAxisOrientation } from './types'; -import lodashGet from 'lodash/get'; import cloneDeep from 'lodash/cloneDeep'; +import min from 'lodash/min'; +import minBy from 'lodash/minBy'; +import max from 'lodash/max'; +import maxBy from 'lodash/maxBy'; +const DEFAULT_AXIS_RANGE = [0, 1]; const DEFAULT_TRANSFORM = { x: 0, y: 0, k: 1 } -export class PodState { - private _xValueRange: [number, number] | undefined = undefined; - private _yValueRange: [number, number] | undefined = undefined; - private _y1ValueRange: [number, number] | undefined = undefined; +// TODO: replace all getters with fields. Because getters will be recalculated on each call +// TODO: remove duplicates in max/min values. +// TODO: add scales +// TODO: PodState can be divided in two classes, but it is hard now. +export class PodState { + private _xValueRange: [number, number]; + private _yValueRange: [number, number]; + private _y1ValueRange: [number, number] | undefined = undefined; // can be undefined, because y1 - is an optional param private _transform: { x: number, y: number, k: number } = cloneDeep(DEFAULT_TRANSFORM); constructor( - options: Options + protected series: T[], + protected options: O, ) { - this._xValueRange = lodashGet(options, 'axis.x.range'); - this._yValueRange = lodashGet(options, 'axis.y.range'); - this._y1ValueRange = lodashGet(options, 'axis.y1.range'); + this.initRanges(); + } + + protected initRanges(): void { + this._xValueRange = [this.minValueX, this.maxValueX]; + this._yValueRange = [this.minValueY, this.maxValueY]; + if(this.options.axis.y1.isActive) { + this._y1ValueRange = [this.minValueY1, this.maxValueY1]; + } } get xValueRange(): [number, number] | undefined { @@ -57,4 +72,119 @@ export class PodState { this._transform.y = transform.y !== undefined ? transform.y : this._transform.y; this._transform.k = transform.k !== undefined ? transform.k : this._transform.k; } + + get minValueY(): number { + if(this.isSeriesUnavailable) { + return DEFAULT_AXIS_RANGE[0]; + } + if(this.options.axis.y !== undefined && this.options.axis.y.range !== undefined) { + return min(this.options.axis.y.range); + } + const minValue = min( + this.series + .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.LEFT)) + .map( + serie => minBy(serie.datapoints, dp => dp[1])[1] + ) + ); + return minValue; + } + + get maxValueY(): number { + if(this.isSeriesUnavailable) { + return DEFAULT_AXIS_RANGE[1]; + } + if(this.options.axis.y !== undefined && this.options.axis.y.range !== undefined) { + return max(this.options.axis.y.range); + } + const maxValue = max( + this.series + .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.LEFT)) + .map( + serie => maxBy(serie.datapoints, dp => dp[1])[1] + ) + ); + return maxValue; + } + + get minValueX(): number { + if(this.isSeriesUnavailable) { + return DEFAULT_AXIS_RANGE[0]; + } + + if(this.options.axis.x !== undefined && this.options.axis.x.range !== undefined) { + return min(this.options.axis.x.range); + } + const minValue = min( + this.series + .filter(serie => serie.visible !== false) + .map( + serie => minBy(serie.datapoints, dp => dp[0])[0] + ) + ); + return minValue; + } + + get maxValueX(): number { + if(this.isSeriesUnavailable) { + return DEFAULT_AXIS_RANGE[1]; + } + if(this.options.axis.x !== undefined && this.options.axis.x.range !== undefined) { + return max(this.options.axis.x.range) + } + const maxValue = max( + this.series + .filter(serie => serie.visible !== false) + .map( + serie => maxBy(serie.datapoints, dp => dp[0])[0] + ) + ); + return maxValue; + } + + get minValueY1(): number { + if(this.isSeriesUnavailable || this.options.axis.y1 === undefined || this.options.axis.y1.isActive === false) { + return DEFAULT_AXIS_RANGE[0]; + } + if(this.options.axis.y1.range !== undefined) { + return min(this.options.axis.y1.range); + } + const minValue = min( + this.series + .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.RIGHT)) + .map( + serie => minBy(serie.datapoints, dp => dp[1])[1] + ) + ); + return minValue; + } + + get maxValueY1(): number { + if(this.isSeriesUnavailable || this.options.axis.y1 === undefined || this.options.axis.y1.isActive === false) { + return DEFAULT_AXIS_RANGE[1]; + } + if(this.options.axis.y1 !== undefined && this.options.axis.y1.range !== undefined) { + return max(this.options.axis.y1.range); + } + const maxValue = max( + this.series + .filter(serie => serie.visible !== false && this.filterSerieByYAxisOrientation(serie, yAxisOrientation.RIGHT)) + .map( + serie => maxBy(serie.datapoints, dp => dp[1])[1] + ) + ); + return maxValue; + } + + get isSeriesUnavailable(): boolean { + return this.series === undefined || this.series.length === 0 || + max(this.series.map(serie => serie.datapoints.length)) === 0; + } + + protected filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean { + if(serie.yOrientation === undefined || serie.yOrientation === yAxisOrientation.BOTH) { + return true; + } + return serie.yOrientation === orientation; + } }