Browse Source

Merge branch 'state-ranges' into 'main'

State class updates

Closes #6 and #7

See merge request chartwerk/core!6
merge-requests/7/merge
Alexey Velikiy 3 years ago
parent
commit
5f0ddbdc78
  1. 14
      dist/components/grid.d.ts
  2. 22
      dist/index.d.ts
  3. 2
      dist/index.js
  4. 37
      dist/state.d.ts
  5. 27
      dist/types.d.ts
  6. 2
      package-lock.json
  7. 216
      src/index.ts
  8. 214
      src/state.ts

14
dist/components/grid.d.ts vendored

@ -0,0 +1,14 @@
import { GridOptions, SvgElParams } from '../types';
import * as d3 from 'd3';
export declare class Grid {
private _d3;
private _svgEl;
private _svgElParams;
protected gridOptions: GridOptions;
constructor(_d3: typeof d3, _svgEl: d3.Selection<SVGElement, unknown, null, undefined>, _svgElParams: SvgElParams, _gridOptions: GridOptions);
protected setOptionDefaults(gridOptions: GridOptions): GridOptions;
render(): void;
renderGridLinesX(): void;
renderGridLinesY(): void;
updateStylesOfTicks(): void;
}

22
dist/index.d.ts vendored

@ -1,6 +1,7 @@
/// <reference types="lodash" /> /// <reference types="lodash" />
import VueChartwerkPodMixin from './VueChartwerkPodMixin'; import VueChartwerkPodMixin from './VueChartwerkPodMixin';
import { PodState } from './state'; import { PodState } from './state';
import { Grid } from './components/grid';
import { Margin, TimeSerie, Options, TickOrientation, TimeFormat, BrushOrientation, AxisFormat, CrosshairOrientation, SvgElementAttributes, KeyEvent, PanOrientation, yAxisOrientation, ScrollPanOrientation, AxisOption } from './types'; import { Margin, TimeSerie, Options, TickOrientation, TimeFormat, BrushOrientation, AxisFormat, CrosshairOrientation, SvgElementAttributes, KeyEvent, PanOrientation, yAxisOrientation, ScrollPanOrientation, AxisOption } from './types';
import { palette } from './colors'; import { palette } from './colors';
import * as d3 from 'd3'; import * as d3 from 'd3';
@ -13,7 +14,7 @@ declare abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
protected brush?: d3.BrushBehavior<unknown>; protected brush?: d3.BrushBehavior<unknown>;
protected zoom?: any; protected zoom?: any;
protected svg?: d3.Selection<SVGElement, unknown, null, undefined>; protected svg?: d3.Selection<SVGElement, unknown, null, undefined>;
protected state?: PodState; protected state?: PodState<T, O>;
protected clipPath?: any; protected clipPath?: any;
protected isPanning: boolean; protected isPanning: boolean;
protected isBrushing: boolean; protected isBrushing: boolean;
@ -31,9 +32,7 @@ declare abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
protected readonly d3: typeof d3; protected readonly d3: typeof d3;
protected deltaYTransform: number; protected deltaYTransform: number;
protected debouncedRender: import("lodash").DebouncedFunc<any>; protected debouncedRender: import("lodash").DebouncedFunc<any>;
private _xScale; protected grid: Grid;
private _yScale;
private _y1Scale;
constructor(_d3: typeof d3, el: HTMLElement, _series: T[], _options: O); constructor(_d3: typeof d3, el: HTMLElement, _series: T[], _options: O);
protected addEventListeners(): void; protected addEventListeners(): void;
protected removeEventListeners(): void; protected removeEventListeners(): void;
@ -51,7 +50,8 @@ declare abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
}): void; }): void;
abstract hideSharedCrosshair(): void; abstract hideSharedCrosshair(): void;
protected initPodState(): void; protected initPodState(): void;
protected renderSvg(): void; protected initComponents(): void;
protected createSvg(): void;
protected renderGrid(): void; protected renderGrid(): void;
protected renderAxes(): void; protected renderAxes(): void;
protected renderXAxis(): void; protected renderXAxis(): void;
@ -83,13 +83,6 @@ declare abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
get xScale(): d3.ScaleLinear<number, number>; get xScale(): d3.ScaleLinear<number, number>;
get yScale(): d3.ScaleLinear<number, number>; get yScale(): d3.ScaleLinear<number, number>;
protected get y1Scale(): d3.ScaleLinear<number, number>; protected get y1Scale(): d3.ScaleLinear<number, number>;
filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean;
get minValue(): number;
get maxValue(): number;
get y1MinValue(): number;
get y1MaxValue(): number;
get minValueX(): number;
get maxValueX(): number;
getd3TimeRangeEvery(count: number): d3.TimeInterval; getd3TimeRangeEvery(count: number): d3.TimeInterval;
get serieTimestampRange(): number | undefined; get serieTimestampRange(): number | undefined;
getAxisTicksFormatter(axisOptions: AxisOption): (d: any, i: number) => any; getAxisTicksFormatter(axisOptions: AxisOption): (d: any, i: number) => any;
@ -100,9 +93,8 @@ declare abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
get height(): number; get height(): number;
get legendRowPositionY(): number; get legendRowPositionY(): number;
get margin(): Margin; get margin(): Margin;
get isSeriesUnavailable(): boolean; formattedBound(alias: string, target: string): string;
formatedBound(alias: string, target: string): string; protected clearState(): void;
protected clearScaleCache(shouldClearState?: boolean): void;
protected getSerieColor(idx: number): string; protected getSerieColor(idx: number): string;
protected get seriesTargetsWithBounds(): any[]; protected get seriesTargetsWithBounds(): any[];
protected get visibleSeries(): any[]; protected get visibleSeries(): any[];

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

