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", |
"name": "@chartwerk/core", |
||||||
"version": "0.6.26", |
"version": "0.3.4", |
||||||
"description": "Chartwerk core", |
"description": "Chartwerk core", |
||||||
"main": "dist/index.js", |
"main": "dist/index.js", |
||||||
"types": "dist/index.d.ts", |
"types": "dist/index.d.ts", |
||||||
"files": [ |
|
||||||
"/dist" |
|
||||||
], |
|
||||||
"scripts": { |
"scripts": { |
||||||
"build": "webpack --config build/webpack.prod.conf.js", |
"build": "webpack --config build/webpack.prod.conf.js", |
||||||
"dev": "webpack --config build/webpack.dev.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" |
"test": "echo \"Error: no test specified\" && exit 1" |
||||||
}, |
}, |
||||||
"repository": { |
"repository": { |
||||||
"type": "git", |
"type": "git", |
||||||
"url": "http://code.corpglory.net/chartwerk/core.git" |
"url": "https://gitlab.com/chartwerk/core.git" |
||||||
}, |
}, |
||||||
"author": "CorpGlory Inc.", |
"author": "CorpGlory Inc.", |
||||||
"license": "ISC", |
"license": "ISC", |
||||||
"dependencies": { |
|
||||||
"d3": "^5.16.0", |
|
||||||
"lodash": "^4.17.21" |
|
||||||
}, |
|
||||||
"devDependencies": { |
"devDependencies": { |
||||||
"css-loader": "^6.8.1", |
"@types/d3": "^5.7.2", |
||||||
"style-loader": "^3.3.3", |
"@types/lodash": "^4.14.149", |
||||||
"ts-loader": "^9.4.3", |
"css-loader": "^3.4.2", |
||||||
"typescript": "^5.1.3", |
"lodash": "^4.17.15", |
||||||
"webpack": "^5.87.0", |
"style-loader": "^1.1.3", |
||||||
"webpack-cli": "^5.1.4" |
"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