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