First pass at a d3 line chart.
authorDavid Schoonover <dsc@wikimedia.org>
Wed, 20 Jun 2012 08:15:25 +0000 (01:15 -0700)
committerDavid Schoonover <dsc@wikimedia.org>
Wed, 20 Jun 2012 08:15:25 +0000 (01:15 -0700)
14 files changed:
lib/base/base-view.co
lib/chart/chart-type.co
lib/chart/type/d3/d3-bar-chart-type.co
lib/chart/type/d3/d3-geo-chart-type.co
lib/chart/type/d3/d3-line-chart-type.co
lib/dashboard/dashboard-model.co
lib/data/metric-model.co
lib/main-edit.co
lib/server/server.co
lib/template/graph/graph-display.jade
www/css/chart.styl [new file with mode: 0644]
www/d3-test.jade [new file with mode: 0644]
www/modules.yaml
www/schema/d3/d3-line.yaml [new file with mode: 0644]

index 21c1cbc..18646a5 100644 (file)
@@ -4,6 +4,8 @@ Backbone = require 'backbone'
 } = require 'kraken/util'
 { BaseBackboneMixin, mixinBase,
 } = require 'kraken/base/base-mixin'
+{ BaseModel,
+} = require 'kraken/base/base-mixin'
 { DataBinding,
 } = require 'kraken/base/data-binding'
 
index ca74132..b10a154 100644 (file)
@@ -395,7 +395,7 @@ class exports.ChartType extends ReadyEmitter
      * 
      * @returns {Object} The derived data.
      */
-    transform: ->
+    transform: (model, view) ->
         @model.getOptions()
     
     
index e69de29..e1fc007 100644 (file)
@@ -0,0 +1,10 @@
+d3 = require 'd3'
+
+{ _, op,
+} = require 'kraken/util'
+{ ChartType,
+} = require 'kraken/chart'
+
+
+
+exports.foo = 1
index 4709aa0..26d5ad9 100644 (file)
@@ -5,6 +5,9 @@ ColorBrewer = require 'colorbrewer'
 { ChartType,
 } = require 'kraken/chart'
 
+
+
+
 class GeoWorldChartType extends ChartType
     __bind__ : <[ dygNumberFormatter dygNumberFormatterHTML ]>
     SPEC_URL : '/schema/d3/d3-geo-world.json'
@@ -72,6 +75,11 @@ class GeoWorldChartType extends ChartType
         path = d3.geo.path()
             .projection projection
         
+        # path objects
+        feature = map.selectAll ".feature"
+        infobox = d3.select '.infobox'
+        
+        
         move = ->
             projection
                 .translate d3.event.translate
@@ -95,9 +103,6 @@ class GeoWorldChartType extends ChartType
                     .attr "transform", "translate(0,0)"
                     .call zoom
         
-        # path objects
-        feature := map.selectAll ".feature"
-        
         # rectangle
         map.append "svg:rect"
             .attr "class", "frame"
@@ -106,8 +111,6 @@ class GeoWorldChartType extends ChartType
         
         
         ### infobox
-        infobox := d3.select '#infobox'
-        
         infobox.select '#ball'
             .append "svg:svg"
                 .attr "width", "100%"
@@ -157,7 +160,7 @@ class GeoWorldChartType extends ChartType
         
     
 
-
+data = null
 main = ->
     jQuery.ajax do
         url : "/data/geo/data/en_geo_editors.json"
