summaryrefslogtreecommitdiffstats
path: root/debian/missing-sources/epoch/src/time
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--debian/missing-sources/epoch/src/time.coffee615
-rw-r--r--debian/missing-sources/epoch/src/time/area.coffee80
-rw-r--r--debian/missing-sources/epoch/src/time/bar.coffee48
-rw-r--r--debian/missing-sources/epoch/src/time/gauge.coffee209
-rw-r--r--debian/missing-sources/epoch/src/time/heatmap.coffee261
-rw-r--r--debian/missing-sources/epoch/src/time/line.coffee39
6 files changed, 1252 insertions, 0 deletions
diff --git a/debian/missing-sources/epoch/src/time.coffee b/debian/missing-sources/epoch/src/time.coffee
new file mode 100644
index 0000000..402cc50
--- /dev/null
+++ b/debian/missing-sources/epoch/src/time.coffee
@@ -0,0 +1,615 @@
+# Real-time Plot Base Class. Uses an html5 canvas to recreate the basic d3 drawing routines
+# while simultaneously reducing the load on the viewer's cpu (and, you know, not leaking
+# memory which ultimately leads to a crashed browser).
+#
+# The class also handles the creation of axes and margins common to all time-series plots.
+# Furthermore it layers the canvas below an SVG element to keep visual consistency when
+# rendering text, glyphs, etc.
+class Epoch.Time.Plot extends Epoch.Chart.Canvas
+ defaults =
+ range: null
+ fps: 24
+ historySize: 120
+ windowSize: 40
+ queueSize: 10
+ axes: ['bottom']
+ ticks:
+ time: 15
+ left: 5
+ right: 5
+ tickFormats:
+ top: Epoch.Formats.seconds
+ bottom: Epoch.Formats.seconds
+ left: Epoch.Formats.si
+ right: Epoch.Formats.si
+
+ defaultAxisMargins =
+ top: 25
+ right: 50
+ bottom: 25
+ left: 50
+
+ optionListeners =
+ 'option:margins': 'marginsChanged'
+ 'option:margins.top': 'marginsChanged'
+ 'option:margins.right': 'marginsChanged'
+ 'option:margins.bottom': 'marginsChanged'
+ 'option:margins.left': 'marginsChanged'
+ 'option:axes': 'axesChanged'
+ 'option:ticks': 'ticksChanged'
+ 'option:ticks.top': 'ticksChanged'
+ 'option:ticks.right': 'ticksChanged'
+ 'option:ticks.bottom': 'ticksChanged'
+ 'option:ticks.left': 'ticksChanged'
+ 'option:tickFormats': 'tickFormatsChanged'
+ 'option:tickFormats.top': 'tickFormatsChanged'
+ 'option:tickFormats.right': 'tickFormatsChanged'
+ 'option:tickFormats.bottom': 'tickFormatsChanged'
+ 'option:tickFormats.left': 'tickFormatsChanged'
+
+ # Creates a new real-time plot.
+ #
+ # @param [Object] options Options for the plot.
+ # @option options [Integer] fps Number of frames per second to use when animating
+ # the plot.
+ # @option options [Integer] historySize Maximum number of elements to keep in history
+ # for the plot.
+ # @option options [Integer] windowSize Number of entries to simultaneously display
+ # when rendering the visualization.
+ # @option options [Integer] queueSize Number of elements to queue while not animating
+ # but still recieving elements. In some browsers, intervals will not fire if the
+ # page containing them is not the active tab. By setting a maximum limit to the
+ # number of unprocessed data points we can ensure that the memory footprint of the
+ # page does not get out of hand.
+ # @option options [Object] margins Explicit margins to use for the visualization. Note
+ # that these are optional and will be automatically generated based on which axes are
+ # used for the visualization. Margins are keyed by their position (top, left, bottom
+ # and/or right) and should map to [Integer] values.
+ # @option options [Array] axes Which axes to display when rendering the visualization
+ # (top, left, bottom, and/or right).
+ # @option options [Object] ticks Number of ticks to display on each axis available axes
+ # ares: time, left, and right. The number provided for the left and right axes are in
+ # absolute terms (i.e. there will be exactly that number of ticks). The time ticks
+ # denote how often a tick should be generated (e.g. if 5 is provided then a tick will
+ # be added every fifth time you push a new data entry into the visualization).
+ # @option options [Object] tickFormats Formatting functions for ticks on the given axes.
+ # The avaiable axes are: top, bottom, left, and right.
+ constructor: (@options) ->
+ givenMargins = Epoch.Util.copy(@options.margins) or {}
+ super(@options = Epoch.Util.defaults(@options, defaults))
+
+ if @options.model
+ @options.model.on 'data:push', => @pushFromModel()
+
+ # Queue entering data to get around memory bloat and "non-active" tab issues
+ @_queue = []
+
+ # Margins
+ @margins = {}
+ for pos in ['top', 'right', 'bottom', 'left']
+ @margins[pos] = if @options.margins? and @options.margins[pos]?
+ @options.margins[pos]
+ else if @hasAxis(pos)
+ defaultAxisMargins[pos]
+ else
+ 6
+
+ # SVG Overlay
+ @svg = @el.insert('svg', ':first-child')
+ .attr('width', @width)
+ .attr('height', @height)
+ .style('z-index', '1000')
+
+ # Position the canvas "under" the SVG element
+ if @el.style('position') != 'absolute' and @el.style('position') != 'relative'
+ @el.style('position', 'relative')
+
+ @canvas.style { position: 'absolute', 'z-index': '999' }
+ @_sizeCanvas()
+
+ # Animation / Transitions
+ @animation =
+ interval: null
+ active: false
+ delta: => -(@w() / @options.fps),
+ tickDelta: => -( (@w() / @pixelRatio) / @options.fps )
+ frame: 0,
+ duration: @options.fps
+
+ # Add SVG Axes
+ @_buildAxes()
+
+ # Callback used for animation
+ @animationCallback = => @_animate()
+
+ # Listen for specific option changes
+ @onAll optionListeners
+
+ # Positions and sizes the canvas based on margins and axes.
+ _sizeCanvas: ->
+ @canvas.attr
+ width: @innerWidth()
+ height: @innerHeight()
+
+ @canvas.style
+ width: "#{@innerWidth() / @pixelRatio}px"
+ height: "#{@innerHeight() / @pixelRatio}px"
+ top: "#{@margins.top}px"
+ left: "#{@margins.left}px"
+
+ # Removes any axes found in the SVG and adds both the time and range axes to the plot.
+ _buildAxes: ->
+ @svg.selectAll('.axis').remove()
+ @_prepareTimeAxes()
+ @_prepareRangeAxes()
+
+ # Works exactly as in Epoch.Chart.Base with the addition of truncating value arrays
+ # to that of the historySize defined in the chart's options.
+ _annotateLayers: (prepared) ->
+ data = []
+ for own i, layer of prepared
+ copy = Epoch.Util.copy(layer)
+ start = Math.max(0, layer.values.length - @options.historySize)
+ copy.values = layer.values.slice(start)
+ classes = ['layer']
+ classes.push "category#{(i|0)+1}"
+ classes.push(Epoch.Util.dasherize layer.label) if layer.label?
+ copy.className = classes.join(' ')
+ copy.visible = true
+ data.push copy
+ return data
+
+ # This method is called to provide a small offset for placement of horizontal ticks.
+ # The value returned will be added to the x value of each tick as they are being
+ # rendered.
+ #
+ # @return [Number] The horizontal offset for the top and bottom axes ticks.
+ _offsetX: -> 0
+
+ # Builds time axes (bottom and top)
+ _prepareTimeAxes: ->
+ if @hasAxis('bottom')
+ axis = @bottomAxis = @svg.append('g')
+ .attr('class', "x axis bottom canvas")
+ .attr('transform', "translate(#{@margins.left-1}, #{@innerHeight()/@pixelRatio+@margins.top})")
+ axis.append('path')
+ .attr('class', 'domain')
+ .attr('d', "M0,0H#{@innerWidth()/@pixelRatio+1}")
+
+ if @hasAxis('top')
+ axis = @topAxis = @svg.append('g')
+ .attr('class', "x axis top canvas")
+ .attr('transform', "translate(#{@margins.left-1}, #{@margins.top})")
+ axis.append('path')
+ .attr('class', 'domain')
+ .attr('d', "M0,0H#{@innerWidth()/@pixelRatio+1}")
+
+ @_resetInitialTimeTicks()
+
+ # Resets the initial ticks for the time axes.
+ _resetInitialTimeTicks: ->
+ tickInterval = @options.ticks.time
+ @_ticks = []
+ @_tickTimer = tickInterval
+
+ @bottomAxis.selectAll('.tick').remove() if @bottomAxis?
+ @topAxis.selectAll('.tick').remove() if @topAxis?
+
+ for layer in @data
+ continue unless Epoch.isNonEmptyArray(layer.values)
+ [i, k] = [@options.windowSize-1, layer.values.length-1]
+ while i >= 0 and k >= 0
+ @_pushTick i, layer.values[k].time, false, true
+ i -= tickInterval
+ k -= tickInterval
+ break
+
+ # Builds the range axes (left and right)
+ _prepareRangeAxes: ->
+ if @hasAxis('left')
+ @svg.append("g")
+ .attr("class", "y axis left")
+ .attr('transform', "translate(#{@margins.left-1}, #{@margins.top})")
+ .call(@leftAxis())
+
+ if @hasAxis('right')
+ @svg.append('g')
+ .attr('class', 'y axis right')
+ .attr('transform', "translate(#{@width - @margins.right}, #{@margins.top})")
+ .call(@rightAxis())
+
+ # @return [Object] The d3 left axis.
+ leftAxis: ->
+ ticks = @options.ticks.left
+ axis = d3.svg.axis().scale(@ySvgLeft()).orient('left')
+ .tickFormat(@options.tickFormats.left)
+ if ticks == 2
+ axis.tickValues @extent((d) -> d.y)
+ else
+ axis.ticks(ticks)
+
+ # @return [Object] The d3 right axis.
+ rightAxis: ->
+ extent = @extent((d) -> d.y)
+ ticks = @options.ticks.right
+ axis = d3.svg.axis().scale(@ySvgRight()).orient('right')
+ .tickFormat(@options.tickFormats.right)
+ if ticks == 2
+ axis.tickValues @extent((d) -> d.y)
+ else
+ axis.ticks(ticks)
+
+ # Determines if the visualization is displaying the axis with the given name.
+ # @param [String] name Name of the axis
+ # @return [Boolean] <code>true</code> if the axis was set in the options, <code>false</code> otherwise.
+ hasAxis: (name) ->
+ @options.axes.indexOf(name) > -1
+
+ # @return [Number] the width of the visualization area of the plot (full width - margins)
+ innerWidth: ->
+ (@width - (@margins.left + @margins.right)) * @pixelRatio
+
+ # @return [Number] the height of the visualization area of the plot (full height - margins)
+ innerHeight: ->
+ (@height - (@margins.top + @margins.bottom)) * @pixelRatio
+
+ # Abstract method for performing any preprocessing before queuing new entries
+ # @param entry [Object] The entry to prepare.
+ # @return [Object] The prepared entry.
+ _prepareEntry: (entry) -> entry
+
+ # Abstract method for preparing a group of layered entries entering the visualization
+ # @param [Array] layers The layered entries to prepare.
+ # @return [Array] The prepared layers.
+ _prepareLayers: (layers) -> layers
+
+ # This method will remove the first incoming entry from the visualization's queue
+ # and shift it into the working set (aka window). It then starts the animating the
+ # transition of the element into the visualization.
+ # @event transition:start in the case that animation is actually started.
+ _startTransition: ->
+ return if @animation.active == true or @_queue.length == 0
+ @trigger 'transition:start'
+ @_shift()
+ @animation.active = true
+ @animation.interval = setInterval(@animationCallback, 1000/@options.fps)
+
+ # Stops animating and clears the animation interval given there is no more
+ # incoming data to process. Also finalizes tick entering and exiting.
+ # @event transition:end After the transition has completed.
+ _stopTransition: ->
+ return unless @inTransition()
+
+ # Shift data off the end
+ for layer in @data
+ continue unless layer.values.length > @options.windowSize + 1
+ layer.values.shift()
+
+ # Finalize tick transitions
+ [firstTick, lastTick] = [@_ticks[0], @_ticks[@_ticks.length-1]]
+
+ if lastTick? and lastTick.enter
+ lastTick.enter = false
+ lastTick.opacity = 1
+
+ if firstTick? and firstTick.exit
+ @_shiftTick()
+
+ # Reset the animation frame modulus
+ @animation.frame = 0
+
+ # Trigger that we are done transitioning
+ @trigger 'transition:end'
+
+ # Clear the transition interval unless another entry is already queued
+ if @_queue.length > 0
+ @_shift()
+ else
+ @animation.active = false
+ clearInterval @animation.interval
+
+ # Determines if the plot is currently animating a transition.
+ # @return [Boolean] <code>true</code> if the plot is animating, <code>false</code> otherwise.
+ inTransition: ->
+ @animation.active
+
+ # This method is used by the application programmer to introduce new data into
+ # the timeseries plot. The method queues the incoming data, ensures a fixed size
+ # for the data queue, and finally calls <code>_startTransition</code> method to
+ # begin animating the plot.
+ # @param [Array] layers Layered incoming visualization data.
+ # @event push Triggered after the new data has been pushed into the queue.
+ push: (layers) ->
+ layers = @_prepareLayers(layers)
+
+ # Handle entry queue maximum size
+ if @_queue.length > @options.queueSize
+ @_queue.splice @options.queueSize, (@_queue.length - @options.queueSize)
+ return false if @_queue.length == @options.queueSize
+
+ # Push the entry into the queue
+ @_queue.push layers.map((entry) => @_prepareEntry(entry))
+
+ @trigger 'push'
+
+ # Begin the transition unless we are already doing so
+ @_startTransition() unless @inTransition()
+
+ # Fetches new entry data from the model in response to a 'data:push' event.
+ pushFromModel: ->
+ @push @options.model.getNext(@options.type, @options.dataFormat)
+
+ # Shift elements off the incoming data queue (see the implementation of
+ # push above).
+ #
+ # If there's data to be shoved into the visualization it will pull it
+ # off the queue and put it into the working dataset. It also calls through
+ # to @_updateTicks to handle horizontal (or "time") axes tick transitions
+ # since we're implementing independent of d3 as well.
+ #
+ # @event before:shift Before an element has been shifted off the queue.
+ # @event after:shift After the element has been shifted off the queue.
+ _shift: ->
+ @trigger 'before:shift'
+ entry = @_queue.shift()
+ layer.values.push(entry[i]) for own i, layer of @data
+ @_updateTicks(entry[0].time)
+ @_transitionRangeAxes()
+ @trigger 'after:shift'
+
+ # Transitions the left and right axes when the range of the plot has changed.
+ _transitionRangeAxes: ->
+ if @hasAxis('left')
+ @svg.selectAll('.y.axis.left').transition()
+ .duration(500)
+ .ease('linear')
+ .call(@leftAxis())
+
+ if @hasAxis('right')
+ @svg.selectAll('.y.axis.right').transition()
+ .duration(500)
+ .ease('linear')
+ .call(@rightAxis())
+
+ # Performs the animation for transitioning elements in the visualization.
+ _animate: ->
+ return unless @inTransition()
+ @_stopTransition() if ++@animation.frame == @animation.duration
+ @draw(@animation.frame * @animation.delta())
+ @_updateTimeAxes()
+
+ # @param [Array] givenDomain A given domain for the scale
+ # @return [Function] The y scale for the plot
+ y: (givenDomain) ->
+ d3.scale.linear()
+ .domain(@_getScaleDomain(givenDomain))
+ .range([@innerHeight(), 0])
+
+ # @param [Array] givenDomain Optional domain to override default
+ # @return [Function] The y scale for the svg portions of the plot
+ ySvg: (givenDomain) ->
+ d3.scale.linear()
+ .domain(@_getScaleDomain(givenDomain))
+ .range([@innerHeight() / @pixelRatio, 0])
+
+ # @return [Function] The y scale for the svg portion of the plot for the left axis
+ ySvgLeft: ->
+ if @options.range?
+ @ySvg @options.range.left
+ else
+ @ySvg()
+
+ # @return [Function] The y scale for the svg portion of the plot for the right axis
+ ySvgRight: ->
+ if @options.range?
+ @ySvg @options.range.right
+ else
+ @ySvg()
+
+ # @return [Number] The width of a single section of the graph pertaining to a data point
+ w: ->
+ @innerWidth() / @options.windowSize
+
+ # This is called every time we introduce new data (as a result of _shift)
+ # it checks to see if we also need to update the working tick set and
+ # makes the approriate changes for handling tick animation (enter, exit,
+ # and update in the d3 model).
+ #
+ # @param [Integer] newTime Current newest timestamp in the data
+ _updateTicks: (newTime) ->
+ return unless @hasAxis('top') or @hasAxis('bottom')
+
+ # Incoming ticks
+ unless (++@_tickTimer) % @options.ticks.time
+ @_pushTick(@options.windowSize, newTime, true)
+
+ # Outgoing ticks
+ return unless @_ticks.length > 0
+ unless @_ticks[0].x - (@w()/@pixelRatio) >= 0
+ @_ticks[0].exit = true
+
+ # Makes and pushes a new tick into the visualization.
+ #
+ # @param bucket Index in the data window where the tick should initially be position
+ # @param time The unix timestamp associated with the tick
+ # @param enter Whether or not the tick should be considered as "newly entering"
+ # Used primarily for performing the tick opacity tween.
+ _pushTick: (bucket, time, enter=false, reverse=false) ->
+ return unless @hasAxis('top') or @hasAxis('bottom')
+ tick =
+ time: time
+ x: bucket*(@w()/@pixelRatio) + @_offsetX()
+ opacity: if enter then 0 else 1
+ enter: if enter then true else false
+ exit: false
+
+ if @hasAxis('bottom')
+ g = @bottomAxis.append('g')
+ .attr('class', 'tick major')
+ .attr('transform', "translate(#{tick.x+1},0)")
+ .style('opacity', tick.opacity)
+
+ g.append('line')
+ .attr('y2', 6)
+
+ g.append('text')
+ .attr('text-anchor', 'middle')
+ .attr('dy', 19)
+ .text(@options.tickFormats.bottom(tick.time))
+
+ tick.bottomEl = g
+
+ if @hasAxis('top')
+ g = @topAxis.append('g')
+ .attr('class', 'tick major')
+ .attr('transform', "translate(#{tick.x+1},0)")
+ .style('opacity', tick.opacity)
+
+ g.append('line')
+ .attr('y2', -6)
+
+ g.append('text')
+ .attr('text-anchor', 'middle')
+ .attr('dy', -10)
+ .text(@options.tickFormats.top(tick.time))
+
+ tick.topEl = g
+
+ if reverse
+ @_ticks.unshift tick
+ else
+ @_ticks.push tick
+ return tick
+
+ # Shifts a tick that is no longer needed out of the visualization.
+ _shiftTick: ->
+ return unless @_ticks.length > 0
+ tick = @_ticks.shift()
+ tick.topEl.remove() if tick.topEl?
+ tick.bottomEl.remove() if tick.bottomEl?
+
+ # This performs animations for the time axes (top and bottom).
+ _updateTimeAxes: ->
+ return unless @hasAxis('top') or @hasAxis('bottom')
+ [dx, dop] = [@animation.tickDelta(), 1 / @options.fps]
+
+ for tick in @_ticks
+ tick.x += dx
+ if @hasAxis('bottom')
+ tick.bottomEl.attr('transform', "translate(#{tick.x+1},0)")
+ if @hasAxis('top')
+ tick.topEl.attr('transform', "translate(#{tick.x+1},0)")
+
+ if tick.enter
+ tick.opacity += dop
+ else if tick.exit
+ tick.opacity -= dop
+
+ if tick.enter or tick.exit
+ tick.bottomEl.style('opacity', tick.opacity) if @hasAxis('bottom')
+ tick.topEl.style('opacity', tick.opacity) if @hasAxis('top')
+
+ # Draws the visualization in the plot's canvas.
+ # @param delta The current x offset to apply to all elements when rendering. This number
+ # will be 0 when the plot is not animating and negative when it is.
+ # @abstract It does nothing on its own but is provided so that subclasses can
+ # define a custom rendering routine.
+ draw: (delta=0) -> super()
+
+ dimensionsChanged: ->
+ super()
+ @svg.attr('width', @width).attr('height', @height)
+ @_sizeCanvas()
+ @_buildAxes()
+ @draw(@animation.frame * @animation.delta())
+
+ # Updates axes in response to an <code>option:axes</code> event.
+ axesChanged: ->
+ for pos in ['top', 'right', 'bottom', 'left']
+ continue if @options.margins? and @options.margins[pos]?
+ if @hasAxis(pos)
+ @margins[pos] = defaultAxisMargins[pos]
+ else
+ @margins[pos] = 6
+ @_sizeCanvas()
+ @_buildAxes()
+ @draw(@animation.frame * @animation.delta())
+
+ # Updates ticks in response to an <code>option.ticks.*</code> event.
+ ticksChanged: ->
+ @_resetInitialTimeTicks()
+ @_transitionRangeAxes()
+ @draw(@animation.frame * @animation.delta())
+
+ # Updates tick formats in response to an <code>option.tickFormats.*</code> event.
+ tickFormatsChanged: ->
+ @_resetInitialTimeTicks()
+ @_transitionRangeAxes()
+ @draw(@animation.frame * @animation.delta())
+
+ # Updates margins in response to an <code>option.margins.*</code> event.
+ marginsChanged: ->
+ return unless @options.margins?
+ for own pos, size of @options.margins
+ unless size?
+ @margins[pos] = 6
+ else
+ @margins[pos] = size
+
+ @_sizeCanvas()
+ @draw(@animation.frame * @animation.delta())
+
+ layerChanged: ->
+ @_transitionRangeAxes()
+ super()
+
+
+# Base class for all "stacked" plot types (e.g. bar charts, area charts, etc.)
+# @abstract It does not perform rendering but instead formats the data
+# so as to ease the process of rendering stacked plots.
+class Epoch.Time.Stack extends Epoch.Time.Plot
+ # Sets stacking information (y0) for each of the points in each layer
+ _stackLayers: ->
+ return unless (layers = @getVisibleLayers()).length > 0
+ for i in [0...layers[0].values.length]
+ y0 = 0
+ for layer in layers
+ layer.values[i].y0 = y0
+ y0 += layer.values[i].y
+
+ # Adds stacking information for layers entering the visualization.
+ # @param [Array] layers Layers to stack.
+ _prepareLayers: (layers) ->
+ y0 = 0
+ for own i, d of layers
+ continue unless @data[i].visible
+ d.y0 = y0
+ y0 += d.y
+ return layers
+
+ # Ensures that elements are stacked when setting the initial data.
+ # @param [Array] data Layered data to set for the visualization.
+ setData: (data) ->
+ super(data)
+ @_stackLayers()
+
+ # Finds the correct extent to use for range axes (left and right).
+ # @return [Array] An extent array with the first element equal to 0
+ # and the second element equal to the maximum value amongst the
+ # stacked entries.
+ extent: ->
+ [max, layers] = [0, @getVisibleLayers()]
+ return [0, 0] unless layers.length
+
+ for i in [0...layers[0].values.length]
+ sum = 0
+ for j in [0...layers.length]
+ sum += layers[j].values[i].y
+ max = sum if sum > max
+
+ [0, max]
+
+ layerChanged: ->
+ @_stackLayers()
+ @_prepareLayers(layers) for layers in @_queue
+ super()
diff --git a/debian/missing-sources/epoch/src/time/area.coffee b/debian/missing-sources/epoch/src/time/area.coffee
new file mode 100644
index 0000000..22bf9db
--- /dev/null
+++ b/debian/missing-sources/epoch/src/time/area.coffee
@@ -0,0 +1,80 @@
+
+# Real-time stacked area chart implementation.
+class Epoch.Time.Area extends Epoch.Time.Stack
+ constructor: (@options={}) ->
+ @options.type ?= 'time.area'
+ super(@options)
+ @draw()
+
+ # Sets the appropriate styles to the graphics context given a particular layer.
+ # @param [Object] layer Layer for which to set the styles.
+ setStyles: (layer) ->
+ if layer? && layer.className?
+ styles = @getStyles "g.#{layer.className.replace(/\s/g,'.')} path.area"
+ else
+ styles = @getStyles "g path.area"
+ @ctx.fillStyle = styles.fill
+ if styles.stroke?
+ @ctx.strokeStyle = styles.stroke
+ if styles['stroke-width']?
+ @ctx.lineWidth = styles['stroke-width'].replace('px', '')
+
+ # Draws areas for the chart
+ _drawAreas: (delta=0) ->
+ [y, w, layers] = [@y(), @w(), @getVisibleLayers()]
+
+ for i in [layers.length-1..0]
+ continue unless (layer = layers[i])
+
+ @setStyles layer
+ @ctx.beginPath()
+
+ [j, k, trans] = [@options.windowSize, layer.values.length, @inTransition()]
+ firstX = null
+ while (--j >= -2) and (--k >= 0)
+ entry = layer.values[k]
+ args = [(j+1)*w+delta, y(entry.y + entry.y0)]
+ args[0] += w if trans
+ if i == @options.windowSize - 1
+ @ctx.moveTo.apply @ctx, args
+ else
+ @ctx.lineTo.apply @ctx, args
+
+ if trans
+ borderX = (j+3)*w+delta
+ else
+ borderX = (j+2)*w+delta
+
+ @ctx.lineTo(borderX, @innerHeight())
+ @ctx.lineTo(@width*@pixelRatio+w+delta, @innerHeight())
+ @ctx.closePath()
+ @ctx.fill()
+
+ # Draws strokes for the chart
+ _drawStrokes: (delta=0) ->
+ [y, w, layers] = [@y(), @w(), @getVisibleLayers()]
+
+ for i in [layers.length-1..0]
+ continue unless (layer = layers[i])
+ @setStyles layer
+ @ctx.beginPath()
+
+ [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()]
+ firstX = null
+ while (--i >= -2) and (--k >= 0)
+ entry = layer.values[k]
+ args = [(i+1)*w+delta, y(entry.y + entry.y0)]
+ args[0] += w if trans
+ if i == @options.windowSize - 1
+ @ctx.moveTo.apply @ctx, args
+ else
+ @ctx.lineTo.apply @ctx, args
+
+ @ctx.stroke()
+
+ # Draws the area chart.
+ draw: (delta=0) ->
+ @clear()
+ @_drawAreas(delta)
+ @_drawStrokes(delta)
+ super()
diff --git a/debian/missing-sources/epoch/src/time/bar.coffee b/debian/missing-sources/epoch/src/time/bar.coffee
new file mode 100644
index 0000000..7bcb7be
--- /dev/null
+++ b/debian/missing-sources/epoch/src/time/bar.coffee
@@ -0,0 +1,48 @@
+
+# Real-time Bar Chart implementation.
+class Epoch.Time.Bar extends Epoch.Time.Stack
+ constructor: (@options={}) ->
+ @options.type ?= 'time.bar'
+ super(@options)
+ @draw()
+
+ # @return [Number] An offset used to align the ticks to the center of the rendered bars.
+ _offsetX: ->
+ 0.5 * @w() / @pixelRatio
+
+ # Sets the styles for the graphics context given a layer class name.
+ # @param [String] className The class name to use when deriving the styles.
+ setStyles: (className) ->
+ styles = @getStyles "rect.bar.#{className.replace(/\s/g,'.')}"
+ @ctx.fillStyle = styles.fill
+
+ if !styles.stroke? or styles.stroke == 'none'
+ @ctx.strokeStyle = 'transparent'
+ else
+ @ctx.strokeStyle = styles.stroke
+
+ if styles['stroke-width']?
+ @ctx.lineWidth = styles['stroke-width'].replace('px', '')
+
+ # Draws the stacked bar chart.
+ draw: (delta=0) ->
+ @clear()
+ [y, w] = [@y(), @w()]
+
+ for layer in @getVisibleLayers()
+ continue unless Epoch.isNonEmptyArray(layer.values)
+ @setStyles(layer.className)
+
+ [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()]
+ iBoundry = if trans then -1 else 0
+
+ while (--i >= iBoundry) and (--k >= 0)
+ entry = layer.values[k]
+ [ex, ey, ey0] = [i*w+delta, entry.y, entry.y0]
+ ex += w if trans
+ args = [ex+1, y(ey+ey0), w-2, @innerHeight()-y(ey)+0.5*@pixelRatio]
+
+ @ctx.fillRect.apply(@ctx, args)
+ @ctx.strokeRect.apply(@ctx, args)
+
+ super()
diff --git a/debian/missing-sources/epoch/src/time/gauge.coffee b/debian/missing-sources/epoch/src/time/gauge.coffee
new file mode 100644
index 0000000..8efadb2
--- /dev/null
+++ b/debian/missing-sources/epoch/src/time/gauge.coffee
@@ -0,0 +1,209 @@
+
+# Real-time Gauge Visualization. Note: Looks best with a 4:3 aspect ratio (w:h)
+class Epoch.Time.Gauge extends Epoch.Chart.Canvas
+ defaults =
+ type: 'time.gauge'
+ domain: [0, 1]
+ ticks: 10
+ tickSize: 5
+ tickOffset: 5
+ fps: 34
+ format: Epoch.Formats.percent
+
+ optionListeners =
+ 'option:domain': 'domainChanged'
+ 'option:ticks': 'ticksChanged'
+ 'option:tickSize': 'tickSizeChanged'
+ 'option:tickOffset': 'tickOffsetChanged'
+ 'option:format': 'formatChanged'
+
+ # Creates the new gauge chart.
+ # @param [Object] options Options for the gauge chart.
+ # @option options [Array] domain The domain to use when rendering values (default: [0, 1]).
+ # @option options [Integer] ticks Number of ticks to render (default: 10).
+ # @option options [Integer] tickSize The length (in pixels) for each tick (default: 5).
+ # @option options [Integer] tickOffset The number of pixels by which to offset ticks from the outer arc (default: 5).
+ # @option options [Integer] fps The number of animation frames to render per second (default: 34).
+ # @option options [Function] format The formatting function to use when rendering the gauge label
+ # (default: Epoch.Formats.percent).
+ constructor: (@options={}) ->
+ super(@options = Epoch.Util.defaults(@options, defaults))
+ @value = @options.value or 0
+
+ if @options.model
+ @options.model.on 'data:push', => @pushFromModel()
+
+ # SVG Labels Overlay
+ if @el.style('position') != 'absolute' and @el.style('position') != 'relative'
+ @el.style('position', 'relative')
+
+ @svg = @el.insert('svg', ':first-child')
+ .attr('width', @width)
+ .attr('height', @height)
+ .attr('class', 'gauge-labels')
+
+ @svg.style
+ 'position': 'absolute'
+ 'z-index': '1'
+
+ @svg.append('g')
+ .attr('transform', "translate(#{@textX()}, #{@textY()})")
+ .append('text')
+ .attr('class', 'value')
+ .text(@options.format(@value))
+
+ # Animations
+ @animation =
+ interval: null
+ active: false
+ delta: 0
+ target: 0
+
+ @_animate = =>
+ if Math.abs(@animation.target - @value) < Math.abs(@animation.delta)
+ @value = @animation.target
+ clearInterval @animation.interval
+ @animation.active = false
+ else
+ @value += @animation.delta
+
+ @svg.select('text.value').text(@options.format(@value))
+ @draw()
+
+ @onAll optionListeners
+ @draw()
+
+ # Sets the value for the gauge to display and begins animating the guage.
+ # @param [Number] value Value to set for the gauge.
+ update: (value) ->
+ @animation.target = value
+ @animation.delta = (value - @value) / @options.fps
+ unless @animation.active
+ @animation.interval = setInterval @_animate, (1000/@options.fps)
+ @animation.active = true
+
+ # Alias for the <code>update()</code> method.
+ # @param [Number] value Value to set for the gauge.
+ push: (value) ->
+ @update value
+
+ # Responds to a model's 'data:push' event.
+ pushFromModel: ->
+ next = @options.model.getNext(@options.type, @options.dataFormat)
+ @update next
+
+ # @return [Number] The radius for the gauge.
+ radius: -> @getHeight() / 1.58
+
+ # @return [Number] The center position x-coordinate for the gauge.
+ centerX: -> @getWidth() / 2
+
+ # @return [Number] The center position y-coordinate for the gauge.
+ centerY: -> 0.68 * @getHeight()
+
+ # @return [Number] The x-coordinate for the gauge text display.
+ textX: -> @width / 2
+
+ # @return [Number] The y-coordinate for the gauge text display.
+ textY: -> 0.48 * @height
+
+ # @return [Number] The angle to set for the needle given a value within the domain.
+ # @param [Number] value Value to translate into a needle angle.
+ getAngle: (value) ->
+ [a, b] = @options.domain
+ ((value - a) / (b - a)) * (Math.PI + 2*Math.PI/8) - Math.PI/2 - Math.PI/8
+
+ # Sets context styles given a particular selector.
+ # @param [String] selector The selector to use when setting the styles.
+ setStyles: (selector) ->
+ styles = @getStyles selector
+ @ctx.fillStyle = styles.fill
+ @ctx.strokeStyle = styles.stroke
+ @ctx.lineWidth = styles['stroke-width'].replace('px', '') if styles['stroke-width']?
+
+ # Draws the gauge.
+ draw: ->
+ [cx, cy, r] = [@centerX(), @centerY(), @radius()]
+ [tickOffset, tickSize] = [@options.tickOffset, @options.tickSize]
+
+ @clear()
+
+ # Draw Ticks
+ t = d3.scale.linear()
+ .domain([0, @options.ticks])
+ .range([ -(9/8)*Math.PI, Math.PI/8 ])
+
+ @setStyles '.epoch .gauge .tick'
+ @ctx.beginPath()
+ for i in [0..@options.ticks]
+ a = t(i)
+ [c, s] = [Math.cos(a), Math.sin(a)]
+
+ x1 = c * (r-tickOffset) + cx
+ y1 = s * (r-tickOffset) + cy
+ x2 = c * (r-tickOffset-tickSize) + cx
+ y2 = s * (r-tickOffset-tickSize) + cy
+
+ @ctx.moveTo x1, y1
+ @ctx.lineTo x2, y2
+
+ @ctx.stroke()
+
+ # Outer arc
+ @setStyles '.epoch .gauge .arc.outer'
+ @ctx.beginPath()
+ @ctx.arc cx, cy, r, -(9/8)*Math.PI, (1/8)*Math.PI, false
+ @ctx.stroke()
+
+ # Inner arc
+ @setStyles '.epoch .gauge .arc.inner'
+ @ctx.beginPath()
+ @ctx.arc cx, cy, r-10, -(9/8)*Math.PI, (1/8)*Math.PI, false
+ @ctx.stroke()
+
+ @drawNeedle()
+
+ super()
+
+ # Draws the needle.
+ drawNeedle: ->
+ [cx, cy, r] = [@centerX(), @centerY(), @radius()]
+ ratio = @value / @options.domain[1]
+
+ @setStyles '.epoch .gauge .needle'
+ @ctx.beginPath()
+ @ctx.save()
+ @ctx.translate cx, cy
+ @ctx.rotate @getAngle(@value)
+
+ @ctx.moveTo 4 * @pixelRatio, 0
+ @ctx.lineTo -4 * @pixelRatio, 0
+ @ctx.lineTo -1 * @pixelRatio, 19-r
+ @ctx.lineTo 1, 19-r
+ @ctx.fill()
+
+ @setStyles '.epoch .gauge .needle-base'
+ @ctx.beginPath()
+ @ctx.arc 0, 0, (@getWidth() / 25), 0, 2*Math.PI
+ @ctx.fill()
+
+ @ctx.restore()
+
+ # Correctly responds to an <code>option:</code>
+ domainChanged: -> @draw()
+
+ # Correctly responds to an <code>option:</code>
+ ticksChanged: -> @draw()
+
+ # Correctly responds to an <code>option:</code>
+ tickSizeChanged: -> @draw()
+
+ # Correctly responds to an <code>option:</code>
+ tickOffsetChanged: -> @draw()
+
+ # Correctly responds to an <code>option:</code>
+ formatChanged: -> @svg.select('text.value').text(@options.format(@value))
+
+
+
+# "The mother of a million sons... CIVILIZATION!" -- Justice
diff --git a/debian/missing-sources/epoch/src/time/heatmap.coffee b/debian/missing-sources/epoch/src/time/heatmap.coffee
new file mode 100644
index 0000000..d7f2c50
--- /dev/null
+++ b/debian/missing-sources/epoch/src/time/heatmap.coffee
@@ -0,0 +1,261 @@
+
+# Real-time Heatmap Implementation.
+class Epoch.Time.Heatmap extends Epoch.Time.Plot
+ defaults =
+ type: 'time.heatmap'
+ buckets: 10
+ bucketRange: [0, 100]
+ opacity: 'linear'
+ bucketPadding: 2
+ paintZeroValues: false
+ cutOutliers: false
+
+ # Easy to use "named" color functions
+ colorFunctions =
+ root: (value, max) -> Math.pow(value/max, 0.5)
+ linear: (value, max) -> value / max
+ quadratic: (value, max) -> Math.pow(value/max, 2)
+ cubic: (value, max) -> Math.pow(value/max, 3)
+ quartic: (value, max) -> Math.pow(value/max, 4)
+ quintic: (value, max) -> Math.pow(value/max, 5)
+
+ optionListeners =
+ 'option:buckets': 'bucketsChanged'
+ 'option:bucketRange': 'bucketRangeChanged'
+ 'option:opacity': 'opacityChanged'
+ 'option:bucketPadding': 'bucketPaddingChanged'
+ 'option:paintZeroValues': 'paintZeroValuesChanged'
+ 'option:cutOutliers': 'cutOutliersChanged'
+
+ # Creates a new heatmap.
+ # @param [Object] options Options for the heatmap.
+ # @option options [Integer] buckets Number of vertical buckets to use when normalizing the
+ # incoming histogram data for visualization in the heatmap (default: 10).
+ # @option options [Array] bucketRange A range of acceptable values to be bucketed (default: [0, 100]).
+ # @option options [String, Function] opacity The opacity coloring function to use when rendering buckets
+ # in a column. The built-in functions (referenced by string) are: 'root', 'linear', 'quadratic', 'cubic',
+ # 'quartic', and 'quintic'. A custom function can be supplied given it accepts two parameters (value, max)
+ # and returns a numeric value from 0 to 1. Default: linear.
+ # @option options [Number] bucketPadding Amount of padding to apply around buckets (default: 2).
+ constructor: (@options={}) ->
+ super(@options = Epoch.Util.defaults(@options, defaults))
+ @_setOpacityFunction()
+ @_setupPaintCanvas()
+ @onAll optionListeners
+ @draw()
+
+ _setOpacityFunction: ->
+ if Epoch.isString(@options.opacity)
+ @_opacityFn = colorFunctions[@options.opacity]
+ Epoch.exception "Unknown coloring function provided '#{@options.opacity}'" unless @_opacityFn?
+ else if Epoch.isFunction(@options.opacity)
+ @_opacityFn = @options.opacity
+ else
+ Epoch.exception "Unknown type for provided coloring function."
+
+ # Prepares initially set data for rendering.
+ # @param [Array] data Layered histogram data for the visualization.
+ setData: (data) ->
+ super(data)
+ for layer in @data
+ layer.values = layer.values.map((entry) => @_prepareEntry(entry))
+
+ # Distributes the full histogram in the entry into the defined buckets
+ # for the visualization.
+ # @param [Object] entry Entry to prepare for visualization.
+ _getBuckets: (entry) ->
+ prepared =
+ time: entry.time
+ max: 0
+ buckets: (0 for i in [0...@options.buckets])
+
+ # Bucket size = (Range[1] - Range[0]) / number of buckets
+ bucketSize = (@options.bucketRange[1] - @options.bucketRange[0]) / @options.buckets
+
+ for own value, count of entry.histogram
+ index = parseInt((value - @options.bucketRange[0]) / bucketSize)
+
+ # Remove outliers from the preprared buckets if instructed to do so
+ if @options.cutOutliers and ((index < 0) or (index >= @options.buckets))
+ continue
+
+ # Bound the histogram to the range (aka, handle out of bounds values)
+ if index < 0
+ index = 0
+ else if index >= @options.buckets
+ index = @options.buckets - 1
+
+ prepared.buckets[index] += parseInt count
+
+ for i in [0...prepared.buckets.length]
+ prepared.max = Math.max(prepared.max, prepared.buckets[i])
+
+ return prepared
+
+ # @return [Function] The y scale for the heatmap.
+ y: ->
+ d3.scale.linear()
+ .domain(@options.bucketRange)
+ .range([@innerHeight(), 0])
+
+ # @return [Function] The y scale for the svg portions of the heatmap.
+ ySvg: ->
+ d3.scale.linear()
+ .domain(@options.bucketRange)
+ .range([@innerHeight() / @pixelRatio, 0])
+
+ # @return [Number] The height to render each bucket in a column (disregards padding).
+ h: ->
+ @innerHeight() / @options.buckets
+
+ # @return [Number] The offset needed to center ticks at the middle of each column.
+ _offsetX: ->
+ 0.5 * @w() / @pixelRatio
+
+ # Creates the painting canvas which is used to perform all the actual drawing. The contents
+ # of the canvas are then copied into the actual display canvas and through some image copy
+ # trickery at the end of a transition the illusion of motion over time is preserved.
+ #
+ # Using two canvases in this way allows us to render an incredible number of buckets in the
+ # visualization and animate them at high frame rates without smashing the cpu.
+ _setupPaintCanvas: ->
+ # Size the paint canvas to have a couple extra columns so we can perform smooth transitions
+ @paintWidth = (@options.windowSize + 1) * @w()
+ @paintHeight = @height * @pixelRatio
+
+ # Create the "memory only" canvas and nab the drawing context
+ @paint = document.createElement('CANVAS')
+ @paint.width = @paintWidth
+ @paint.height = @paintHeight
+ @p = Epoch.Util.getContext @paint
+
+ # Paint the initial data (rendering backwards from just before the fixed paint position)
+ @redraw()
+
+ # Hook into the events to paint the next row after it's been shifted into the data
+ @on 'after:shift', '_paintEntry'
+
+ # At the end of a transition we must reset the paint canvas by shifting the viewable
+ # buckets to the left (this allows for a fixed cut point and single renders below in @draw)
+ @on 'transition:end', '_shiftPaintCanvas'
+ @on 'transition:end', => @draw(@animation.frame * @animation.delta())
+
+ # Redraws the entire heatmap for the current data.
+ redraw: ->
+ return unless Epoch.isNonEmptyArray(@data) and Epoch.isNonEmptyArray(@data[0].values)
+ entryIndex = @data[0].values.length
+ drawColumn = @options.windowSize
+
+ # This addresses a strange off-by-one issue when the chart is transitioning
+ drawColumn++ if @inTransition()
+
+ while (--entryIndex >= 0) and (--drawColumn >= 0)
+ @_paintEntry(entryIndex, drawColumn)
+ @draw(@animation.frame * @animation.delta())
+
+ # Computes the correct color for a given bucket.
+ # @param [Integer] value Normalized value at the bucket.
+ # @param [Integer] max Normalized maximum for the column.
+ # @param [String] color Computed base color for the bucket.
+ _computeColor: (value, max, color) ->
+ Epoch.Util.toRGBA(color, @_opacityFn(value, max))
+
+ # Paints a single entry column on the paint canvas at the given column.
+ # @param [Integer] entryIndex Index of the entry to paint.
+ # @param [Integer] drawColumn Column on the paint canvas to place the visualized entry.
+ _paintEntry: (entryIndex=null, drawColumn=null) ->
+ [w, h] = [@w(), @h()]
+
+ entryIndex ?= @data[0].values.length - 1
+ drawColumn ?= @options.windowSize
+
+ entries = []
+ bucketTotals = (0 for i in [0...@options.buckets])
+ maxTotal = 0
+
+ for layer in @getVisibleLayers()
+ entry = @_getBuckets( layer.values[entryIndex] )
+ for own bucket, count of entry.buckets
+ bucketTotals[bucket] += count
+ maxTotal += entry.max
+ styles = @getStyles ".#{layer.className.split(' ').join('.')} rect.bucket"
+ entry.color = styles.fill
+ entries.push entry
+
+ xPos = drawColumn * w
+
+ @p.clearRect xPos, 0, w, @paintHeight
+
+ j = @options.buckets
+
+ for own bucket, sum of bucketTotals
+ color = @_avgLab(entries, bucket)
+ max = 0
+ for entry in entries
+ max += (entry.buckets[bucket] / sum) * maxTotal
+ if sum > 0 or @options.paintZeroValues
+ @p.fillStyle = @_computeColor(sum, max, color)
+ @p.fillRect xPos, (j-1) * h, w-@options.bucketPadding, h-@options.bucketPadding
+ j--
+
+ # This shifts the image contents of the paint canvas to the left by 1 column width.
+ # It is called after a transition has ended (yay, slight of hand).
+ _shiftPaintCanvas: ->
+ data = @p.getImageData @w(), 0, @paintWidth-@w(), @paintHeight
+ @p.putImageData data, 0, 0
+
+ # Performs an averaging of the colors for muli-layer heatmaps using the lab color space.
+ # @param [Array] entries The layers for which the colors are to be averaged.
+ # @param [Number] bucket The bucket in the entries that must be averaged.
+ # @return [String] The css color code for the average of all the layer colors.
+ _avgLab: (entries, bucket) ->
+ [l, a, b, total] = [0, 0, 0, 0]
+ for entry in entries
+ continue unless entry.buckets[bucket]?
+ total += entry.buckets[bucket]
+
+ for own i, entry of entries
+ if entry.buckets[bucket]?
+ value = entry.buckets[bucket]|0
+ else
+ value = 0
+ ratio = value / total
+ color = d3.lab(entry.color)
+ l += ratio * color.l
+ a += ratio * color.a
+ b += ratio * color.b
+
+ d3.lab(l, a, b).toString()
+
+ # Copies the paint canvas onto the display canvas, thus rendering the heatmap.
+ draw: (delta=0) ->
+ @clear()
+ @ctx.drawImage @paint, delta, 0
+ super()
+
+ # Changes the number of buckets in response to an <code>option:buckets</code> event.
+ bucketsChanged: -> @redraw()
+
+ # Changes the range of the buckets in response to an <code>option:bucketRange</code> event.
+ bucketRangeChanged: ->
+ @_transitionRangeAxes()
+ @redraw()
+
+ # Changes the opacity function in response to an <code>option:opacity</code> event.
+ opacityChanged: ->
+ @_setOpacityFunction()
+ @redraw()
+
+ # Changes the bucket padding in response to an <code>option:bucketPadding</code> event.
+ bucketPaddingChanged: -> @redraw()
+
+ # Changes whether or not to paint zeros in response to an <code>option:paintZeroValues</code> event.
+ paintZeroValuesChanged: -> @redraw()
+
+ # Changes whether or not to cut outliers when bucketing in response to an
+ # <code>option:cutOutliers</code> event.
+ cutOutliersChanged: -> @redraw()
+
+ layerChanged: -> @redraw()
+
+# "Audio... Audio... Audio... Video Disco..." - Justice
diff --git a/debian/missing-sources/epoch/src/time/line.coffee b/debian/missing-sources/epoch/src/time/line.coffee
new file mode 100644
index 0000000..8a4271b
--- /dev/null
+++ b/debian/missing-sources/epoch/src/time/line.coffee
@@ -0,0 +1,39 @@
+
+# Real-time line chart implementation
+class Epoch.Time.Line extends Epoch.Time.Plot
+ constructor: (@options={}) ->
+ @options.type ?= 'time.line'
+ super(@options)
+ @draw()
+
+ # Sets the graphics context styles based ont he given layer class name.
+ # @param [String] className The class name of the layer for which to set the styles.
+ setStyles: (className) ->
+ styles = @getStyles "g.#{className.replace(/\s/g,'.')} path.line"
+ @ctx.fillStyle = styles.fill
+ @ctx.strokeStyle = styles.stroke
+ @ctx.lineWidth = @pixelRatio * styles['stroke-width'].replace('px', '')
+
+ # Draws the line chart.
+ draw: (delta=0) ->
+ @clear()
+ w = @w()
+ for layer in @getVisibleLayers()
+ continue unless Epoch.isNonEmptyArray(layer.values)
+ @setStyles(layer.className)
+ @ctx.beginPath()
+ y = @y(layer.range)
+ [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()]
+
+ while (--i >= -2) and (--k >= 0)
+ entry = layer.values[k]
+ args = [(i+1)*w+delta, y(entry.y)]
+ args[0] += w if trans
+ if i == @options.windowSize - 1
+ @ctx.moveTo.apply @ctx, args
+ else
+ @ctx.lineTo.apply @ctx, args
+
+ @ctx.stroke()
+
+ super()