Commit 8085b97b authored by David Anderson's avatar David Anderson
Browse files

charts are stable again with rxjs state

parent 0e5792aa
......@@ -18,18 +18,24 @@ export class ScatterChartDemoComponent implements AfterViewInit, OnDestroy {
ngAfterViewInit() {
const me = this;
me.chart = new ChartCanvas( me.drawspace.nativeElement );
const pointCount = 20;
const baseChart = new ScatterChart();
const selectionChart = new ScatterChart();
me.chart = new ChartCanvas( me.drawspace.nativeElement );
baseChart.margin = 5;
selectionChart.margin = 15;
me.chart.debugLogger.logPrefix = 'Chart Canvas - ';
baseChart.debugLogger.logPrefix = 'Base Chart - ';
selectionChart.debugLogger.logPrefix = 'Selection Chart - ';
me.chart.debugLogger.isEnabled = true;
// baseChart.debugLogger.isEnabled = true;
// selectionChart.debugLogger.isEnabled = true;
selectionChart.background = 'rgba(255,255,255,0.5)';
// baseChart.onRenderStateChange = console.log;
selectionChart.background = 'rgba(255,255,255,0.1)';
selectionChart.isActive = false;
baseChart.getStyle = ( dp: DataPoint<ScatterData> ) => {
......@@ -73,7 +79,11 @@ export class ScatterChartDemoComponent implements AfterViewInit, OnDestroy {
let selectedData: DataPoint[] = [];
baseChart.margin = 5;
selectionChart.datalist( selectedData );
me.chart.addChart( [ baseChart, selectionChart ] );
me.chart.addChart(
[ baseChart
, selectionChart
]
);
me.chart.addAxis( axis );
const getData = new GetDataAtMouseEvent(
'click',
......
.text-center {
text-align: center;
text-align: center;
}
.text-right {
text-align: right;
text-align: right;
}
.nowrap {
white-space: nowrap;
white-space: nowrap;
}
.chartList {
padding-left: 30px;
padding-right: 30px;
padding-left: 30px;
padding-right: 30px;
}
.popover {
padding: 5px;
border-radius: 5px;
display: block;
padding: 5px;
border-radius: 5px;
display: block;
}
.htmlBlock .tag {
color: steelblue;
color: steelblue;
}
.htmlBlock .attr {
color: seagreen;
color: seagreen;
}
.htmlBlock .val {
color: sandybrown;
color: sandybrown;
}
* {
position: relative;
margin: 0;
padding: 0;
position: relative;
margin: 0;
padding: 0;
}
*,
*:after,
*:before {
box-sizing: inherit;
box-sizing: inherit;
}
svg {
pointer-events: none;
pointer-events: none;
}
svg * {
pointer-events: all;
pointer-events: all;
}
body,
html {
position: relative;
min-height: 100%;
height: 100%;
width: 100%;
overflow: hidden;
box-sizing: border-box;
font-family: 'Open Sans', sans-serif;
font-size: 18px;
position: relative;
min-height: 100%;
height: 100%;
width: 100%;
overflow: hidden;
box-sizing: border-box;
font-family: 'Open Sans', sans-serif;
font-size: 18px;
}
router-outlet {
display: none;
display: none;
}
button {
cursor: pointer;
font-family: 'Open Sans', sans-serif;
background: #fff;
color: #333;
font-size: 14px;
padding: 5px 10px;
border-width: 1px;
cursor: pointer;
font-family: 'Open Sans', sans-serif;
background: #fff;
color: #333;
font-size: 14px;
padding: 5px 10px;
border-width: 1px;
}
input {
padding: 5px;
padding: 5px;
}
a {
cursor: pointer;
text-decoration: none;
color: #1f7cb6;
cursor: pointer;
text-decoration: none;
color: #1f7cb6;
}
header {
padding-top: 10px;
padding-bottom: 10px;
padding-top: 10px;
padding-bottom: 10px;
}
header h1,
header h2,
header h3 {
margin-top: 0px;
margin-bottom: 0px;
margin-top: 0px;
margin-bottom: 0px;
}
ul,
li {
list-style: none;
list-style: none;
}
ul.bulleted,
li.bulleted {
padding-left: 10px;
margin-left: 10px;
list-style: disc;
padding-left: 10px;
margin-left: 10px;
list-style: disc;
}
ul.bulleted ul.bulleted,
li.bulleted ul.bulleted,
ul.bulleted li.bulleted,
li.bulleted li.bulleted {
list-style: square;
list-style: square;
}
.chart {
width: 500px;
height: 500px;
}
\ No newline at end of file
width: 500px;
height: 500px;
background: white;
border-radius: 5px;
}
import { combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable, Subscription } from 'rxjs';
import { distinctUntilChanged, map, publishReplay, refCount } from 'rxjs/operators';
import { GistBehavior } from '../behaviors/gistBehavior.class';
import { GistCanvas } from '../classes/gistCanvas.class';
......@@ -88,7 +88,9 @@ export class ChartCanvas {
private animationFrame: number = 0;
private areChartsRendering!: Observable<boolean>;
public areChartsRendering: Observable<boolean> | undefined;
private activeStateChange: Observable<any> | undefined;
private stateSubscription: Subscription | undefined;
// private areAxisRending: Observable<boolean>;
// private isMainCanvasRendering: Observable<boolean>;
......@@ -232,6 +234,9 @@ export class ChartCanvas {
me.chartList.forEach( ( chart ) => {
chart._implementation.stopDrawing();
} );
if ( this.stateSubscription ) {
this.stateSubscription.unsubscribe();
}
}
/**
......@@ -462,57 +467,43 @@ export class ChartCanvas {
chart.debugLogger._log( 'INVALID CHART SIZE: CHART WILL NOT RENDER: chartWidth = ' + width + ', chartHeight = ' + height );
}
} );
this.debugLogger._log( 'starting chart config polling' );
me.pollCharts();
}
this.debugLogger._log( 'Charts now checking for data to draw' );
private pollCharts() {
const me = this;
let isDirty = false;
me.pollPixelRatio();
me.areChartsRendering = combineLatest( me.chartList.map( chart => chart._implementation.isRenderInProgress ) ).pipe(
map( array => array.indexOf( false ) === -1 ),
distinctUntilChanged()
me.areChartsRendering = combineLatest(
me.chartList.map( chart => chart._implementation.isRenderInProgress as Observable<boolean> )
).pipe(
map( array => array.indexOf( true ) !== -1 ),
distinctUntilChanged(),
publishReplay( 1 ),
refCount(),
);
me.areChartsRendering.subscribe( isRenderInProgress => {
if ( isRenderInProgress ) {
isDirty = true;
this.showCharts();
} else if ( isDirty ) {
isDirty = false;
this.copyCharts();
}
} )
me.activeStateChange = combineLatest(
me.chartList.map( chart => chart._implementation.isActive.pipe( distinctUntilChanged() ) ) );
let isDirty = true;
this.stateSubscription = combineLatest( [ me.areChartsRendering, me.activeStateChange ] )
.subscribe( data => {
let areChartsRendering = data[ 0 ];
me.debugLogger._log( 'Render state changed ' + isDirty );
if ( areChartsRendering ) {
this.showCharts();
} else {
this.copyCharts();
isDirty = false;
}
} )
}
private pollPixelRatio() {
const me = this;
if ( me.renderedRatio !== window.devicePixelRatio ) {
me.debugLogger._log( 'GistCharts is now redrawing due to pixel ratio change. This is normally caused by dragging changing computer monitors.' );
me.beginRender();
}
// me.chartList.forEach( chart => {
// let canvas = chart._implementation.canvas;
// if ( chart.isActive ) {
// isRenderInProgress = isRenderInProgress || chart._implementation.isRenderInProgress;
// isDirty = isDirty || chart._implementation.isDirty;
// } else {
// canvas.canvasEle.style.visibility = 'hidden';
// }
// hasActiveChanged = hasActiveChanged || chart._implementation.hasActiveChanged;
// } );
// if ( isRenderInProgress ) {
// if ( !this._areRenderChartsShowing ) {
// this._areRenderChartsShowing = true;
// this.showCharts();
// }
// } else {
// this._areRenderChartsShowing = false;
// if ( isDirty || hasActiveChanged ) {
// this.copyCharts();
// }
// }
// me.animationFrame = window.requestAnimationFrame( () => { me.pollCharts(); } );
me.animationFrame = window.requestAnimationFrame( () => { me.pollPixelRatio(); } );
}
private showCharts() {
......@@ -546,8 +537,6 @@ export class ChartCanvas {
private copyCharts() {
this.debugLogger._log( 'copying charts over' );
const me = this;
const rs = me.renderedSizes;
let cb = rs.chartBounds;
me.canvas.clearCanvas( me.background );
me.renderAxis();
me.drawGridLines();
......@@ -566,22 +555,22 @@ export class ChartCanvas {
axis._implementation.canvas.renderTo( me.canvas.canvasEle, x, y );
} );
let cb = me.renderedSizes.chartBounds;
me.chartList.forEach( chart => {
if ( chart.isActive ) {
let imp = chart._implementation;
let margin = chart.margin;
let canvas = imp.canvas;
let imp = chart._implementation;
let margin = chart.margin;
let canvas = imp.canvas;
if ( chart.isActive ) {
canvas.renderTo( me.canvas.canvasEle, cb.left - margin, cb.top - margin );
canvas.canvasEle.style.zIndex = '-1';
canvas.canvasEle.style.visibility = 'hidden';
imp.isDirty = false;
if ( chart.debugLogger.isEnabled ) {
let progress = imp.getDrawProgress();
chart.debugLogger._log( 'chart copied with ' + progress.drawnCount + ' out of ' + progress.totalCount + ' datapoints' );
}
}
canvas.canvasEle.style.zIndex = '-1';
canvas.canvasEle.style.visibility = 'hidden';
} );
}
......
import { BehaviorSubject } from 'rxjs';
import { DataPoint } from '../../classes/datapoint.class';
import { DebugLogger } from '../../classes/debugLogger.class';
import { BaseChartImplementation } from '../../implementations/charts/baseChartImplementation.class';
......@@ -89,12 +87,13 @@ export abstract class BaseChart<T extends DataPoint = DataPoint> {
* @memberof BaseChart
*/
public set isActive( val: boolean ) {
if ( val !== this._isActive.value ) {
let subject = this._implementation.isActive;
if ( val !== subject.value ) {
this.debugLogger._log( 'chart isActive change', val );
this._isActive.next( val );
subject.next( val );
}
}
public get isActive() { return this._isActive.value; }
public get isActive() { return this._implementation.isActive.value; }
/**
......@@ -158,7 +157,6 @@ export abstract class BaseChart<T extends DataPoint = DataPoint> {
public drawCountPerRender: number = 100;
private _zIndex: number | undefined;
private _isActive: BehaviorSubject<boolean> = new BehaviorSubject<boolean>( true );
private _background: string | undefined;
}
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
import { DataPoint } from '../../classes/datapoint.class';
import { GistCanvas } from '../../classes/gistCanvas.class';
......@@ -93,7 +92,9 @@ export abstract class BaseChartImplementation {
* @memberof BaseChartImplementation
*/
private _isRenderInProgress: BehaviorSubject<boolean> = new BehaviorSubject<boolean>( false );
public isRenderInProgress: Observable<boolean> = this._isRenderInProgress.asObservable().pipe( distinctUntilChanged() );
public isRenderInProgress: Observable<boolean> = this._isRenderInProgress.asObservable();
public isActive: BehaviorSubject<boolean> = new BehaviorSubject<boolean>( true );
/**
* This is used to let the chartCanvas it needs to recopy charts without this one.
......@@ -196,23 +197,25 @@ export abstract class BaseChartImplementation {
*/
public startDrawing(): void {
const me = this;
if ( !me.animationFrame ) {
me.animationFrame = window.requestAnimationFrame( () => {
if ( this.chart.datalist().length !== this.drawnData.length ) {
this._isDirty = true;
this._isRenderInProgress.next( true );
me.chart.debugLogger._log( 'rendering new points, isDirty will be true without clearing' );
me.render();
}
else {
this._isRenderInProgress.next( false );
}
me.animationFrame = 0;
me.startDrawing();
} );
} else {
me.chart.debugLogger._log( 'no longer rendering. datalist length: ' + this.chart.datalist() + ', drawn data length: ' + this.drawnData.length );
}
if ( me.animationFrame ) return;
me.animationFrame = window.requestAnimationFrame( () => {
let isRenderInProgress = this.chart.isActive && this.drawnData.length !== this.chart.datalist().length;
if ( this._isRenderInProgress.value !== isRenderInProgress ) {
if ( isRenderInProgress ) { me.chart.debugLogger._log( 'new points detected: ' + this.drawnData.length + ' drawn out of ' + this.chart.datalist().length ); }
else { me.chart.debugLogger._log( 'rendering is done' ); }
this._isRenderInProgress.next( isRenderInProgress );
}
if ( isRenderInProgress ) {
this._isDirty = true;
me.render();
}
me.animationFrame = 0;
me.startDrawing();
} );
}
/**
......@@ -223,8 +226,10 @@ export abstract class BaseChartImplementation {
if ( me.animationFrame ) {
me.chart.debugLogger._log( 'polling datalist stopped' );
window.cancelAnimationFrame( me.animationFrame );
me.animationFrame = 0;
}
me._isRenderInProgress.next( false );
me.animationFrame = 0;
}
protected onDataWipe(): void {
......
import { Observable } from 'rxjs';
import { GistBehavior } from '../behaviors/gistBehavior.class';
import { BaseAxis } from '../configs/axis/baseAxis.class';
import { BaseChart } from '../configs/charts/baseChart.class';
......@@ -62,7 +63,9 @@ export declare class ChartCanvas {
private renderedRatio;
private renderedSizes;
private animationFrame;
private areChartsRendering;
areChartsRendering: Observable<boolean> | undefined;
private activeStateChange;
private stateSubscription;
constructor(container: HTMLElement);
/**
* Add one or many charts to be rendered
......@@ -147,7 +150,7 @@ export declare class ChartCanvas {
* @private
*/
private renderCharts();
private pollCharts();
private pollPixelRatio();
private showCharts();
private drawGridLines();
private copyCharts();
......
......@@ -112,6 +112,5 @@ export declare abstract class BaseChart<T extends DataPoint = DataPoint> {
*/
drawCountPerRender: number;
private _zIndex;
private _isActive;
private _background;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
import { Observable } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { DataPoint } from '../../classes/datapoint.class';
import { GistCanvas } from '../../classes/gistCanvas.class';
import { BaseAxis } from '../../configs/axis/baseAxis.class';
......@@ -79,6 +79,7 @@ export declare abstract class BaseChartImplementation {
*/
private _isRenderInProgress;
isRenderInProgress: Observable<boolean>;
isActive: BehaviorSubject<boolean>;
/**
* This is used to let the chartCanvas it needs to recopy charts without this one.
* Will be set to true any time chart.isActive is toggled, will be set to false, when the chartCanvas copies it.
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment