Skip to content

Commit

Permalink
Generalize ChartCanvasNode from CanvasLinePlot, see #16
Browse files Browse the repository at this point in the history
  • Loading branch information
samreid committed Dec 11, 2020
1 parent 04bae6b commit 7543a51
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 38 deletions.
25 changes: 25 additions & 0 deletions js/CanvasPainter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2020, University of Colorado Boulder

import bamboo from './bamboo.js';

/**
* Performs an operation on a canvas context. Typically this would render something, but some implementations
* may just change the context state (such as transform or stroke).
*
* @author Sam Reid (PhET Interactive Simulations)
*/

class CanvasPainter {

// Modeled as a class for readability because JavaScript does not have interfaces
constructor() {
}

// @public @abstract - override to paint or change the canvas context state
paintCanvas( context ) {
assert && assert( false, 'should be overridden in subclasses' );
}
}

bamboo.register( 'CanvasPainter', CanvasPainter );
export default CanvasPainter;
73 changes: 73 additions & 0 deletions js/ChartCanvasLinePlot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2020, University of Colorado Boulder

/**
* Renders a dataset of Vector2[] on a canvas. Typically it is preferable to use LinePlot, but this alternative
* is provided for cases where canvas must be used for performance.
*
* @author Sam Reid (PhET Interactive Simulations)
*/

import merge from '../../phet-core/js/merge.js';
import bamboo from './bamboo.js';
import CanvasPainter from './CanvasPainter.js';

class ChartCanvasLinePlot extends CanvasPainter {

/**
* @param {ChartModel} chartModel
* @param {Vector2[]} dataSet
* @param {Object} [options]
*/
constructor( chartModel, dataSet, options ) {

options = merge( {
stroke: 'black',
lineWidth: 1
}, options );

super();

// @private
this.chartModel = chartModel;

// @public if you change this directly, you are responsible for calling update on the corresponding ChartCanvasNode
this.dataSet = dataSet;
this.stroke = options.stroke;
this.lineWidth = options.lineWidth;
}

/**
* Sets dataSet. You are responsible for calling update on the ChartCanvasNode
* @param {Vector2[]} dataSet
* @public
*/
setDataSet( dataSet ) {
this.dataSet = dataSet;
}

// @public
paintCanvas( context ) {
context.beginPath();
context.strokeStyle = this.stroke;
context.lineWidth = this.lineWidth;

for ( let i = 0; i < this.dataSet.length; i++ ) {
const point = this.chartModel.modelToViewPosition( this.dataSet[ i ] );
i === 0 && context.moveTo( point.x, point.y );
i !== 0 && context.lineTo( point.x, point.y );
}
context.stroke();
}

/**
* @public
* @override
*/
dispose() {
this.disposeLinePlot();
super.dispose();
}
}

bamboo.register( 'ChartCanvasLinePlot', ChartCanvasLinePlot );
export default ChartCanvasLinePlot;
41 changes: 15 additions & 26 deletions js/CanvasLinePlot.js → js/ChartCanvasNode.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright 2019-2020, University of Colorado Boulder

/**
* CanvasLinePlot renders line plots of one or more data sets using Canvas, for performance.
* ChartCanvasNode renders to a canvas. It is usually preferable to use the other scenery Node-based
* renderers, but this one can be necessary for performance-critical charts.
*
* @author Sam Reid (PhET Interactive Simulations)
*/
Expand All @@ -10,18 +11,18 @@ import Bounds2 from '../../dot/js/Bounds2.js';
import CanvasNode from '../../scenery/js/nodes/CanvasNode.js';
import bamboo from './bamboo.js';

