diff --git a/coffee/datasource.coffee b/coffee/datasource.coffee
index f146ccd..8b7db26 100644
--- a/coffee/datasource.coffee
+++ b/coffee/datasource.coffee
@@ -55,7 +55,7 @@ class @DataSource
viewModelIndex = (row * DATA_POINTS) + col
# Create the view model for this grid point
- @viewModels[viewModelIndex] ||= {}
+ @viewModels[viewModelIndex] = {}
# Calculate the brightness of a grid point according to how far away it is from the data point
@viewModels[viewModelIndex].brightness = Math.min(1, Math.max(0, (DISPLAY_SPREAD - Math.abs(dataPoint - ROW_AMPLITUDES[row])) / DISPLAY_SPREAD))
@@ -91,9 +91,7 @@ class @DataSource
doWork: =>
# Produce the data
- Perf.time 'producing data' if PROFILE
data = produceData.call(this)
- Perf.timeEnd 'producing data' if PROFILE
# Notify anyone interested in the data
listener(data) for listener in @dataListeners
diff --git a/coffee/native.coffee b/coffee/native.coffee
index 07974af..8a6d52c 100644
--- a/coffee/native.coffee
+++ b/coffee/native.coffee
@@ -10,8 +10,6 @@ dataSource = new (getDataSource())
# Attach a handler to that data source, to be called whenever new data is available
dataSource.onData (data) ->
- Perf.time 'reconstructing DOM'
-
# Remove the visualizer from the DOM to prevent reflows
visualizerElementParent.removeChild(visualizerElement)
@@ -31,19 +29,5 @@ dataSource.onData (data) ->
# Reattach the visualizer element
visualizerElementParent.appendChild visualizerElement
- Perf.timeEnd 'reconstructing DOM'
-
# Make the data source work as fast as possible
-workIt = ->
- # Work!
- dataSource.doWork()
-
- # Force a synchronous reflow, then schedule another work package.
- # NOTE: This is preferable to using requestAnimationFrame, because RAF fires at funny times.
- Perf.time 'redrawing' if PROFILE
- forceReflow()
- Perf.timeEnd 'redrawing' if PROFILE
-
- # Schedule that next work package
- setZeroTimeout workIt
-workIt()
+setInterval dataSource.doWork.bind(dataSource), 0
diff --git a/coffee/perf.coffee b/coffee/perf.coffee
deleted file mode 100644
index e02b870..0000000
--- a/coffee/perf.coffee
+++ /dev/null
@@ -1,71 +0,0 @@
-# Enable performance profiling?
-window.PROFILE = yes
-
-window.Perf = class
- startTimes = {}
- results = {}
-
- # Polyfill for timers
- ((w) ->
- perfNow = undefined
- perfNowNames = ["now", "webkitNow", "msNow", "mozNow"]
- unless not w["performance"]
- i = 0
-
- while i < perfNowNames.length
- n = perfNowNames[i]
- unless not w["performance"][n]
- perfNow = ->
- w["performance"][n]()
-
- break
- ++i
- perfNow = Date.now unless perfNow
- w.perfNow = perfNow
- ) window
-
- # Simple method to average an array of numbers
- arrayAverage = do ->
- sum = (a, b) -> a + b
- (array) -> array.reduce(sum) / array.length
-
- arrayMin = do ->
- compare = (a, b) -> if a < b then a else b
- (array) -> array.reduce(compare)
-
- arrayMax = do ->
- compare = (a, b) -> if a > b then a else b
- (array) -> array.reduce(compare)
-
- @time: (key) ->
- console.time key
- startTimes[key] = perfNow()
- @timeEnd: (key) ->
- throw 'timeEnd() called without preceding call to time()' unless startTime = startTimes[key]
- (results[key] ||= []).push perfNow() - startTime
- console.timeEnd key
-
- @measure: (key, callback) ->
- Perf.time key
- out = callback()
- Perf.timeEnd key
- out
-
- @iqrMean: (key) ->
- # What are the results for this key?
- resultsForKey = results[key]
- return resultsForKey[0] if resultsForKey.length is 1
- # What values are at the boundary of the interquartile range?
- lowerBound = ss.quantile(resultsForKey, 0.25)
- upperBound = ss.quantile(resultsForKey, 0.75)
- # Throw out values outside the interquartile range
- middleFifty = []
- for result in resultsForKey
- middleFifty.push(result) if lowerBound < result and upperBound > result
- # Average the interquartile range
- arrayAverage middleFifty
- @min: (key) -> arrayMin results[key]
- @max: (key) -> arrayMax results[key]
-
- @results: ->
- ("#{key}: max: #{Perf.max(key).toFixed(2)}ms min: #{Perf.min(key).toFixed(2)}ms interquartile mean: #{Perf.iqrMean(key).toFixed(2)}ms" for key of results).join("\n")
diff --git a/coffee/react.coffee b/coffee/react.coffee
index cc10c9a..23d5b09 100644
--- a/coffee/react.coffee
+++ b/coffee/react.coffee
@@ -1,25 +1,20 @@
-# Given a data point, produce an object where the keys represent DOM element attributes
-dataPointToAttributes = (dataPoint) ->
- style:
- backgroundColor: "rgba(0,255,0,#{dataPoint.brightness})"
+DataPoint = React.createClass
+ shouldComponentUpdate: (prevProps) -> prevProps.dataPoint.brightness != @props.dataPoint.brightness
+ render: ->
+ React.DOM.div (style: (backgroundColor: "rgba(0,255,0,#{@props.dataPoint.brightness})"))
# Create a React component for the visualizer
VisualizerComponent = React.createClass
getInitialState: -> { data: [] }
componentDidMount: ->
- # Instantiate a data source
- @dataSource = @props.dataSource or new BoringDataSource
-
# Attach a handler to that data source, to be called whenever new data is available
- @dataSource.onData (data) =>
- Perf.time 'setting state' if PROFILE
- @replaceState { data: data }
- Perf.timeEnd 'setting state' if PROFILE
+ @props.dataSource.onData (data) =>
+ @setState { data: data }
render: ->
# Return data point elements, wrapped in a visualizer
- React.DOM.div @props, (React.DOM.div dataPointToAttributes(dataPoint) for dataPoint in @state.data)
+ React.DOM.div @props, ((DataPoint (dataPoint: dataPoint)) for dataPoint in @state.data)
# Create a visualizer
visualizer = new VisualizerComponent { id: 'visualizer', dataSource: (dataSource = new (getDataSource())) }
@@ -32,17 +27,5 @@ app = React.DOM.body null, [visualizer, markerButton]
# Render the app
React.renderComponent app, document.body, ->
- # Make the data source work as fast as possible
- workIt = ->
- # Work!
- dataSource.doWork()
-
- # Force a synchronous reflow, then schedule another work package.
- # NOTE: This is preferable to using requestAnimationFrame, because RAF fires at funny times.
- Perf.time 'redrawing' if PROFILE
- forceReflow()
- Perf.timeEnd 'redrawing' if PROFILE
-
- # Schedule that next work package
- setZeroTimeout workIt
- workIt()
+ setInterval dataSource.doWork.bind(dataSource), 0
+
diff --git a/js/fpscounter.js b/js/fpscounter.js
new file mode 100644
index 0000000..7dfad77
--- /dev/null
+++ b/js/fpscounter.js
@@ -0,0 +1 @@
+(function(){var script=document.createElement('script');script.src='http://github.com/mrdoob/stats.js/raw/master/build/stats.min.js';document.body.appendChild(script);script=document.createElement('script');script.innerHTML='var interval=setInterval(function(){if(typeof Stats==\'function\'){clearInterval(interval);var stats=new Stats();stats.domElement.style.position=\'fixed\';stats.domElement.style.left=\'0px\';stats.domElement.style.top=\'0px\';stats.domElement.style.zIndex=\'10000\';document.body.appendChild(stats.domElement);setInterval(function(){stats.update();},1000/60);}},100);';document.body.appendChild(script);})();
\ No newline at end of file
diff --git a/native.html b/native.html
index 8ad331f..12cdbd9 100644
--- a/native.html
+++ b/native.html
@@ -20,6 +20,7 @@
+