37
dist/state.d.ts vendored

@ -1,10 +1,33 @@
import { Options } from './types'; import { TimeSerie, Options, yAxisOrientation } from './types';
export declare class PodState { import * as d3 from 'd3';
export declare class PodState<T extends TimeSerie, O extends Options> {
protected _d3: typeof d3;
protected boxParams: {
height: number;
width: number;
};
protected series: T[];
protected options: O;
private _xValueRange; private _xValueRange;
private _yValueRange; private _yValueRange;
private _y1ValueRange; private _y1ValueRange;
private _transform; private _transform;
constructor(options: Options); private _xScale;
private _yScale;
private _y1Scale;
constructor(_d3: typeof d3, boxParams: {
height: number;
width: number;
}, series: T[], options: O);
protected setInitialRanges(): void;
protected initScales(): void;
protected setYScale(): void;
protected setXScale(): void;
protected setY1Scale(): void;
clearState(): void;
get yScale(): d3.ScaleLinear<number, number>;
get xScale(): d3.ScaleLinear<number, number>;
get y1Scale(): d3.ScaleLinear<number, number>;
get xValueRange(): [number, number] | undefined; get xValueRange(): [number, number] | undefined;
get yValueRange(): [number, number] | undefined; get yValueRange(): [number, number] | undefined;
get y1ValueRange(): [number, number] | undefined; get y1ValueRange(): [number, number] | undefined;
@ -21,4 +44,12 @@ export declare class PodState {
y?: number; y?: number;
k?: number; k?: number;
}); });
getMinValueY(): number;
getMaxValueY(): number;
getMinValueX(): number;
getMaxValueX(): number;
getMinValueY1(): number;
getMaxValueY1(): number;
get isSeriesUnavailable(): boolean;
protected filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean;
} }

27
dist/types.d.ts vendored