class CanvasLinePlot extends CanvasNode {
class ChartCanvasNode extends CanvasNode {

/**
* @param {ChartModel} chartModel
* @param {Vector2[][]} dataSets
* @param {CanvasPainter[]} painters
* @param {Object} [options]
*/
constructor( chartModel, dataSets, options ) {
constructor( chartModel, painters, options ) {

options = options || {};

assert && assert( !options.canvasBounds, 'CanvasLinePlot sets canvasBounds' );
assert && assert( !options.canvasBounds, 'ChartCanvasNode sets canvasBounds' );
options.canvasBounds = new Bounds2( 0, 0, chartModel.width, chartModel.height );

super( options );
Expand All @@ -30,24 +31,24 @@ class CanvasLinePlot extends CanvasNode {
this.chartModel = chartModel;

// @public if you change this directly, you are responsible for calling update
this.dataSets = dataSets;
this.painters = painters;

const update = () => this.update();
chartModel.link( update );

// @private
this.disposeCanvasLinePlot = () => {
this.disposeChartCanvasLinePlot = () => {
chartModel.link( update );
};
}

/**
* Sets the data sets and redraws the chart.
* @param {Vector2[][]}dataSets
* @param {CanvasPainter} painters
* @public
*/
setDataSets( dataSets ) {
this.dataSets = dataSets;
setPainters( painters ) {
this.painters = painters;
this.update();
}

Expand All @@ -64,30 +65,18 @@ class CanvasLinePlot extends CanvasNode {
* @param {CanvasRenderingContext2D} context
*/
paintCanvas( context ) {
context.beginPath();
context.strokeStyle = 'black';
context.lineWidth = 0.1;

this.dataSets.forEach( dataSet => {
for ( let i = 0; i < dataSet.length; i++ ) {
const point = this.chartModel.modelToViewPosition( dataSet[ i ] );
i === 0 && context.moveTo( point.x, point.y );
i !== 0 && context.lineTo( point.x, point.y );
}
} );

context.stroke();
this.painters.forEach( painter => painter.paintCanvas( context ) );
}

/**
* @public
* @override
*/
dispose() {
this.disposeCanvasLinePlot();
this.disposeChartCanvasLinePlot();
super.dispose();
}
}

bamboo.register( 'CanvasLinePlot', CanvasLinePlot );
export default CanvasLinePlot;
bamboo.register( 'ChartCanvasNode', ChartCanvasNode );
export default ChartCanvasNode;
4 changes: 2 additions & 2 deletions js/demo/BambooDemoScreenView.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import sceneryPhetQueryParameters from '../../../scenery-phet/js/sceneryPhetQuer
import DemosScreenView from '../../../sun/js/demo/DemosScreenView.js';
import bamboo from '../bamboo.js';
import DemoBarPlot from './DemoBarPlot.js';
import DemoCanvasLinePlot from './DemoCanvasLinePlot.js';
import DemoChartCanvasNode from './DemoChartCanvasNode.js';
import DemoLinePlot from './DemoLinePlot.js';
import DemoMultiplePlots from './DemoMultiplePlots.js';
import DemoScatterPlot from './DemoScatterPlot.js';
Expand Down Expand Up @@ -43,7 +43,7 @@ class BambooDemoScreenView extends DemosScreenView {
} )
},
{
label: 'CanvasLinePlot', createNode: layoutBounds => new DemoCanvasLinePlot( {
label: 'ChartCanvasLinePlot', createNode: layoutBounds => new DemoChartCanvasNode( {
center: layoutBounds.center
} )
},
Expand Down
25 changes: 15 additions & 10 deletions js/demo/DemoCanvasLinePlot.js → js/demo/DemoChartCanvasNode.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2020, University of Colorado Boulder

/**
* Demonstrates a CanvasLinePlot.
* Demonstrates a ChartCanvasNode.
*
* @author Sam Reid (PhET Interactive Simulations)
*/
Expand All @@ -14,15 +14,16 @@ import ZoomButtonGroup from '../../../scenery-phet/js/ZoomButtonGroup.js';
import Node from '../../../scenery/js/nodes/Node.js';
import Text from '../../../scenery/js/nodes/Text.js';
import AxisNode from '../AxisNode.js';
import CanvasLinePlot from '../CanvasLinePlot.js';
import ChartCanvasNode from '../ChartCanvasNode.js';
import ChartCanvasLinePlot from '../ChartCanvasLinePlot.js';
import ChartModel from '../ChartModel.js';
import ChartRectangle from '../ChartRectangle.js';
import GridLineSet from '../GridLineSet.js';
import LabelSet from '../LabelSet.js';
import TickMarkSet from '../TickMarkSet.js';
import bamboo from '../bamboo.js';

class DemoCanvasLinePlot extends Node {
class DemoChartCanvasNode extends Node {

constructor( options ) {

Expand Down Expand Up @@ -62,12 +63,16 @@ class DemoCanvasLinePlot extends Node {
zoomLevel === 4 ? new Range( -Math.PI / 2, Math.PI / 2 ) : null );
} );

const dataSets = [];
const painters = [];

for ( let i = 0; i < 500; i++ ) {
// plots.push( new LinePlot( chartModel, createDataSet( -2, 2, 5 + i / 10 + phet.joist.random.nextDouble() / 10, phet.joist.random.nextDouble() * 2 ), { stroke: 'red', lineWidth: 2 } ) )
const colors = [ 'red', 'blue', 'green', 'violet', 'pink', 'yellow' ];

for ( let i = 0; i < 20; i++ ) {
const d = createDataSet( -2, 2, 5 + i / 10 + phet.joist.random.nextDouble() / 10, phet.joist.random.nextDouble() * 2 );
dataSets.push( d );
painters.push( new ChartCanvasLinePlot( chartModel, d, {
stroke: colors[ i % colors.length ],
lineWidth: i % 4 + 1
} ) );
}

// Anything you want clipped goes in here
Expand All @@ -90,7 +95,7 @@ class DemoCanvasLinePlot extends Node {
new AxisNode( chartModel, Orientation.VERTICAL ),

// Some data
new CanvasLinePlot( chartModel, dataSets )
new ChartCanvasNode( chartModel, painters )
]
} ),

Expand All @@ -112,5 +117,5 @@ class DemoCanvasLinePlot extends Node {
}
}

bamboo.register( 'DemoCanvasLinePlot', DemoCanvasLinePlot );
export default DemoCanvasLinePlot;
bamboo.register( 'DemoChartCanvasNode', DemoChartCanvasNode );
export default DemoChartCanvasNode;

0 comments on commit 7543a51

Please sign in to comment.