-/**
- * @class Base model, extending Backbone.Model, used by scaffold and others.
- * @extends Backbone.Model
- */
-BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{
- ctorName : 'BaseModel'
+BaseBackboneMixin = exports.BaseBackboneMixin =
- # A list of method-names to bind on initialize; set this on a subclass to override.
+ initialize: ->
+ @__apply_bind__()
+
+
+ ### Auto-Bound methods
+
+ /**
+ * A list of method-names to bind on `initialize`; set this on a subclass to override.
+ * @type Array<String>
+ */
__bind__ : []
+ /**
+ * Applies the contents of `__bind__`.
+ */
+ __apply_bind__: ->
+ names = _ @pluckSuperAndSelf '__bind__' .chain().flatten().compact().unique().value()
+ _.bindAll this, ...names if names.length
+
+
+
+ ### Synchronization
+
+ /**
+ * Count of outstanding tasks.
+ * @type Number
+ */
+ waitingOn : 0
+
+
+ /**
+ * Increment the waiting task counter.
+ * @returns {this}
+ */
+ wait: ->
+ count = @waitingOn
+ @waitingOn += 1
+ console.log "#this.wait! #count --> #{@waitingOn}"
+ @trigger('start-waiting', this) if count is 0 and @waitingOn > 0
+ this
+
+ /**
+ * Decrement the waiting task counter.
+ * @returns {this}
+ */
+ unwait: ->
+ count = @waitingOn
+ @waitingOn -= 1
+ console.warn "#this.unwait! #{@waitingOn} < 0" if @waitingOn < 0
+ console.log "#this.unwait! #count --> #{@waitingOn}"
+ @trigger('stop-waiting', this) if @waitingOn is 0 and count > 0
+ this
+
+ /**
+ * @param {Function} fn Function to wrap.
+ * @returns {Function} A function wrapping the passed function with a call
+ * to `unwait()`, then delegating with current context and arguments.
+ */
+ unwaitAnd: (fn) ->
+ self = this
+ ->
+ console.log "#self.unwaitAnd( function #{fn.name or fn.displayName}() )"
+ self.unwait()
+ fn ...
+
+
+mixinBase = exports.mixinBase = (body) ->
+ _.clone(BaseBackboneMixin) import body
+
+
+/**
+ * @class Base model, extending Backbone.Model, used by scaffold and others.
+ * @extends Backbone.Model
+ */
+BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{
constructor : function BaseModel
@__class__ = @constructor
@__superclass__ = @..__super__.constructor
+ @waitingOn = 0
Backbone.Model ...
@trigger 'create', this
- initialize: ->
- _.bindAll this, ...@__bind__ if @__bind__.length
+
### Accessors
#
+
+
+
### Serialization
serialize: (v) ->
toURL: ->
"?#{@toKV ...}"
- toString: -> "#{@ctorName}(id=#{@id})"
+ toString: -> "#{@..name or @..displayName}(cid=#{@cid}, id=#{@id})"
# Class Methods
BaseModel import do
-
/**
* Factory method which constructs an instance of this model from a string of KV-pairs.
* This is a class method inherited by models which extend {BaseModel}.
* @class Base collection, extending Backbone.Collection, used by scaffold and others.
* @extends Backbone.Collection
*/
-BaseList = exports.BaseList = Backbone.Collection.extend do # {{{
- ctorName : 'BaseList'
-
- # A list of method-names to bind on initialize; set this on a subclass to override.
- __bind__ : []
-
+BaseList = exports.BaseList = Backbone.Collection.extend mixinBase do # {{{
constructor : function BaseList
@__class__ = @constructor
@__superclass__ = @..__super__.constructor
+ @waitingOn = 0
Backbone.Collection ...
@trigger 'create', this
- initialize : ->
- _.bindAll this, ...@__bind__ if @__bind__.length
+ ### Serialization
toKVPairs: ->
_.collapseObject @toJSON()
toURL: (item_delim='&', kv_delim='=') ->
"?#{@toKV ...}"
- toString: -> "#{@ctorName}(length=#{@length})"
+ toString: -> "#{@..name or @..displayName}(length=#{@length})"
# }}}
* @class Base view, extending Backbone.View, used by scaffold and others.
* @extends Backbone.View
*/
-BaseView = exports.BaseView = Backbone.View.extend do # {{{
- ctorName : 'BaseView'
-
- /**
- * A list of method-names to bind on initialize; set this on a subclass to override.
- * @type Array<String>
- */
- __bind__ : []
+BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
+ tagName : 'section'
/**
- * @type Array<BaseView>
+ * Array of [view, selector]-pairs.
+ * @type Array<[BaseView, String]>
*/
subviews : []
constructor : function BaseView
@__class__ = @constructor
@__superclass__ = @..__super__.constructor
+ @waitingOn = 0
@subviews = []
Backbone.View ...
@trigger 'create', this
initialize: ->
- _.bindAll this, ...@__bind__ if @__bind__.length
+ @__apply_bind__()
@setModel @model
@build()
@model.on 'destroy', @remove, this
@model
+
+
+ ### Subviews
+
+ addSubview: (selector, view) ->
+ [view, selector] = [selector, null] unless view
+ @subviews.push [view, selector]
+ view
+
+ removeSubview: (view) ->
+ for [v, sel], idx of @subviews
+ if v is view
+ @subviews.splice(idx, 1)
+ return [v, sel]
+ null
+
+ hasSubview: (view) ->
+ _.any @subviews, ([v]) -> v is view
+
+ attachSubviews: ->
+ for [view, selector] of @subviews
+ return unless view
+ view.undelegateEvents()
+ return unless el = view.render()?.el
+ if selector
+ @$el.find selector .append el
+ else
+ @$el.append el
+ view.delegateEvents()
+ this
+
+
+ ### Rendering Chain
+
toTemplateLocals: ->
json = {value:v} = @model.toJSON()
if _.isArray(v) or _.isObject(v)
json.value = JSON.stringify v
- { $, _, op, @model, view:this } import json
+ json
$template: (locals={}) ->
- $ @template @toTemplateLocals() import locals
+ $ @template do
+ { $, _, op, @model, view:this } import @toTemplateLocals() import locals
build: ->
return this unless @template
-
outer = @$template()
@$el.html outer.html()
.attr do
id : outer.attr 'id'
class : outer.attr('class')
-
+ @attachSubviews()
this
render: ->
@trigger 'render', this
this
+ renderSubviews: ->
+ _.invoke _.pluck(@subviews, 0), 'render'
+ this
+
+
+
+
+ ### UI Utilities
+
hide : -> @$el.hide(); this
show : -> @$el.show(); this
remove : -> @$el.remove(); this
# @$el.appendTo parent if parent?.length
# this
- toString : -> "#{@ctorName}(model=#{@model})"
+ toString : -> "#{@..name or @..displayName}(model=#{@model})"
# Proxy model methods
* @class Field with chart-option-specific handling for validation, parsing, tags, etc.
*/
ChartOption = exports.ChartOption = Field.extend do # {{{
- ctorName : 'ChartOption'
IGNORED_TAGS : <[ callback deprecated debugging ]>
* @class List of ChartOption fields.
*/
ChartOptionList = exports.ChartOptionList = FieldList.extend do # {{{
- ctorName : 'ChartOptionList'
model : ChartOption
*/
ChartOptionView = exports.ChartOptionView = FieldView.extend do # {{{
# __bind__ : <[ onClick ]>
- ctorName : 'ChartOptionView'
tagName : 'div'
className : 'field option'
template : require 'kraken/template/chart-option'
@$el.addClass 'collapsed' if @isCollapsed
this
+
+ /**
+ * Sets the state of `isCollapsed` and updates the UI. If the state changed,
+ * a `'change:collapse`` event will be fired.`
+ *
+ * @param {Boolean} [makeCollapsed=true] If true, set state to collapsed.
+ * @returns {Boolean} Whether the state changed.
+ */
+ collapse: (state=true) ->
+ state = !! state
+ @isCollapsed = @$el.hasClass 'collapsed'
+
+ return this if state is @isCollapsed
+ if state
+ @$el.addClass 'collapsed'
+ else
+ @$el.removeClass 'collapsed'
+ @isCollapsed = state
+ @trigger 'change:collapse', this, @isCollapsed
+ true
+
+ /**
+ * Toggles the collapsed state, updating the UI and firing a `'change:collapse'` event.
+ * @returns {this}
+ */
+ toggleCollapsed: ->
+ @collapse not @$el.hasClass 'collapsed'
+ this
+
onClick: (evt) ->
target = $ evt.target
- # console.log "#this.onClick()", target
@toggleCollapsed() if @$el.hasClass('collapsed') and not target.hasClass('close')
- toggleCollapsed: ->
- starting = @$el.hasClass 'collapsed' #@isCollapsed
- @$el.toggleClass 'collapsed'
- @isCollapsed = not starting
- # console.log "#this.toggleCollapsed!", starting, '->', @isCollapsed
- @trigger 'change:collapse', this, @isCollapsed
- this
# }}}
* @class View for configuring a chart type.
*/
ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{
- ctorName : 'ChartOptionScaffold'
+ __bind__ : <[ collapseAll expandAll ]>
tagName : 'form'
className : 'options scaffold'
template : require 'kraken/template/chart-scaffold'
+
collectionType : ChartOptionList
subviewType : ChartOptionView
fields : '.fields'
+ events:
+ 'click .options-filter-button' : 'onFilterOptions'
+ 'click .collapse-all-options-button' : 'collapseAll'
+ 'click .expand-all-options-button' : 'expandAll'
+
# GraphView will set this
- ready : false
+ ready : false
+
constructor: function ChartOptionScaffold
@render = _.debounce @render.bind(this), DEBOUNCE_RENDER
Scaffold::initialize ...
+
render: ->
- # console.log "#this.render() -> .isotope()"
+ console.log "#this.render(ready=#{@ready}) -> .isotope()"
# Scaffold::render ...
return this unless @ready
container = if @fields then @$el.find @fields else @$el
.find '.field.option' .addClass 'isotope-item'
container.isotope do
# itemPositionDataEnabled : true
- itemSelector : '.field.option'
- layoutMode : 'masonry'
- masonry : columnWidth : 10
- getSortData :
+ itemSelector : '.field.option'
+ layoutMode : 'masonry'
+ masonry : { columnWidth:10 }
+ filter : @getOptionsFilter()
+ sortBy : 'category'
+ getSortData :
category: ($el) ->
$el.data 'model' .getCategory()
- sortBy: 'category'
+ this
+
+ getOptionsFilter: ->
+ data = @$el.find '.options-filter-button.active' .toArray().map -> $ it .data()
+ sel = data.reduce do
+ (sel, d) ->
+ sel += that if d.filter
+ sel
+ ''
+ sel
+
+ collapseAll: ->
+ _.invoke @_subviews, 'collapse', true
+ # @renderSubviews()
+ false
+
+ expandAll: ->
+ _.invoke @_subviews, 'collapse', false
+ # @renderSubviews()
+ false
+
/**
* Add a ChartOption to this scaffold, rerendering the isotope
*/
addOne: (field) ->
view = Scaffold::addOne ...
- view.on 'change:collapse render', @render
+ view.on 'change:collapse render', @render, this
view
toKV: ->
@collection.toKV ...
+
+ onFilterOptions: (evt) ->
+ evt.preventDefault()
+ _.defer @render
+
# }}}
* @class
*/
DataView = exports.DataView = BaseView.extend do # {{{
- ctorName : 'DataView'
+ __bind__ : <[ onReady ]>
tagName : 'section'
className : 'data-ui'
template : require 'kraken/template/data'
- data : {}
+ datasources : {}
initialize: ->
@graph_id = @options.graph_id
BaseView::initialize ...
-
+ @on 'ready', @onReady
@load()
-
- # @subviews.push @dataset_view = new DataSetView {@model, @graph_id}
- # @$el.append @dataset_view.render().el
- # @dataset_view.on 'edit-metric', @editMetric, this
- #
- # @subviews.push @metric_edit_view = new MetricEditView {dataset:@model, @graph_id}
- # @$el.append @metric_edit_view.render().hide().el
-
-
- toTemplateLocals: ->
- attrs = _.clone @model.attributes
- { $, _, op, @model, view:this, @data, @graph_id } import attrs
load: ->
- $.getJSON '/datasources/all', (@data) ~>
+ $.getJSON '/datasources/all', (@datasources) ~>
+ @canonicalizeDataSources @datasources
@ready = true
@render()
@trigger 'ready', this
+ /**
+ * Transform the `columns` field to ensure an Array of {label, type} objects.
+ */
+ canonicalizeDataSources: (datasources) ->
+ _.each datasources, (ds) ->
+ ds.shortName or= ds.name
+ ds.title or= ds.name
+ ds.subtitle or= ''
+
+ cols = ds.columns
+ if _.isArray cols
+ ds.metrics = _.map cols, (col, idx) ->
+ if _.isArray col
+ [label, type] = col
+ {idx, label, type or 'int'}
+ else
+ col
+ else
+ ds.metrics = _.map cols.labels, (label, idx) ->
+ {idx, label, type:cols.types[idx] or 'int'}
+ datasources
+
+
+ onReady: ->
+ @metric_edit_view = @addSubview new MetricEditView {@graph_id, dataset:@model, @datasources}
+ @metric_edit_view
+ .on 'update', @onUpdateMetric, this
+
+ @dataset_view = @addSubview new DataSetView {@model, @graph_id, @dataset, @datasources}
+ @dataset_view
+ .on 'add-metric', @onMetricsChanged, this
+ .on 'remove-metric', @onMetricsChanged, this
+ .on 'edit-metric', @editMetric, this
+
+ @attachSubviews()
+ this
+
+
+ toTemplateLocals: ->
+ attrs = _.clone @model.attributes
+ { $, _, op, @model, view:this, @graph_id, @datasources, } import attrs
+
+ # attachSubviews: ->
+ # @$el.empty()
+ # BaseView::attachSubviews ...
+ # @$el.append '<div class="clearer"/>'
+ # this
+
# Don't rebuild HTML, simply notify subviews
render: ->
- BaseView::render ...
-
- # _.invoke @subviews, 'render'
+ @renderSubviews()
+ @trigger 'render', this
this
editMetric: (metric) ->
@metric_edit_view.editMetric metric
+ onMetricsChanged: ->
+ @$el.css 'min-height', @dataset_view.$el.height()
+
+ onUpdateMetric: ->
+ @renderSubviews()
# }}}
* @class
*/
DataSet = exports.DataSet = BaseModel.extend do # {{{
- ctorName : 'DataSet'
urlRoot : '/datasets'
/**
* @class
*/
DataSetView = exports.DataSetView = BaseView.extend do # {{{
- ctorName : 'DataSetView'
tagName : 'section'
className : 'dataset-ui dataset'
template : require 'kraken/template/dataset'
newMetric: ->
+ console.log "#this.newMetric!"
# triggers 'add' on @model.metrics
@model.newMetric()
false
addMetric: (metric) ->
console.log "#this.addMetric!", metric
if metric.view
- _.remove @subviews, metric.view
+ @removeSubview metric.view
delete @views_by_cid[metric.cid]
- @subviews.push view = new DataSetMetricView {model:metric, @graph_id}
+ view = @addSubview new DataSetMetricView {model:metric, @graph_id}
@views_by_cid[metric.cid] = view
@$el.find '.metrics' .append view.render().el
# @render()
+ @trigger 'add-metric', metric, view, this
view
editMetric: (metric) ->
console.log "#this.editMetric!", metric
if metric instanceof [jQuery.Event, Event]
metric = $ metric.currentTarget .data 'model'
- view = @views_by_cid[metric.cid]
+ view = @active_view = @views_by_cid[metric.cid]
console.log ' --> metric:', metric, 'view:', view
- @$el.find '.metrics' .removeClass 'metric-active'
+ @$el.find '.metrics .dataset-metric' .removeClass 'metric-active'
view.$el.addClass 'metric-active'
view.$el.find '.activity-arrow' .css 'font-size', 2+view.$el.height()
- @trigger 'edit-metric', metric
+
+ @trigger 'edit-metric', metric, view, this
+ this
+
+ render: ->
+ this
# }}}
* @class
*/
DataSetMetricView = exports.DataSetMetricView = BaseView.extend do # {{{
- ctorName : 'DataSetMetricView'
tagName : 'tr'
className : 'dataset-metric metric'
template : require 'kraken/template/dataset-metric'
* @class
*/
DataSource = exports.DataSource = BaseModel.extend do # {{{
- ctorName : 'DataSource'
urlRoot : '/datasources'
* @class
*/
DataSourceList = exports.DataSourceList = BaseList.extend do # {{{
- ctorName : 'DataSourceList'
urlRoot : '/datasources'
model : DataSource
*/
DataSourceUIView = exports.DataSourceUIView = BaseView.extend do # {{{
__bind__ : <[ ]>
- ctorName : 'DataSourceUIView'
tagName : 'section'
className : 'datasource-ui'
template : require 'kraken/template/datasource-ui'
+ events :
+ 'click .datasource-summary': 'onHeaderClick'
+
+ graph_id : null
+ dataset : null
+ datasources : null
+
+
constructor: function DataSourceUIView
BaseView ...
initialize: ->
- @graph_id = @options.graph_id
+ this import @options.{graph_id, dataset, datasources}
BaseView::initialize ...
toTemplateLocals: ->
locals = @model.toJSON()
- locals import
- graph_id : @graph_id
- source_summary : 'Source Summary'
- metric_summary : 'Metric Summary'
- timespan_summary : 'Timespan Summary'
+ locals import {
+ @graph_id, @dataset, @datasources,
+ source_summary : '<Select Source>'
+ metric_summary : '<Select Metric>'
+ timespan_summary : '<Select Timespan>'
+ }
+
+ onHeaderClick: ->
+ @$el.toggleClass 'in'
+
# }}}
*/
DataSourceView = exports.DataSourceView = BaseView.extend do # {{{
__bind__ : <[ ]>
- ctorName : 'DataSourceView'
tagName : 'section'
className : 'datasource'
template : require 'kraken/template/datasource'
* @class
*/
MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
- ctorName : 'MetricEditView'
tagName : 'section'
className : 'metric-edit-ui'
template : require 'kraken/template/metric-edit'
graph_id : null
dataset : null
+ datasources : null
datasource_ui_view : null
BaseView ...
initialize: ->
- this import @options.{graph_id, dataset}
+ this import @options.{graph_id, dataset, datasources}
@model or= new Metric
BaseView::initialize ...
- @subviews.push @datasource_ui_view = new DataSourceUIView {@model, @graph_id}
+ @datasource_ui_view = @addSubview '.metric-datasource', new DataSourceUIView {@model, @graph_id, @dataset, @datasources}
@$el.find '.metric-datasource' .append @datasource_ui_view.render().el
toTemplateLocals: ->
locals = BaseView::toTemplateLocals ...
- locals import {@graph_id}
+ locals import { @graph_id, @dataset, @datasources, }
build: ->
BaseView::build ...
* @class
*/
Metric = exports.Metric = BaseModel.extend do # {{{
- ctorName : 'Metric'
urlRoot : '/metrics'
/**
* @class
*/
MetricList = exports.MetricList = BaseList.extend do # {{{
- ctorName : 'MetricList'
urlRoot : '/metrics'
model : Metric
-
### Formatters {{{
axisFormatter: (fmttr) ->
valueFormatter xValueFormatter yValueFormatter
]>
__bind__ : <[
- render renderAll stopAndRender stopAndRenderAll resizeViewport
+ render renderAll stopAndRender stopAndRenderAll resizeViewport wait unwait checkWaiting
numberFormatter numberFormatterHTML
- onReady onSync onModelChange onScaffoldChange onDataChange onFirstClickRenderOptionsTab
+ onReady onSync onModelChange onScaffoldChange onFirstClickRenderOptionsTab
]>
__debounce__: <[ render renderAll ]>
- ctorName : 'GraphEditView'
tagName : 'section'
className : 'graph graph-edit'
template : require 'kraken/template/graph-edit'
'submit form.options' : 'onOptionsSubmit'
'change input[type="checkbox"]' : 'onOptionsSubmit'
- data : {}
+
+ /**
+ * Count of outstanding tasks until we stop the spinner.
+ * @type Number
+ */
+ waitingOn : 0
+
+ /**
+ * Whether we're ready.
+ * @type Boolean
+ */
ready : false
+
constructor: function GraphEditView
BaseView ...
initialize : (o={}) ->
- @data = {}
+ # @data = {}
@model or= new Graph
- @id = _.domize 'graph', (@model.id or @model.get('slug') or @model.cid)
+ @id = @graph_id = _.domize 'graph', (@model.id or @model.get('slug') or @model.cid)
BaseView::initialize ...
# console.log "#this.initialize!"
for name of @__debounce__
@[name] = _.debounce @[name], DEBOUNCE_RENDER
- @viewport = @$el.find '.viewport'
+ # Set up the spinner
+ @on 'start-waiting', @onStartWaiting, this
+ @on 'stop-waiting', @onStopWaiting, this
+ @onStartWaiting() if @waitingOn # In case we missed the first call to @wait() somehow
+
+ # Start a wait for the `ready` event
+ @wait()
### Model Events
@model
- .on 'ready', @onReady, this
- .on 'sync', @onSync, this
- .on 'destroy', @remove, this
- .on 'change', @render, this
- .on 'change:dataset', @onModelChange
- .on 'change:options', @onModelChange
- .on 'error', ~>
- console.error "#this.error!", arguments
- # TODO: UI alert
+ .on 'start-waiting', @wait, this
+ .on 'stop-waiting', @unwait, this
+ .on 'sync', @onSync, this
+ .on 'destroy', @remove, this
+ .on 'change', @render, this
+ .on 'change:dataset', @onModelChange, this
+ .on 'change:options', @onModelChange, this
+ .on 'error', @onModelError, this
+ .on 'ready', @onReady, this
### Chart Options Tab, Scaffold
- @scaffold = new ChartOptionScaffold
+ @scaffold = @addSubview '.graph-options-pane', new ChartOptionScaffold
@$el.find '.graph-options-pane' .append @scaffold.el
@scaffold.collection.reset that if o.graph_spec
@chartOptions @model.getOptions(), {+silent}
# Rerender the options boxes once the tab is visible
+ # Can't use @events because we need to bind before registering
@$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
### Graph Data UI
- # @subviews.push @data = new DataView { model:@model.get('dataset'), graph_id:@id }
- @subviews.push @data = new DataView { @model, graph_id:@id }
+ @data = @addSubview '.graph-data-pane', new DataView { model:@model.get('dataset'), graph_id:@id }
@$el.find '.graph-data-pane' .append @data.render().el
- @data.on 'change', @onDataChange
+ @data
+ .on 'change', @onDataChange, this
+ .on 'start-waiting', @wait, this
+ .on 'stop-waiting', @unwait, this
+
+ @checkWaiting()
### Chart Viewport
@resizeViewport()
+ ### Persistence {{{
+
load: ->
console.log "#this.load!"
- @model.fetch()
+ @wait()
+ @model.fetch { success:@unwait, error:@unwait }
false
save: ->
console.log "#this.save!"
+ @wait()
id = @model.get('slug') or @model.id
- @model.save {id}, {+wait}
+ @model.save {id}, { +wait, success:@unwait, error:@unwait }
false
done: ->
@scaffold.invoke 'change'
this
+
+ ### }}}
+ ### Rendering {{{
+
chartOptions: (values, opts) ->
# Handle @chartOptions(k, v, opts)
if arguments.length > 1 and typeof values is 'string'
delete options[k]
options
-
toTemplateLocals: ->
attrs = _.clone @model.attributes
delete attrs.options
# delete attrs.dataset
- attrs.data = @data
- { $, _, op, @model, view:this } import attrs
+ # attrs.data = @data
+ { $, _, op, @model, view:this, @graph_id, slug:'', name:'', desc:'' } import attrs
/**
modelH = height = @model.get 'height'
return { width, height } unless @ready
+ viewport = @$el.find '.viewport'
+
# Remove old style, as it confuses dygraph after options update
- @viewport.attr 'style', ''
+ viewport.attr 'style', ''
label = @$el.find '.graph-label'
if width is 'auto'
- vpWidth = @viewport.innerWidth()
+ vpWidth = viewport.innerWidth()
labelW = label.outerWidth()
width = vpWidth - labelW - 10 - (vpWidth - label.position().left - labelW)
width ?= modelW
if height is 'auto'
- height = @viewport.innerHeight()
+ height = viewport.innerHeight()
height ?= modelH
size = { width, height }
- @viewport.css size
- # console.log 'resizeViewport!', JSON.stringify(size), @viewport
+ viewport.css size
+ # console.log 'resizeViewport!', JSON.stringify(size), viewport
# @chart.resize size if forceRedraw
size
el.val txt
form.find "textarea[name=#k]" .text txt
+
+ # Graph Name field is not part of the form due to the layout.
+ @$el.find "input.graph-name[name='name']" .val @get 'name'
this
# Redraw chart inside viewport.
renderChart: ->
- data = @model.get 'dataset' #.getData()
+ data = @model.get 'dataset'
+ data = data.getData() if typeof data is not 'string'
size = @resizeViewport()
# XXX: use @model.changedAttributes() to calculate what to update
# dygraphs to reset the current option state.
@chart?.destroy()
@chart = new Dygraph do
- @viewport.0
+ @$el.find '.viewport' .0
data
options
# unless @chart
# @chart = new Dygraph do
- # @viewport.0
+ # @$el.find '.viewport' .0
# data
# options
# else
this
+ attachSubviews: ->
+ @$el.find '.graph-options-pane' .append @scaffold.el if @scaffold
+ @$el.find '.graph-data-pane' .append @data.render().el if @data
+ @checkWaiting()
render: ->
return this unless @ready
+ @wait()
+ @checkWaiting() # fix up the spinner element as the DOM is now settled
@renderDetails()
- _.invoke @subviews, 'render'
+ @attachSubviews()
+ # _.invoke @subviews, 'render'
@renderChart()
@updateURL()
@trigger 'render', this
- false
+ @unwait()
+ this
renderAll: ->
return this unless @ready
# console.log "#this.renderAll!"
+ @wait()
_.invoke @scaffold.subviews, 'render'
@scaffold.render()
@render()
+ @unwait()
+
/**
* Update the page URL using HTML5 History API
History.pushState data, title, url
+ /**
+ * Retrieve or construct the spinner.
+ */
+ spinner: ->
+ el = @$el.find '.graph-spinner'
+ unless el.data 'spinner'
+ ### Spin.js Options ###
+ opts =
+ lines : 9 # [12] The number of lines to draw
+ length : 2 # [7] The length of each line
+ width : 1 # [5] The line thickness
+ radius : 7 # [10] The radius of the inner circle
+ rotate : -10.5 # [0] rotation offset
+ trail : 50 # [100] Afterglow percentage
+ opacity : 1/4 # [1/4] Opacity of the lines
+ shadow : false # [false] Whether to render a shadow
+ speed : 1 # [1] Spins per second
+ zIndex : 2e9 # [2e9] zIndex; uses a very high z-index by default
+ color : '#000' # ['#000'] Line color; '#rgb' or '#rrggbb'.
+ top : 'auto' # ['auto'] Top position relative to parent in px; 'auto' = center vertically.
+ left : 'auto' # ['auto'] Left position relative to parent in px; 'auto' = center horizontally.
+ className : 'spinner' # ['spinner'] CSS class to assign to the element
+ fps : 20 # [20] Frames per second when falling back to `setTimeout()`.
+ hwaccel : Modernizr.csstransforms3d # [false] Whether to use hardware acceleration.
+
+ isHidden = el.css('display') is 'none'
+ el.show().spin opts
+ el.hide() if isHidden
+ el
+
+ checkWaiting: ->
+ spinner = @spinner()
+ if isWaiting = (@waitingOn > 0)
+ spinner.show()
+ if spinner.find('.spinner').css('top') is '0px'
+ # delete spinner
+ spinner.spin(false)
+ # re-add to DOM with correct parent sizing
+ @spinner()
+ else
+ spinner.hide()
+ isWaiting
+
+ ### }}}
### Formatters {{{
axisFormatter: (fmttr) ->
numberFormatterHTML: (n, opts, g) ->
digits = opts('digitsAfterDecimal') ? 2
{ whole, fraction, suffix } = @_numberFormatter n, digits
- """
- <span class="value"><span class="whole">#whole</span><span class="fraction">#fraction</span><span class="suffix">#suffix</span></span>
- """
+ # coco will trim all the whitespace
+ "<span class='value'>
+ <span class='whole'>#whole</span>
+ <span class='fraction'>#fraction</span>
+ <span class='suffix'>#suffix</span>
+ </span>"
### }}}
### Event Handlers {{{
-
onReady: ->
return if @ready
- $.getJSON '/datasources/all', (@data) ~>
- console.log "(#this via GraphEditView).ready!"
- @ready = @scaffold.ready = true
- @onSync()
+ # $.getJSON '/datasources/all', (@data) ~>
+ console.log "(#this via GraphEditView).ready!"
+ @ready = @scaffold.ready = true
+ @unwait() # clears `wait()` from `initialize`
+ @onSync()
+
+ # fix up the spinner element once the DOM is settled
+ _.delay @checkWaiting, 50
onSync: ->
return unless @ready
@chartOptions @model.getOptions(), {+silent}
@renderAll()
+ onStartWaiting: ->
+ console.log "#this.onStartWaiting!", @checkWaiting()
+
+ onStopWaiting: ->
+ console.log "#this.onStopWaiting!", @checkWaiting()
+
+ onModelError: ->
+ console.error "#this.error!", arguments
+ # TODO: UI alert
+
onModelChange: ->
changes = @model.changedAttributes()
options = @model.getOptions()
- # console.log "Graph.changed( options ) ->\n\tchanges: #{JSON.stringify changes}\n\toptions: #{JSON.stringify options}" #"\n\t^opts: #{JSON.stringify _.intersection _.keys(changes), _.keys(options)}"
+ # console.log """
+ # Graph.changed( options ) ->
+ # \tchanges: #{JSON.stringify changes}
+ # \toptions: #{JSON.stringify options}
+ # \t^opts: #{JSON.stringify _.intersection _.keys(changes), _.keys(options)}
+ # """
@chart?.updateOptions file:that if changes?.dataset
@chartOptions options, {+silent} if changes?.options
@render()
false
- # Needed because (sigh) _.debounce returns undefined
- stopAndRender: ->
- @render ...
- false
-
- stopAndRenderAll: ->
- @renderAll ...
- false
+ # Needed because (sigh) _.debounce returns undefined, and we need to preventDefault()
+ stopAndRender : -> @render ... ; false
+ stopAndRenderAll : -> @renderAll ... ; false
# }}}
- toString: -> "#{@ctorName}(#{@model})"
# }}}
-Seq = require 'seq'
+Seq = require "seq"
_ = require 'kraken/util/underscore'
Cascade = require 'kraken/util/cascade'
* other settings for both its content and presentation.
*/
Graph = exports.Graph = BaseModel.extend do # {{{
- ctorName : 'Graph'
IGNORE_OPTIONS : <[ width height timingName ]>
urlRoot : '/graphs'
name : ''
desc : ''
notes : ''
- dataset : '/data/datasources/rc/rc_comscore_region_uv.csv'
+ # dataset : '/data/datasources/rc/rc_comscore_region_uv.csv'
# dataset : null
width : 'auto'
height : 320
@chartType = ChartType.lookup @get('chartType')
# Insert submodels in place of JSON
- # @set 'dataset', new DataSet(@get('dataset')), {+silent}
+ @set 'dataset', new DataSet(@get('dataset')), {+silent}
@trigger 'init', this
@load() if opts.autoload
load: (opts={}) ->
return this if @ready and not opts.force
+ @wait()
@trigger 'load', this
Seq()
.seq_ (next) ~>
next.ok()
else
console.log "#{this}.fetch()..."
+ @wait()
@fetch do
- error : (err) ~>
+ error : @unwaitAnd (err) ~>
console.error "#{this}.fetch() --> error! #arguments"
next.ok()
- success : (model, res) ~>
+ success : @unwaitAnd (model, res) ~>
# console.log "#{this}.fetch() --> success!", res
next.ok res
.seq_ (next) ~>
next.ok @get('parents')
.flatten()
- .seqMap -> Graph.lookup it, this
+ .seqMap_ (next, parent_id) ~>
+ @wait()
+ Graph.lookup parent_id, next
.seqEach_ (next, parent) ~>
@parents.add parent
@optionCascade.addLookup parent.get('options')
+ @unwait()
next.ok()
.seq ~>
@ready = true
@trigger 'ready', this
+ @unwait() # terminates the `load` wait
this
options
+
### Serialization
parse: (data) ->
* @returns {String} URL identifying this model.
*/
toURL: ->
- slug = @get 'slug', ''
+ slug = @get('slug') or ''
slug = "/#slug" if slug
"#{@urlRoot}#slug?#{@toKV { keepSlug: !!slug }}"
- toString: -> "#{@ctorName}(id=#{@id}, cid=#{@cid})"
# }}}
GraphList = exports.GraphList = BaseList.extend do # {{{
- ctorName : 'GraphList'
urlRoot : '/graphs'
model : Graph
modelIds = _.pluck @models, 'id'
.map -> "\"#it\""
.join ', '
- "#{@ctorName}(#modelIds)"
+ "#{@..name or @..displayName}(#modelIds)"
# }}}
# Extract id from URL
if match = /\/graphs\/([^\/?]+)/i.exec loc
- data.id = data.slug = match[1]
+ id = match[1]
+ data.id = data.slug = id unless /(edit|new)/.test id
# _.dump _.clone(data.options), 'data.options'
# Extract id from URL
if match = /\/graphs\/([^\/?]+)/i.exec loc
- data.id = data.slug = match[1]
+ id = match[1]
+ data.id = data.slug = id unless /(edit|new)/.test id
# _.dump _.clone(data.options), 'data.options'
### Scaffold Models
Field = exports.Field = BaseModel.extend do # {{{
- ctorName : 'Field'
idAttribute : 'name'
valueAttribute : 'value'
FieldList = exports.FieldList = BaseList.extend do # {{{
- ctorName : 'FieldList'
model : Field
toURL: (item_delim='&', kv_delim='=') ->
"?#{@toKV ...}"
- toString: -> "#{@ctorName}(length=#{@length})"
# }}}
constructor: function Scaffold
+ @_subviews = []
BaseView ...
initialize: ->
CollectionType = @collectionType
- @model = @collection or= new CollectionType
+ @model = (@collection or= new CollectionType)
BaseView::initialize ...
@collection.on 'add', @addOne
@$el.data { model:@collection, view:this } .addClass @className
+ renderSubviews: ->
+ _.invoke @_subviews, 'render'
+ this
+
addOne: (field) ->
# console.log "[S] #this.addOne!", @..__super__
- _.remove @subviews, field.view if field.view
+ _.remove @_subviews, field.view if field.view
# avoid duplicating event propagation
field.off 'change:value', @change, this
SubviewType = @subviewType
view = new SubviewType model:field
- @subviews.push view
+ @_subviews.push view
container = if @fields then @$el.find @fields else @$el
container.append view.render().el unless field.get 'ignore'
view.on 'update', @change.bind(this, field)
view
addAll: ->
- # _.invoke @subviews, 'remove'
- # @subviews = []
+ _.invoke @_subviews, 'remove'
+ @_subviews = []
@collection.each @addOne
this
this
- toString: -> "#{@ctorName}(collection=#{@collection})"
-form.options.scaffold
+form.graph-options.scaffold.form-inline
+
+ .graph-options-controls.control-group
+ a.collapse-all-options-button.btn(href="#") Collapse All
+ a.expand-all-options-button.btn(href="#") Expand All
+
+ .std-adv-filter-buttons.btn-group.pull-right(data-toggle="buttons-radio")
+ a.standard-filter-button.options-filter-button.btn.active(href="#", data-filter=".tag_standard") Standard
+ a.advanced-filter-button.options-filter-button.btn(href="#", data-filter="") Advanced
+
+
.fields.control-group
section.data-ui
- .row-fluid
- .dataset-controls.dropdown
- select(name="dataset")
- for datum, k in data
- - var selected = (datum.url === dataset ? 'selected' : null);
- option(value=datum.url, name=datum.id, selected=selected) #{datum.name}
-
- //-
- a.span3.dataset.dropdown-toggle(data-toggle="dropdown", data-target="#{graph_id} .dataset-dropdown", href="#dataset-dropdown") Select...
- ul.dropdown-menu
- for datum, k in data
- li: a(href="#", data-dataset="#{datum.id}") #{datum.name}
-
+ //-
+ .row-fluid
+ //-
+ .dataset-controls.dropdown
+ select(name="dataset")
+ for datum, k in datasources
+ - var selected = (datum.url === dataset ? 'selected' : null);
+ option(value=datum.url, name=datum.id, selected=selected) #{datum.name}
+
+ //-
+ a.span3.dataset.dropdown-toggle(data-toggle="dropdown", data-target="#{graph_id} .dataset-dropdown", href="#dataset-dropdown") Select...
+ ul.dropdown-menu
+ for datum, k in datasources
+ li: a(href="#", data-dataset="#{datum.id}") #{datum.name}
+
-section.dataset-ui.dataset
- .inner
- h4 Graph Data Set
-
- .dataset-buttons
- a.new-metric-button.btn.btn-success.btn-small(href="#")
- i.icon-plus-sign.icon-white
- | Add Metric
- //-
- a.clear-metrics-button.btn.btn-danger.btn-small(href="#")
- i.icon-remove-sign.icon-white
- | Remove All Metrics
-
- .dataset-metrics
- table.table.table-striped
- thead
- tr
- th.col-label Label
- th.col-source Source
- th.col-times Timespan
- th.col-actions Actions
- tbody.metrics
- //- DataSetMetricViews attach here
-
-
+section.dataset-ui.dataset: div.inner
+ h4 Graph Data Set
+
+ .dataset-buttons
+ a.new-metric-button.btn.btn-success.btn-small(href="#")
+ i.icon-plus-sign.icon-white
+ | Add Metric
+ //-
+ a.clear-metrics-button.btn.btn-danger.btn-small(href="#")
+ i.icon-remove-sign.icon-white
+ | Remove All Metrics
+
+ .dataset-metrics
+ table.table.table-striped
+ thead
+ tr
+ th.col-label Label
+ th.col-source Source
+ th.col-times Timespan
+ th.col-actions Actions
+ tbody.metrics
+ //- DataSetMetricViews attach here
+
+
section.datasource-ui
section.datasource-summary(data-toggle="collapse", data-target="##{graph_id} .datasource-ui .datasource-selector")
+ i.expand-datasource-ui-button.icon-chevron-down
+ i.collapse-datasource-ui-button.icon-chevron-up
+
ul.breadcrumb
- li #{source_summary}
- span.divider
- li #{metric_summary}
- span.divider
+ li #{source_summary}
+ span.divider /
+ li #{metric_summary}
+ span.divider /
li #{timespan_summary}
-
- i.icon-chevron-down.pull-right
- i.icon-chevron-up.pull-right
- section.datasource-selector.collapse.in
- .tabbable.tabs-left
- ul.datasource-sources-list.nav.nav-tabs
- li: h4 Data Sources
- //- li: a(href="#", data-toggle="tab", data-target="")
- .datasource-sources-details.tab-content
- .datasource-source.tab
- .datasource-source-details
- .datasource-source-time
- .datasource-source-metrics
+ section.datasource-selector.collapse
+ .datasource-tabs
+ .tabbable.tabs-left
+ ul.datasource-sources-list.nav.nav-tabs
+ li: h6 Data Sources
+ for ds, k in datasources
+ - var ds_target = "#"+graph_id+" .datasource-ui .datasource-selector .datasource-source.datasource-source-"+ds.id
+ li: a(href="#datasource-selector_datasource-source-#{ds.id}", data-toggle="tab", data-target=ds_target) #{ds.shortName}
+
+ .datasource-sources-details.tab-content
+ for ds, k in datasources
+ .datasource-source.tab-pane(class="datasource-source-#{ds.id}")
+ .datasource-source-details
+ .source-name #{ds.name}
+ .source-id #{ds.id}
+ .source-url #{ds.url}
+ .source-format #{ds.format}
+ .source-charttype #{ds.chart.chartType}
+ .datasource-source-time
+ .source-time-start #{ds.timespan.start}
+ .source-time-end #{ds.timespan.end}
+ .source-time-step #{ds.timespan.step}
+ ol.datasource-source-metrics
+ for m, idx in ds.metrics.slice(1)
+ li.datasource-source-metric
+ span.source-metric-idx #{m.idx}
+ |
+ span.source-metric-label #{m.label}
+ |
+ span.source-metric-type #{m.type}
include browser-helpers
- var graph_id = view.id
-section.graph.graph-display(id=view.id)
+section.graph.graph-display(id=graph_id)
.graph-name-row.page-header.row-fluid
h2.graph-name #{name}
- var graph_id = view.id || model.id || model.cid
section.graph.graph-edit(id=graph_id)
- form.details.form-horizontal
-
- .name-row.row-fluid.control-group
- //- label.name.control-label(for="#{id}_name"): h3 Graph Name
- input.span6.name(type='text', id="#{graph_id}_name", name="name", placeholder='Graph Name', value=name)
-
- .row-fluid
- .viewport
- .graph-label
-
- .row-fluid
- .graph-settings.tabbable
- //- nav.navbar: div.navbar-inner: div.container
- nav
- .graph-controls.pull-right
- .btn-group
- a.redraw-button.btn(href="#")
- i.icon-refresh
- | Redraw
- .btn-group
- a.load-button.btn(href="#")
- i.icon-download
- | Revert
- a.save-button.btn(href="#")
- i.icon-upload
- | Save
- a.done-button.btn-primary.btn(href="#")
- i.icon-ok-sign.icon-white
- | Done
- ul.nav.subnav.nav-pills
- li: h3 Graph
- li.active: a(href="##{graph_id}-tab-info", data-toggle="tab") Info
- li: a(href="##{graph_id}-tab-data", data-toggle="tab") Data
- li: a.graph-options-tab(href="##{graph_id}-tab-options", data-toggle="tab") Options
-
+
+ //- Graph Name field is not part of the form due to the layout.
+ .graph-name-row.row-fluid.control-group
+ //- label.name.control-label(for="#{id}_name"): h3 Graph Name
+ input.span6.graph-name(type='text', id="#{graph_id}_name", name="name", placeholder='Graph Name', value=name)
+
+ .graph-viewport-row.row-fluid
+ .viewport
+ .graph-label
+
+ .graph-settings-row.row-fluid
+ .graph-settings.tabbable
+
+ //--- Main Tabs ---//
+ nav.graph-settings-nav
+ .graph-controls.pull-right
+ .btn-group
+ a.redraw-button.btn(href="#")
+ i.icon-refresh
+ | Redraw
+ .btn-group
+ a.load-button.btn(href="#")
+ i.icon-download
+ | Revert
+ a.save-button.btn(href="#")
+ i.icon-upload
+ | Save
+ a.done-button.btn-primary.btn(href="#")
+ i.icon-ok-sign.icon-white
+ | Done
+ ul.nav.subnav.nav-pills
+ li
+ .graph-spinner
+ h3 Graph
+ li.active: a(href="##{graph_id}-tab-info", data-toggle="tab") Info
+ li: a(href="##{graph_id}-tab-data", data-toggle="tab") Data
+ li: a.graph-options-tab(href="##{graph_id}-tab-options", data-toggle="tab") Options
- .tab-content
- .graph-info-pane.tab-pane.active(id="#{graph_id}-tab-info")
+
+ //--- Tab Panes ---//
+ .graph-tab-content.tab-content
+ .graph-info-pane.tab-pane.active(id="#{graph_id}-tab-info")
+ form.graph-details.form-horizontal
.row-fluid
.half.control-group
.control-group
label.slug.control-label(for="#{graph_id}_slug") Slug
.controls
input.span3.slug(type='text', id="#{graph_id}_slug", name='slug', placeholder='graph_slug', value=slug)
- p.help-block The slug uniquely identifies this graph and will be displayed in the URL.
+ p.help-block The slug uniquely identifies this graph and will be displayed in the URL once saved.
.control-group
label.width.control-label(for="#{graph_id}_width") Size
.controls
//- textarea.span3.desc(id='desc', name='desc', placeholder='Graph description.') #{desc}
<textarea class="span3 desc" id="#{graph_id}_desc" name="desc" placeholder="Graph description.">#{desc}</textarea>
p.help-block A description of the graph.
-
- .graph-data-pane.tab-pane(id="#{graph_id}-tab-data")
+
+ .graph-data-pane.tab-pane(id="#{graph_id}-tab-data")
+ //-
.row-fluid
- //-
- label.dataset.control-label(for="#{graph_id}_dataset") Data Set
- input.span3.dataset(type='text', id="#{graph_id}_dataset", name='dataset', placeholder='URL to dataset file', value=dataset)
- p.help-block This dataset filename will soon be replaced by a friendly UI.
-
- .graph-options-pane.tab-pane(id="#{graph_id}-tab-options")
+ label.dataset.control-label(for="#{graph_id}_dataset") Data Set
+ input.span3.dataset(type='text', id="#{graph_id}_dataset", name='dataset', placeholder='URL to dataset file', value=dataset)
+ p.help-block This dataset filename will soon be replaced by a friendly UI.
+
+ .graph-options-pane.tab-pane(id="#{graph_id}-tab-options")
section.metric-edit-ui
.inner: form.form-horizontal
- .metric-header.control-group.row-fluid
- .color-picker
+ .metric-header.control-group
+ .color-swatch(style="background-color: #{color};")
input.metric-color(type='hidden', id="#{graph_id}_metric", name="color", value=color)
input.metric-label(type='text', id="#{graph_id}_metric_label", name='label', placeholder='Metric Label', value=label)
- .metric-datasource.row-fluid
+ .metric-datasource.control-group
- .metric-actions.row-fluid
+ .metric-actions.control-group
a.delete-button.btn.btn-danger(href="#")
i.icon-remove.icon-white
| Delete
+### Patches to make Backbone work with browserify
# Expose Underscore so Backbone can find it
_ = require 'underscore'
Backbone.setDomLibrary that if window? and (window.jQuery or window.Zepto or window.ender)
+
+
+/**
+ * @namespace Meta-utilities for working with Backbone classes.
+ */
_backbone = do
/**
* @returns {Array<Class>} The list of all superclasses for this class or object.
*/
getSuperClasses: function getSuperClasses(Cls)
- Cls .= constructor if cls and typeof Cls is not 'function'
- if superclass = Cls?.__super__?.constructor
+ return [] unless Cls
+
+ if Cls.__superclass__
+ superclass = that
+ else
+ Cls = Cls.constructor unless typeof Cls is 'function'
+ superclass = Cls.__super__?.constructor
+
+ if superclass
[superclass].concat getSuperClasses superclass
else
[]
+ /**
+ * Looks up an attribute on the prototype of each class in the class
+ * hierarchy.
+ * @returns {Array}
+ */
+ pluckSuper: (obj, prop) ->
+ return [] unless obj
+ _ _backbone.getSuperClasses(obj) .chain()
+ .pluck 'prototype'
+ .pluck prop
+ .value()
+
+ /**
+ * As `.pluckSuper()` but includes value of `prop` on passed `obj`.
+ * @returns {Array}
+ */
+ pluckSuperAndSelf: (obj, prop) ->
+ return [] unless obj
+ [ obj[prop] ].concat _backbone.pluckSuper(obj, prop)
+
exports import _backbone
+
/**
* Decorates a function so that its receiver (`this`) is always added as the
* first argument, followed by the call arguments.
section.graph section.data-ui
+ height 100%
+ min-height 300px
+ clearfix()
+
h4
display none
/* * * * DataSet UI * * * */
section.dataset-ui
+ absolute 0 0
width 40%
+ height 100%
+ min-height 300px
border-right 1px solid #ccc
.inner
/* * * * Edit Metric UI * * * */
section.metric-edit-ui
- position absolute
- top 0
- left 40%
+ display none
+ float right
+ width 60%
+ min-height 100%
.inner
padding 1em
+ .metric-header
+ min-height 38px
+ font-size 18px
+ line-height 25px
+
+ .color-swatch
+ absolute top 3px right 0
+ width 30px
+ height 30px
+ border 1px solid #333
+ border-color #ddd #333 #333 #ddd
+ border-radius 3px
+ cursor pointer
+
+ input
+ display block
+ absolute top 0 left 0
+ right 38px
+ width auto
+ font-size 18px
+ line-height 25px
+ height 25px
+ padding 6px
+ /* * * * DataSource UI * * * */
+ section.datasource-ui
+
+ // Collapse/Expand icon
+ i
+ z-index 100
+ .expand-datasource-ui-button
+ display block
+ .collapse-datasource-ui-button
+ display none
+ &.in
+ .expand-datasource-ui-button
+ display none
+ .collapse-datasource-ui-button
+ display block
+
+ // Summary Header
+ .datasource-summary
+ &, &:hover
+ cursor pointer
+ i
+ absolute top 50% right 14px
+ margin-top -7px
+ .breadcrumb
+ margin-bottom 0
+ font-weight bold
+ // &, .breadcrumb
+ // color #3a87ad
+ // background-color #d9edf7
+ // border-color #bce8f1
+ // background-image linear-gradient(top, #d9edf7, darken(#d9edf7, 10%))
+
+ // Selector UI (with tabs per-source, info, etc)
+ .datasource-selector
+ .datasource-tabs
+ top -1px
+ padding 7px
+ border 1px solid #ddd
+ border-top 0
+ border-radius 3px
+
+ h1, h2, h3, h4, h5, h6
+ margin 0 -1px 3px 0
+ padding 8px 12px
+ min-width 74px
+
+ .datasource-sources-list
+ height 100%
+ display table-cell
+ float none
+ max-width 200px
+
+ .datasource-sources-details
+ display table-cell
+ padding 1em
+ width auto
+
+
+
+
+
+ .data-ui
+ clearfix()
+
+
section.graph
position relative
- max-width 900px
margin 0 auto
+ min-width 640px
+ max-width 960px
*
position relative
+ .graph-details > *
+ margin-left auto
+ margin-right auto
+
+
/* * * * Chart & Viewport * * * {{{ */
+ .graph-viewport-row
+ // margin 1em 1em 3em
+ margin-bottom 3em
+
+ .viewport
+ position relative
+ min-width 200px
+ min-height 320px
+ overflow hidden
+
.graph-label
position absolute
z-index 100
width 200px
padding 1em
- border-radius 5px
background-color rgba(255,255,255, 0.75)
font 12px/1.5 "helvetica neue", helvetica, arial, sans-serif
+ border 1px solid $light
+ border-radius 5px
b
display inline-block
.viewport:hover + .graph-label
border 1px solid $light
- .viewport
- position relative
- min-width 200px
- min-height 320px
- margin-bottom 1.5em
- overflow hidden
+ /* }}} */
+
+ /* * * * Graph Details & Info Pane * * * {{{ */
+ .graph-name-row
+ // margin 0 1em 1em
+ // max-width 900px
+ font-size 120%
+ line-height 2em
+ input.graph-name
+ font-size 120%
+ line-height 1.2
+ height 1.2em
+ border-color $light
+ width 98%
+ .graph-info-pane
+ .row-fluid
+ .half.control-group
+ width 50%
+ float left
+ margin-left 0
+ margin-right 0
+ label
+ width 100px
+ .controls
+ margin-left 110px
+ .help-block
+ font-size 11px
+ line-height 1.3
/* }}} */
/* * * * Subnav & Tabs * * * {{{ */
+ .graph-settings-row
+ max-width 900px
+
.graph-settings.tabbable
- .nav
+ .graph-settings-nav > .nav
margin-bottom 0
li h3
li
margin-right 4px
- .tab-pane
+ .graph-tab-content > .tab-pane
padding 0.5em
- margin-top 18px
+ margin-top 1em
+ height 100%
&.graph-data-pane
margin-top 0
padding 0
+ border-bottom 1px solid #ddd
.graph-controls
z-index 100
min-width 5em
text-align center
- /* }}} */
-
+ .graph-spinner
+ display none
+ absolute bottom 3px left 0
+ margin-left -2.0em
+ width 2.5em
+ height 2.5em
- /* * * * Graph Details * * * {{{ */
- form.details
- position relative
-
- .name-row
- font-size 120%
- line-height 2em
- input.name
- font-size 120%
- line-height 1.2
- height 1.2em
- border-color $light
- width 98%
- .row-fluid
- .half.control-group
- width 50%
- float left
- margin-left 0
- margin-right 0
- label
- width 100px
- .controls
- margin-left 110px
- .help-block
- font-size 11px
- line-height 1.3
/* }}} */
/* * * * Chart Options * * * {{{ */
- .options fieldset
- border 0px
+ .graph-options
+
+ .graph-options-controls
+ font-size 11px
+
+ .control-group, .btn-group
+ display inline-block
+ font-size 11px
+
+ .btn
+ font-size 11px
+ padding 3px 9px 3px
+ & > .btn, & :not(.btn-group) .btn
+ margin-right 0.75em
+
.field.option
float left
& > .inner
position relative
margin 0 auto
- max-width 960px
+ // max-width 960px
&.fluid-inner
width 80%
- jquery.history.min
- jquery.hotkeys.min
- jquery.isotope.min
+ - jquery.spin.min
- bootstrap.min
- # - spin.min
- # - jquery.spin.min
### CommonJS Support Starts Here:
### Browserify must come before any .mod files