diff --git a/js/CanvasPainter.js b/js/CanvasPainter.js new file mode 100644 index 0000000..e3790a1 --- /dev/null +++ b/js/CanvasPainter.js @@ -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; \ No newline at end of file diff --git a/js/ChartCanvasLinePlot.js b/js/ChartCanvasLinePlot.js new file mode 100644 index 0000000..a5959fc --- /dev/null +++ b/js/ChartCanvasLinePlot.js @@ -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; \ No newline at end of file diff --git a/js/CanvasLinePlot.js b/js/ChartCanvasNode.js similarity index 55% rename from js/CanvasLinePlot.js rename to js/ChartCanvasNode.js index 16c1a43..f9d87d5 100644 --- a/js/CanvasLinePlot.js +++ b/js/ChartCanvasNode.js @@ -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) */ @@ -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 ); @@ -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(); } @@ -64,19 +65,7 @@ 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 ) ); } /** @@ -84,10 +73,10 @@ class CanvasLinePlot extends CanvasNode { * @override */ dispose() { - this.disposeCanvasLinePlot(); + this.disposeChartCanvasLinePlot(); super.dispose(); } } -bamboo.register( 'CanvasLinePlot', CanvasLinePlot ); -export default CanvasLinePlot; \ No newline at end of file +bamboo.register( 'ChartCanvasNode', ChartCanvasNode ); +export default ChartCanvasNode; \ No newline at end of file diff --git a/js/demo/BambooDemoScreenView.js b/js/demo/BambooDemoScreenView.js index c09b36c..cfa0924 100644 --- a/js/demo/BambooDemoScreenView.js +++ b/js/demo/BambooDemoScreenView.js @@ -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'; @@ -43,7 +43,7 @@ class BambooDemoScreenView extends DemosScreenView { } ) }, { - label: 'CanvasLinePlot', createNode: layoutBounds => new DemoCanvasLinePlot( { + label: 'ChartCanvasLinePlot', createNode: layoutBounds => new DemoChartCanvasNode( { center: layoutBounds.center } ) }, diff --git a/js/demo/DemoCanvasLinePlot.js b/js/demo/DemoChartCanvasNode.js similarity index 85% rename from js/demo/DemoCanvasLinePlot.js rename to js/demo/DemoChartCanvasNode.js index 95c22fe..5ec0c33 100644 --- a/js/demo/DemoCanvasLinePlot.js +++ b/js/demo/DemoChartCanvasNode.js @@ -1,7 +1,7 @@ // Copyright 2020, University of Colorado Boulder /** - * Demonstrates a CanvasLinePlot. + * Demonstrates a ChartCanvasNode. * * @author Sam Reid (PhET Interactive Simulations) */ @@ -14,7 +14,8 @@ 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'; @@ -22,7 +23,7 @@ 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 ) { @@ -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 @@ -90,7 +95,7 @@ class DemoCanvasLinePlot extends Node { new AxisNode( chartModel, Orientation.VERTICAL ), // Some data - new CanvasLinePlot( chartModel, dataSets ) + new ChartCanvasNode( chartModel, painters ) ] } ), @@ -112,5 +117,5 @@ class DemoCanvasLinePlot extends Node { } } -bamboo.register( 'DemoCanvasLinePlot', DemoCanvasLinePlot ); -export default DemoCanvasLinePlot; \ No newline at end of file +bamboo.register( 'DemoChartCanvasNode', DemoChartCanvasNode ); +export default DemoChartCanvasNode; \ No newline at end of file