index e69de29..ccf27a1 100644 (file)
@@ -0,0 +1,121 @@
+d3 = require 'd3'
+ColorBrewer = require 'colorbrewer'
+
+{ _, op,
+} = require 'kraken/util'
+{ ChartType,
+} = require 'kraken/chart'
+
+
+class LineChartType extends ChartType
+    __bind__ : <[ determineSize ]>
+    SPEC_URL : '/schema/d3/d3-line.json'
+    
+    # NOTE: ChartType.register() must come AFTER `typeName` declaration.
+    typeName : 'd3-line'
+    ChartType.register this
+    
+    
+    /**
+     * Hash of role-names to the selector which, when applied to the view,
+     * returns the correct element.
+     * @type Object
+     */
+    roles :
+        viewport : '.viewport'
+        legend   : '.graph-legend'
+    
+    
+    
+    -> super ...
+    
+    transform: ->
+        dataset = @model.dataset
+        options = @model.getOptions() import @determineSize()
+        options import do
+            colors             : dataset.getColors()
+        options
+    
+    
+    renderChart: (rawData, viewport, options, lastChart) ->
+        viewport.empty()
+        
+        ### Starting with http://bost.ocks.org/mike/chart/
+        
+        
+        # formatDate = d3.time.format("%b %Y")
+        # chart = timeSeriesChart()
+        #     .x (d) -> formatDate.parse(d.date)
+        #     .y (d) -> +d.price
+        # d3.csv("sp500.csv", (data) ->
+        #   d3.select("#example")
+        #       .datum(data)
+        #       .call(chart)
+        
+        margin = {top: 20, right: 20, bottom: 20, left: 20}
+        width  = 760
+        height = 320
+        xValue = (d) -> d[0]
+        yValue = (d) -> d[1]
+        xScale = d3.time.scale()
+        yScale = d3.scale.linear()
+        xAxis  = d3.svg.axis().scale(xScale).orient("bottom").tickSize(6, 0)
+        X      = (d) -> xScale(d[0])
+        Y      = (d) -> yScale(d[1])
+        line   = d3.svg.line().x(X).y(Y)
+        
+        
+        chart = (selection) ->
+            selection.each (data) ->
+            
+                # Convert data to standard representation greedily
+                # this is needed for nondeterministic accessors.
+                data = data.map (d, i) ->
+                    [ xValue.call(data, d, i), yValue.call(data, d, i) ]
+                
+                
+                # Update the x-scale.
+                xScale
+                    .domain d3.extent data, xValue
+                    .range [ 0, width - margin.left - margin.right ]
+                
+                # Update the y-scale.
+                yScale
+                    .domain [ 0, d3.max data, yValue ]
+                    .range [ height - margin.top - margin.bottom, 0 ]
+                
+                # Select the svg element, if it exists.
+                svg = d3.select(this).selectAll("svg").data([data])
+                
+                # Otherwise, create the skeletal chart.
+                gEnter = svg.enter().append("svg").append("g")
+                gEnter.append("path")
+                    .attr "class", "line"
+                    .style "stroke", (d, i) -> options.colors[i]
+                gEnter.append("g").attr("class", "x axis")
+                
+                # Update the outer dimensions.
+                svg .attr("width", width)
+                    .attr("height", height)
+                
+                # Update the inner dimensions.
+                g = svg.select("g")
+                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
+                
+                # Update the line path.
+                g.select(".line")
+                    .attr("d", line)
+                
+                # Update the x-axis.
+                g.select(".x.axis")
+                    .attr("transform", "translate(0," + yScale.range()[0] + ")")
+                    .call(xAxis)
+        
+        d3.select viewport.0
+            .datum rawData
+            .call chart
+    
+    
+    
+
+module.exports = exports = LineChartType
\ No newline at end of file
index 4ae98dc..16e1dd8 100644 (file)
@@ -1,8 +1,8 @@
 { _, op,
 } = require 'kraken/util'
