Checkpoint on Data UI.
authordsc <dsc@wikimedia.org>
Mon, 16 Apr 2012 20:08:18 +0000 (13:08 -0700)
committerdsc <dsc@wikimedia.org>
Mon, 16 Apr 2012 20:08:18 +0000 (13:08 -0700)
12 files changed:
data/graphs/ohai.json
lib/base/base-view.co
lib/dataset/data-view.co
lib/dataset/dataset-model.co
lib/dataset/dataset-view.co
lib/dataset/datasource-model.co
lib/dataset/datasource-ui-view.co
lib/dataset/metric-edit-view.co
lib/dataset/metric-model.co
lib/main-edit.co
lib/template/datasource-ui.jade
www/css/data.styl

index 1be77f0..6dbc35f 100644 (file)
@@ -7,22 +7,19 @@
     
     "dataset": "/data/datasources/rc/rc_page_requests.csv",
     "data" : {
-        "metrics" : {
-            "defaults" : {
-                "source_id" : "rc_page_requests"
-            },
-            "columns" : [
-                {
-                    "source_col" : 1,
-                    "label"      : "All Wikipedias (+Mobile)",
-                    "color"      : "#E62F74"
-                }, {
-                    "source_col" : 2,
-                    "label"      : "English",
-                    "color"      : "#244792"
-                }
-            ]
-        }
+        "metrics" : [
+            {
+                "source_id" : "rc_page_requests",
+                "source_col" : 1,
+                "label"      : "All Wikipedias (+Mobile)",
+                "color"      : "#E62F74"
+            }, {
+                "source_id" : "rc_page_requests",
+                "source_col" : 2,
+                "label"      : "English",
+                "color"      : "#244792"
+            }
+        ]
     },
     
     "width": "auto",
index 8172727..7d22832 100644 (file)
@@ -15,16 +15,32 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
     tagName : 'section'
     
     /**
+     * The identifier for this view class.
+     * By default, the class name is converted to underscore-case, and a
+     * trailing '_view' suffix is dropped.
+     * Example: "CamelCaseView" becomes "camel_case"
+     * @type String
+     */
+    __view_type_id__: null
+    
+    /**
      * Array of [view, selector]-pairs.
      * @type Array<[BaseView, String]>
      */
     subviews : []
     
+    /**
+     * Whether this view has been added to the DOM.
+     * @type Boolean
+     */
+    _parented: false
+    
     
     
     constructor : function BaseView
         @__class__      = @constructor
         @__superclass__ = @..__super__.constructor
+        @__view_type_id__ or= _.str.underscored @getClassName() .replace /_view$/, ''
         @waitingOn      = 0
         @subviews       = []
         Backbone.View ...
@@ -90,6 +106,10 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
         _.pluck @subviews, 0 .forEach @removeSubview, this
         this
     
+    bubbleEvent: (evt) ->
+        @invokeSubviews 'trigger', ...arguments
+        this
+    
     
     ### Rendering Chain
     
@@ -123,14 +143,32 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
         this
     
     
+    attach: (el) ->
+        @$el.appendTo el
+        # only trigger the event the first time
+        return this if @_parented
+        @_parented = true
+        _.delay do
+            ~> # have to let DOM settle to ensure elements can be found
+                @delegateEvents()
+                @trigger 'parent', this
+            50
+        this
+    
+    remove : ->
+        @undelegateEvents()
+        @$el.remove()
+        return this unless @_parented
+        @_parented = false
+        @trigger 'unparent', this
+        this
     
     
     ### UI Utilities
     
-    hide   : -> @$el.hide();      this
-    show   : -> @$el.show();      this
-    remove : -> @$el.remove();    this
-    clear  : -> @model.destroy(); @remove()
+    hide   : -> @$el.hide();      @trigger('hide', this);       this
+    show   : -> @$el.show();      @trigger('show', this);       this
+    clear  : -> @model.destroy(); @trigger('clear', this);      @remove()
     
     
     # remove : ->
index 298b6d2..d031f97 100644 (file)
@@ -18,7 +18,8 @@ DataView = exports.DataView = BaseView.extend do # {{{
     className      : 'data-ui'
     template       : require 'kraken/template/data'
     
-    datasources : {}
+    data        : {}
+    datasources : null
     
     
     
@@ -28,6 +29,7 @@ DataView = exports.DataView = BaseView.extend do # {{{
     initialize: ->
         @graph_id = @options.graph_id
         BaseView::initialize ...
+        @datasources = @model.sources
         @on 'ready', @onReady, this
         @load()
     
@@ -50,8 +52,9 @@ DataView = exports.DataView = BaseView.extend do # {{{
     
     load: ->
         @wait()
-        $.getJSON '/datasources/all', (@datasources) ~>
-            _.each @datasources, @canonicalizeDataSource, this
+        $.getJSON '/datasources/all', (@data) ~>
+            _.each @data, @canonicalizeDataSource, this
+            @model.sources.reset _.map @data, -> it
             @ready = true
             @unwait()
             @render()
@@ -81,7 +84,7 @@ DataView = exports.DataView = BaseView.extend do # {{{
     
     toTemplateLocals: ->
         attrs = _.clone @model.attributes
-        { $, _, op, @model, view:this, @graph_id, @datasources, } import attrs
+        { $, _, op, @model, view:this, @graph_id, @datasources } import attrs
     
     # attachSubviews: ->
     #     @$el.empty()
index fc8983c..0cef0ad 100644 (file)
@@ -29,6 +29,11 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
      */
     metrics : null
     
