Commit 2b42f5da authored by David Anderson's avatar David Anderson
Browse files

getDataAtEvent now groups the charts and datapoints into one callback

parent f0a534da
......@@ -73,9 +73,13 @@ export class AreaChartDemoComponent {
'click',
{
chart: activeChart,
onEvent: ( dpl: DataPoint[] ) => {
let first = dpl[ 0 ];
if ( !first ) {
onEvent: ( reports ) => {
let dpl: DataPoint[] = [];
reports.forEach( r => {
dpl = dpl.concat( r.dpl );
});
if (!dpl.length ) {
console.info( 'no data ' );
return;
}
......
......@@ -36,9 +36,12 @@ export class BarChartDemoComponent {
'click',
{
chart: barChart,
onEvent: ( dpl: DataPoint[] ) => {
let first = dpl[ 0 ];
if ( !first ) {
onEvent: ( reports ) => {
let dpl: DataPoint[] = [];
reports.forEach( r => dpl.push( ...r.dpl ) );
if (!dpl.length ) {
console.info( 'no data ' );
return;
}
console.info( dpl );
......
......@@ -65,12 +65,10 @@ export class BoxplotDemoComponent {
'click',
{
chart: boxPlot,
onEvent: ( dpl: DataPoint[], chart: BoxPlot ) => {
let first = dpl[ 0 ];
if ( !first ) {
return;
}
console.info( `You are hovering over ${ dpl.length } points in Chart ${ chart.id }.` );
onEvent: ( reports ) => {
reports
.filter( r => r.dpl.length > 0 )
.forEach( r => console.info( `You are hovering over ${ r.dpl.length } points in Chart ${ r.chart.id }.` ) );
}
} as any
) );
......
......@@ -56,12 +56,15 @@ export class HeatmapChartDemoComponent{
'click',
{
chart: barChart,
onEvent: ( dpl: DataPoint[] ) => {
let first = dpl[ 0 ];
if ( !first ) {
onEvent: ( reports ) => {
let dpl: DataPoint[] = [];
reports.forEach( r => dpl.push( ...r.dpl ) );
if (!dpl.length ) {
console.info( 'no data ' );
return;
}
console.info( first.getExtent(barChart) );
console.info( dpl );
}
},
) );
......
......@@ -89,9 +89,15 @@ export class LineChartDemoComponent {
'mousemove',
{
chart: activeChart,
onEvent: ( dpl: DataPoint[], chart: LineChart ) => {
let first = dpl[ 0 ];
console.log( first );
onEvent: ( reports ) => {
let dpl: DataPoint[] = [];
reports.forEach( r => dpl.push( ...r.dpl ) );
if (!dpl.length ) {
console.info( 'no data ' );
return;
}
console.info( dpl );
}
} as any
) );
......
......@@ -72,13 +72,15 @@ export class RadialChartDemoComponent {
'click',
{
chart: activeChart,
onEvent: ( dpl: DataPoint[] ) => {
let first = dpl[ 0 ];
if ( !first ) {
// console.info( 'no data ' );
onEvent: ( reports ) => {
let dpl: DataPoint[] = [];
reports.forEach( r => dpl.push( ...r.dpl ) );
if (!dpl.length ) {
console.info( 'no data ' );
return;
}
console.info( dpl.map( dp => dp) );
console.info( dpl );
}
},
) );
......
......@@ -11,11 +11,11 @@ import {
import { Component, ViewChild, ElementRef } from '@angular/core';
/* cSpell: words divs scdp */
@Component({
@Component( {
selector: 'app-scatterchart-demo',
templateUrl: './scatterchart-demo.component.html',
styleUrls: ['./scatterchart-demo.component.css']
})
styleUrls: [ './scatterchart-demo.component.css' ]
} )
export class ScatterChartDemoComponent {
@ViewChild( 'drawspace' )
drawspace: ElementRef;
......@@ -51,7 +51,7 @@ export class ScatterChartDemoComponent {
let leftAxis: ContinuousAxis = new ContinuousAxis( 'left' );
let bottomAxis: ContinuousAxis = new ContinuousAxis( 'bottom' );
let rightAxis: ContinuousAxis = new ContinuousAxis( 'right' );
let topAxis: ContinuousAxis = new ContinuousAxis('top');
let topAxis: ContinuousAxis = new ContinuousAxis( 'top' );
let axis = [
// topAxis,
bottomAxis,
......@@ -61,12 +61,12 @@ export class ScatterChartDemoComponent {
me.chart = new ChartCanvas( me.drawspace.nativeElement );
// me.chart.background = 'rgb(255,255,255)';
axis.forEach(a => {
a.registerChart(charts);
axis.forEach( a => {
a.registerChart( charts );
a.registerChart( selection );
a.explicitBounds = [0, pointCount ]
a.explicitBounds = [ 0, pointCount ]
// a.fill = 'white';
});
} );
topAxis.getValue = bottomAxis.getValue = ( dp ) => { return dp.data.x; };
rightAxis.getValue = leftAxis.getValue = ( dp ) => { return dp.data.y; };
......@@ -80,7 +80,6 @@ export class ScatterChartDemoComponent {
selection.isActive = false;
}
console.log( selectedData );
// me.chart.beginRender();
}
......@@ -92,35 +91,34 @@ export class ScatterChartDemoComponent {
leftAxis.shouldExtendTicksAcrossChart = true;
bottomAxis.shouldExtendTicksAcrossChart = true;
me.chart.addChart(charts);
me.chart.addChart(selection);
me.chart.addAxis( axis);
me.chart.addChart( charts );
me.chart.addChart( selection );
me.chart.addAxis( axis );
let getData = new GetDataAtMouseEvent(
'click',
{
chart: charts,
onEvent: ( dpl: DataPoint[] ) => {
let first = dpl[ 0 ];
if ( !first ) {
onEvent: ( reports ) => {
let pointsAtEvent: DataPoint[] = [];
reports.forEach( r => pointsAtEvent.push( ...r.dpl ) );
if ( !pointsAtEvent.length ) {
selectedData = [];
selectionUpdate();
return;
}
// first.data.selected = true;
selectedData.push(first);
selectedData.push( ...pointsAtEvent );
selectionUpdate();
}
},
);
let zoom = new ZoomBehavior();
// zoom.limitZoom( axis, 0.5, 1.5 );
zoom.limitZoom( axis, 0.5, 1.5 );
let pan = new PanBehavior();
// pan.limitPan(bottomAxis, pointCount * -0.25, pointCount * 1.25 );
let pan = new PanBehavior('wheel'); // use wheel because we want to click on the data
pan.limitPan( bottomAxis, pointCount * -0.25, pointCount * 1.25 );
me.chart.addBehavior( [getData, zoom, pan] );
me.chart.addBehavior( [ getData, zoom, pan ] );
charts[0].alpha = 1;
charts[ 0 ].alpha = 1;
let dataList = randomData( pointCount );
charts[ 0 ].datalist( dataList );
me.chart.beginRender();
......@@ -142,17 +140,17 @@ function randomData( pointCount: number = 10000 ): Array<DataPoint> {
// scdp.length = pointCount;
let tmpArr = [ 'blue', 'green', 'grey', 'black', 'brown', 'yellow', 'orange', 'purple', 'pink', 'red', 'blue', 'green', 'grey', 'black', 'brown', 'yellow', 'orange', 'purple', 'pink', 'red' ];
for ( let i = 0; i < pointCount; i++ ) {
let fill = tmpArr[ randomInt( 0, 9 )];
let fill = tmpArr[ randomInt( 0, 9 ) ];
let dp = new DataPoint( {
x: randomInt(0, pointCount),
x: randomInt( 0, pointCount ),
y: randomInt( 0, pointCount ),
radius: 3,
fill: fill,
stroke: undefined
});
} );
// let stroke = ( randomColors ) ? fill : tmpArr[ Math.floor(( i + xStart + div / 2 ) / div ) ];
scdp.push(dp );
scdp.push( dp );
}
// scdp.sort();
return scdp;
......@@ -167,6 +165,6 @@ class ScatterData {
x: number = 0;
y: number = 0;
radius: number = 0;
fill: string ='';
stroke: string ='';
fill: string = '';
stroke: string = '';
}
import { EventHandler } from '../classes/eventHandler.class';
import { BaseChart } from '../';
import { EventHandler } from '../types/eventHandler.interface';
import { makeList } from '../utility/makeList.method';
import { GistBehavior } from './gistBehavior.class';
import { LimiterType } from "../types/limiterType.type";
import { DataPoint } from '../classes/datapoint.class';
export type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'mousemove' | 'mouseout' | 'mouseover' | 'mouseup';
......@@ -12,24 +14,37 @@ export type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'mousemove
* @class GetDataAtMouseEvent
*/
export class GetDataAtMouseEvent extends GistBehavior {
/**
*Creates an instance of GetDataAtMouseEvent.
* @param {(supportedEventType | supportedEventType[])} 'dblclick' | 'click' | 'mousedown' | 'mousemove' | 'mouseout' | 'mouseover' | 'mouseup'
* @param {(EventHandler | EventHandler[])} eventHandlers Define what to do when the event happens
* @param {number} [throttleTime=250] How often this event can happen
* @param {LimiterType} [limiter='throttle'] 'throttle' will ensure the event will wait throttleTime, before firing again. 'debounce' will wait throttletime without a repeat event to fire.
* @memberof GetDataAtMouseEvent
*/
constructor(
eventNames: supportedEventType | supportedEventType[],
eventHandlers: EventHandler | EventHandler[],
waitTime: number = 250,
throttleTime: number = 250,
limiter: LimiterType = 'throttle' ) {
super( eventNames, eventHandlers, waitTime, limiter );
super( eventNames, eventHandlers, throttleTime, limiter );
}
_activate(): void {
const me = this;
let reportList: Array<{ dpl: DataPoint[], chart: BaseChart }> ;
me.eventHandlers.forEach( ( eh ) => {
makeList( eh.chart ).forEach( chart => {
if ( chart.isActive ) {
eh.onEvent( chart._implementation.getDataAtCoord( me.currentX, me.currentY ), chart, me.currentEvent );
}
reportList = [];
makeList( eh.chart )
.filter( chart => chart !== undefined && chart.isActive )
.forEach( chart => {
reportList.push( {
dpl: chart._implementation.getDataAtCoord( me.currentX, me.currentY ),
chart: chart
} );
} );
eh.onEvent( reportList, me.currentEvent );
} );
}
}
import { EventHandler } from '../classes/eventHandler.class';
import { EventHandler } from '../types/eventHandler.interface';
import { ChartCanvas } from '../classes/chartCanvas.class';
import { makeList } from '../utility/makeList.method';
import { uID } from '../utility/uID.method';
......@@ -63,7 +63,7 @@ export abstract class GistBehavior {
* @type {number}
* @memberof GistBehavior
*/
public readonly waitTime: number;
public readonly throttleTime: number;
/**
......@@ -137,11 +137,11 @@ export abstract class GistBehavior {
constructor(
eventNames: string | string[],
eventHandlers: EventHandler | EventHandler[],
waitTime: number = 250,
throttleTime: number = 250,
limiter: LimiterType ) {
this.eventNames = makeList( eventNames );
this.eventHandlers = makeList( eventHandlers );
this.waitTime = waitTime;
this.throttleTime = throttleTime;
this.limiter = limiter;
}
......@@ -155,7 +155,7 @@ export abstract class GistBehavior {
protected _activate(): void {
const me = this;
me.eventHandlers.forEach(( eh ) => {
eh.onEvent( [], undefined, me.currentEvent );
eh.onEvent( [], me.currentEvent );
} );
}
......@@ -220,7 +220,7 @@ export abstract class GistBehavior {
me.reset();
}
}, me.waitTime );
}, me.throttleTime );
}
}
......@@ -232,7 +232,7 @@ export abstract class GistBehavior {
me.currentEvent = event;
me.timer = window.setTimeout(() => {
me.callActivate();
}, me.waitTime );
}, me.throttleTime );
}
private callActivate(): void {
......
import { BaseChart } from '../configs/charts/baseChart.class';
import { EventHandler } from '../types/eventHandler.interface';
import { GistBehavior } from './gistBehavior.class';
import { DataPoint } from '../classes/datapoint.class';
import { EventHandler } from '../classes/eventHandler.class';
import { SupportedPanEvent } from "../types/supportedPanEvent.type";
import { makeList } from '../utility/makeList.method';
import { BaseAxis } from '../configs/axis/baseAxis.class';
......@@ -16,18 +14,21 @@ import { BaseAxis } from '../configs/axis/baseAxis.class';
*/
export class PanBehavior extends GistBehavior {
// private zoomConstant: number = 1.15;
// private zoomMethod: zoomMethod;
private mousedown: boolean;
private lastPosition!: { x: number, y: number };
private maskingElement!: HTMLElement;
private userSelectValue!: string | null;
private hasWarned: boolean | undefined;
/**
*Creates an instance of PanBehavior.
* @param {(SupportedPanEvent | SupportedPanEvent[])} [eventNames='mousedrag'] // mouse drag works by holding the mouse down and dragging, will stop when the mouse is released. Wheel works on scroll ( magic mouse compatible )
* @param {number} [throttleTime=15] // the delay on pans
* @memberof PanBehavior
*/
constructor(
eventNames: SupportedPanEvent | SupportedPanEvent[] = 'mousedrag' ,
waitTime: number = 15,
throttleTime: number = 15,
) {
let givenEvents = makeList( eventNames );
let dragIndex = givenEvents.indexOf( 'mousedrag' );
......@@ -37,15 +38,26 @@ export class PanBehavior extends GistBehavior {
super(
givenEvents,
{
onEvent: ( dpl: DataPoint[], chart: BaseChart, mouseEvent: MouseEvent ) => { this._togglepan( dpl, chart, mouseEvent ); }
onEvent: ( [], mouseEvent: MouseEvent ) => {
this._togglepan( mouseEvent );
}
}as EventHandler
, waitTime,
, throttleTime,
'throttle'
);
this.mousedown = false;
}
/**
* BETA feature! still buggy
* Set the min and max pan value for the given axis. Values inside the pan limits will always be visible.
*
* @param {(BaseAxis | Array<BaseAxis>)} targets
* @param {number} minZoomLevel
* @param {number} maxZoomLevel
* @memberof ZoomBehavior
*/
public limitPan( axis: BaseAxis, minValue: any, maxValue: any ) {
if ( !this.hasWarned ) {
window.console.warn( 'limitZoom is still in beta. There are known bugs, but the api should not change' );
......@@ -54,7 +66,7 @@ export class PanBehavior extends GistBehavior {
axis._implementation.panExtent = [minValue, maxValue];
}
_togglepan( dpl: DataPoint[], chart: BaseChart | undefined, mouseEvent: MouseEvent ): void {
_togglepan( mouseEvent: MouseEvent ): void {
if ( mouseEvent.type === 'mousedown' ) {
this.mousedown = true;
this.maskingElement = document.body.appendChild( document.createElement( 'div' ) );
......@@ -83,15 +95,15 @@ export class PanBehavior extends GistBehavior {
this.eventNames.filter( name => name !== 'mouseup' ).forEach( name => {
this.maskingElement.addEventListener( name, ( event ) => {
this._pan( dpl, chart, event as MouseEvent);
this._pan(event as MouseEvent);
} )
} )
} else if ( mouseEvent.type === 'wheel' || mouseEvent.type === 'dblclick' ) {
this._pan(dpl, chart, mouseEvent);
this._pan( mouseEvent);
}
}
_pan( dpl: DataPoint[], chart: BaseChart | undefined, mouseEvent: MouseEvent ): void {
_pan(mouseEvent: MouseEvent ): void {
const me = this;
let xy: { x: number, y: number } | undefined;
switch ( mouseEvent.type ) {
......
import { BaseChart } from '../configs/charts/baseChart.class';
import { GistBehavior } from './gistBehavior.class';
import { DataPoint } from '../classes/datapoint.class';
import { EventHandler } from '../classes/eventHandler.class';
import { EventHandler } from '../types/eventHandler.interface';
import { ZoomMethod } from "../types/zoomMethod.type";
import { LimiterType } from "../types/limiterType.type";
import { BaseAxis } from '../configs/axis/baseAxis.class';
import { makeList } from '../utility/makeList.method';
......@@ -25,28 +22,37 @@ export class ZoomBehavior extends GistBehavior {
*Creates an instance of ZoomBehavior.
* @param {number} [zoomConstant=1.15] controls how strong the zoom step is
* @param {ZoomMethod} [zoomMethod='multiplication'] multiplication zoom steps are based on the current zoom level resulting in a smoother movement. addition will jump by steps
* @param {number} [waitTime=50] how long in milliseconds to throttle the event
* @param {number} [throttleTime=50] how long in milliseconds to throttle the event
* @memberof ZoomBehavior
*/
constructor(
zoomConstant: number = 1.15,
zoomMethod: ZoomMethod = 'multiplication',
waitTime: number = 50,
throttleTime: number = 50,
) {
super(
'wheel',
{
onEvent: ( dpl: DataPoint[], chart: BaseChart, mouseEvent: MouseEvent ) => {
this._zoom( dpl, chart, mouseEvent );
onEvent: ([], mouseEvent: MouseEvent ) => {
this._zoom( mouseEvent );
}
} as EventHandler
, waitTime
, throttleTime
, 'throttle'
);
this.zoomConstant = zoomConstant;
this.zoomMethod = zoomMethod;
}
/**
* BETA feature! still buggy
* Set the min and max zoom level for the given axis.
*
* @param {(BaseAxis | Array<BaseAxis>)} targets
* @param {number} minZoomLevel
* @param {number} maxZoomLevel
* @memberof ZoomBehavior
*/
public limitZoom( targets: BaseAxis | Array<BaseAxis>, minZoomLevel: number, maxZoomLevel: number ) {
if ( !this.hasWarned ) {
window.console.warn( 'limitZoom is still in beta. There are known bugs, but the api should not change' );
......@@ -57,14 +63,14 @@ export class ZoomBehavior extends GistBehavior {
} );
}
_zoom( dpl: DataPoint[], chart: BaseChart | undefined, mouseEvent: MouseEvent ): void {
_zoom( mouseEvent: MouseEvent ): void {
const me = this;
let multiplier = 1;
let zoomDirection = 1;
if ( mouseEvent.type === 'wheel' ) {
const deltaY = ( mouseEvent as WheelEvent ).deltaY;
zoomDirection = -1 * ( deltaY ) / Math.abs( deltaY );
multiplier *= ( Math.abs( deltaY ) * me.waitTime / 10000 );
multiplier *= ( Math.abs( deltaY ) * me.throttleTime / 10000 );
me.preventDefault = true;
}
......
//Classes
export { EventReport } from './types/eventReport.interface';
export { EventHandler } from './types/eventHandler.interface';
export { TBLR } from './classes/tblr.class';
export { CanvasStyle } from './utility/canvasStyle.class';
export { GistCanvas } from './classes/gistCanvas.class';
......
import {DataPoint} from './datapoint.class';
import { EventReport } from './eventReport.interface';
import {BaseChart} from '../configs/charts/baseChart.class';
export interface EventHandler {
chart: BaseChart | Array<BaseChart> | undefined;
onEvent: ( dpl: DataPoint[], chart: BaseChart | undefined, mouseEvent: MouseEvent | undefined ) => void;
onEvent: (
reportList: Array<EventReport>,
mouseEvent: MouseEvent | undefined
) => void;
}
\ No newline at end of file
import { DataPoint } from '../classes/datapoint.class';
import {BaseChart} from '../configs/charts/baseChart.class';
export interface EventReport {
dpl: DataPoint[],
chart: BaseChart
}
\ No newline at end of file
import { EventHandler } from '../classes/eventHandler.class';
import { EventHandler } from '../types/eventHandler.interface';
import { GistBehavior } from './gistBehavior.class';
import { LimiterType } from "../types/limiterType.type";
export declare type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'mousemove' | 'mouseout' | 'mouseover' | 'mouseup';
......@@ -9,6 +9,14 @@ export declare type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'm
* @class GetDataAtMouseEvent
*/
export declare class GetDataAtMouseEvent extends GistBehavior {
constructor(eventNames: supportedEventType | supportedEventType[], eventHandlers: EventHandler | EventHandler[], waitTime?: number, limiter?: LimiterType);
/**
*Creates an instance of GetDataAtMouseEvent.
* @param {(supportedEventType | supportedEventType[])} 'dblclick' | 'click' | 'mousedown' | 'mousemove' | 'mouseout' | 'mouseover' | 'mouseup'
* @param {(EventHandler | EventHandler[])} eventHandlers Define what to do when the event happens
* @param {number} [throttleTime=250] How often this event can happen
* @param {LimiterType} [limiter='throttle'] 'throttle' will ensure the event will wait throttleTime, before firing again. 'debounce' will wait throttletime without a repeat event to fire.
* @memberof GetDataAtMouseEvent
*/
constructor(eventNames: supportedEventType | supportedEventType[], eventHandlers: EventHandler | EventHandler[], throttleTime?: number, limiter?: LimiterType);
_activate(): void;
}
import { EventHandler } from '../classes/eventHandler.class';
import { EventHandler } from '../types/eventHandler.interface';
import { ChartCanvas } from '../classes/chartCanvas.class';
import { LimiterType } from "../types/limiterType.type";