-{ BaseView,
+{ BaseModel,
 } = require 'kraken/base'
-{ Graph,
+{ Graph, GraphList,
 } = require 'kraken/graph/graph-model'
 
 
  * @class
  */
 Dashboard = exports.Dashboard = BaseModel.extend do # {{{
-    urlRoot  : '/dashboards'
+    urlRoot : '/dashboards'
+    graphs  : null # GraphList
+    byTab   : null # GraphList[]
     
     
     constructor: function Dashboard
+        @graphs = new GraphList
+        @byTab  = []
         BaseModel ...
     
     initialize: ->
@@ -22,7 +26,31 @@ Dashboard = exports.Dashboard = BaseModel.extend do # {{{
     
     defaults: ->
         name : null
-        graphs : []
+        tabs : [ { name:"Main", graph_ids:[] } ]
+    
+    
+    
+    
+    addTab: (tabName) ->
+        ...
+    
+    
+    /**
+     * Look up a tab.
+     * 
+     * @param {String|Number} tab Tab name or index.
+     * @returns {Tab} Tab object.
+     */
+    getTab: (tab) ->
+        tabs = @get 'tabs'
+        return tabs[tab] if typeof tab is 'number'
+        _.find tabs, -> it.name is tab
+    
+    addGraph: (graph, tabName) ->
+        ...
+    
+    getGraphs: (tab) ->
+        ...
     
     
 # }}}
index 3077fd6..e270c82 100644 (file)
@@ -65,10 +65,10 @@ Metric = exports.Metric = BaseModel.extend do # {{{
     
     
     getDateColumn: ->
-        @source.getDateColumn()
+        @source?.getDateColumn()
     
     getData: ->
-        @source.getColumn @get 'source_col'
+        @source?.getColumn @get 'source_col'
     
     getLabel: ->
         @get('label') or @getPlaceholderLabel()
index ae9b194..74040d5 100644 (file)
@@ -1,5 +1,3 @@
-{EventEmitter} = require 'events'
-
 Seq      = require 'seq'
 Backbone = require 'backbone'
 
index bb01baa..1f8788a 100755 (executable)
@@ -118,7 +118,7 @@ app.configure ->
         log_level : LOG_LEVEL
     app.use require('browserify') do
         mount   : '/vendor/browserify.js'
-        require : <[ seq events ]>
+        require : <[ seq d3 events ]>
         cache   : false
         # cache   : "#CWD/.cache/browserify/cache.json"
     
index f15e2b6..24de1a0 100644 (file)
@@ -12,7 +12,7 @@ section.graph-display.graph(id=graph_id)
                 span.dates(data-bind='callout.month.dates') #{callout.month.dates}
                 span.value(data-bind='callout.month.value') #{callout.month.value}
         h2
-            a.graph-name(href="#{model.toLink()}", data-bind='name') #{name}
+            a.graph-name(href="#{model.toLink()}", data-bind='name', name="#{graph_id}") #{name}
     
     .graph-viewport-row.row-fluid: .inner
         .viewport
diff --git a/www/css/chart.styl b/www/css/chart.styl
new file mode 100644 (file)
index 0000000..b9b7239
--- /dev/null
@@ -0,0 +1,11 @@
+// Styling for SVG charts
+
+.viewport svg
+    
+    .line
+      fill none
+      stroke #000
+      stroke-width 1.5px
+    
+    
+
diff --git a/www/d3-test.jade b/www/d3-test.jade
new file mode 100644 (file)
index 0000000..39fbc87
--- /dev/null
@@ -0,0 +1,23 @@
+extends layout
+
+block title
+    title Edit Graph | Limn
+
+append styles
+    mixin css('/vendor/bootstrap-colorpicker/css/colorpicker.css')
+    mixin css('/vendor/bootstrap-datepicker/css/datepicker.css')
+    mixin css('graph.css')
+    mixin css('chart.css')
+    mixin css('data.css')
+    mixin css('isotope.css')
+
+block main-scripts
+    script
+        d3 = require('d3')
+        LineChartType = require('kraken/chart/type/d3/d3-line-chart-type')
+        jQuery(function(){
+            setTimeout(function(){
+                ct = view.chartType = graph.chartType = new LineChartType(graph, view); 
+            }, 100);
+        });
+    script(src="/js/kraken/main-edit.js?"+version)
index c11206c..4ee875a 100644 (file)
@@ -40,7 +40,7 @@ dev:
         - moment.mod.min
         - dygraph
         - dygraph-extra
-        - d3
+        # - d3
 
 -   suffix: .mod.js
     paths:
@@ -111,6 +111,11 @@ dev:
                     - chart-option-view
                     - index
                 - type:
+                    - d3:
+                        - d3-line-chart-type
+                        - d3-geo-chart-type
+                        - d3-bar-chart-type
+                        - index
                     - dygraphs
                     - index
                 - chart-type
diff --git a/www/schema/d3/d3-line.yaml b/www/schema/d3/d3-line.yaml
new file mode 100644 (file)
index 0000000..88a3564
--- /dev/null
@@ -0,0 +1,9 @@
+-   name: zoom.start
+    tags:
+    - interactivity
+    - zoom
+    type: Float
+    default: 1.0
+    desc: Initial zoom-level, where 1.0 (100% zoom) shows the full map in the
+        frame (the default).
+