+    defaults : ->
+        palette : null
+        lines   : []
+        metrics : []
+    
     
     constructor: function DataSet
         BaseModel ...
@@ -36,24 +41,21 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
     initialize : ->
         BaseModel::initialize ...
         @sources = new DataSourceList
-        mx = @attributes.metrics or= {}
-        @metrics = new MetricList mx.columns
+        @metrics = new MetricList @attributes.metrics
         @on 'change:metrics', @onMetricChange, this
     
     
-    defaults : ->
-        palette : null
-        lines   : []
-        metrics :
-            defaults : {}
-            columns  : []
-    
     
     load: (opts={}) ->
         return this if @ready and not opts.force
         @wait()
         @trigger 'load', this
-        Seq()
+        Seq @metrics.pluck 'source_id'
+            .parMap_ (next, source_id) ->
+                DataSource.lookup source_id, next
+            .seqEach_ (next, source) ~>
+                @sources.add source
+                next.ok source
             .seq ~>
                 @ready = true
                 @trigger 'ready', this
@@ -61,6 +63,10 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
         this
     
     
+    # TODO: toJSON() must ensure columns in MetricList are ordered by index
+    #   ...in theory, MetricList.comparator now does this
+    
+    
     /**
      * @returns {Array} The reified dataset, materialized to an array of data-series arrays.
      */
@@ -74,10 +80,7 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
     
     
     onMetricChange: ->
-        mx = @get 'metrics'
-        cols = mx.columns.map (col) ->
-            _.clone(col) import mx.defaults
-        @metrics.reset cols
+        @metrics.reset @get 'metrics'
     
 # }}}
 
index aeb6910..efb7d0d 100644 (file)
@@ -28,9 +28,10 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
         BaseView::initialize ...
         @views_by_cid = {}
         @addAllMetrics()
-        @model.metrics.on 'add',    @addMetric,         this
-        @model.metrics.on 'remove', @removeMetric,      this
-        @model.metrics.on 'reset',  @addAllMetrics,     this
+        @model.metrics
+            .on 'add',    @addMetric,         this
+            .on 'remove', @removeMetric,      this
+            .on 'reset',  @addAllMetrics,     this
     
     
     newMetric: ->
index 61a9660..7d3ef82 100644 (file)
@@ -8,14 +8,28 @@
  * @class
  */
 DataSource = exports.DataSource = BaseModel.extend do # {{{
+    __bind__ : <[ onLoadSuccess onLoadError ]>
     urlRoot  : '/datasources'
+    ready    : false
+    
+    /**
+     * Parsed data for this datasource.
+     * @type Array
+     */
+    data : null
+    
+    
+    
     
     
     constructor: function DataSource
         BaseModel ...
     
     initialize: ->
+        @attributes = @canonicalize @attributes
         BaseModel::initialize ...
+        @constructor.register this
+        @on 'load-success', @onLoadSuccess, this
     
     
     defaults: ->
@@ -32,7 +46,7 @@ DataSource = exports.DataSource = BaseModel.extend do # {{{
         
         timespan : 
             start     : null
-            stop      : null
+            end       : null
             step      : '1mo'
         
         columns       : []
@@ -44,10 +58,83 @@ DataSource = exports.DataSource = BaseModel.extend do # {{{
     url: ->
         "/datasources/#{@id}.json"
     
+    canonicalize: (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.type or= 'int'
+                    col
+        else
+            ds.metrics = _.map cols.labels, (label, idx) ->
+                {idx, label, type:cols.types[idx] or 'int'}
+        ds
+    
+    load: ->
+        @trigger 'load', this
+        url = @get 'url'
+        switch @get 'format'
+        case 'json'
+            @loadJSON url
+        case 'csv'
+            @loadCSV url
+        default
+            console.error "#this.load() Unknown Data Format!"
+            @trigger 'load-error', this, 'Unknown Data Format!'
+        this
+    
+    onLoadSuccess: ->
+        return if @ready
+        @ready = true
+        @trigger 'ready', this
+    
+    onLoadError: (jqXHR, txtStatus, err) ->
+        @_loadError = true
+        console.error "#this Error loading data! -- #msg: #{err or ''}"
+    
+    
+    loadJSON: (url) ->
+        $.ajax do
+            url      : url
+            dataType : 'json'
+            success : (@data) ->
+                @trigger 'load-success', this
+            error : (jqXHR, txtStatus, err) ->
+                @trigger 'load-error', this, txtStatus, err
+        this
+    
+    loadCSV: (url) ->
+        $.ajax do
+            url      : url
+            dataType : 'text'
+            success : (@data) ->
+                @trigger 'load-success', this
+            error : (jqXHR, txtStatus, err) ->
+                @trigger 'load-error', this, txtStatus, err
+        this
+    
+    parseCSV: (data) ->
+        ...
+    
+    
+    getColumnName: (idx) ->
+        @get('metrics')?[idx]?.label
+    
+    getColumnIndex: (name) ->
+        return that.idx if _.find @get('metrics'), -> it.label is name
+        -1
+    
+    
 # }}}
 
 
-
 /**
  * @class
  */
@@ -57,15 +144,16 @@ DataSourceList = exports.DataSourceList = BaseList.extend do # {{{
     
     constructor: function DataSourceList then BaseList ...
     initialize : -> BaseList::initialize ...
-    
-    
 # }}}
 
 
+
+
 /* * * *  DataSource Cache  * * * */
 
 DataSource import do
     CACHE : new DataSourceList
+    ready : false
     
     register: (model) -&