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 { ...@@ -73,9 +73,13 @@ export class AreaChartDemoComponent {
'click', 'click',
{ {
chart: activeChart, chart: activeChart,
onEvent: ( dpl: DataPoint[] ) => { onEvent: ( reports ) => {
let first = dpl[ 0 ]; let dpl: DataPoint[] = [];
if ( !first ) { reports.forEach( r => {
dpl = dpl.concat( r.dpl );
});
if (!dpl.length ) {
console.info( 'no data ' ); console.info( 'no data ' );
return; return;
} }
......
...@@ -36,12 +36,15 @@ export class BarChartDemoComponent { ...@@ -36,12 +36,15 @@ export class BarChartDemoComponent {
'click', 'click',
{ {
chart: barChart, chart: barChart,
onEvent: ( dpl: DataPoint[] ) => { onEvent: ( reports ) => {
let first = dpl[ 0 ]; let dpl: DataPoint[] = [];
if ( !first ) { reports.forEach( r => dpl.push( ...r.dpl ) );
return;
} if (!dpl.length ) {
console.info( dpl ); console.info( 'no data ' );
return;
}
console.info( dpl );
} }
}, },
) ); ) );
......
...@@ -65,12 +65,10 @@ export class BoxplotDemoComponent { ...@@ -65,12 +65,10 @@ export class BoxplotDemoComponent {
'click', 'click',
{ {
chart: boxPlot, chart: boxPlot,
onEvent: ( dpl: DataPoint[], chart: BoxPlot ) => { onEvent: ( reports ) => {
let first = dpl[ 0 ]; reports
if ( !first ) { .filter( r => r.dpl.length > 0 )
return; .forEach( r => console.info( `You are hovering over ${ r.dpl.length } points in Chart ${ r.chart.id }.` ) );
}
console.info( `You are hovering over ${ dpl.length } points in Chart ${ chart.id }.` );
} }
} as any } as any
) ); ) );
......
...@@ -56,12 +56,15 @@ export class HeatmapChartDemoComponent{ ...@@ -56,12 +56,15 @@ export class HeatmapChartDemoComponent{
'click', 'click',
{ {
chart: barChart, chart: barChart,
onEvent: ( dpl: DataPoint[] ) => { onEvent: ( reports ) => {
let first = dpl[ 0 ]; let dpl: DataPoint[] = [];
if ( !first ) { reports.forEach( r => dpl.push( ...r.dpl ) );
return;
} if (!dpl.length ) {
console.info( first.getExtent(barChart) ); console.info( 'no data ' );
return;
}
console.info( dpl );
} }
}, },
) ); ) );
......
...@@ -89,9 +89,15 @@ export class LineChartDemoComponent { ...@@ -89,9 +89,15 @@ export class LineChartDemoComponent {
'mousemove', 'mousemove',
{ {
chart: activeChart, chart: activeChart,
onEvent: ( dpl: DataPoint[], chart: LineChart ) => { onEvent: ( reports ) => {
let first = dpl[ 0 ]; let dpl: DataPoint[] = [];
console.log( first ); reports.forEach( r => dpl.push( ...r.dpl ) );
if (!dpl.length ) {
console.info( 'no data ' );
return;
}
console.info( dpl );
} }
} as any } as any
) ); ) );
......
...@@ -72,13 +72,15 @@ export class RadialChartDemoComponent { ...@@ -72,13 +72,15 @@ export class RadialChartDemoComponent {
'click', 'click',
{ {
chart: activeChart, chart: activeChart,
onEvent: ( dpl: DataPoint[] ) => { onEvent: ( reports ) => {
let first = dpl[ 0 ]; let dpl: DataPoint[] = [];
if ( !first ) { reports.forEach( r => dpl.push( ...r.dpl ) );
// console.info( 'no data ' );
return; if (!dpl.length ) {
} console.info( 'no data ' );
console.info( dpl.map( dp => dp) ); return;
}
console.info( dpl );
} }
}, },
) ); ) );
......
import { import {
ScatterStyle ScatterStyle
, PanBehavior , PanBehavior
, GetDataAtMouseEvent , GetDataAtMouseEvent
, DataPoint , DataPoint
, ScatterChart , ScatterChart
, ContinuousAxis , ContinuousAxis
, ChartCanvas , ChartCanvas
, ZoomBehavior, , ZoomBehavior,
} from 'gist-charts'; } from 'gist-charts';
import { Component, ViewChild, ElementRef } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
/* cSpell: words divs scdp */ /* cSpell: words divs scdp */
@Component({ @Component( {
selector: 'app-scatterchart-demo', selector: 'app-scatterchart-demo',
templateUrl: './scatterchart-demo.component.html', templateUrl: './scatterchart-demo.component.html',
styleUrls: ['./scatterchart-demo.component.css'] styleUrls: [ './scatterchart-demo.component.css' ]
}) } )
export class ScatterChartDemoComponent { export class ScatterChartDemoComponent {
@ViewChild( 'drawspace' ) @ViewChild( 'drawspace' )
drawspace: ElementRef; drawspace: ElementRef;
chart: ChartCanvas; chart: ChartCanvas;
// rightChart: ScatterChart; // rightChart: ScatterChart;
ngAfterViewInit() { ngAfterViewInit() {
let me = this; let me = this;
const pointCount = 20; const pointCount = 20;
let charts: ScatterChart[] = []; let charts: ScatterChart[] = [];
let draws: number = 1; let draws: number = 1;
for ( let i = 0; i < draws; i++ ) { for ( let i = 0; i < draws; i++ ) {
let chart = new ScatterChart(); let chart = new ScatterChart();
chart.getStyle = ( dp: DataPoint<ScatterData> ) => { chart.getStyle = ( dp: DataPoint<ScatterData> ) => {
let style = dp.data as ScatterData; let style = dp.data as ScatterData;
return new ScatterStyle( style.stroke, style.fill, style.radius ); return new ScatterStyle( style.stroke, style.fill, style.radius );
}; };
charts.push( chart ); charts.push( chart );
} }
let selection = new ScatterChart(); let selection = new ScatterChart();
selection.getStyle = ( dp: DataPoint<ScatterData> ) => { selection.getStyle = ( dp: DataPoint<ScatterData> ) => {
let style = dp.data as ScatterData; let style = dp.data as ScatterData;
return new ScatterStyle( style.stroke, style.fill, style.radius ); return new ScatterStyle( style.stroke, style.fill, style.radius );
}; };
selection.background = 'rgba(255,255,255,0.5)'; selection.background = 'rgba(255,255,255,0.5)';
selection.isActive = false; selection.isActive = false;
let leftAxis: ContinuousAxis = new ContinuousAxis( 'left' ); let leftAxis: ContinuousAxis = new ContinuousAxis( 'left' );
let bottomAxis: ContinuousAxis = new ContinuousAxis( 'bottom' ); let bottomAxis: ContinuousAxis = new ContinuousAxis( 'bottom' );
let rightAxis: ContinuousAxis = new ContinuousAxis( 'right' ); let rightAxis: ContinuousAxis = new ContinuousAxis( 'right' );
let topAxis: ContinuousAxis = new ContinuousAxis('top'); let topAxis: ContinuousAxis = new ContinuousAxis( 'top' );
let axis = [ let axis = [
// topAxis, // topAxis,
bottomAxis, bottomAxis,
// rightAxis, // rightAxis,
leftAxis leftAxis
]; ];
me.chart = new ChartCanvas( me.drawspace.nativeElement ); me.chart = new ChartCanvas( me.drawspace.nativeElement );
// me.chart.background = 'rgb(255,255,255)'; // me.chart.background = 'rgb(255,255,255)';
axis.forEach(a => { axis.forEach( a => {
a.registerChart(charts); a.registerChart( charts );
a.registerChart( selection ); a.registerChart( selection );
a.explicitBounds = [0, pointCount ] a.explicitBounds = [ 0, pointCount ]
// a.fill = 'white'; // a.fill = 'white';
}); } );
topAxis.getValue = bottomAxis.getValue = ( dp ) => { return dp.data.x; }; topAxis.getValue = bottomAxis.getValue = ( dp ) => { return dp.data.x; };
rightAxis.getValue = leftAxis.getValue = ( dp ) => { return dp.data.y; }; rightAxis.getValue = leftAxis.getValue = ( dp ) => { return dp.data.y; };
let selectedData: DataPoint[] = []; let selectedData: DataPoint[] = [];
function selectionUpdate() { function selectionUpdate() {
if ( selectedData.length ) { if ( selectedData.length ) {
selection.datalist( selectedData ); selection.datalist( selectedData );
selection.isActive = true; selection.isActive = true;
} else { } else {
selection.isActive = false; selection.isActive = false;
} }
console.log( selectedData ); console.log( selectedData );
// me.chart.beginRender(); }
}
leftAxis.label = 'left';
leftAxis.label = 'left'; bottomAxis.label = 'bottom';
bottomAxis.label = 'bottom'; rightAxis.label = 'right';
rightAxis.label = 'right'; topAxis.label = 'top';
topAxis.label = 'top';
leftAxis.shouldExtendTicksAcrossChart = true;
leftAxis.shouldExtendTicksAcrossChart = true; bottomAxis.shouldExtendTicksAcrossChart = true;
bottomAxis.shouldExtendTicksAcrossChart = true;
me.chart.addChart( charts );
me.chart.addChart(charts); me.chart.addChart( selection );
me.chart.addChart(selection); me.chart.addAxis( axis );
me.chart.addAxis( axis); let getData = new GetDataAtMouseEvent(
let getData = new GetDataAtMouseEvent( 'click',
'click', {
{ chart: charts,
chart: charts, onEvent: ( reports ) => {
onEvent: ( dpl: DataPoint[] ) => { let pointsAtEvent: DataPoint[] = [];
let first = dpl[ 0 ]; reports.forEach( r => pointsAtEvent.push( ...r.dpl ) );
if ( !first ) {
selectedData = []; if ( !pointsAtEvent.length ) {
selectionUpdate(); selectedData = [];
return; }
} selectedData.push( ...pointsAtEvent );
// first.data.selected = true; selectionUpdate();
selectedData.push(first); }
selectionUpdate(); },
} );
}, let zoom = new ZoomBehavior();
); zoom.limitZoom( axis, 0.5, 1.5 );
let zoom = new ZoomBehavior();
// zoom.limitZoom( axis, 0.5, 1.5 ); let pan = new PanBehavior('wheel'); // use wheel because we want to click on the data
pan.limitPan( bottomAxis, pointCount * -0.25, pointCount * 1.25 );
let pan = new PanBehavior();
// 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;
let dataList = randomData( pointCount );
charts[0].alpha = 1; charts[ 0 ].datalist( dataList );
let dataList = randomData( pointCount ); me.chart.beginRender();
charts[ 0 ].datalist( dataList );
me.chart.beginRender();
// setTimeout(redraw, 20000);
}
// setTimeout(redraw, 20000); intervals: number[] = [];
} ngOnDestroy() {
intervals: number[] = []; this.intervals.forEach( int => {
ngOnDestroy() { window.clearInterval( int );
this.intervals.forEach( int => { } );
window.clearInterval( int ); }
} );
}
} }
function randomData( pointCount: number = 10000 ): Array<DataPoint> { function randomData( pointCount: number = 10000 ): Array<DataPoint> {
let scdp: Array<DataPoint> = []; let scdp: Array<DataPoint> = [];
// scdp.length = pointCount; // 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' ]; 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++ ) { for ( let i = 0; i < pointCount; i++ ) {
let fill = tmpArr[ randomInt( 0, 9 )]; let fill = tmpArr[ randomInt( 0, 9 ) ];
let dp = new DataPoint( { let dp = new DataPoint( {
x: randomInt(0, pointCount), x: randomInt( 0, pointCount ),
y: randomInt( 0, pointCount ), y: randomInt( 0, pointCount ),
radius: 3, radius: 3,
fill: fill, fill: fill,
stroke: undefined stroke: undefined
}); } );
// let stroke = ( randomColors ) ? fill : tmpArr[ Math.floor(( i + xStart + div / 2 ) / div ) ]; // let stroke = ( randomColors ) ? fill : tmpArr[ Math.floor(( i + xStart + div / 2 ) / div ) ];
scdp.push(dp ); scdp.push( dp );
} }
// scdp.sort(); // scdp.sort();
return scdp; return scdp;
} }
function randomInt( min: number, max: number ): number { function randomInt( min: number, max: number ): number {
let diff = max - min; let diff = max - min;
return Math.round( Math.random() * diff + min ); return Math.round( Math.random() * diff + min );
} }
class ScatterData { class ScatterData {
x: number = 0; x: number = 0;
y: number = 0; y: number = 0;
radius: number = 0; radius: number = 0;
fill: string =''; fill: string = '';
stroke: 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 { makeList } from '../utility/makeList.method';
import { GistBehavior } from './gistBehavior.class'; import { GistBehavior } from './gistBehavior.class';
import { LimiterType } from "../types/limiterType.type"; import { LimiterType } from "../types/limiterType.type";
import { DataPoint } from '../classes/datapoint.class';
export type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'mousemove' | 'mouseout' | 'mouseover' | 'mouseup'; export type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'mousemove' | 'mouseout' | 'mouseover' | 'mouseup';
...@@ -12,24 +14,37 @@ export type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'mousemove ...@@ -12,24 +14,37 @@ export type supportedEventType = 'dblclick' | 'click' | 'mousedown' | 'mousemove
* @class GetDataAtMouseEvent * @class GetDataAtMouseEvent
*/ */
export class GetDataAtMouseEvent extends GistBehavior { 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( constructor(
eventNames: supportedEventType | supportedEventType[], eventNames: supportedEventType | supportedEventType[],
eventHandlers: EventHandler | EventHandler[], eventHandlers: EventHandler | EventHandler[],
waitTime: number = 250, throttleTime: number = 250,
limiter: LimiterType = 'throttle' ) { limiter: LimiterType = 'throttle' ) {
super( eventNames, eventHandlers, waitTime, limiter ); super( eventNames, eventHandlers, throttleTime, limiter );
} }
_activate(): void { _activate(): void {
const me = this; const me = this;
me.eventHandlers.forEach( ( eh ) => { let reportList: Array<{ dpl: DataPoint[], chart: BaseChart }> ;
makeList( eh.chart ).forEach( chart => {
if ( chart.isActive ) {
eh.onEvent( chart._implementation.getDataAtCoord( me.currentX, me.currentY ), chart, me.currentEvent );
}
} );
me.eventHandlers.forEach( ( eh ) => {
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 { ChartCanvas } from '../classes/chartCanvas.class';
import { makeList } from '../utility/makeList.method'; import { makeList } from '../utility/makeList.method';
import { uID } from '../utility/uID.method'; import { uID } from '../utility/uID.method';
...@@ -63,7 +63,7 @@ export abstract class GistBehavior { ...@@ -63,7 +63,7 @@ export abstract class GistBehavior {
* @type {number} * @type {number}
* @memberof GistBehavior * @memberof GistBehavior
*/ */
public readonly waitTime: number; public readonly throttleTime: number;
/** /**
...@@ -137,11 +137,11 @@ export abstract class GistBehavior { ...@@ -137,11 +137,11 @@ export abstract class GistBehavior {
constructor( constructor(
eventNames: string | string[], eventNames: string | string[],
eventHandlers: EventHandler | EventHandler[], eventHandlers: EventHandler | EventHandler[],
waitTime: number = 250, throttleTime: number = 250,
limiter: LimiterType ) {