From ef03469fec14f1f0358b690934fc173d744f4e7d Mon Sep 17 00:00:00 2001
From: Daniel Baumann It should display a plot of It should display a plot of
+ It should display a plot of the following functions stacked atop one another:
+
+ It should display a plot of the following functions stacked atop one another:
+
+ It should correctly transition between the plots
+
+
+
+ It should correctly render and transition between Set A:
+
+
+
+
+ It should correctly transition between a single series, plotting the functions:
+
+
+
+
+ It should display the first layer of the plot as pink, the second layer as green, and the third layer as blue.
+
+ It should change layer colors automatically when switching between the following categorical color classes on the containing element:
+
+
+
+
+
+
+ Correctly render a multi-series plot of:
+ Correctly hide and show multiple layers
+
+
+
+ Display a plot of Display a plot of
+ Display a plot of the following functions stacked atop one another:
+
+ Display a plot of the following functions stacked atop one another:
+
+ Correctly transition between the plots
+
+
+
+ Correctly render and transition between Set A:
+
+
+
+
+ Correctly transition between a single series, plotting the functions:
+
+
+
+
+ Display the first layer of the plot as pink, the second layer as green, and the third layer as blue.
+
+ Change layer colors automatically when switching between the following categorical color classes on the containing element:
+
+
+
+
+
+
+ Correctly render a multi-series plot of:
+
+ Correctly render the single series plot of:
+
+ Correctly render the multi series plot of:
+
+ Correctly render the Horizontally oriented multi series plot of:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ Ensure the chart works with the array, tuple, and key-value data formats.
+ Ensure that we can use ticks option with bar charts.
+ Plot a random selection of points from the Beta(2, 5) distribution as a histogram. Plot a random selection of points from Beta(2, 5) and display in a horizontal histogram. Plot Beta(2, 2) and change number of buckets on the fly.
+
+
+
+
+ |
+
+ Plot beta(2, 5) and change the bucket range on the fly.
+
+
+ |
+
+ |
+
+ Display a plot of Display a plot of
+ Display a plot of the following functions over the range [0, 2π]:
+
+ Correctly transition between the functions
+
+
+
+ Correctly transition between Set A:
+
+
+
+
+
+ Correctly transition between Set A:
+
+
+
+
+ Display the first layer of the plot as pink, the second layer as green, and the third layer as blue.
+
+ Change layer colors automatically when switching between the following categorical color classes on the containing element:
+
+
+
+
+
+
+ Correctly render a multi-series plot of:
+
+ Display a plot of the following functions:
+
+
+
+
+
+
+
+
+
+ Correctly add and remove axes when options are set.
+
+ Correctly resize margins when options are set.
+
+ Correctly resize margins when options are set.
+
+ Correctly resize the chart when the width and height options are set.
+
+
+
+
+
+
+
+ Correctly render a pie chart with three categories:
+
+ Correctly render a donut chart with three categories:
+
+ Correctly transition between set A:
+
+
+
+
+ Correctly transition between set A:
+
+
+
+
+ Override the colors as such:
+
+ Correctly transition between different categorical colors sets.
+
+
+
+
+
+
+ Correctly render a pie chart with three categories:
+
+
+
+
+
+
+
+
+
+
+
+ Render a single random series scatter plot. Render three random scatter series in the same plot.
+ Transition from one random series to another random series. Use the
+ buttons below the plot to initiate the transition.
+
+
+
+
+ Transition from a set of multiple randoms series random series to another of multiple random series. Use the
+ buttons below the plot to initiate the transition.
+
+
+
+
+ Transition from a multi-series set of random data to a single series set of random data.
+
+
+
+ The first series should be pink, the second green, and thrid blue.
+ Correctly transition between different categorical color sets.
+
+
+
+
+
+ Correctly render two random scatter plots when labels are not specified for the layers. Render a single random series scatter plot with different radii.
+
+
+
+
+
+
+
+
+
+
+
+ true
if the chart has an axis with a given name, false
otherwise.
+ # @param [String] axis Name of axis to check.
+ hasAxis: (axis) ->
+ @options.axes.indexOf(axis) > -1
+
+ # @return [Number] Width of the visualization portion of the chart (width - margins).
+ innerWidth: ->
+ @width - (@margins.left + @margins.right)
+
+ # @return [Number] Height of the visualization portion of the chart (height - margins).
+ innerHeight: ->
+ @height - (@margins.top + @margins.bottom)
+
+ # @return [Function] The x scale for the visualization.
+ x: ->
+ domain = @options.domain ? @extent((d) -> d.x)
+ d3.scale.linear()
+ .domain(domain)
+ .range([0, @innerWidth()])
+
+ # @return [Function] The y scale for the visualization.
+ y: (givenDomain) ->
+ d3.scale.linear()
+ .domain(@_getScaleDomain(givenDomain))
+ .range([@innerHeight(), 0])
+
+ # @return [Function] d3 axis to use for the bottom of the visualization.
+ bottomAxis: ->
+ d3.svg.axis().scale(@x()).orient('bottom')
+ .ticks(@options.ticks.bottom)
+ .tickFormat(@options.tickFormats.bottom)
+
+ # @return [Function] d3 axis to use for the top of the visualization.
+ topAxis: ->
+ d3.svg.axis().scale(@x()).orient('top')
+ .ticks(@options.ticks.top)
+ .tickFormat(@options.tickFormats.top)
+
+ # @return [Function] d3 axis to use on the left of the visualization.
+ leftAxis: ->
+ range = if @options.range then @options.range.left else null
+ d3.svg.axis().scale(@y(range)).orient('left')
+ .ticks(@options.ticks.left)
+ .tickFormat(@options.tickFormats.left)
+
+ # @return [Function] d3 axis to use on the right of the visualization.
+ rightAxis: ->
+ range = if @options.range then @options.range.right else null
+ d3.svg.axis().scale(@y(range)).orient('right')
+ .ticks(@options.ticks.right)
+ .tickFormat(@options.tickFormats.right)
+
+ # Renders the axes for the visualization (subclasses must implement specific
+ # drawing routines).
+ draw: ->
+ if @_axesDrawn
+ @_redrawAxes()
+ else
+ @_drawAxes()
+ super()
+
+ # Redraws the axes for the visualization.
+ _redrawAxes: ->
+ if @hasAxis('bottom')
+ @g.selectAll('.x.axis.bottom').transition()
+ .duration(500)
+ .ease('linear')
+ .call(@bottomAxis())
+ if @hasAxis('top')
+ @g.selectAll('.x.axis.top').transition()
+ .duration(500)
+ .ease('linear')
+ .call(@topAxis())
+ if @hasAxis('left')
+ @g.selectAll('.y.axis.left').transition()
+ .duration(500)
+ .ease('linear')
+ .call(@leftAxis())
+ if @hasAxis('right')
+ @g.selectAll('.y.axis.right').transition()
+ .duration(500)
+ .ease('linear')
+ .call(@rightAxis())
+
+ # Draws the initial axes for the visualization.
+ _drawAxes: ->
+ if @hasAxis('bottom')
+ @g.append("g")
+ .attr("class", "x axis bottom")
+ .attr("transform", "translate(0, #{@innerHeight()})")
+ .call(@bottomAxis())
+ if @hasAxis('top')
+ @g.append("g")
+ .attr('class', 'x axis top')
+ .call(@topAxis())
+ if @hasAxis('left')
+ @g.append("g")
+ .attr("class", "y axis left")
+ .call(@leftAxis())
+ if @hasAxis('right')
+ @g.append('g')
+ .attr('class', 'y axis right')
+ .attr('transform', "translate(#{@innerWidth()}, 0)")
+ .call(@rightAxis())
+ @_axesDrawn = true
+
+ dimensionsChanged: ->
+ super()
+ @g.selectAll('.axis').remove()
+ @_axesDrawn = false
+ @draw()
+
+ # Updates margins in response to a option:margin.*
event.
+ marginsChanged: ->
+ return unless @options.margins?
+ for own pos, size of @options.margins
+ unless size?
+ @margins[pos] = 6
+ else
+ @margins[pos] = size
+
+ @g.transition()
+ .duration(750)
+ .attr("transform", "translate(#{@margins.left}, #{@margins.top})")
+
+ @draw()
+
+ # Updates axes in response to a option:axes
event.
+ axesChanged: ->
+ # Remove default axis margins
+ 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
+
+ # Update the margin offset
+ @g.transition()
+ .duration(750)
+ .attr("transform", "translate(#{@margins.left}, #{@margins.top})")
+
+ # Remove the axes and redraw
+ @g.selectAll('.axis').remove()
+ @_axesDrawn = false
+ @draw()
+
+ # Updates ticks in response to a option:ticks.*
event.
+ ticksChanged: -> @draw()
+
+ # Updates tick formats in response to a option:tickFormats.*
event.
+ tickFormatsChanged: -> @draw()
+
+ # Updates chart in response to a option:domain
event.
+ domainChanged: -> @draw()
+
+ # Updates chart in response to a option:range
event.
+ rangeChanged: -> @draw()
+
+# "They will see us waving from such great heights, come down now..." - The Postal Service
diff --git a/debian/missing-sources/epoch/src/basic/area.coffee b/debian/missing-sources/epoch/src/basic/area.coffee
new file mode 100644
index 0000000..e44b480
--- /dev/null
+++ b/debian/missing-sources/epoch/src/basic/area.coffee
@@ -0,0 +1,51 @@
+
+# Static stacked area chart implementation using d3.
+class Epoch.Chart.Area extends Epoch.Chart.Plot
+ constructor: (@options={}) ->
+ @options.type ?= 'area'
+ super(@options)
+ @draw()
+
+ # Generates a scale needed to appropriately render the stacked visualization.
+ # @return [Function] The y scale for the visualization.
+ y: ->
+ a = []
+ for layer in @getVisibleLayers()
+ for own k, v of layer.values
+ a[k] += v.y if a[k]?
+ a[k] = v.y unless a[k]?
+ d3.scale.linear()
+ .domain(@options.range ? [0, d3.max(a)])
+ .range([@height - @margins.top - @margins.bottom, 0])
+
+ # Renders the SVG elements needed to display the stacked area chart.
+ draw: ->
+ [x, y, layers] = [@x(), @y(), @getVisibleLayers()]
+
+ @g.selectAll('.layer').remove()
+ return if layers.length == 0
+
+ area = d3.svg.area()
+ .x((d) -> x(d.x))
+ .y0((d) -> y(d.y0))
+ .y1((d) -> y(d.y0 + d.y))
+
+ stack = d3.layout.stack()
+ .values((d) -> d.values)
+
+ data = stack layers
+
+ layer = @g.selectAll('.layer')
+ .data(layers, (d) -> d.category)
+
+ layer.select('.area')
+ .attr('d', (d) -> area(d.values))
+
+ layer.enter().append('g')
+ .attr('class', (d) -> d.className)
+
+ layer.append('path')
+ .attr('class', 'area')
+ .attr('d', (d) -> area(d.values))
+
+ super()
diff --git a/debian/missing-sources/epoch/src/basic/bar.coffee b/debian/missing-sources/epoch/src/basic/bar.coffee
new file mode 100644
index 0000000..8fbc427
--- /dev/null
+++ b/debian/missing-sources/epoch/src/basic/bar.coffee
@@ -0,0 +1,273 @@
+# Static bar chart implementation (using d3).
+class Epoch.Chart.Bar extends Epoch.Chart.Plot
+ defaults =
+ type: 'bar'
+ style: 'grouped'
+ orientation: 'vertical'
+ padding:
+ bar: 0.08
+ group: 0.1
+ outerPadding:
+ bar: 0.08
+ group: 0.1
+
+ horizontal_specific =
+ tickFormats:
+ top: Epoch.Formats.si
+ bottom: Epoch.Formats.si
+ left: Epoch.Formats.regular
+ right: Epoch.Formats.regular
+
+ horizontal_defaults = Epoch.Util.defaults(horizontal_specific, defaults)
+
+ optionListeners =
+ 'option:orientation': 'orientationChanged'
+ 'option:padding': 'paddingChanged'
+ 'option:outerPadding': 'paddingChanged'
+ 'option:padding:bar': 'paddingChanged'
+ 'option:padding:group': 'paddingChanged'
+ 'option:outerPadding:bar': 'paddingChanged'
+ 'option:outerPadding:group': 'paddingChanged'
+
+ constructor: (@options={}) ->
+ if @_isHorizontal()
+ @options = Epoch.Util.defaults(@options, horizontal_defaults)
+ else
+ @options = Epoch.Util.defaults(@options, defaults)
+ super(@options)
+ @onAll optionListeners
+ @draw()
+
+ # @return [Boolean] True if the chart is vertical, false otherwise
+ _isVertical: ->
+ @options.orientation == 'vertical'
+
+ # @return [Boolean] True if the chart is horizontal, false otherwise
+ _isHorizontal: ->
+ @options.orientation == 'horizontal'
+
+ # @return [Function] The scale used to generate the chart's x scale.
+ x: ->
+ if @_isVertical()
+ d3.scale.ordinal()
+ .domain(Epoch.Util.domain(@getVisibleLayers()))
+ .rangeRoundBands([0, @innerWidth()], @options.padding.group, @options.outerPadding.group)
+ else
+ extent = @extent((d) -> d.y)
+ extent[0] = Math.min(0, extent[0])
+ d3.scale.linear()
+ .domain(extent)
+ .range([0, @width - @margins.left - @margins.right])
+
+ # @return [Function] The x scale used to render the horizontal bar chart.
+ x1: (x0) ->
+ d3.scale.ordinal()
+ .domain((layer.category for layer in @getVisibleLayers()))
+ .rangeRoundBands([0, x0.rangeBand()], @options.padding.bar, @options.outerPadding.bar)
+
+ # @return [Function] The y scale used to render the bar chart.
+ y: ->
+ if @_isVertical()
+ extent = @extent((d) -> d.y)
+ extent[0] = Math.min(0, extent[0])
+ d3.scale.linear()
+ .domain(extent)
+ .range([@height - @margins.top - @margins.bottom, 0])
+ else
+ d3.scale.ordinal()
+ .domain(Epoch.Util.domain(@getVisibleLayers()))
+ .rangeRoundBands([0, @innerHeight()], @options.padding.group, @options.outerPadding.group)
+
+ # @return [Function] The x scale used to render the vertical bar chart.
+ y1: (y0) ->
+ d3.scale.ordinal()
+ .domain((layer.category for layer in @getVisibleLayers()))
+ .rangeRoundBands([0, y0.rangeBand()], @options.padding.bar, @options.outerPadding.bar)
+
+ # Remaps the bar chart data into a form that is easier to display.
+ # @return [Array] The reorganized data.
+ _remapData: ->
+ map = {}
+ for layer in @getVisibleLayers()
+ className = 'bar ' + layer.className.replace(/\s*layer\s*/, '')
+ for entry in layer.values
+ map[entry.x] ?= []
+ map[entry.x].push { label: layer.category, y: entry.y, className: className }
+ ({group: k, values: v} for own k, v of map)
+
+ # Draws the bar char.
+ draw: ->
+ if @_isVertical()
+ @_drawVertical()
+ else
+ @_drawHorizontal()
+ super()
+
+ # Draws the bar chart with a vertical orientation
+ _drawVertical: ->
+ [x0, y] = [@x(), @y()]
+ x1 = @x1(x0)
+ height = @height - @margins.top - @margins.bottom
+ data = @_remapData()
+
+ # 1) Join
+ layer = @g.selectAll(".layer")
+ .data(data, (d) -> d.group)
+
+ # 2) Update
+ layer.transition().duration(750)
+ .attr("transform", (d) -> "translate(#{x0(d.group)}, 0)")
+
+ # 3) Enter / Create
+ layer.enter().append("g")
+ .attr('class', 'layer')
+ .attr("transform", (d) -> "translate(#{x0(d.group)}, 0)")
+
+ rects = layer.selectAll('rect')
+ .data((group) -> group.values)
+
+ rects.attr('class', (d) -> d.className)
+
+ rects.transition().duration(600)
+ .attr('x', (d) -> x1(d.label))
+ .attr('y', (d) -> y(d.y))
+ .attr('width', x1.rangeBand())
+ .attr('height', (d) -> height - y(d.y))
+
+ rects.enter().append('rect')
+ .attr('class', (d) -> d.className)
+ .attr('x', (d) -> x1(d.label))
+ .attr('y', (d) -> y(d.y))
+ .attr('width', x1.rangeBand())
+ .attr('height', (d) -> height - y(d.y))
+
+ rects.exit().transition()
+ .duration(150)
+ .style('opacity', '0')
+ .remove()
+
+ # 4) Update new and existing
+
+ # 5) Exit / Remove
+ layer.exit()
+ .transition()
+ .duration(750)
+ .style('opacity', '0')
+ .remove()
+
+ # Draws the bar chart with a horizontal orientation
+ _drawHorizontal: ->
+ [x, y0] = [@x(), @y()]
+ y1 = @y1(y0)
+ width = @width - @margins.left - @margins.right
+ data = @_remapData()
+
+ # 1) Join
+ layer = @g.selectAll(".layer")
+ .data(data, (d) -> d.group)
+
+ # 2) Update
+ layer.transition().duration(750)
+ .attr("transform", (d) -> "translate(0, #{y0(d.group)})")
+
+ # 3) Enter / Create
+ layer.enter().append("g")
+ .attr('class', 'layer')
+ .attr("transform", (d) -> "translate(0, #{y0(d.group)})")
+
+ rects = layer.selectAll('rect')
+ .data((group) -> group.values)
+
+ rects.attr('class', (d) -> d.className)
+
+ rects.transition().duration(600)
+ .attr('x', (d) -> 0)
+ .attr('y', (d) -> y1(d.label))
+ .attr('height', y1.rangeBand())
+ .attr('width', (d) -> x(d.y))
+
+ rects.enter().append('rect')
+ .attr('class', (d) -> d.className)
+ .attr('x', (d) -> 0)
+ .attr('y', (d) -> y1(d.label))
+ .attr('height', y1.rangeBand())
+ .attr('width', (d) -> x(d.y))
+
+ rects.exit().transition()
+ .duration(150)
+ .style('opacity', '0')
+ .remove()
+
+ # 4) Update new and existing
+
+ # 5) Exit / Remove
+ layer.exit()
+ .transition()
+ .duration(750)
+ .style('opacity', '0')
+ .remove()
+
+ # Generates specific tick marks to emulate d3's linear scale axis ticks
+ # for ordinal scales. Note: this should only be called if the user has
+ # defined a set number of ticks for a given axis.
+ # @param [Number] numTicks Number of ticks to generate
+ # @param [String] dataKey Property name of a datum to use for the tick value
+ # @return [Array] The ticks for the given axis
+ _getTickValues: (numTicks, dataKey='x') ->
+ return [] unless @data[0]?
+ total = @data[0].values.length
+ step = Math.ceil(total / numTicks)|0
+ tickValues = (@data[0].values[i].x for i in [0...total] by step)
+
+ # @return [Function] d3 axis to use for the bottom of the visualization.
+ bottomAxis: ->
+ axis = d3.svg.axis().scale(@x()).orient('bottom')
+ .ticks(@options.ticks.bottom)
+ .tickFormat(@options.tickFormats.bottom)
+ if @_isVertical() and @options.ticks.bottom?
+ axis.tickValues @_getTickValues(@options.ticks.bottom)
+ axis
+
+ # @return [Function] d3 axis to use for the top of the visualization.
+ topAxis: ->
+ axis = d3.svg.axis().scale(@x()).orient('top')
+ .ticks(@options.ticks.top)
+ .tickFormat(@options.tickFormats.top)
+ if @_isVertical() and @options.ticks.top?
+ axis.tickValues @_getTickValues(@options.ticks.top)
+ axis
+
+ # @return [Function] d3 axis to use on the left of the visualization.
+ leftAxis: ->
+ axis = d3.svg.axis().scale(@y()).orient('left')
+ .ticks(@options.ticks.left)
+ .tickFormat(@options.tickFormats.left)
+ if @_isHorizontal() and @options.ticks.left?
+ axis.tickValues @_getTickValues(@options.ticks.left)
+ axis
+
+ # @return [Function] d3 axis to use on the right of the visualization.
+ rightAxis: ->
+ axis = d3.svg.axis().scale(@y()).orient('right')
+ .ticks(@options.ticks.right)
+ .tickFormat(@options.tickFormats.right)
+ if @_isHorizontal() and @options.ticks.right?
+ axis.tickValues @_getTickValues(@options.ticks.right)
+ axis
+
+ # Updates orientation in response option:orientation
.
+ orientationChanged: ->
+ top = @options.tickFormats.top
+ bottom = @options.tickFormats.bottom
+ left = @options.tickFormats.left
+ right = @options.tickFormats.right
+
+ @options.tickFormats.left = top
+ @options.tickFormats.right = bottom
+ @options.tickFormats.top = left
+ @options.tickFormats.bottom = right
+
+ @draw()
+
+ # Updates padding in response to option:padding:*
and option:outerPadding:*
.
+ paddingChanged: -> @draw()
diff --git a/debian/missing-sources/epoch/src/basic/histogram.coffee b/debian/missing-sources/epoch/src/basic/histogram.coffee
new file mode 100644
index 0000000..4548e67
--- /dev/null
+++ b/debian/missing-sources/epoch/src/basic/histogram.coffee
@@ -0,0 +1,61 @@
+class Epoch.Chart.Histogram extends Epoch.Chart.Bar
+ defaults =
+ type: 'histogram'
+ domain: [0, 100]
+ bucketRange: [0, 100]
+ buckets: 10
+ cutOutliers: false
+
+ optionListeners =
+ 'option:bucketRange': 'bucketRangeChanged'
+ 'option:buckets': 'bucketsChanged'
+ 'option:cutOutliers': 'cutOutliersChanged'
+
+ constructor: (@options={}) ->
+ super(@options = Epoch.Util.defaults(@options, defaults))
+ @onAll optionListeners
+ @draw()
+
+ # Prepares data by sorting it into histogram buckets as instructed by the chart options.
+ # @param [Array] data Data to prepare for rendering.
+ # @return [Array] The data prepared to be displayed as a histogram.
+ _prepareData: (data) ->
+ bucketSize = (@options.bucketRange[1] - @options.bucketRange[0]) / @options.buckets
+
+ prepared = []
+ for layer in data
+ buckets = (0 for i in [0...@options.buckets])
+ for point in layer.values
+ index = parseInt((point.x - @options.bucketRange[0]) / bucketSize)
+
+ if @options.cutOutliers and ((index < 0) or (index >= @options.buckets))
+ continue
+ if index < 0
+ index = 0
+ else if index >= @options.buckets
+ index = @options.buckets - 1
+
+ buckets[index] += parseInt point.y
+
+ preparedLayer = { values: (buckets.map (d, i) -> {x: parseInt(i) * bucketSize, y: d}) }
+ for own k, v of layer
+ preparedLayer[k] = v unless k == 'values'
+
+ prepared.push preparedLayer
+
+ return prepared
+
+ # Called when options change, this prepares the raw data for the chart according to the new
+ # options, sets it, and renders the chart.
+ resetData: ->
+ @setData @rawData
+ @draw()
+
+ # Updates the chart in response to an option:bucketRange
event.
+ bucketRangeChanged: -> @resetData()
+
+ # Updates the chart in response to an option:buckets
event.
+ bucketsChanged: -> @resetData()
+
+ # Updates the chart in response to an option:cutOutliers
event.
+ cutOutliersChanged: -> @resetData()
diff --git a/debian/missing-sources/epoch/src/basic/line.coffee b/debian/missing-sources/epoch/src/basic/line.coffee
new file mode 100644
index 0000000..de56780
--- /dev/null
+++ b/debian/missing-sources/epoch/src/basic/line.coffee
@@ -0,0 +1,46 @@
+# Static line chart implementation (using d3).
+class Epoch.Chart.Line extends Epoch.Chart.Plot
+ constructor: (@options={}) ->
+ @options.type ?= 'line'
+ super(@options)
+ @draw()
+
+ # @return [Function] The line generator used to construct the plot.
+ line: (layer) ->
+ [x, y] = [@x(), @y(layer.range)]
+ d3.svg.line()
+ .x((d) -> x(d.x))
+ .y((d) -> y(d.y))
+
+ # Draws the line chart.
+ draw: ->
+ [x, y, layers] = [@x(), @y(), @getVisibleLayers()]
+
+ # Zero visible layers, just drop all and get out
+ if layers.length == 0
+ return @g.selectAll('.layer').remove()
+
+ # 1) Join
+ layer = @g.selectAll('.layer')
+ .data(layers, (d) -> d.category)
+
+ # 2) Update (only existing)
+ layer.select('.line').transition().duration(500)
+ .attr('d', (l) => @line(l)(l.values))
+
+ # 3) Enter (Create)
+ layer.enter().append('g')
+ .attr('class', (l) -> l.className)
+ .append('path')
+ .attr('class', 'line')
+ .attr('d', (l) => @line(l)(l.values))
+
+ # 4) Update (existing & new)
+ # Nuuupp
+
+ # 5) Exit (Remove)
+ layer.exit().transition().duration(750)
+ .style('opacity', '0')
+ .remove()
+
+ super()
diff --git a/debian/missing-sources/epoch/src/basic/pie.coffee b/debian/missing-sources/epoch/src/basic/pie.coffee
new file mode 100644
index 0000000..e794a2f
--- /dev/null
+++ b/debian/missing-sources/epoch/src/basic/pie.coffee
@@ -0,0 +1,59 @@
+
+# Static Pie Chart implementation (using d3).
+class Epoch.Chart.Pie extends Epoch.Chart.SVG
+ defaults =
+ type: 'pie'
+ margin: 10
+ inner: 0
+
+ # Creates a new pie chart.
+ # @param [Object] options Options for the pie chart.
+ # @option options [Number] margin Margins to add around the pie chart (default: 10).
+ # @option options [Number] inner The inner radius for the chart (default: 0).
+ constructor: (@options={}) ->
+ super(@options = Epoch.Util.defaults(@options, defaults))
+ @pie = d3.layout.pie().sort(null)
+ .value (d) -> d.value
+ @arc = d3.svg.arc()
+ .outerRadius(=> (Math.max(@width, @height) / 2) - @options.margin)
+ .innerRadius(=> @options.inner)
+ @g = @svg.append('g')
+ .attr("transform", "translate(#{@width/2}, #{@height/2})")
+ @on 'option:margin', 'marginChanged'
+ @on 'option:inner', 'innerChanged'
+ @draw()
+
+ # Draws the pie chart
+ draw: ->
+ @g.selectAll('.arc').remove()
+
+ arcs = @g.selectAll(".arc")
+ .data(@pie(@getVisibleLayers()), (d) -> d.data.category)
+
+ arcs.enter().append('g')
+ .attr('class', (d) -> "arc pie " + d.data.className)
+
+ arcs.select('path')
+ .attr('d', @arc)
+
+ arcs.select('text')
+ .attr("transform", (d) => "translate(#{@arc.centroid(d)})")
+ .text((d) -> d.data.label or d.data.category)
+
+ path = arcs.append("path")
+ .attr("d", @arc)
+ .each((d) -> @._current = d)
+
+ text = arcs.append("text")
+ .attr("transform", (d) => "translate(#{@arc.centroid(d)})")
+ .attr("dy", ".35em")
+ .style("text-anchor", "middle")
+ .text((d) -> d.data.label or d.data.category)
+
+ super()
+
+ # Updates margins in response to an option:margin
event.
+ marginChanged: -> @draw()
+
+ # Updates inner margin in response to an option:inner
event.
+ innerChanged: -> @draw()
diff --git a/debian/missing-sources/epoch/src/basic/scatter.coffee b/debian/missing-sources/epoch/src/basic/scatter.coffee
new file mode 100644
index 0000000..b8563ec
--- /dev/null
+++ b/debian/missing-sources/epoch/src/basic/scatter.coffee
@@ -0,0 +1,59 @@
+
+# Static scatter plot implementation (using d3).
+class Epoch.Chart.Scatter extends Epoch.Chart.Plot
+ defaults =
+ type: 'scatter'
+ radius: 3.5
+ axes: ['top', 'bottom', 'left', 'right']
+
+ # Creates a new scatter plot.
+ # @param [Object] options Options for the plot.
+ # @option options [Number] radius The default radius to use for the points in
+ # the plot (default 3.5). This can be overrwitten by individual points.
+ constructor: (@options={}) ->
+ super(@options = Epoch.Util.defaults(@options, defaults))
+ @on 'option:radius', 'radiusChanged'
+ @draw()
+
+ # Draws the scatter plot.
+ draw: ->
+ [x, y, layers] = [@x(), @y(), @getVisibleLayers()]
+ radius = @options.radius
+
+ if layers.length == 0
+ return @g.selectAll('.layer').remove()
+
+ layer = @g.selectAll('.layer')
+ .data(layers, (d) -> d.category)
+
+ layer.enter().append('g')
+ .attr('class', (d) -> d.className)
+
+ dots = layer.selectAll('.dot')
+ .data((l) -> l.values)
+
+ dots.transition().duration(500)
+ .attr("r", (d) -> d.r ? radius)
+ .attr("cx", (d) -> x(d.x))
+ .attr("cy", (d) -> y(d.y))
+
+ dots.enter().append('circle')
+ .attr('class', 'dot')
+ .attr("r", (d) -> d.r ? radius)
+ .attr("cx", (d) -> x(d.x))
+ .attr("cy", (d) -> y(d.y))
+
+ dots.exit().transition()
+ .duration(750)
+ .style('opacity', 0)
+ .remove()
+
+ layer.exit().transition()
+ .duration(750)
+ .style('opacity', 0)
+ .remove()
+
+ super()
+
+ # Updates radius in response to an option:radius
event.
+ radiusChanged: -> @draw()
diff --git a/debian/missing-sources/epoch/src/core/chart.coffee b/debian/missing-sources/epoch/src/core/chart.coffee
new file mode 100644
index 0000000..068335a
--- /dev/null
+++ b/debian/missing-sources/epoch/src/core/chart.coffee
@@ -0,0 +1,361 @@
+# The base class for all charts in Epoch. Defines chart dimensions, keeps a reference
+# of the chart's containing elements. And defines core method for handling data and
+# drawing.
+class Epoch.Chart.Base extends Epoch.Events
+ defaults =
+ width: 320
+ height: 240
+ dataFormat: null
+
+ optionListeners =
+ 'option:width': 'dimensionsChanged'
+ 'option:height': 'dimensionsChanged'
+ 'layer:shown': 'layerChanged'
+ 'layer:hidden': 'layerChanged'
+
+ # Creates a new base chart.
+ # @param [Object] options Options to set for this chart.
+ # @option options [Integer] width Sets an explicit width for the visualization.
+ # @option options [Integer] height Sets an explicit height for the visualization.
+ # @option options [Object, String] dataFormat Specific data format for the chart.
+ # @option options [Object] model Data model for the chart.
+ constructor: (@options={}) ->
+ super()
+
+ if @options.model
+ if @options.model.hasData()?
+ @setData(@options.model.getData(@options.type, @options.dataFormat))
+ else
+ @setData(@options.data or [])
+ @options.model.on 'data:updated', => @setDataFromModel()
+ else
+ @setData(@options.data or [])
+
+ if @options.el?
+ @el = d3.select(@options.el)
+
+ @width = @options.width
+ @height = @options.height
+
+ if @el?
+ @width = @el.width() unless @width?
+ @height = @el.height() unless @height?
+ else
+ @width = defaults.width unless @width?
+ @height = defaults.height unless @height?
+ @el = d3.select(document.createElement('DIV'))
+ .attr('width', @width)
+ .attr('height', @height)
+
+ @onAll optionListeners
+
+ # @return [Object] A copy of this charts options.
+ _getAllOptions: ->
+ Epoch.Util.defaults({}, @options)
+
+ # Chart option accessor.
+ # @param key Name of the option to fetch. Can be hierarchical, e.g. 'margins.left'
+ # @return The requested option if found, undefined otherwise.
+ _getOption: (key) ->
+ parts = key.split('.')
+ scope = @options
+ while parts.length and scope?
+ subkey = parts.shift()
+ scope = scope[subkey]
+ scope
+
+ # Chart option mutator.
+ # @param key Name of the option to fetch. Can be hierarchical, e.g. 'margins.top'
+ # @param value Value to set for the option.
+ # @event option:`key` Triggers an option event with the given key being set.
+ _setOption: (key, value) ->
+ parts = key.split('.')
+ scope = @options
+ while parts.length
+ subkey = parts.shift()
+ if parts.length == 0
+ scope[subkey] = arguments[1]
+ @trigger "option:#{arguments[0]}"
+ return
+ unless scope[subkey]?
+ scope[subkey] = {}
+ scope = scope[subkey]
+
+ # Sets all options given an object of mixed hierarchical keys and nested objects.
+ # @param [Object] options Options to set.
+ # @event option:* Triggers an option event for each key that was set
+ _setManyOptions: (options, prefix='') ->
+ for own key, value of options
+ if Epoch.isObject(value)
+ @_setManyOptions value, "#{prefix + key}."
+ else
+ @_setOption prefix + key, value
+
+ # General accessor / mutator for chart options.
+ #
+ # @overload option()
+ # Fetches chart options.
+ # @return a copy of this chart's options.
+ #
+ # @overload option(name)
+ # Fetches the value the option with the given name.
+ # @param [String] name Name of the option to fetch. Can be hierarchical, e.g. 'margins.left'
+ # @return The requested option if found, undefined
otherwise.
+ #
+ # @overload option(name, value)
+ # Sets an option and triggers the associated event.
+ # @param [String] name Name of the option to fetch. Can be hierarchical, e.g. 'margins.top'
+ # @param value Value to set for the option.
+ # @event option:`name` Triggers an option event with the given key being set.
+ #
+ # @overload option(options)
+ # Sets multiple options at once.
+ # @param [Object] options Options to set for the chart.
+ # @event option:* Triggers an option event for each key that was set.
+ option: ->
+ if arguments.length == 0
+ @_getAllOptions()
+ else if arguments.length == 1 and Epoch.isString(arguments[0])
+ @_getOption arguments[0]
+ else if arguments.length == 2 and Epoch.isString(arguments[0])
+ @_setOption arguments[0], arguments[1]
+ else if arguments.length == 1 and Epoch.isObject(arguments[0])
+ @_setManyOptions arguments[0]
+
+ # Retrieves and sets data from the chart's model
+ setDataFromModel: ->
+ prepared = @_prepareData @options.model.getData(@options.type, @options.dataFormat)
+ @data = @_annotateLayers(prepared)
+ @draw()
+
+ # Set the initial data for the chart.
+ # @param data Data to initially set for the given chart. The data format can vary
+ # from chart to chart. The base class assumes that the data provided will be an
+ # array of layers.
+ setData: (data, options={}) ->
+ prepared = @_prepareData (@rawData = @_formatData(data))
+ @data = @_annotateLayers(prepared)
+
+ # Performs post formatted data preparation.
+ # @param data Data to prepare before setting.
+ # @return The prepared data.
+ _prepareData: (data) -> data
+
+ # Performs data formatting before setting the charts data
+ # @param data Data to be formatted.
+ # @return The chart specific formatted data.
+ _formatData: (data) ->
+ Epoch.Data.formatData(data, @options.type, @options.dataFormat)
+
+ # Annotates data to add class names, categories, and initial visibility states
+ _annotateLayers: (data) ->
+ category = 1
+ for layer in data
+ classes = ['layer']
+ classes.push "category#{category}"
+ layer.category = category
+ layer.visible = true
+ classes.push(Epoch.Util.dasherize layer.label) if layer.label?
+ layer.className = classes.join(' ')
+ category++
+ return data
+
+ # Finds a layer in the chart's current data that has the given label or index.
+ # @param [String, Number] labelOrIndex The label or index of the layer to find.
+ _findLayer: (labelOrIndex) ->
+ layer = null
+ if Epoch.isString(labelOrIndex)
+ for l in @data
+ if l.label == labelOrIndex
+ layer = l
+ break
+ else if Epoch.isNumber(labelOrIndex)
+ index = parseInt(labelOrIndex)
+ layer = @data[index] unless index < 0 or index >= @data.length
+ return layer
+
+ # Instructs the chart that a data layer should be displayed.
+ # @param [String, Number] labelOrIndex The label or index of the layer to show.
+ # @event 'layer:shown' If a layer that was previously hidden now became visible.
+ showLayer: (labelOrIndex) ->
+ return unless (layer = @_findLayer labelOrIndex)
+ return if layer.visible
+ layer.visible = true
+ @trigger 'layer:shown'
+
+ # Instructs the chart that a data layer should not be displayed.
+ # @param [String, Number] labelOrIndex The label or index of the layer to hide.
+ # @event 'layer:hidden' If a layer that was visible was made hidden.
+ hideLayer: (labelOrIndex) ->
+ return unless (layer = @_findLayer labelOrIndex)
+ return unless layer.visible
+ layer.visible = false
+ @trigger 'layer:hidden'
+
+ # Instructs the chart that a data layer's visibility should be toggled.
+ # @param [String, Number] labelOrIndex The label or index of the layer to toggle.
+ # @event 'layer:shown' If the layer was made visible
+ # @event 'layer:hidden' If the layer was made invisible
+ toggleLayer: (labelOrIndex) ->
+ return unless (layer = @_findLayer labelOrIndex)
+ layer.visible = !layer.visible
+ if layer.visible
+ @trigger 'layer:shown'
+ else
+ @trigger 'layer:hidden'
+
+ # Determines whether or not a data layer is visible.
+ # @param [String, Number] labelOrIndex The label or index of the layer to toggle.
+ # @return true
if the layer is visible, false
otherwise.
+ isLayerVisible: (labelOrIndex) ->
+ return null unless (layer = @_findLayer labelOrIndex)
+ layer.visible
+
+ # Calculates an array of layers in the charts data that are flagged as visible.
+ # @return [Array] The chart's visible layers.
+ getVisibleLayers: ->
+ return @data.filter((layer) -> layer.visible)
+
+ # Updates the chart with new data.
+ # @param data Data to replace the current data for the chart.
+ # @param [Boolean] draw Whether or not to redraw the chart after the data has been set.
+ # Default: true.
+ update: (data, draw=true) ->
+ @setData data
+ @draw() if draw
+
+ # Draws the chart. Triggers the 'draw' event, subclasses should call super() after drawing to
+ # ensure that the event is triggered.
+ # @abstract Must be overriden in child classes to perform chart specific drawing.
+ draw: -> @trigger 'draw'
+
+ # Determines a resulting scale domain for the y axis given a domain
+ # @param [Mixed] givenDomain A set domain, a label associated with mutliple
+ # layers, or null.
+ # @return The domain for the y scale
+ _getScaleDomain: (givenDomain) ->
+ # Explicitly set given domain
+ if Array.isArray(givenDomain)
+ return givenDomain
+
+ # Check for "labeled" layer ranges
+ if Epoch.isString(givenDomain)
+ layers = @getVisibleLayers()
+ .filter((l) -> l.range == givenDomain)
+ .map((l) -> l.values)
+ if layers? && layers.length
+ values = Epoch.Util.flatten(layers).map((d) -> d.y)
+ minFn = (memo, curr) -> if curr < memo then curr else memo
+ maxFn = (memo, curr) -> if curr > memo then curr else memo
+ return [values.reduce(minFn, values[0]), values.reduce(maxFn, values[0])]
+
+ # Find the domain based on chart options
+ if Array.isArray(@options.range)
+ @options.range
+ else if @options.range && Array.isArray(@options.range.left)
+ @options.range.left
+ else if @options.range && Array.isArray(@options.range.right)
+ @options.range.right
+ else
+ @extent((d) -> d.y)
+
+ # Calculates an extent throughout the layers based on the given comparator.
+ # @param [Function] cmp Comparator to use for performing the min and max for the extent
+ # calculation.
+ # @return [Array] an extent array with the first element as the minimum value in the
+ # chart's data set and the second element as the maximum.
+ extent: (cmp) ->
+ [
+ d3.min(@getVisibleLayers(), (layer) -> d3.min(layer.values, cmp)),
+ d3.max(@getVisibleLayers(), (layer) -> d3.max(layer.values, cmp))
+ ]
+
+ # Updates the width and height members and container dimensions in response to an
+ # 'option:width' or 'option:height' event.
+ dimensionsChanged: ->
+ @width = @option('width') or @width
+ @height = @option('height') or @height
+ @el.width(@width)
+ @el.height(@height)
+
+ # Updates the chart in response to a layer being shown or hidden
+ layerChanged: ->
+ @draw()
+
+# Base class for all SVG charts (via d3).
+class Epoch.Chart.SVG extends Epoch.Chart.Base
+ # Initializes the chart and places the rendering SVG in the specified HTML
+ # containing element.
+ # @param [Object] options Options for the SVG chart.
+ # @option options [HTMLElement] el Container element for the chart.
+ # @option options [Array] data Layered data used to render the chart.
+ constructor: (@options={}) ->
+ super(@options)
+ if @el?
+ @svg = @el.append('svg')
+ else
+ @svg = d3.select(document.createElement('svg'))
+ @svg.attr
+ xmlns: 'http://www.w3.org/2000/svg',
+ width: @width,
+ height: @height
+
+ # Resizes the svg element in response to a 'option:width' or 'option:height' event.
+ dimensionsChanged: ->
+ super()
+ @svg.attr('width', @width).attr('height', @height)
+
+# Base Class for all Canvas based charts.
+class Epoch.Chart.Canvas extends Epoch.Chart.Base
+ # Initializes the chart and places the rendering canvas in the specified
+ # HTML container element.
+ # @param [Object] options Options for the SVG chart.
+ # @option options [HTMLElement] el Container element for the chart.
+ # @option options [Array] data Layered data used to render the chart.
+ constructor: (@options={}) ->
+ super(@options)
+
+ if @options.pixelRatio?
+ @pixelRatio = @options.pixelRatio
+ else if window.devicePixelRatio?
+ @pixelRatio = window.devicePixelRatio
+ else
+ @pixelRatio = 1
+
+ @canvas = d3.select( document.createElement('CANVAS') )
+ @canvas.style
+ 'width': "#{@width}px"
+ 'height': "#{@height}px"
+
+ @canvas.attr
+ width: @getWidth()
+ height: @getHeight()
+
+ @el.node().appendChild @canvas.node() if @el?
+ @ctx = Epoch.Util.getContext @canvas.node()
+
+ # @return [Number] width of the canvas with respect to the pixel ratio of the display
+ getWidth: -> @width * @pixelRatio
+
+ # @return [Number] height of the canvas with respect to the pixel ratio of the display
+ getHeight: -> @height * @pixelRatio
+
+ # Clears the render canvas.
+ clear: ->
+ @ctx.clearRect(0, 0, @getWidth(), @getHeight())
+
+ # @return [Object] computed styles for the given selector in the context of this chart.
+ # @param [String] selector The selector used to compute the styles.
+ getStyles: (selector) ->
+ Epoch.QueryCSS.getStyles(selector, @el)
+
+ # Resizes the canvas element when the dimensions of the container change
+ dimensionsChanged: ->
+ super()
+ @canvas.style {'width': "#{@width}px", 'height': "#{@height}px"}
+ @canvas.attr { width: @getWidth(), height: @getHeight() }
+
+ # Purges QueryCSS cache and redraws the Canvas based chart.
+ redraw: ->
+ Epoch.QueryCSS.purge()
+ @draw()
diff --git a/debian/missing-sources/epoch/src/core/context.coffee b/debian/missing-sources/epoch/src/core/context.coffee
new file mode 100644
index 0000000..8566552
--- /dev/null
+++ b/debian/missing-sources/epoch/src/core/context.coffee
@@ -0,0 +1,25 @@
+# Rendering context used for unit testing.
+class Epoch.TestContext
+ VOID_METHODS = [
+ 'arc', 'arcTo', 'beginPath', 'bezierCurveTo', 'clearRect',
+ 'clip', 'closePath', 'drawImage', 'fill', 'fillRect', 'fillText',
+ 'moveTo', 'quadraticCurveTo', 'rect', 'restore', 'rotate', 'save',
+ 'scale', 'scrollPathIntoView', 'setLineDash', 'setTransform',
+ 'stroke', 'strokeRect', 'strokeText', 'transform', 'translate', 'lineTo'
+ ]
+
+ # Creates a new test rendering context.
+ constructor: ->
+ @_log = []
+ @_makeFauxMethod(method) for method in VOID_METHODS
+
+ # Creates a fake method with the given name that logs the method called
+ # and arguments passed when executed.
+ # @param name Name of the fake method to create.
+ _makeFauxMethod: (name) ->
+ @[name] = -> @_log.push "#{name}(#{(arg.toString() for arg in arguments).join(',')})"
+
+ # Faux method that emulates the "getImageData" method
+ getImageData: ->
+ @_log.push "getImageData(#{(arg.toString() for arg in arguments).join(',')})"
+ return { width: 0, height: 0, resolution: 1.0, data: [] }
diff --git a/debian/missing-sources/epoch/src/core/css.coffee b/debian/missing-sources/epoch/src/core/css.coffee
new file mode 100644
index 0000000..03a7310
--- /dev/null
+++ b/debian/missing-sources/epoch/src/core/css.coffee
@@ -0,0 +1,131 @@
+# Singelton class used to query CSS styles by way of reference elements.
+# This allows canvas based visualizations to use the same styles as their
+# SVG counterparts.
+class QueryCSS
+ # Reference container id
+ REFERENCE_CONTAINER_ID = '_canvas_css_reference'
+
+ # Container Hash Attribute
+ CONTAINER_HASH_ATTR = 'data-epoch-container-id'
+
+ # Handles automatic container id generation
+ containerCount = 0
+ nextContainerId = -> "epoch-container-#{containerCount++}"
+
+ # Expression used to derive tag name, id, and class names from
+ # selectors given the the put method.
+ PUT_EXPR = /^([^#. ]+)?(#[^. ]+)?(\.[^# ]+)?$/
+
+ # Whether or not to log full selector lists
+ logging = false
+
+ # Converts selectors into actual dom elements (replaces put.js)
+ # Limited the functionality to what Epoch actually needs to
+ # operate correctly. We detect class names, ids, and element
+ # tag names.
+ put = (selector) ->
+ match = selector.match(PUT_EXPR)
+ return Epoch.error('Query CSS cannot match given selector: ' + selector) unless match?
+ [whole, tag, id, classNames] = match
+ tag = (tag ? 'div').toUpperCase()
+
+ element = document.createElement(tag)
+ element.id = id.substr(1) if id?
+ if classNames?
+ element.className = classNames.substr(1).replace(/\./g, ' ')
+
+ return element
+
+ # Lets the user set whether or not to log selector lists and resulting DOM trees.
+ # Useful for debugging QueryCSS itself.
+ @log: (b) ->
+ logging = b
+
+ # Key-Value cache for computed styles that we found using this class.
+ @cache = {}
+
+ # List of styles to pull from the full list of computed styles
+ @styleList = ['fill', 'stroke', 'stroke-width']
+
+ # The svg reference container
+ @container = null
+
+ # Purges the selector to style cache
+ @purge: ->
+ QueryCSS.cache = {}
+
+ # Gets the reference element container.
+ @getContainer: ->
+ return QueryCSS.container if QueryCSS.container?
+ container = document.createElement('DIV')
+ container.id = REFERENCE_CONTAINER_ID
+ document.body.appendChild(container)
+ QueryCSS.container = d3.select(container)
+
+ # @return [String] A unique identifier for the given container and selector.
+ # @param [String] selector Selector from which to derive the styles
+ # @param container The containing element for a chart.
+ @hash: (selector, container) ->
+ containerId = container.attr(CONTAINER_HASH_ATTR)
+ unless containerId?
+ containerId = nextContainerId()
+ container.attr(CONTAINER_HASH_ATTR, containerId)
+ return "#{containerId}__#{selector}"
+
+ # @return The computed styles for the given selector in the given container element.
+ # @param [String] selector Selector from which to derive the styles.
+ # @param container HTML containing element in which to place the reference SVG.
+ @getStyles: (selector, container) ->
+ # 0) Check for cached styles
+ cacheKey = QueryCSS.hash(selector, container)
+ cache = QueryCSS.cache[cacheKey]
+ return cache if cache?
+
+ # 1) Build a full reference tree (parents, container, and selector elements)
+ parents = []
+ parentNode = container.node().parentNode
+
+ while parentNode? and parentNode.nodeName.toLowerCase() != 'body'
+ parents.unshift parentNode
+ parentNode = parentNode.parentNode
+ parents.push container.node()
+
+ selectorList = []
+ for element in parents
+ sel = element.nodeName.toLowerCase()
+ if element.id? and element.id.length > 0
+ sel += '#' + element.id
+ if element.className? and element.className.length > 0
+ sel += '.' + Epoch.Util.trim(element.className).replace(/\s+/g, '.')
+ selectorList.push sel
+
+ selectorList.push('svg')
+
+ for subSelector in Epoch.Util.trim(selector).split(/\s+/)
+ selectorList.push(subSelector)
+
+ console.log(selectorList) if logging
+
+ parent = root = put(selectorList.shift())
+ while selectorList.length
+ el = put(selectorList.shift())
+ parent.appendChild el
+ parent = el
+
+ console.log(root) if logging
+
+ # 2) Place the reference tree and fetch styles given the selector
+ QueryCSS.getContainer().node().appendChild(root)
+
+ ref = d3.select('#' + REFERENCE_CONTAINER_ID + ' ' + selector)
+ styles = {}
+ for name in QueryCSS.styleList
+ styles[name] = ref.style(name)
+ QueryCSS.cache[cacheKey] = styles
+
+ # 3) Cleanup and return the styles
+ QueryCSS.getContainer().html('')
+ return styles
+
+
+Epoch.QueryCSS = QueryCSS
\ No newline at end of file
diff --git a/debian/missing-sources/epoch/src/core/d3.coffee b/debian/missing-sources/epoch/src/core/d3.coffee
new file mode 100644
index 0000000..a33d717
--- /dev/null
+++ b/debian/missing-sources/epoch/src/core/d3.coffee
@@ -0,0 +1,25 @@
+# Gets the width of the first node, or sets the width of all the nodes
+# in a d3 selection.
+# @param value [Number, String] (optional) Width to set for all the nodes in the selection.
+# @return The selection if setting the width of the nodes, or the width
+# in pixels of the first node in the selection.
+d3.selection::width = (value) ->
+ if value? and Epoch.isString(value)
+ @style('width', value)
+ else if value? and Epoch.isNumber(value)
+ @style('width', "#{value}px")
+ else
+ +Epoch.Util.getComputedStyle(@node(), null).width.replace('px', '')
+
+# Gets the height of the first node, or sets the height of all the nodes
+# in a d3 selection.
+# @param value (optional) Height to set for all the nodes in the selection.
+# @return The selection if setting the height of the nodes, or the height
+# in pixels of the first node in the selection.
+d3.selection::height = (value) ->
+ if value? and Epoch.isString(value)
+ @style('height', value)
+ else if value? and Epoch.isNumber(value)
+ @style('height', "#{value}px")
+ else
+ +Epoch.Util.getComputedStyle(@node(), null).height.replace('px', '')
\ No newline at end of file
diff --git a/debian/missing-sources/epoch/src/core/format.coffee b/debian/missing-sources/epoch/src/core/format.coffee
new file mode 100644
index 0000000..d65932f
--- /dev/null
+++ b/debian/missing-sources/epoch/src/core/format.coffee
@@ -0,0 +1,15 @@
+# Tick formatter identity.
+Epoch.Formats.regular = (d) -> d
+
+# Tick formatter that formats the numbers using standard SI postfixes.
+Epoch.Formats.si = (d) -> Epoch.Util.formatSI(d)
+
+# Tick formatter for percentages.
+Epoch.Formats.percent = (d) -> (d*100).toFixed(1) + "%"
+
+# Tick formatter for seconds from timestamp data.
+Epoch.Formats.seconds = (t) -> d3Seconds(new Date(t*1000))
+d3Seconds = d3.time.format('%I:%M:%S %p')
+
+# Tick formatter for bytes
+Epoch.Formats.bytes = (d) -> Epoch.Util.formatBytes(d)
diff --git a/debian/missing-sources/epoch/src/core/util.coffee b/debian/missing-sources/epoch/src/core/util.coffee
new file mode 100644
index 0000000..30995bf
--- /dev/null
+++ b/debian/missing-sources/epoch/src/core/util.coffee
@@ -0,0 +1,236 @@
+typeFunction = (objectName) -> (v) ->
+ Object::toString.call(v) == "[object #{objectName}]"
+
+# @return [Boolean] true
if the given value is an array, false
otherwise.
+# @param v Value to test.
+Epoch.isArray = Array.isArray ? typeFunction('Array')
+
+# @return [Boolean] true
if the given value is an object, false
otherwise.
+# @param v Value to test.
+Epoch.isObject = typeFunction('Object')
+
+# @return [Boolean] true
if the given value is a string, false
otherwise.
+# @param v Value to test.
+Epoch.isString = typeFunction('String')
+
+# @return [Boolean] true
if the given value is a function, false
otherwise.
+# @param v Value to test.
+Epoch.isFunction = typeFunction('Function')
+
+# @return [Boolean] true
if the given value is a number, false
otherwise.
+# @param v Value to test.
+Epoch.isNumber = typeFunction('Number')
+
+# Attempts to determine if a given value represents a DOM element. The result is always correct if the
+# browser implements DOM Level 2, but one can fool it on certain versions of IE. Adapted from:
+# Stack Overflow #384286.
+# @return [Boolean] true
if the given value is a DOM element, false
otherwise.
+# @param v Value to test.
+Epoch.isElement = (v) ->
+ if HTMLElement?
+ v instanceof HTMLElement
+ else
+ v? and Epoch.isObject(v) and v.nodeType == 1 and Epoch.isString(v.nodeName)
+
+# Determines if a given value is a non-empty array.
+# @param v Value to test.
+# @return [Boolean] true
if the given value is an array with at least one element.
+Epoch.isNonEmptyArray = (v) ->
+ Epoch.isArray(v) and v.length > 0
+
+# Generates shallow copy of an object.
+# @return A shallow copy of the given object.
+# @param [Object] original Object for which to make the shallow copy.
+Epoch.Util.copy = (original) ->
+ return null unless original?
+ copy = {}
+ copy[k] = v for own k, v of original
+ return copy
+
+# Creates a deep copy of the given options filling in missing defaults.
+# @param [Object] options Options to copy.
+# @param [Object] defaults Default values for the options.
+Epoch.Util.defaults = (options, defaults) ->
+ result = Epoch.Util.copy(options)
+ for own k, v of defaults
+ opt = options[k]
+ def = defaults[k]
+ bothAreObjects = Epoch.isObject(opt) and Epoch.isObject(def)
+
+ if opt? and def?
+ if bothAreObjects and not Epoch.isArray(opt)
+ result[k] = Epoch.Util.defaults(opt, def)
+ else
+ result[k] = opt
+ else if opt?
+ result[k] = opt
+ else
+ result[k] = def
+
+ return result
+
+# Formats numbers with standard postfixes (e.g. K, M, G)
+# @param [Number] v Value to format.
+# @param [Integer] fixed Number of floating point digits to fix after conversion.
+# @param [Boolean] fixIntegers Whether or not to add floating point digits to non-floating point results.
+# @example Formatting a very large number
+# Epoch.Util.formatSI(1120000) == "1.1 M"
+Epoch.Util.formatSI = (v, fixed=1, fixIntegers=false) ->
+ if v < 1000
+ q = v
+ q = q.toFixed(fixed) unless (q|0) == q and !fixIntegers
+ return q
+
+ for own i, label of ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
+ base = Math.pow(10, ((i|0)+1)*3)
+ if v >= base and v < Math.pow(10, ((i|0)+2)*3)
+ q = v/base
+ q = q.toFixed(fixed) unless (q % 1) == 0 and !fixIntegers
+ return "#{q} #{label}"
+
+# Formats large bandwidth and disk space usage numbers with byte postfixes (e.g. KB, MB, GB, etc.)
+# @param [Number] v Value to format.
+# @param [Integer] fixed Number of floating point digits to fix after conversion.
+# @param [Boolean] fixIntegers Whether or not to add floating point digits to non-floating point results.
+# @example Formatting a large number of bytes
+# Epoch.Util.formatBytes(5.21 * Math.pow(2, 20)) == "5.2 MB"
+Epoch.Util.formatBytes = (v, fixed=1, fix_integers=false) ->
+ if v < 1024
+ q = v
+ q = q.toFixed(fixed) unless (q % 1) == 0 and !fix_integers
+ return "#{q} B"
+
+ for own i, label of ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+ base = Math.pow(1024, (i|0)+1)
+ if v >= base and v < Math.pow(1024, (i|0)+2)
+ q = v/base
+ q = q.toFixed(fixed) unless (q % 1) == 0 and !fix_integers
+ return "#{q} #{label}"
+
+# @return a "dasherized" css class names from a given string
+# @example Using dasherize
+# Epoch.Util.dasherize('My Awesome Name') == 'my-awesome-name'
+Epoch.Util.dasherize = (str) ->
+ Epoch.Util.trim(str).replace("\n", '').replace(/\s+/g, '-').toLowerCase()
+
+# @return the full domain of a given variable from an array of layers
+# @param [Array] layers Layered plot data.
+# @param [String] key The key name of the value at on each entry in the layers.
+Epoch.Util.domain = (layers, key='x') ->
+ set = {}
+ domain = []
+ for layer in layers
+ for entry in layer.values
+ continue if set[entry[key]]?
+ domain.push(entry[key])
+ set[entry[key]] = true
+ return domain
+
+# Strips whitespace from the beginning and end of a string.
+# @param [String] string String to trim.
+# @return [String] The string without leading or trailing whitespace.
+# Returns null if the given parameter was not a string.
+Epoch.Util.trim = (string) ->
+ return null unless Epoch.isString(string)
+ string.replace(/^\s+/g, '').replace(/\s+$/g, '')
+
+# Returns the computed styles of an element in the document
+# @param [HTMLElement] Element for which to fetch the styles.
+# @param [String] pseudoElement Pseudo selectors on which to search for the element.
+# @return [Object] The styles for the given element.
+Epoch.Util.getComputedStyle = (element, pseudoElement) ->
+ if Epoch.isFunction(window.getComputedStyle)
+ window.getComputedStyle(element, pseudoElement)
+ else if element.currentStyle?
+ element.currentStyle
+
+# Converts a CSS color string into an RGBA string with the given opacity
+# @param [String] color Color string to convert into an rgba
+# @param [Number] opacity Opacity to use for the resulting color.
+# @return the resulting rgba color string.
+Epoch.Util.toRGBA = (color, opacity) ->
+ if (parts = color.match /^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*[0-9\.]+\)/)
+ [all, r, g, b] = parts
+ result = "rgba(#{r},#{g},#{b},#{opacity})"
+ else if (v = d3.rgb color)
+ result = "rgba(#{v.r},#{v.g},#{v.b},#{opacity})"
+ return result
+
+# Obtains a graphics context for the given canvas node. Nice to have
+# this abstracted out in case we want to support WebGL in the future.
+# Also allows us to setup a special context when unit testing, as
+# jsdom doesn't have canvas support, and node-canvas is a pain in the
+# butt to install properly across different platforms.
+Epoch.Util.getContext = (node, type='2d') ->
+ node.getContext(type)
+
+# Basic eventing base class for all Epoch classes.
+class Epoch.Events
+ constructor: ->
+ @_events = {}
+
+ # Registers a callback to a given event.
+ # @param [String] name Name of the event.
+ # @param [Function, String] callback Either a closure to call when the event fires
+ # or a string that denotes a method name to call on this object.
+ on: (name, callback) ->
+ return unless callback?
+ @_events[name] ?= []
+ @_events[name].push callback
+
+ # Registers a map of event names to given callbacks. This method calls .on
+ # directly for each of the events given.
+ # @param [Object] map A map of event names to callbacks.
+ onAll: (map) ->
+ return unless Epoch.isObject(map)
+ @on(name, callback) for own name, callback of map
+
+ # Removes a specific callback listener or all listeners for a given event.
+ # @param [String] name Name of the event.
+ # @param [Function, String] callback (Optional) Callback to remove from the listener list.
+ # If this parameter is not provided then all listeners will be removed for the event.
+ off: (name, callback) ->
+ return unless Epoch.isArray(@_events[name])
+ return delete(@_events[name]) unless callback?
+ while (i = @_events[name].indexOf(callback)) >= 0
+ @_events[name].splice(i, 1)
+
+ # Removes a set of callback listeners for all events given in the map or array of strings.
+ # This method calls .off
directly for each event and callback to remove.
+ # @param [Object, Array] mapOrList Either a map that associates event names to specific callbacks
+ # or an array of event names for which to completely remove listeners.
+ offAll: (mapOrList) ->
+ if Epoch.isArray(mapOrList)
+ @off(name) for name in mapOrList
+ else if Epoch.isObject(mapOrList)
+ @off(name, callback) for own name, callback of mapOrList
+
+ # Triggers an event causing all active listeners to be executed.
+ # @param [String] name Name of the event to fire.
+ trigger: (name) ->
+ return unless @_events[name]?
+ args = (arguments[i] for i in [1...arguments.length])
+ for callback in @_events[name]
+ fn = null
+ if Epoch.isString(callback)
+ fn = @[callback]
+ else if Epoch.isFunction(callback)
+ fn = callback
+ unless fn?
+ Epoch.exception "Callback for event '#{name}' is not a function or reference to a method."
+ fn.apply @, args
+
+# Performs a single pass flatten on a multi-array
+# @param [Array] multiarray A deep multi-array to flatten
+# @returns [Array] A single pass flatten of the multi-array
+Epoch.Util.flatten = (multiarray) ->
+ if !Array.isArray(multiarray)
+ throw new Error('Epoch.Util.flatten only accepts arrays')
+ result = []
+ for array in multiarray
+ if Array.isArray(array)
+ for item in array
+ result.push item
+ else
+ result.push array
+ result
diff --git a/debian/missing-sources/epoch/src/data.coffee b/debian/missing-sources/epoch/src/data.coffee
new file mode 100644
index 0000000..7627fd4
--- /dev/null
+++ b/debian/missing-sources/epoch/src/data.coffee
@@ -0,0 +1,311 @@
+Epoch.Data ?= {}
+Epoch.Data.Format ?= {}
+
+# Private Helper Function for data formats below
+applyLayerLabel = (layer, options, i, keys=[]) ->
+ [labels, autoLabels, keyLabels] = [options.labels, options.autoLabels, options.keyLabels]
+ if labels? and Epoch.isArray(labels) and labels.length > i
+ layer.label = labels[i]
+ else if keyLabels and keys.length > i
+ layer.label = keys[i]
+ else if autoLabels
+ label = []
+ while i >= 0
+ label.push String.fromCharCode(65+(i%26))
+ i -= 26
+ layer.label = label.join('')
+ return layer
+
+# Formats a given input array for the chart of the specified type. Notes:
+#
+# * Basic pie charts require a flat array of numbers
+# * Real-time histogram charts require sparse histogram objects
+#
+# @param data Data array to format (can be multidimensional to allow for multiple layers).
+# @option options [String] type Type of chart for which to format the data.
+# @option options [Function] x(d, i) Maps the data to x values given a data point and the index of the point.
+# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point.
+# @option options [Function] time(d, i, startTime) Maps the data to time values for real-time plots given the point and index.
+# @option options [Array] labels Labels to apply to each data layer.
+# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true.
+# @option options [Number] startTime Unix timestamp used as the starting point for auto acsending times in
+# real-time data formatting.
+Epoch.Data.Format.array = (->
+ defaultOptions =
+ x: (d, i) -> i
+ y: (d, i) -> d
+ time: (d, i, startTime) -> parseInt(startTime) + parseInt(i)
+ type: 'area'
+ autoLabels: false
+ labels: []
+ startTime: parseInt(new Date().getTime() / 1000)
+
+ buildLayers = (data, options, mapFn) ->
+ result = []
+ if Epoch.isArray(data[0])
+ for own i, series of data
+ result.push applyLayerLabel({values: series.map(mapFn)}, options, parseInt(i))
+ else
+ result.push applyLayerLabel({values: data.map(mapFn)}, options, 0)
+ return result
+
+ formatBasicPlot = (data, options) ->
+ buildLayers data, options, (d, i) ->
+ { x: options.x(d, i), y: options.y(d, i) }
+
+ formatTimePlot = (data, options) ->
+ buildLayers data, options, (d, i) ->
+ { time: options.time(d, i, options.startTime), y: options.y(d, i) }
+
+ formatHeatmap = (data, options) ->
+ buildLayers data, options, (d, i) ->
+ { time: options.time(d, i, options.startTime), histogram: d }
+
+ formatPie = (data, options) ->
+ result = []
+ for own i, v of data
+ return [] unless Epoch.isNumber(data[0])
+ result.push applyLayerLabel({ value: v }, options, i)
+ return result
+
+ format = (data=[], options={}) ->
+ return [] unless Epoch.isNonEmptyArray(data)
+ opt = Epoch.Util.defaults options, defaultOptions
+
+ if opt.type == 'time.heatmap'
+ formatHeatmap data, opt
+ else if opt.type.match /^time\./
+ formatTimePlot data, opt
+ else if opt.type == 'pie'
+ formatPie data, opt
+ else
+ formatBasicPlot data, opt
+
+ format.entry = (datum, options={}) ->
+ if options.type == 'time.gauge'
+ return 0 unless datum?
+ opt = Epoch.Util.defaults options, defaultOptions
+ d = if Epoch.isArray(datum) then datum[0] else datum
+ return opt.y(d, 0)
+
+ return [] unless datum?
+ unless options.startTime?
+ options.startTime = parseInt(new Date().getTime() / 1000)
+
+ if Epoch.isArray(datum)
+ data = datum.map (d) -> [d]
+ else
+ data = [datum]
+
+ (layer.values[0] for layer in format(data, options))
+
+ return format
+)()
+
+# Formats an input array of tuples such that the first element of the tuple is set
+# as the x-coordinate and the second element as the y-coordinate. Supports layers
+# of tupled series. For real-time plots the first element of a tuple is set as the
+# time component of the value.
+#
+# This formatter will return an empty array if the chart type
option is
+# set as 'time.heatmap', 'time.gauge', or 'pie'.
+#
+# @param data Data array to format (can be multidimensional to allow for multiple layers).
+# @option options [String] type Type of chart for which to format the data.
+# @option options [Function] x(d, i) Maps the data to x values given a data point and the index of the point.
+# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point.
+# @option options [Function] time(d, i, startTime) Maps the data to time values for real-time plots given the point and index.
+# @option options [Array] labels Labels to apply to each data layer.
+# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true.
+Epoch.Data.Format.tuple = (->
+ defaultOptions =
+ x: (d, i) -> d
+ y: (d, i) -> d
+ time: (d, i) -> d
+ type: 'area'
+ autoLabels: false
+ labels: []
+
+ buildLayers = (data, options, mapFn) ->
+ return [] unless Epoch.isArray(data[0])
+ result = []
+ if Epoch.isArray(data[0][0])
+ for own i, series of data
+ result.push applyLayerLabel({values: series.map(mapFn)}, options, parseInt(i))
+ else
+ result.push applyLayerLabel({values: data.map(mapFn)}, options, 0)
+ return result
+
+ format = (data=[], options={}) ->
+ return [] unless Epoch.isNonEmptyArray(data)
+ opt = Epoch.Util.defaults options, defaultOptions
+
+ if opt.type == 'pie' or opt.type == 'time.heatmap' or opt.type == 'time.gauge'
+ return []
+ else if opt.type.match /^time\./
+ buildLayers data, opt, (d, i) ->
+ {time: opt.time(d[0], parseInt(i)), y: opt.y(d[1], parseInt(i))}
+ else
+ buildLayers data, opt, (d, i) ->
+ {x: opt.x(d[0], parseInt(i)), y: opt.y(d[1], parseInt(i))}
+
+ format.entry = (datum, options={}) ->
+ return [] unless datum?
+ unless options.startTime?
+ options.startTime = parseInt(new Date().getTime() / 1000)
+
+ if Epoch.isArray(datum) and Epoch.isArray(datum[0])
+ data = datum.map (d) -> [d]
+ else
+ data = [datum]
+
+ (layer.values[0] for layer in format(data, options))
+
+ return format
+)()
+
+# This formatter expects to be passed a flat array of objects and a list of keys.
+# It then extracts the value for each key across each of the objects in the array
+# to produce multi-layer plot data of the given chart type. Note that this formatter
+# also can be passed an x
or time
option as a string that
+# allows the programmer specify a key to use for the value of the first component
+# (x or time) of each resulting layer value.
+#
+# Note that this format does not work with basic pie charts nor real-time gauge charts.
+#
+# @param [Array] data Flat array of objects to format.
+# @param [Array] keys List of keys used to extract data from each of the objects.
+# @option options [String] type Type of chart for which to format the data.
+# @option options [Function, String] x Either the key to use for the x-componet of
+# the resulting values or a function of the data at that point and index of the data.
+# @option options [Function, String] time Either an object key or function to use for the
+# time-component of resulting real-time plot values.
+# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point.
+# @option options [Array] labels Labels to apply to each data layer.
+# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true.
+# @option options [Boolean] keyLabels Apply labels using the keys passed to the formatter (defaults to true).
+# @option options [Number] startTime Unix timestamp used as the starting point for auto acsending times in
+# real-time data formatting.
+Epoch.Data.Format.keyvalue = (->
+ defaultOptions =
+ type: 'area',
+ x: (d, i) -> parseInt(i)
+ y: (d, i) -> d
+ time: (d, i, startTime) -> parseInt(startTime) + parseInt(i)
+ labels: []
+ autoLabels: false
+ keyLabels: true
+ startTime: parseInt(new Date().getTime() / 1000)
+
+ buildLayers = (data, keys, options, mapFn) ->
+ result = []
+ for own j, key of keys
+ values = []
+ for own i, d of data
+ values.push mapFn(d, key, parseInt(i))
+ result.push applyLayerLabel({ values: values }, options, parseInt(j), keys)
+ return result
+
+ formatBasicPlot = (data, keys, options) ->
+ buildLayers data, keys, options, (d, key, i) ->
+ if Epoch.isString(options.x)
+ x = d[options.x]
+ else
+ x = options.x(d, parseInt(i))
+ { x: x, y: options.y(d[key], parseInt(i)) }
+
+ formatTimePlot = (data, keys, options, rangeName='y') ->
+ buildLayers data, keys, options, (d, key, i) ->
+ if Epoch.isString(options.time)
+ value = { time: d[options.time] }
+ else
+ value = { time: options.time(d, parseInt(i), options.startTime) }
+ value[rangeName] = options.y(d[key], parseInt(i))
+ value
+
+ format = (data=[], keys=[], options={}) ->
+ return [] unless Epoch.isNonEmptyArray(data) and Epoch.isNonEmptyArray(keys)
+ opt = Epoch.Util.defaults options, defaultOptions
+
+ if opt.type == 'pie' or opt.type == 'time.gauge'
+ return []
+ else if opt.type == 'time.heatmap'
+ formatTimePlot data, keys, opt, 'histogram'
+ else if opt.type.match /^time\./
+ formatTimePlot data, keys, opt
+ else
+ formatBasicPlot data, keys, opt
+
+ format.entry = (datum, keys=[], options={}) ->
+ return [] unless datum? and Epoch.isNonEmptyArray(keys)
+ unless options.startTime?
+ options.startTime = parseInt(new Date().getTime() / 1000)
+ (layer.values[0] for layer in format([datum], keys, options))
+
+ return format
+)()
+
+# Convenience data formatting method for easily accessing the various formatters.
+# @param [String] formatter Name of the formatter to use.
+# @param [Array] data Data to format.
+# @param [Object] options Options to pass to the formatter (if any).
+Epoch.data = (formatter, args...) ->
+ return [] unless (formatFn = Epoch.Data.Format[formatter])?
+ formatFn.apply formatFn, args
+
+
+# Method used by charts and models for handling option based data formatting.
+# Abstracted here because we'd like to allow models and indivisual charts to
+# perform this action depending on the context.
+Epoch.Data.formatData = (data=[], type, dataFormat) ->
+ return data unless Epoch.isNonEmptyArray(data)
+
+ if Epoch.isString(dataFormat)
+ opts = { type: type }
+ return Epoch.data(dataFormat, data, opts)
+
+ return data unless Epoch.isObject(dataFormat)
+ return data unless dataFormat.name? and Epoch.isString(dataFormat.name)
+ return data unless Epoch.Data.Format[dataFormat.name]?
+
+ args = [dataFormat.name, data]
+ if dataFormat.arguments? and Epoch.isArray(dataFormat.arguments)
+ args.push(a) for a in dataFormat.arguments
+
+ if dataFormat.options?
+ opts = dataFormat.options
+ if type?
+ opts.type ?= type
+ args.push opts
+ else if type?
+ args.push {type: type}
+
+ Epoch.data.apply(Epoch.data, args)
+
+# Method used to format incoming entries for real-time charts.
+Epoch.Data.formatEntry = (datum, type, format) ->
+ return datum unless format?
+
+ if Epoch.isString(format)
+ opts = { type: type }
+ return Epoch.Data.Format[format].entry datum, opts
+
+ return datum unless Epoch.isObject(format)
+ return datum unless format.name? and Epoch.isString(format.name)
+ return datum unless Epoch.Data.Format[format.name]?
+
+ dataFormat = Epoch.Util.defaults format, {}
+
+ args = [datum]
+ if dataFormat.arguments? and Epoch.isArray(dataFormat.arguments)
+ args.push(a) for a in dataFormat.arguments
+
+ if dataFormat.options?
+ opts = dataFormat.options
+ opts.type = type
+ args.push opts
+ else if type?
+ args.push {type: type}
+
+ entry = Epoch.Data.Format[dataFormat.name].entry
+ entry.apply entry, args
diff --git a/debian/missing-sources/epoch/src/epoch.coffee b/debian/missing-sources/epoch/src/epoch.coffee
new file mode 100644
index 0000000..b2d06ca
--- /dev/null
+++ b/debian/missing-sources/epoch/src/epoch.coffee
@@ -0,0 +1,17 @@
+window.Epoch ?= {}
+window.Epoch.Chart ?= {}
+window.Epoch.Time ?= {}
+window.Epoch.Util ?= {}
+window.Epoch.Formats ?= {}
+
+# Sends a warning to the developer console with the given message.
+# @param [String] msg Message for the warning.
+Epoch.warn = (msg) ->
+ (console.warn or console.log)("Epoch Warning: #{msg}")
+
+# Raises an exception with the given message (with the 'Epoch Error:' preamble).
+# @param [String] msg Specific message for the exception.
+Epoch.exception = (msg) ->
+ throw "Epoch Error: #{msg}"
+
+# "I think, baby, I was born just a little late!" -- Middle Class Rut
diff --git a/debian/missing-sources/epoch/src/model.coffee b/debian/missing-sources/epoch/src/model.coffee
new file mode 100644
index 0000000..76f6acd
--- /dev/null
+++ b/debian/missing-sources/epoch/src/model.coffee
@@ -0,0 +1,55 @@
+# Data model for epoch charts. By instantiating a model and passing it to each
+# of the charts on a page the application programmer can update data once and
+# have each of the charts respond accordingly.
+#
+# In addition to setting basic / historical data via the setData method, the
+# model also supports the push method, which when used will cause real-time
+# plots to automatically update and animate.
+class Epoch.Model extends Epoch.Events
+ defaults =
+ dataFormat: null
+
+ # Creates a new Model.
+ # @option options dataFormat The default data fromat for the model.
+ # @option data Initial data for the model.
+ constructor: (options={}) ->
+ super()
+ options = Epoch.Util.defaults options, defaults
+ @dataFormat = options.dataFormat
+ @data = options.data
+ @loading = false
+
+ # Sets the model's data.
+ # @param data Data to set for the model.
+ # @event data:updated Instructs listening charts that new data is available.
+ setData: (data) ->
+ @data = data
+ @trigger 'data:updated'
+
+ # Pushes a new entry into the model.
+ # @param entry Entry to push.
+ # @event data:push Instructs listening charts that a new data entry is available.
+ push: (entry) ->
+ @entry = entry
+ @trigger 'data:push'
+
+ # Determines if the model has data.
+ # @return true if the model has data, false otherwise.
+ hasData: ->
+ @data?
+
+ # Retrieves and formats adata for the specific chart type and data format.
+ # @param [String] type Type of the chart for which to fetch the data.
+ # @param [String, Object] dataFormat (optional) Used to override the model's default data format.
+ # @return The model's data formatted based the parameters.
+ getData: (type, dataFormat) ->
+ dataFormat ?= @dataFormat
+ Epoch.Data.formatData @data, type, dataFormat
+
+ # Retrieves the latest data entry that was pushed into the model.
+ # @param [String] type Type of the chart for which to fetch the data.
+ # @param [String, Object] dataFormat (optional) Used to override the model's default data format.
+ # @return The model's next data entry formatted based the parameters.
+ getNext: (type, dataFormat) ->
+ dataFormat ?= @dataFormat
+ Epoch.Data.formatEntry @entry, type, dataFormat
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] true
if the axis was set in the options, false
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] true
if the plot is animating, false
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 _startTransition
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 option:axes
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 option.ticks.*
event.
+ ticksChanged: ->
+ @_resetInitialTimeTicks()
+ @_transitionRangeAxes()
+ @draw(@animation.frame * @animation.delta())
+
+ # Updates tick formats in response to an option.tickFormats.*
event.
+ tickFormatsChanged: ->
+ @_resetInitialTimeTicks()
+ @_transitionRangeAxes()
+ @draw(@animation.frame * @animation.delta())
+
+ # Updates margins in response to an option.margins.*
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 update()
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 option:
+ domainChanged: -> @draw()
+
+ # Correctly responds to an option:
+ ticksChanged: -> @draw()
+
+ # Correctly responds to an option:
+ tickSizeChanged: -> @draw()
+
+ # Correctly responds to an option:
+ tickOffsetChanged: -> @draw()
+
+ # Correctly responds to an option:
+ 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 option:buckets
event.
+ bucketsChanged: -> @redraw()
+
+ # Changes the range of the buckets in response to an option:bucketRange
event.
+ bucketRangeChanged: ->
+ @_transitionRangeAxes()
+ @redraw()
+
+ # Changes the opacity function in response to an option:opacity
event.
+ opacityChanged: ->
+ @_setOpacityFunction()
+ @redraw()
+
+ # Changes the bucket padding in response to an option:bucketPadding
event.
+ bucketPaddingChanged: -> @redraw()
+
+ # Changes whether or not to paint zeros in response to an option:paintZeroValues
event.
+ paintZeroValuesChanged: -> @redraw()
+
+ # Changes whether or not to cut outliers when bucketing in response to an
+ # option:cutOutliers
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()
diff --git a/debian/missing-sources/epoch/tests/render/basic/area.html b/debian/missing-sources/epoch/tests/render/basic/area.html
new file mode 100644
index 0000000..ea23851
--- /dev/null
+++ b/debian/missing-sources/epoch/tests/render/basic/area.html
@@ -0,0 +1,450 @@
+
+
+
+
+
+
+
+
+
+
+
+ Basic Area Chart Test
+
+
+
+
+
+
+
+ 1. Single Series
+ y = cos(x) + 1
over the range [0, 2π)
.2. Single Series II
+ y = sin(x) + 1
over the range [0, 2π)
.3. Multi-series Plot
+
+
+ over the range y = x
y = 2x
y = 3x
[0, 10)
.
+
+
+ 4. Multi-series Plot II
+
+
+ over the range [-1, 1).
+
+
+ y = |x|
y = x2
y = |x3|
5. Single Series Transition
+ y = |x|
over the range [-10, 10) and y = x2
over the range [-20, 20). The transition should be initiated when pressing the buttons below the plot.
+ 6. Multi Series Transition
+
+
+ over the range [1, 100). and Set B:
+ y = x
y = 2*x
y = 3*x
+
+ over the range [1, 100). The transition should be initiated when pressing the buttons below the plot.
+
+ y = ln(x)
y = 2*ln(x)
y = 3*ln(x)
7. Single Series to Multi Series Transition
+
+
+ To a multi series set, plotting the functions:
+ y = x2 - 0.5*x
+
+ over the range [1, 4) for all plots. The transition should be initiated when pressing the buttons below the plot.
+
+
+ y = ln(x)
y = x
y = x * ln(x)
8. Layer Color Override
+ 9. Categorical Color Switching
+
+
+ The colors should change when pressing the buttons for each categorical type below the chart.
+
+
+
+
+ category10
category20
category20b
category20c
10. Multi Series without Labels
+
+
+ where the layers are given without labels.
+
+
+ y = sin(x) + 1
y = cos(x) + 1
11. Hide/Show Layers
+ Basic Bar Chart Test
+
+
+
+
+
+
+
+ 1. Single Series
+ y = cos(x) + 1
over the range [0, 2π)
.2. Single Series II
+ y = sin(x) + 1
over the range [0, 2π)
.3. Multi-series Plot
+
+
+ over the range y = x
y = 2x
y = 3x
[0, 10)
.
+
+
+ 4. Multi-series Plot II
+
+
+ over the range [-1, 1).
+
+
+ y = |x|
y = x2
y = |x3|
5. Single Series Transition
+ y = |x|
over the range [-10, 10) and y = x2
over the range [-20, 20). The transition is initiated by pressing the buttons below the plot.
+ 6. Multi Series Transition
+
+
+ over the range [1, 100). and Set B:
+ y = x
y = 2*x
y = 3*x
+
+ over the range [1, 100). The transition is initiated by pressing the buttons below the plot.
+
+ y = ln(x)
y = 2*ln(x)
y = 3*ln(x)
7. Single Series to Multi Series Transition
+
+
+ To a multi series set, plotting the functions:
+ y = x2 - 0.5*x
+
+ over the range [1, 4) for all plots. The transition is initiated by pressing the buttons below the plot.
+
+
+ y = ln(x)
y = x
y = x * ln(x)
8. Layer Color Override
+ 9. Categorical Color Switching
+
+
+ Change the categorical colors by pressing the buttons below the chart.
+
+
+
+
+ category10
category20
category20b
category20c
10. Multi Series without Labels
+
+
+ where the layers are given without labels.
+
+
+ y = sin(x) + 1
y = cos(x) + 1
11. Horizontally Oriented Single Series
+
+
+ using a horizontal orientation.
+
+
+ 12. Horizontally Oriented Multi Series
+
+
+ using a horizontal orientation.
+
+
+ 13. Horizontally Oriented Multi Series Transition
+
+
+ and transition to the single series plot:
+
+
+
+
+ 14. Vertical to Horizontal Transition
+
+ 15. Padding Changes
+
+ 16. Hide/Show Layers
+
+ 17. Data Formatting
+ 18. Bar Ticks
+ Basic Bar Chart Test
+
+
+
+
+
+ 1. Beta(2, 5)
+ 2. Beta(2, 5) Horizontal
+ 3. Option: buckets
+ 4. Options: bucketRange & cutOutliers
+ Basic Line Chart Test
+
+
+
+
+
+
+ 1. Single Series
+ y = cos(x)
over the range [-2π, 2π)
.2. Single Series II
+ y = ex*sin(x)
from [0, &pi)
.3. Multi Series
+
+
+
+
+ x*sin(x)
x*cos(x)
4. Single Series Transition
+ y = 1 / x
and y = x2
over the range [1, 2).
+ Use the buttons below the chart to initiate the transitions.
+ 5. Multi Series Transition
+
+
+ Set B:
+ y = sin(x)
y = x - x3/3! + x5/5!
+
+ and Set C:
+ y = cos(x)
y = 1 - x2/2! + x4/4!
+
+
+
+ y = sin(x) - (x - x3/3! + x5/5!)
y = cos(x) - (1 - x2/2! + x4/4!)
6. Single to Multi Series Transition
+
+
+ Set B:
+ y = ln(x)
+
+
+
+ y = ln(x)
y = x * ln(x)
y = x * ln(x)2
7. Color Override
+ Categorical Colors
+
+
+ Change the categorical colors by pressing the buttons below the chart.
+
+
+ category10
category20
category20b
category20c
9. Multi Series without Labels
+
+
+ where the layers are given without labels.
+
+
+ y = sin(x)
y = cos(x)
10. Multi Series with Fixed Domain
+
+
+ On the domain x*sin(x)
x*cos(x)
[0, 5]
and range [0, 4]
.
+
+
+ 11. Show/Hide Layers
+
+ 12. Multi-axis
+
+ Basic Chart Model / Data Test
+
+
+ Basic Chart Options and Events
+
+
+
+
+ 1. Axes
+ 2. Margins
+ 3. Ticks and Tick Formats
+ 4. Resize
+ 6. Option: domain
+
+ 7. Option: range
+
+ Basic Pie Chart Test
+
+
+
+
+
+ Basic Pie Test
+
+
+
+
+ 2. Basic Donut Test
+
+
+
+
+ 3. Pie Tranisition I
+
+
+ and set B:
+
+
+ Use the buttons below the chart to initiate the transitions.
+
+
+ 4. Pie Tranisition II
+
+
+ and set B:
+
+
+ Use the buttons below the chart to initiate the transitions.
+
+
+ 5. Color Override
+
+
+
+
+ 6. Categorical Colors
+ 7. Pie Chart Layers without Labels
+
+
+ when the layers are not provided labels.
+
+
+ 8. Margin Changes
+
+ 9. Inner Changes
+
+ 10. Show/Hide Layers
+
+ Basic Scatter Plot Test
+
+
+
+
+
+ 1. Single Series
+ 2. Multi Series
+ 3. Single Series Transition
+ 4. Multi Series Transition
+ 5. Multi Series Transition II
+ 6. Color Override
+ 7. Categorical Colors
+ 8. Multi Series without Labels
+ 9. Single Series with Radius
+ 10. Radius Change
+
+ 11. Show/Hide Layers
+
+ Epoch Chart Rendering Tests
+
+ Basic Charts
+
+
+ + ++
+ + ++
+ Correctly render a single series plot of y = cos(x) + 1
over the range [0, 2π]
.
+
+ Correctly render a single series plot of y = sin(x) + 1
. When the button is pressed push a new data point to the chart and correctly animate/render the transiton.
+
Correctly play / pause a single series stream of values from the plot y = cos(x) + 1
.
Correctly render a multi series plot of the following functions:
+y = log(x+1)
y = 2*log(x+1)
y = 3*log(x+1)
+ Correctly render a multi series plot of the following functions: +
y = x
y = x1.25
y = x1.5
+ +
++ Correctly play / pause a multi series stream of values over the following plots: +
x
x * log(x)
x * log2(x)
The first layer should pink, the second green, and the third blue.
+ +Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:
+
+Epoch.QueryCSS.purge();
+chart.draw();
+
+ + + + +
++ + + | + +
+
+ Render a single series plot of y = cos(x)
. Instead of automatically setting the range to [-1, 1]
, the range is manually set to [-2, 5]
.
+
+ Correctly render a single series plot of y = cos(x) + 1
over the range [0, 2π]
.
+
+ Correctly render a single series plot of y = sin(x) + 1
. When the button is pressed push a new data point to the chart and correctly animate/render the transiton.
+
Correctly play / pause a single series stream of values from the plot y = cos(x) + 1
.
Correctly render a multi series plot of the following functions:
+y = log(x+1)
y = 2*log(x+1)
y = 3*log(x+1)
+ Correctly render a multi series plot of the following functions: +
y = x
y = x1.25
y = x1.5
+ +
++ Correctly play / pause a multi series stream of values over the following plots: +
x
x * log(x)
x * log2(x)
The first layer should pink, the second green, and the third blue.
+ +Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:
+
+Epoch.QueryCSS.purge();
+chart.draw();
+
+ + + + +
++ + + | + +
+
+ Render a single series plot of y = cos(x) + 1
. Instead of automatically setting the range to [0, 2]
, the range is manually set to [0, 5]
.
+
Display a single value of 25%
+ +Display value of 0% and transition to a random value when the button is pressed.
+ + +Display value of 0% and transition to a random value every second when the button is pressed.
+ + +Display the four built-in gauge sizes in this order: tiny, small, medium, large.
+ + + + ++ Override the basic gauge styles with the following +
+ | + + +
++ | + + + + +
++ | + + + + +
++ | + + + +
++ | + + +
+Select random values from the normal distribution and display them with the heatmap.
+ +Select random values from the Beta(2, 5) distribution and display them with the heatmap.
+ ++ Plot the normal distribution and transition a new element when the button is pressed. +
+ + ++ Plot the Beta(2, 5) distribution and begin streaming new elements each second once the + button is pressed. +
+ + +Change the bucket base color to orange and plot the Beta(2, 2) distribution.
+ ++ Plot the Beta(2,5) and Normal distributions as two layers in a single heatmap. Stream elements to the plot + by pressing the button. The normal distribution layer is in blue and the beta in green. +
+ + ++ Plot the normal distribution and the Beta(2, 5) distribution overrding normal to be in red, and beta to + be in purple. +
+ ++ Discrete bucketing of sparse histogram values should produce similar looking graphs regardless + of numeric relation between the range of the plot and the number of buckets. +
++ | + + + +
++ | + + + + + +
++ | + + + + +
++ | + + +
++ | + +
++ + | + +
+
+ Correctly render a single series plot of y = cos(x) + 1
over the range [0, 2π]
.
+
+ Correctly render a single series plot of y = sin(x) + 1
. When the button is pressed push a new data point to the chart and correctly animate/render the transiton.
+
Correctly play / pause a single series stream of values from the plot y = cos(x) + 1
.
Correctly render a multi series plot of the following functions:
+y = log(x+1)
y = 2*log(x+1)
y = 3*log(x+1)
+ Correctly render a multi series plot of the following functions: +
y = x
y = x1.25
y = x1.5
+ +
++ Correctly play / pause a multi series stream of values over the following plots: +
x
x * log(x)
x * log2(x)
The first layer should pink, the second green, and the third blue.
+ +Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:
+
+Epoch.QueryCSS.purge();
+chart.draw();
+
+ + + + +
++ + + | + +
+
+ Render a single series plot of y = cos(x)
. Instead of automatically setting the range to [-1, 1]
, the range is manually set to [-2, 5]
.
+
+ Render a plot that uses independent axes ranges for the left and + right sides. +
+ ++ Render a plot that uses independent axes ranges for the left and + right sides that are associated to labels in the chart data. +
+ +Correctly Resize a Real-time Chart.
+ ++ | + + + +
++ | + + +
++ | + + +
++ | + + + +
+