@ -40,16 +40,7 @@ export declare type Options = {
y?: AxisOption; y?: AxisOption;
y1?: AxisOption; y1?: AxisOption;
}; };
grid?: { grid?: GridOptions;
x?: {
isActive?: boolean;
ticksCount?: number;
};
y?: {
isActive?: boolean;
ticksCount?: number;
};
};
crosshair?: { crosshair?: {
orientation?: CrosshairOrientation; orientation?: CrosshairOrientation;
color?: string; color?: string;
@ -103,6 +94,16 @@ export declare type Options = {
renderTicksfromTimestamps?: boolean; renderTicksfromTimestamps?: boolean;
renderLegend?: boolean; renderLegend?: boolean;
}; };
export declare type GridOptions = {
x?: {
enabled?: boolean;
ticksCount?: number;
};
y?: {
enabled?: boolean;
ticksCount?: number;
};
};
export declare type AxisOption = { export declare type AxisOption = {
isActive?: boolean; isActive?: boolean;
ticksCount?: number; ticksCount?: number;
@ -173,3 +174,9 @@ export declare enum yAxisOrientation {
RIGHT = "right", RIGHT = "right",
BOTH = "both" BOTH = "both"
} }
export declare type SvgElParams = {
height: number;
width: number;
xScale: d3.ScaleLinear<number, number>;
yScale: d3.ScaleLinear<number, number>;
};

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "@chartwerk/core", "name": "@chartwerk/core",
"version": "0.1.1", "version": "0.2.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

216
src/index.ts

@ -32,14 +32,8 @@ 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 mergeWith from 'lodash/mergeWith';
import min from 'lodash/min';
import minBy from 'lodash/minBy';
import max from 'lodash/max';
import maxBy from 'lodash/maxBy';
import add from 'lodash/add'; import add from 'lodash/add';
import replace from 'lodash/replace'; import replace from 'lodash/replace';
import reverse from 'lodash/reverse';
import sortBy from 'lodash/sortBy';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import has from 'lodash/has'; import has from 'lodash/has';
@ -119,7 +113,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
protected brush?: d3.BrushBehavior<unknown>; protected brush?: d3.BrushBehavior<unknown>;
protected zoom?: any; protected zoom?: any;
protected svg?: d3.Selection<SVGElement, unknown, null, undefined>; protected svg?: d3.Selection<SVGElement, unknown, null, undefined>;
protected state?: PodState; protected state?: PodState<T, O>;
protected clipPath?: any; protected clipPath?: any;
protected isPanning = false; protected isPanning = false;
protected isBrushing = false; protected isBrushing = false;
@ -141,11 +135,6 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
// components // components
protected grid: Grid; protected grid: Grid;
// TODO: test variables instead of functions with cache
private _xScale: d3.ScaleLinear<number, number> | null = null;
private _yScale: d3.ScaleLinear<number, number> | null = null;
private _y1Scale: d3.ScaleLinear<number, number> | null = null;
constructor( constructor(
// maybe it's not the best idea // maybe it's not the best idea
_d3: typeof d3, _d3: typeof d3,
@ -162,13 +151,11 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
this.series = cloneDeep(_series); this.series = cloneDeep(_series);
this.d3 = _d3; this.d3 = _d3;
// TODO: mb move it to render();
this.initPodState();
this.d3Node = this.d3.select(this.el); this.d3Node = this.d3.select(this.el);
this.addEventListeners(); this.addEventListeners();
this.createSvg(); this.createSvg();
this.initPodState();
this.initComponents(); this.initComponents();
} }
@ -181,8 +168,6 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
public render(): void { public render(): void {
this.clearScaleCache();
this.renderAxes(); this.renderAxes();
this.renderGrid(); this.renderGrid();
@ -235,16 +220,21 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
public abstract hideSharedCrosshair(): void; public abstract hideSharedCrosshair(): void;
protected initPodState(): void { protected initPodState(): void {
this.state = new PodState(this.options); const boxPararms = {
height: this.height,
width: this.width,
}
this.state = new PodState(this.d3, boxPararms, this.series, this.options);
} }
protected initComponents(): void { protected initComponents(): void {
// TODO: make chartContainer a separate class with SvgElParams inside to avoid duplication // TODO: make chartContainer a separate class with SvgElParams inside to avoid duplication
// TODO: bad connection between State and Grid
const svgElParams = { const svgElParams = {
height: this.height, height: this.height,
width: this.width, width: this.width,
xScale: this.xScale, xScale: this.state.xScale,
yScale: this.yScale, yScale: this.state.yScale,
} }
this.grid = new Grid(this.d3, this.chartContainer, svgElParams, this.options.grid); this.grid = new Grid(this.d3, this.chartContainer, svgElParams, this.options.grid);
@ -605,8 +595,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
this.onPanningRescale(event); this.onPanningRescale(event);
const shouldClearState = false; // TODO: check clear state for necessity
this.clearScaleCache(shouldClearState);
this.renderYAxis(); this.renderYAxis();
this.renderXAxis(); this.renderXAxis();
@ -673,9 +662,6 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
const signX = Math.sign(event.transform.x); const signX = Math.sign(event.transform.x);
const transformX = this.absXScale.invert(Math.abs(transformStep)); const transformX = this.absXScale.invert(Math.abs(transformStep));
let rangeX = this.state.xValueRange; 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]; this.state.xValueRange = [rangeX[0] + signX * transformX, rangeX[1] + signX * transformX];
const translateX = this.state.transform.x + signX * transformStep; const translateX = this.state.transform.x + signX * transformStep;
this.state.transform = { x: translateX }; this.state.transform = { x: translateX };
@ -687,7 +673,7 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
if(this.options.axis.y.invert === true) { if(this.options.axis.y.invert === true) {
signY = -signY; signY = -signY;
} }
let rangeY = this.state.yValueRange || [this.maxValue, this.minValue]; let rangeY = this.state.yValueRange;
const transformY = this.absYScale.invert(deltaY); const transformY = this.absYScale.invert(deltaY);
this.deltaYTransform = this.deltaYTransform + deltaY; this.deltaYTransform = this.deltaYTransform + deltaY;
// TODO: not hardcoded bounds // TODO: not hardcoded bounds
@ -839,173 +825,31 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
} }
// TODO: move to State
get absXScale(): d3.ScaleLinear<number, number> { get absXScale(): d3.ScaleLinear<number, number> {
const domain = [0, Math.abs(this.maxValueX - this.minValueX)]; const domain = [0, Math.abs(this.state.getMaxValueX() - this.state.getMinValueX())];
return this.d3.scaleLinear() return this.d3.scaleLinear()
.domain(domain) .domain(domain)
.range([0, this.width]); .range([0, this.width]);
} }
get absYScale(): d3.ScaleLinear<number, number> { get absYScale(): d3.ScaleLinear<number, number> {
const domain = [0, Math.abs(this.maxValue - this.minValue)]; const domain = [0, Math.abs(this.state.getMaxValueY() - this.state.getMinValueY())];
return this.d3.scaleLinear() return this.d3.scaleLinear()
.domain(domain) .domain(domain)
.range([0, this.height]); .range([0, this.height]);
} }
get xScale(): d3.ScaleLinear<number, number> { get xScale(): d3.ScaleLinear<number, number> {
if(this._xScale === null) { return this.state.xScale;
const domain = this.state.xValueRange || [this.minValueX, this.maxValueX];
this._xScale = this.d3.scaleLinear()
.domain(domain)
.range([0, this.width]);
}
return this._xScale;
} }
get yScale(): d3.ScaleLinear<number, number> { get yScale(): d3.ScaleLinear<number, number> {
if(this._yScale === null) { return this.state.yScale;
let domain = this.state.yValueRange || [this.maxValue, this.minValue];
domain = sortBy(domain) as [number, number];
if(this.options.axis.y.invert === true) {
domain = reverse(domain);
}
this._yScale = this.d3.scaleLinear()
.domain(domain)
.range([this.height, 0]); // inversed, because d3 y-axis goes from top to bottom
}
return this._yScale;
} }
protected get y1Scale(): d3.ScaleLinear<number, number> { protected get y1Scale(): d3.ScaleLinear<number, number> {
if(this.isSeriesUnavailable || this.options.axis.y1 === undefined || this.options.axis.y1.isActive === false) { return this.state.y1Scale;
return null;
}
// scale for y1 axis(right y axis)
if(this._y1Scale === null) {
let domain = this.state.y1ValueRange || [this.y1MaxValue, this.y1MinValue];
domain = sortBy(domain) as [number, number];
if(this.options.axis.y1.invert === true) {
domain = reverse(domain);
}
this._y1Scale = this.d3.scaleLinear()
.domain(domain)
.range([this.height, 0]); // inversed, because d3 y-axis goes from top to bottom
}
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<number[]>(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<number[]>(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<number[]>(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<number[]>(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<number[]>(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<number[]>(serie.datapoints, dp => dp[0])[0]
)
);
return maxValue;
} }
getd3TimeRangeEvery(count: number): d3.TimeInterval { getd3TimeRangeEvery(count: number): d3.TimeInterval {
@ -1146,27 +990,13 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
return mergeWith({}, DEFAULT_MARGIN, this.extraMargin, add); return mergeWith({}, DEFAULT_MARGIN, this.extraMargin, add);
} }
get isSeriesUnavailable(): boolean { formattedBound(alias: string, target: string): string {
// 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); const confidenceMetric = replace(alias, '$__metric_name', target);
return confidenceMetric; return confidenceMetric;
} }
protected clearScaleCache(shouldClearState = true): void { protected clearState(): void {
this._xScale = null; this.state.clearState();
this._yScale = null;
this._y1Scale = null;
if(shouldClearState) {
this.state.xValueRange = undefined;
this.state.yValueRange = undefined;
this.state.y1ValueRange = undefined;
this.state.transform = { x: 0, y: 0, k: 1 };
}
} }
protected getSerieColor(idx: number): string { protected getSerieColor(idx: number): string {
@ -1192,8 +1022,8 @@ abstract class ChartwerkPod<T extends TimeSerie, O extends Options> {
} }
let series = []; let series = [];
this.series.forEach(serie => { this.series.forEach(serie => {
series.push(this.formatedBound(this.options.bounds.upper, serie.target)); series.push(this.formattedBound(this.options.bounds.upper, serie.target));
series.push(this.formatedBound(this.options.bounds.lower, serie.target)); series.push(this.formattedBound(this.options.bounds.lower, serie.target));
}); });
return series; return series;
} }

214
src/state.ts

@ -1,27 +1,103 @@
import { Options, TimeFormat, TickOrientation, AxisFormat } from './types'; import { TimeSerie, Options, yAxisOrientation } from './types';
// we import only d3 types here
import * as d3 from 'd3';
import lodashGet from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep'; 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';
import sortBy from 'lodash/sortBy';
import reverse from 'lodash/reverse';
const DEFAULT_AXIS_RANGE = [0, 1];
const DEFAULT_TRANSFORM = { const DEFAULT_TRANSFORM = {
x: 0, x: 0,
y: 0, y: 0,
k: 1 k: 1
} }
export class PodState { // TODO: replace all getters with fields. Because getters will be recalculated on each call. Use scales as example.
private _xValueRange: [number, number] | undefined = undefined; // TODO: remove duplicates in max/min values.
private _yValueRange: [number, number] | undefined = undefined; // TODO: PodState can be divided in two classes, but it is hard now.
private _y1ValueRange: [number, number] | undefined = undefined; export class PodState<T extends TimeSerie, O extends Options> {
private _xValueRange: [number, number];
private _yValueRange: [number, number];
private _y1ValueRange: [number, number];
private _transform: { x: number, y: number, k: number } = cloneDeep(DEFAULT_TRANSFORM); private _transform: { x: number, y: number, k: number } = cloneDeep(DEFAULT_TRANSFORM);
private _xScale: d3.ScaleLinear<number, number>;
private _yScale: d3.ScaleLinear<number, number>;
private _y1Scale: d3.ScaleLinear<number, number>;
constructor( constructor(
options: Options protected _d3: typeof d3,
protected boxParams: { height: number, width: number },
protected series: T[],
protected options: O,
) { ) {
this._xValueRange = lodashGet(options, 'axis.x.range'); this.setInitialRanges();
this._yValueRange = lodashGet(options, 'axis.y.range'); this.initScales();
this._y1ValueRange = lodashGet(options, 'axis.y1.range'); }
protected setInitialRanges(): void {
this._xValueRange = [this.getMinValueX(), this.getMaxValueX()];
this._yValueRange = [this.getMinValueY(), this.getMaxValueY()];
this._y1ValueRange = [this.getMinValueY1(), this.getMaxValueY1()];
}
protected initScales(): void {
this.setXScale();
this.setYScale();
this.setY1Scale();
}
protected setYScale(): void {
let domain = this._yValueRange;
domain = sortBy(domain) as [number, number];
if(this.options.axis.y.invert === true) {
domain = reverse(domain);
}
this._yScale = this._d3.scaleLinear()
.domain(domain)
.range([this.boxParams.height, 0]); // inversed, because d3 y-axis goes from top to bottom;
}
protected setXScale(): void {
const domain = this._xValueRange;
this._xScale = this._d3.scaleLinear()
.domain(domain)
.range([0, this.boxParams.width]);
}
protected setY1Scale(): void {
let domain = this._y1ValueRange;
domain = sortBy(domain) as [number, number];
if(this.options.axis.y1.invert === true) {
domain = reverse(domain);
}
this._y1Scale = this._d3.scaleLinear()
.domain(domain)
.range([this.boxParams.height, 0]); // inversed, because d3 y-axis goes from top to bottom
}
public clearState(): void {
this.setInitialRanges();
this.initScales();
this._transform = { x: 0, y: 0, k: 1 };
}
get yScale(): d3.ScaleLinear<number, number> {
return this._yScale;
}
get xScale(): d3.ScaleLinear<number, number> {
return this._xScale;
}
get y1Scale(): d3.ScaleLinear<number, number> {
return this._y1Scale;
} }
get xValueRange(): [number, number] | undefined { get xValueRange(): [number, number] | undefined {
@ -42,14 +118,17 @@ export class PodState {
set xValueRange(range: [number, number]) { set xValueRange(range: [number, number]) {
this._xValueRange = range; this._xValueRange = range;
this.setXScale();
} }
set yValueRange(range: [number, number]) { set yValueRange(range: [number, number]) {
this._yValueRange = range; this._yValueRange = range;
this.setYScale();
} }
set y1ValueRange(range: [number, number]) { set y1ValueRange(range: [number, number]) {
this._y1ValueRange = range; this._y1ValueRange = range;
this.setY1Scale();
} }
set transform(transform: { x?: number, y?: number, k?: number }) { set transform(transform: { x?: number, y?: number, k?: number }) {
@ -57,4 +136,119 @@ export class PodState {
this._transform.y = transform.y !== undefined ? transform.y : this._transform.y; this._transform.y = transform.y !== undefined ? transform.y : this._transform.y;
this._transform.k = transform.k !== undefined ? transform.k : this._transform.k; this._transform.k = transform.k !== undefined ? transform.k : this._transform.k;
} }
public getMinValueY(): 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<number[]>(serie.datapoints, dp => dp[1])[1]
)
);
return minValue;
}
public getMaxValueY(): 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<number[]>(serie.datapoints, dp => dp[1])[1]
)
);
return maxValue;
}
public getMinValueX(): 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<number[]>(serie.datapoints, dp => dp[0])[0]
)
);
return minValue;
}
public getMaxValueX(): 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<number[]>(serie.datapoints, dp => dp[0])[0]
)
);
return maxValue;
}
public getMinValueY1(): 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<number[]>(serie.datapoints, dp => dp[1])[1]
)
);
return minValue;
}
public getMaxValueY1(): 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<number[]>(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;
}
} }

Loading…
Cancel
Save