Compare commits
2 Commits
main
...
charts-bac
Author | SHA1 | Date |
---|---|---|
vargburz | 46910fa3da | 3 years ago |
vargburz | 81b66f971b | 3 years ago |
28 changed files with 5505 additions and 2387 deletions
@ -1,40 +0,0 @@
|
||||
const path = require('path'); |
||||
|
||||
function resolve(dir) { |
||||
return path.join(__dirname, '..', dir) |
||||
} |
||||
|
||||
module.exports = { |
||||
context: resolve('demo'), |
||||
entry: './demo_pod.ts', |
||||
plugins: [], |
||||
devtool: 'inline-source-map', |
||||
watch: true, |
||||
mode: 'development', |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.ts$/, |
||||
use: 'ts-loader', |
||||
exclude: /node_modules/ |
||||
}, |
||||
{ |
||||
test: /\.css$/, |
||||
use: [ |
||||
{ loader: 'style-loader', options: { injectType: 'lazyStyleTag' } }, |
||||
'css-loader', |
||||
], |
||||
exclude: /node_modules/ |
||||
} |
||||
], |
||||
}, |
||||
resolve: { |
||||
extensions: ['.ts', '.js'], |
||||
}, |
||||
output: { |
||||
filename: 'demo.js', |
||||
path: resolve('demo/dist'), |
||||
libraryTarget: 'umd', |
||||
umdNamedDefine: true |
||||
} |
||||
}; |
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"> |
||||
<meta content="utf-8" http-equiv="encoding"> |
||||
|
||||
<script src="./dist/index.js" type="text/javascript"></script> |
||||
</head> |
||||
<body> |
||||
<div id="chart" style="width: 500px; height: 500px;"></div> |
||||
|
||||
<script type="text/javascript"> |
||||
new ChartwerkPod(document.getElementById('chart')) |
||||
</script> |
||||
</body> |
||||
</html> |
@ -1,5 +0,0 @@
|
||||
### HOW TO RUN |
||||
|
||||
run `yarn run dev` and `yarn run demo` in separate terminals simultaneously. |
||||
|
||||
open `demo.html` in your browser. |
@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"> |
||||
<meta content="utf-8" http-equiv="encoding"> |
||||
|
||||
<script src="./dist/demo.js" type="text/javascript"></script> |
||||
</head> |
||||
<body> |
||||
<div id="chart" style="width: 50%; height: 500px;"></div> |
||||
|
||||
<script type="text/javascript"> |
||||
const startTime = 1590590148; |
||||
const arrayLength = 20; |
||||
const data1 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 40)]); |
||||
|
||||
let options = { |
||||
renderLegend: false, |
||||
axis: { |
||||
y: { range: [0, 350] }, |
||||
x: { format: 'time' } |
||||
}, |
||||
zoomEvents: { |
||||
mouse: { zoom: { isActive: false }, pan: { isActive: false } }, |
||||
scroll: { zoom: { isActive: false } } |
||||
}, |
||||
} |
||||
var pod = new DemoPod( |
||||
document.getElementById('chart'), |
||||
[ |
||||
{ target: 'test1', datapoints: data1, color: 'green' }, |
||||
], |
||||
options |
||||
); |
||||
pod.render(); |
||||
</script> |
||||
</body> |
||||
</html> |
@ -1,56 +0,0 @@
|
||||
import { |
||||
ChartwerkPod, |
||||
Serie, |
||||
Options |
||||
} from '../dist/index'; |
||||
|
||||
import * as d3 from 'd3'; |
||||
import * as _ from 'lodash'; |
||||
|
||||
|
||||
class DemoPod extends ChartwerkPod<Serie, Options> { |
||||
lineGenerator = null; |
||||
|
||||
constructor( |
||||
_el: HTMLElement, |
||||
_series: Serie[] = [], |
||||
_options: Options = {}, |
||||
) { |
||||
super(_el, _series, _options); |
||||
} |
||||
|
||||
override renderMetrics(): void { |
||||
this.clearAllMetrics(); |
||||
this.initLineGenerator(); |
||||
for(const serie of this.series.visibleSeries) { |
||||
this.renderLine(serie); |
||||
} |
||||
} |
||||
|
||||
clearAllMetrics(): void { |
||||
// TODO: temporary hack before it will be implemented in core.
|
||||
this.chartContainer.selectAll('.metric-el').remove(); |
||||
} |
||||
|
||||
initLineGenerator(): void { |
||||
this.lineGenerator = d3.line() |
||||
.x(d => this.state.xScale(d[0])) |
||||
.y(d => this.state.yScale(d[1])); |
||||
} |
||||
|
||||
renderLine(serie: Serie): void { |
||||
this.metricContainer |
||||
.append('path') |
||||
.datum(serie.datapoints) |
||||
.attr('class', `metric-path-${serie.idx} metric-el ${serie.class}`) |
||||
.attr('fill-opacity', 0) |
||||
.attr('stroke', serie.color) |
||||
.attr('stroke-width', 1) |
||||
.attr('stroke-opacity', 0.7) |
||||
.attr('pointer-events', 'none') |
||||
.attr('d', this.lineGenerator); |
||||
} |
||||
|
||||
} |
||||
|
||||
export { DemoPod }; |
@ -0,0 +1,47 @@
|
||||
declare const _default: { |
||||
props: { |
||||
id: { |
||||
type: StringConstructor; |
||||
required: boolean; |
||||
}; |
||||
series: { |
||||
type: ArrayConstructor; |
||||
required: boolean; |
||||
default: () => any[]; |
||||
}; |
||||
options: { |
||||
type: ObjectConstructor; |
||||
required: boolean; |
||||
default: () => {}; |
||||
}; |
||||
}; |
||||
watch: { |
||||
id(): void; |
||||
series(): void; |
||||
options(): void; |
||||
}; |
||||
mounted(): void; |
||||
destroyed(): void; |
||||
methods: { |
||||
render(): void; |
||||
renderSharedCrosshair(values: { |
||||
x?: number; |
||||
y?: number; |
||||
}): void; |
||||
hideSharedCrosshair(): void; |
||||
onPanningRescale(event: any): void; |
||||
renderChart(): void; |
||||
appendEvents(): void; |
||||
zoomIn(range: any): void; |
||||
zoomOut(centers: any): void; |
||||
mouseMove(evt: any): void; |
||||
mouseOut(): void; |
||||
onLegendClick(idx: any): void; |
||||
panningEnd(range: any): void; |
||||
panning(range: any): void; |
||||
contextMenu(evt: any): void; |
||||
sharedCrosshairMove(event: any): void; |
||||
renderEnd(): void; |
||||
}; |
||||
}; |
||||
export default _default; |
@ -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; |
||||
} |
@ -0,0 +1,111 @@
|
||||
/// <reference types="lodash" />
|
||||
import VueChartwerkPodMixin from './VueChartwerkPodMixin'; |
||||
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 { palette } from './colors'; |
||||
import * as d3 from 'd3'; |
||||
declare abstract class ChartwerkPod<T extends TimeSerie, O extends Options> { |
||||
protected readonly el: HTMLElement; |
||||
protected d3Node?: d3.Selection<HTMLElement, unknown, null, undefined>; |
||||
protected customOverlay?: d3.Selection<SVGRectElement, unknown, null, undefined>; |
||||
protected crosshair?: d3.Selection<SVGGElement, unknown, null, undefined>; |
||||
protected brush?: d3.BrushBehavior<unknown>; |
||||
protected zoom?: any; |
||||
protected svg?: d3.Selection<SVGElement, unknown, null, undefined>; |
||||
protected state: PodState<T, O>; |
||||
protected pan?: d3.ZoomBehavior<Element, unknown>; |
||||
protected clipPath?: any; |
||||
protected isPanning: boolean; |
||||
protected isBrushing: boolean; |
||||
protected brushStartSelection: [number, number] | null; |
||||
protected initScaleX?: d3.ScaleLinear<any, any>; |
||||
protected initScaleY?: d3.ScaleLinear<any, any>; |
||||
protected initScaleY1?: d3.ScaleLinear<any, any>; |
||||
protected xAxisElement?: d3.Selection<SVGGElement, unknown, null, undefined>; |
||||
protected yAxisElement?: d3.Selection<SVGGElement, unknown, null, undefined>; |
||||
protected y1AxisElement?: d3.Selection<SVGGElement, unknown, null, undefined>; |
||||
protected yAxisTicksColors?: string[]; |
||||
private _clipPathUID; |
||||
protected series: T[]; |
||||
protected options: O; |
||||
protected readonly d3: typeof d3; |
||||
protected deltaYTransform: number; |
||||
protected debouncedRender: import("lodash").DebouncedFunc<any>; |
||||
protected chartContainer: d3.Selection<SVGGElement, unknown, null, undefined>; |
||||
protected metricContainer: d3.Selection<SVGGElement, unknown, null, undefined>; |
||||
protected grid: Grid; |
||||
constructor(_d3: typeof d3, el: HTMLElement, _series: T[], _options: O); |
||||
protected addEventListeners(): void; |
||||
protected removeEventListeners(): void; |
||||
render(): void; |
||||
updateData(series?: T[], options?: O, shouldRerender?: boolean): void; |
||||
forceRerender(): void; |
||||
protected updateOptions(newOptions: O): void; |
||||
protected updateSeries(newSeries: T[]): void; |
||||
protected abstract renderMetrics(): void; |
||||
protected abstract onMouseOver(): void; |
||||
protected abstract onMouseOut(): void; |
||||
protected abstract onMouseMove(): void; |
||||
abstract renderSharedCrosshair(values: { |
||||
x?: number; |
||||
y?: number; |
||||
}): void; |
||||
abstract hideSharedCrosshair(): void; |
||||
protected initPodState(): void; |
||||
protected initComponents(): void; |
||||
protected renderMetricsContainer(): void; |
||||
protected createSvg(): void; |
||||
protected renderGrid(): void; |
||||
protected renderAxes(): void; |
||||
protected renderXAxis(): void; |
||||
protected renderYAxis(): void; |
||||
protected renderY1Axis(): void; |
||||
protected renderCrosshair(): void; |
||||
updateBackground(): void; |
||||
protected addEvents(): void; |
||||
protected initBrush(): void; |
||||
protected filterByKeyEvent(key: KeyEvent): () => boolean; |
||||
protected isD3EventKeyEqualOption(event: d3.D3ZoomEvent<any, any>, optionsKeyEvent: KeyEvent): boolean; |
||||
protected initPan(): void; |
||||
protected renderClipPath(): void; |
||||
protected renderLegend(): void; |
||||
protected renderYLabel(): void; |
||||
protected renderXLabel(): void; |
||||
protected renderNoDataPointsMessage(): void; |
||||
protected onPanning(): void; |
||||
rescaleMetricAndAxis(event: d3.D3ZoomEvent<any, any>): void; |
||||
protected onPanningRescale(event: d3.D3ZoomEvent<any, any>): void; |
||||
rescaleAxisX(transformX: number): void; |
||||
rescaleAxisY(transformY: number): void; |
||||
protected onScrollPanningRescale(event: d3.D3ZoomEvent<any, any>): void; |
||||
protected onPanningEnd(): void; |
||||
protected onBrush(): void; |
||||
protected getSelectionAttrs(selection: number[][]): SvgElementAttributes | undefined; |
||||
protected onBrushStart(): void; |
||||
protected onBrushEnd(): void; |
||||
protected zoomOut(): void; |
||||
get absXScale(): d3.ScaleLinear<number, number>; |
||||
get absYScale(): d3.ScaleLinear<number, number>; |
||||
get xScale(): d3.ScaleLinear<number, number>; |
||||
get yScale(): d3.ScaleLinear<number, number>; |
||||
protected get y1Scale(): d3.ScaleLinear<number, number>; |
||||
getd3TimeRangeEvery(count: number): d3.TimeInterval; |
||||
get serieTimestampRange(): number | undefined; |
||||
getAxisTicksFormatter(axisOptions: AxisOption): (d: any, i: number) => any; |
||||
get timeInterval(): number; |
||||
get xTickTransform(): string; |
||||
get extraMargin(): Margin; |
||||
get width(): number; |
||||
get height(): number; |
||||
get legendRowPositionY(): number; |
||||
get margin(): Margin; |
||||
formattedBound(alias: string, target: string): string; |
||||
protected clearState(): void; |
||||
protected getSerieColor(idx: number): string; |
||||
protected get seriesTargetsWithBounds(): any[]; |
||||
protected get visibleSeries(): any[]; |
||||
protected get rectClipId(): string; |
||||
isOutOfChart(): boolean; |
||||
} |
||||
export { ChartwerkPod, VueChartwerkPodMixin, Margin, TimeSerie, Options, TickOrientation, TimeFormat, BrushOrientation, PanOrientation, AxisFormat, yAxisOrientation, CrosshairOrientation, ScrollPanOrientation, KeyEvent, palette }; |
File diff suppressed because one or more lines are too long
@ -0,0 +1,55 @@
|
||||
import { TimeSerie, Options, yAxisOrientation } from './types'; |
||||
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 _yValueRange; |
||||
private _y1ValueRange; |
||||
private _transform; |
||||
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 yValueRange(): [number, number] | undefined; |
||||
get y1ValueRange(): [number, number] | undefined; |
||||
get transform(): { |
||||
x?: number; |
||||
y?: number; |
||||
k?: number | string; |
||||
}; |
||||
set xValueRange(range: [number, number]); |
||||
set yValueRange(range: [number, number]); |
||||
set y1ValueRange(range: [number, number]); |
||||
set transform(transform: { |
||||
x?: number; |
||||
y?: number; |
||||
k?: number | string; |
||||
}); |
||||
getMinValueY(): number; |
||||
getMaxValueY(): number; |
||||
getMinValueX(): number; |
||||
getMaxValueX(): number; |
||||
getMinValueY1(): number; |
||||
getMaxValueY1(): number; |
||||
get isSeriesUnavailable(): boolean; |
||||
protected filterSerieByYAxisOrientation(serie: T, orientation: yAxisOrientation): boolean; |
||||
} |
@ -0,0 +1,189 @@
|
||||
export declare type Margin = { |
||||
top: number; |
||||
right: number; |
||||
bottom: number; |
||||
left: number; |
||||
}; |
||||
export declare type Timestamp = number; |
||||
export declare type TimeSerie = { |
||||
target: string; |
||||
datapoints: [Timestamp, number][]; |
||||
alias?: string; |
||||
visible?: boolean; |
||||
color?: string; |
||||
yOrientation?: yAxisOrientation; |
||||
}; |
||||
export declare type Options = { |
||||
margin?: Margin; |
||||
confidence?: number; |
||||
eventsCallbacks?: { |
||||
zoomIn?: (range: AxisRange[]) => void; |
||||
panning?: (event: { |
||||
ranges: AxisRange[]; |
||||
d3Event: any; |
||||
}) => void; |
||||
panningEnd?: (range: AxisRange[]) => void; |
||||
zoomOut?: (centers: { |
||||
x: number; |
||||
y: number; |
||||
}) => void; |
||||
mouseMove?: (evt: any) => void; |
||||
mouseOut?: () => void; |
||||
onLegendClick?: (idx: number) => void; |
||||
onLegendLabelClick?: (idx: number) => void; |
||||
contextMenu?: (evt: any) => void; |
||||
sharedCrosshairMove?: (event: any) => void; |
||||
renderEnd?: () => void; |
||||
}; |
||||
axis?: { |
||||
x?: AxisOption; |
||||
y?: AxisOption; |
||||
y1?: AxisOption; |
||||
}; |
||||
grid?: GridOptions; |
||||
crosshair?: { |
||||
orientation?: CrosshairOrientation; |
||||
color?: string; |
||||
}; |
||||
background?: { |
||||
color?: string; |
||||
}; |
||||
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; |
||||
}; |
||||
zoomEvents?: { |
||||
mouse?: { |
||||
zoom?: { |
||||
isActive: boolean; |
||||
keyEvent?: KeyEvent; |
||||
orientation?: BrushOrientation; |
||||
}; |
||||
pan?: { |
||||
isActive: boolean; |
||||
keyEvent?: KeyEvent; |
||||
orientation?: PanOrientation; |
||||
}; |
||||
}; |
||||
scroll?: { |
||||
zoom?: { |
||||
isActive: boolean; |
||||
keyEvent?: KeyEvent; |
||||
orientation?: PanOrientation; |
||||
}; |
||||
pan?: { |
||||
isActive: boolean; |
||||
keyEvent?: KeyEvent; |
||||
panStep?: number; |
||||
orientation?: ScrollPanOrientation; |
||||
}; |
||||
}; |
||||
}; |
||||
renderTicksfromTimestamps?: boolean; |
||||
renderLegend?: boolean; |
||||
}; |
||||
export declare type GridOptions = { |
||||
color?: string; |
||||
opacity?: number; |
||||
strokeWidth?: number; |
||||
x?: { |
||||
enabled?: boolean; |
||||
ticksCount?: number; |
||||
}; |
||||
y?: { |
||||
enabled?: boolean; |
||||
ticksCount?: number; |
||||
}; |
||||
}; |
||||
export declare type AxisOption = { |
||||
isActive?: boolean; |
||||
ticksCount?: number; |
||||
format?: AxisFormat; |
||||
range?: [number, number]; |
||||
invert?: boolean; |
||||
valueFormatter?: (value: number, i: number) => string; |
||||
colorFormatter?: (value: number, i: number) => string; |
||||
}; |
||||
export declare type AxisRange = [number, number] | undefined; |
||||
export declare type VueOptions = Omit<Options, 'eventsCallbacks'>; |
||||
export declare enum TickOrientation { |
||||
VERTICAL = "vertical", |
||||
HORIZONTAL = "horizontal", |
||||
DIAGONAL = "diagonal" |
||||
} |
||||
export declare enum TimeFormat { |
||||
SECOND = "second", |
||||
MINUTE = "minute", |
||||
HOUR = "hour", |
||||
DAY = "day", |
||||
MONTH = "month", |
||||
YEAR = "year" |
||||
} |
||||
export declare enum BrushOrientation { |
||||
VERTICAL = "vertical", |
||||
HORIZONTAL = "horizontal", |
||||
RECTANGLE = "rectangle", |
||||
SQUARE = "square" |
||||
} |
||||
export declare enum PanOrientation { |
||||
VERTICAL = "vertical", |
||||
HORIZONTAL = "horizontal", |
||||
BOTH = "both" |
||||
} |
||||
export declare enum ScrollPanOrientation { |
||||
VERTICAL = "vertical", |
||||
HORIZONTAL = "horizontal" |
||||
} |
||||
export declare enum AxisFormat { |
||||
TIME = "time", |
||||
NUMERIC = "numeric", |
||||
STRING = "string", |
||||
CUSTOM = "custom" |
||||
} |
||||
export declare enum CrosshairOrientation { |
||||
VERTICAL = "vertical", |
||||
HORIZONTAL = "horizontal", |
||||
BOTH = "both" |
||||
} |
||||
export declare type SvgElementAttributes = { |
||||
x: number; |
||||
y: number; |
||||
width: number; |
||||
height: number; |
||||
}; |
||||
export declare enum KeyEvent { |
||||
MAIN = "main", |
||||
SHIFT = "shift" |
||||
} |
||||
export declare enum xAxisOrientation { |
||||
TOP = "top", |
||||
BOTTOM = "bottom", |
||||
BOTH = "both" |
||||
} |
||||
export declare enum yAxisOrientation { |
||||
LEFT = "left", |
||||
RIGHT = "right", |
||||
BOTH = "both" |
||||
} |
||||
export declare type SvgElParams = { |
||||
height: number; |
||||
width: number; |
||||
xScale: d3.ScaleLinear<number, number>; |
||||
yScale: d3.ScaleLinear<number, number>; |
||||
}; |
@ -1,34 +1,29 @@
|
||||
{ |
||||
"name": "@chartwerk/core", |
||||
"version": "0.6.26", |
||||
"version": "0.3.4", |
||||
"description": "Chartwerk core", |
||||
"main": "dist/index.js", |
||||
"types": "dist/index.d.ts", |
||||
"files": [ |
||||
"/dist" |
||||
], |
||||
"scripts": { |
||||
"build": "webpack --config build/webpack.prod.conf.js", |
||||
"dev": "webpack --config build/webpack.dev.conf.js", |
||||
"demo": "webpack --config build/webpack.demo.conf.js", |
||||
"test": "echo \"Error: no test specified\" && exit 1" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "http://code.corpglory.net/chartwerk/core.git" |
||||
"url": "https://gitlab.com/chartwerk/core.git" |
||||
}, |
||||
"author": "CorpGlory Inc.", |
||||
"license": "ISC", |
||||
"dependencies": { |
||||
"d3": "^5.16.0", |
||||
"lodash": "^4.17.21" |
||||
}, |
||||
"devDependencies": { |
||||
"css-loader": "^6.8.1", |
||||
"style-loader": "^3.3.3", |
||||
"ts-loader": "^9.4.3", |
||||
"typescript": "^5.1.3", |
||||
"webpack": "^5.87.0", |
||||
"webpack-cli": "^5.1.4" |
||||
"@types/d3": "^5.7.2", |
||||
"@types/lodash": "^4.14.149", |
||||
"css-loader": "^3.4.2", |
||||
"lodash": "^4.17.15", |
||||
"style-loader": "^1.1.3", |
||||
"ts-loader": "^6.2.1", |
||||
"typescript": "^3.8.3", |
||||
"webpack": "^4.42.0", |
||||
"webpack-cli": "^3.3.11" |
||||
} |
||||
} |
||||
|
@ -1,14 +0,0 @@
|
||||
.grid path { |
||||
stroke-width: 0; |
||||
} |
||||
.grid line { |
||||
stroke: lightgrey; |
||||
stroke-opacity: 0.7; |
||||
shape-rendering: crispEdges; |
||||
} |
||||
.grid .tick { |
||||
opacity: 0.5; |
||||
} |
||||
.grid .domain { |
||||
pointer-events: none; |
||||
} |
@ -1,252 +0,0 @@
|
||||
import { |
||||
Options, |
||||
GridOptions, AxesOptions, AxisFormat, |
||||
CrosshairOptions, CrosshairOrientation, |
||||
ZoomEvents, MouseZoomEvent, MousePanEvent, DoubleClickEvent, ScrollZoomEvent, ScrollPanEvent, |
||||
ScrollPanOrientation, ScrollPanDirection, PanOrientation, KeyEvent, BrushOrientation, |
||||
Margin, TimeFormat, AxisRange, RenderComponent, |
||||
} from '../types'; |
||||
|
||||
import lodashDefaultsDeep from 'lodash/defaultsDeep'; |
||||
import lodashCloneDeep from 'lodash/cloneDeep'; |
||||
import has from 'lodash/has'; |
||||
|
||||
|
||||
const DEFAULT_TICK_COUNT = 4; |
||||
const DEFAULT_SCROLL_PAN_STEP = 50; |
||||
const DEFAULT_GRID_TICK_COUNT = 5; |
||||
|
||||
const DEFAULT_GRID_OPTIONS: GridOptions = { |
||||
x: { |
||||
enabled: true, |
||||
ticksCount: DEFAULT_GRID_TICK_COUNT, |
||||
}, |
||||
y: { |
||||
enabled: true, |
||||
ticksCount: DEFAULT_GRID_TICK_COUNT, |
||||
}, |
||||
} |
||||
|
||||
const DEFAULT_AXES_OPTIONS: AxesOptions = { |
||||
x: { |
||||
isActive: true, |
||||
ticksCount: DEFAULT_TICK_COUNT, |
||||
format: AxisFormat.TIME |
||||
}, |
||||
y: { |
||||
isActive: true, |
||||
ticksCount: DEFAULT_TICK_COUNT, |
||||
format: AxisFormat.NUMERIC |
||||
}, |
||||
y1: { |
||||
isActive: false, |
||||
ticksCount: DEFAULT_TICK_COUNT, |
||||
format: AxisFormat.NUMERIC |
||||
} |
||||
} |
||||
|
||||
const DEFAULT_CROSSHAIR_OPTIONS = { |
||||
orientation: CrosshairOrientation.VERTICAL, |
||||
color: 'gray', |
||||
} |
||||
|
||||
const DEFAULT_MARGIN: Margin = { top: 30, right: 20, bottom: 20, left: 30 }; |
||||
|
||||
export const CORE_DEFAULT_OPTIONS: Options = { |
||||
zoomEvents: { |
||||
mouse: { |
||||
zoom: { |
||||
isActive: true, |
||||
keyEvent: KeyEvent.MAIN, |
||||
orientation: BrushOrientation.HORIZONTAL |
||||
}, |
||||
pan: { |
||||
isActive: true, |
||||
keyEvent: KeyEvent.SHIFT, |
||||
orientation: PanOrientation.HORIZONTAL |
||||
}, |
||||
doubleClick: { |
||||
isActive: true, |
||||
keyEvent: KeyEvent.MAIN, |
||||
}, |
||||
}, |
||||
scroll: { |
||||
zoom: { |
||||
isActive: true, |
||||
keyEvent: KeyEvent.MAIN, |
||||
orientation: PanOrientation.BOTH, |
||||
}, |
||||
pan: { |
||||
isActive: false, |
||||
keyEvent: KeyEvent.SHIFT, |
||||
panStep: DEFAULT_SCROLL_PAN_STEP, |
||||
orientation: ScrollPanOrientation.HORIZONTAL, |
||||
direction: ScrollPanDirection.BOTH, |
||||
}, |
||||
}, |
||||
}, |
||||
axis: DEFAULT_AXES_OPTIONS, |
||||
grid: DEFAULT_GRID_OPTIONS, |
||||
crosshair: DEFAULT_CROSSHAIR_OPTIONS, |
||||
renderLegend: true, |
||||
margin: DEFAULT_MARGIN, |
||||
} |
||||
|
||||
export class CoreOptions<O extends Options> { |
||||
_options: O; |
||||
|
||||
constructor(options: O, private _podDefaults?: Partial<O>) { |
||||
this.setOptions(options); |
||||
} |
||||
|
||||
public updateOptions(options: O): void { |
||||
this.setOptions(options); |
||||
} |
||||
|
||||
protected setOptions(options: O): void { |
||||
this._options = lodashDefaultsDeep(lodashCloneDeep(options), this.getDefaults()); |
||||
if(this._options.eventsCallbacks !== undefined) { |
||||
if(this._options.events !== undefined) { |
||||
throw new Error('events and eventsCallbacks are mutually exclusive'); |
||||
} |
||||
this._options.events = this._options.eventsCallbacks; |
||||
} |
||||
// also bakward compatibility for clients who use "eventsCallbacks" instead of "events"
|
||||
this._options.eventsCallbacks = this._options.events; |
||||
} |
||||
|
||||
// this getter can be overrited in Pod
|
||||
protected getDefaults(): Partial<O> { |
||||
return lodashDefaultsDeep(this._podDefaults, CORE_DEFAULT_OPTIONS); |
||||
} |
||||
|
||||
get allOptions(): O { |
||||
return this._options; |
||||
} |
||||
|
||||
get grid(): GridOptions { |
||||
return this._options.grid; |
||||
} |
||||
|
||||
get axis(): AxesOptions { |
||||
return this._options.axis; |
||||
} |
||||
|
||||
get crosshair(): CrosshairOptions { |
||||
return this._options.crosshair; |
||||
} |
||||
|
||||
get margin(): Margin { |
||||
return this._options.margin; |
||||
} |
||||
|
||||
// events
|
||||
get allEvents(): ZoomEvents { |
||||
return this._options.zoomEvents; |
||||
} |
||||
|
||||
get mouseZoomEvent(): MouseZoomEvent { |
||||
return this._options.zoomEvents.mouse.zoom; |
||||
} |
||||
|
||||
get mousePanEvent(): MousePanEvent { |
||||
return this._options.zoomEvents.mouse.pan; |
||||
} |
||||
|
||||
get doubleClickEvent(): DoubleClickEvent { |
||||
return this._options.zoomEvents.mouse.doubleClick; |
||||
} |
||||
|
||||
get scrollZoomEvent(): ScrollZoomEvent { |
||||
return this._options.zoomEvents.scroll.zoom; |
||||
} |
||||
|
||||
get scrollPanEvent(): ScrollPanEvent { |
||||
return this._options.zoomEvents.scroll.pan; |
||||
} |
||||
|
||||
// event callbacks
|
||||
callbackRenderStart(): void { |
||||
if(has(this._options.events, 'renderStart')) { |
||||
this._options.events.renderStart(); |
||||
} |
||||
} |
||||
|
||||
callbackRenderEnd(): void { |
||||
if(has(this._options.events, 'renderEnd')) { |
||||
this._options.events.renderEnd(); |
||||
} |
||||
} |
||||
|
||||
callbackComponentRenderEnd(part: RenderComponent): void { |
||||
if(has(this._options.events, 'componentRenderEnd')) { |
||||
this._options.events.componentRenderEnd(part); |
||||
} |
||||
} |
||||
|
||||
callbackLegendClick(idx: number): void { |
||||
if(has(this._options.events, 'onLegendClick')) { |
||||
this._options.events.onLegendClick(idx); |
||||
} |
||||
} |
||||
|
||||
callbackLegendLabelClick(idx: number): void { |
||||
if(has(this._options.events, 'onLegendLabelClick')) { |
||||
this._options.events.onLegendLabelClick(idx); |
||||
} |
||||
} |
||||
|
||||
callbackPanning(event: { ranges: AxisRange[], d3Event: any }): void { |
||||
if(has(this._options.events, 'panning')) { |
||||
this._options.events.panning(event); |
||||
} |
||||
} |
||||
|
||||
callbackPanningEnd(ranges: AxisRange[]): void { |
||||
if(has(this._options.events, 'panningEnd')) { |
||||
this._options.events.panningEnd(ranges); |
||||
} |
||||
} |
||||
|
||||
callbackZoomIn(ranges: AxisRange[]): void { |
||||
if(has(this._options.events, 'zoomIn')) { |
||||
this._options.events.zoomIn(ranges); |
||||
} |
||||
} |
||||
|
||||
callbackZoomOut(centers: { x: number, y: number }): void { |
||||
if(has(this._options.events, 'zoomOut')) { |
||||
this._options.events.zoomOut(centers); |
||||
} |
||||
} |
||||
|
||||
callbackSharedCrosshairMove(event: { datapoints, eventX, eventY }): void { |
||||
if(has(this._options.events, 'sharedCrosshairMove')) { |
||||
this._options.events.sharedCrosshairMove(event); |
||||
} |
||||
} |
||||
|
||||
callbackMouseOver(): void { |
||||
if(has(this._options.events, 'mouseOver')) { |
||||
this._options.events.mouseOver(); |
||||
} |
||||
} |
||||
|
||||
callbackMouseMove(event): void { |
||||
if(has(this._options.events, 'mouseMove')) { |
||||
this._options.events.mouseMove(event); |
||||
} |
||||
} |
||||
|
||||
callbackMouseOut(): void { |
||||
if(has(this._options.events, 'mouseOut')) { |
||||
this._options.events.mouseOut(); |
||||
} |
||||
} |
||||
|
||||
callbackMouseClick(event): void { |
||||
if(has(this._options.events, 'mouseClick')) { |
||||
this._options.events.mouseClick(event); |
||||
} |
||||
} |
||||
} |
@ -1,182 +0,0 @@
|
||||
import { Serie, 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'; |
||||
import lodashMin from 'lodash/min'; |
||||
import lodashMinBy from 'lodash/minBy'; |
||||
import lodashMax from 'lodash/max'; |
||||
import lodashMaxBy from 'lodash/maxBy'; |
||||
import lodashIsNil from 'lodash/isNil'; |
||||
import lodashIncludes from 'lodash/includes'; |
||||
|
||||
|
||||
export const CORE_SERIE_DEFAULTS = { |
||||
alias: '', |
||||
target: '', |
||||
visible: true, |
||||
yOrientation: yAxisOrientation.LEFT, |
||||
datapoints: [], |
||||
class: '', |
||||
// fields below will be set in "fillDefaults" method
|
||||
idx: undefined, |
||||
color: undefined, |
||||
}; |
||||
|
||||
export enum Extremum { |
||||
MIN = 'min', |
||||
MAX = 'max', |
||||
} |
||||
|
||||
|
||||
export class CoreSeries<T extends Serie> { |
||||
_series: Array<T> = []; |
||||
|
||||
constructor(series: T[], private _podDefaults?: Partial<T>) { |
||||
// TODO: create separate Serie class, and store instances in this._series
|
||||
this.setSeries(series); |
||||
} |
||||
|
||||
public updateSeries(series: T[]): void { |
||||
this.setSeries(series); |
||||
} |
||||
|
||||
protected setSeries(series: T[]): void { |
||||
this._series = lodashMap(series, (serie, serieIdx) => this.fillDefaults(lodashCloneDeep(serie), serieIdx)); |
||||
this.ensureSeriesValid(this._series); |
||||
} |
||||
|
||||
ensureSeriesValid(series: T[]): void { |
||||
const targets = lodashMap(series, serie => serie.target); |
||||
const uniqTargets = lodashUniq(targets); |
||||
if(uniqTargets.length !== series.length) { |
||||
throw new Error(`All serie.target should be uniq`); |
||||
} |
||||
} |
||||
|
||||
protected fillDefaults(serie: T, idx: number): T { |
||||
let defaults = lodashCloneDeep(this.getDefaults()); |
||||
defaults.color = palette[idx % palette.length]; |
||||
defaults.idx = idx; |
||||
lodashDefaultsDeep(serie, defaults); |
||||
return serie; |
||||
} |
||||
|
||||
// this getter can be overrited in Pod
|
||||
protected getDefaults(): Partial<T> { |
||||
return lodashDefaultsDeep(this._podDefaults, CORE_SERIE_DEFAULTS); |
||||
} |
||||
|
||||
private _isSerieEmpty(serie: T): boolean { |
||||
if(serie.datapoints.length > 0) { |
||||
for(const datapoint of serie.datapoints) { |
||||
// TODO: axis-related
|
||||
if(!lodashIsNil(datapoint[1])) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
get isSeriesAvailable(): boolean { |
||||
if(this.visibleSeries.length > 0) { |
||||
const seriesEmptiness = lodashMap(this.visibleSeries, this._isSerieEmpty.bind(this)); |
||||
|
||||
return lodashIncludes(seriesEmptiness, false); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
get visibleSeries(): Array<T> { |
||||
return this._series.filter(serie => serie.visible); |
||||
} |
||||
|
||||
get allSeries(): Array<T> { |
||||
return this._series; |
||||
} |
||||
|
||||
get leftYRelatedSeries(): Array<T> { |
||||
return this.visibleSeries.filter(serie => serie.yOrientation === yAxisOrientation.LEFT); |
||||
} |
||||
|
||||
get rightYRelatedSeries(): Array<T> { |
||||
return this.visibleSeries.filter(serie => serie.yOrientation === yAxisOrientation.RIGHT); |
||||
} |
||||
|
||||
get areSeriesForY1Exist(): boolean { |
||||
return this.rightYRelatedSeries.length > 0; |
||||
} |
||||
|
||||
get areSeriesForYExist(): boolean { |
||||
return this.leftYRelatedSeries.length > 0; |
||||
} |
||||
|
||||
get minValueY(): number | undefined { |
||||
return lodashMin( |
||||
this.leftYRelatedSeries.map( |
||||
serie => { |
||||
const mins = lodashMinBy<number[]>(serie.datapoints, dp => dp[1]); |
||||
return !lodashIsNil(mins) ? mins[1] : undefined; |
||||
} |
||||
) |
||||
); |
||||
} |
||||
|
||||
get maxValueY(): number | undefined { |
||||
return lodashMax( |
||||
this.leftYRelatedSeries.map( |
||||
serie => { |
||||
const maxs = lodashMaxBy<number[]>(serie.datapoints, dp => dp[1]); |
||||
return !lodashIsNil(maxs) ? maxs[1] : undefined; |
||||
} |
||||
) |
||||
); |
||||
} |
||||
|
||||
get minValueX(): number | undefined { |
||||
return lodashMin( |
||||
this.visibleSeries.map( |
||||
serie => { |
||||
const mins = lodashMinBy<number[]>(serie.datapoints, dp => dp[0]); |
||||
return !lodashIsNil(mins) ? mins[0] : undefined; |
||||
} |
||||
) |
||||
); |
||||
} |
||||
|
||||
get maxValueX(): number | undefined { |
||||
return lodashMax( |
||||
this.visibleSeries.map( |
||||
serie => { |
||||
const maxs = lodashMaxBy<number[]>(serie.datapoints, dp => dp[0]); |
||||
return !lodashIsNil(maxs) ? maxs[0] : undefined; |
||||
} |
||||
) |
||||
); |
||||
} |
||||
|
||||
get minValueY1(): number | undefined { |
||||
return lodashMin( |
||||
this.rightYRelatedSeries.map( |
||||
serie => { |
||||
const mins = lodashMinBy<number[]>(serie.datapoints, dp => dp[1]); |
||||
return !lodashIsNil(mins) ? mins[1] : undefined; |
||||
} |
||||
) |
||||
); |
||||
} |
||||
|
||||
get maxValueY1(): number | undefined { |
||||
return lodashMax( |
||||
this.rightYRelatedSeries.map( |
||||
serie => { |
||||
const maxs = lodashMaxBy<number[]>(serie.datapoints, dp => dp[1]); |
||||
return !lodashIsNil(maxs) ? maxs[1] : undefined; |
||||
} |
||||
) |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue