Created a D3ChartType ChartType, abstracting the different d3 elements (line,bar...
authordeclerambaul <fabian.kaelin@gmail.com>
Wed, 27 Jun 2012 16:41:05 +0000 (18:41 +0200)
committerdeclerambaul <fabian.kaelin@gmail.com>
Wed, 27 Jun 2012 16:41:05 +0000 (18:41 +0200)
12 files changed:
lib/chart/chart-type.co
lib/chart/index.co
lib/chart/type/d3-chart.co [moved from lib/chart/type/d3/d3-chart-type.co with 84% similarity]
lib/chart/type/d3/d3-bar-chart-type.co [deleted file]
lib/chart/type/d3/d3-bar-element.co [new file with mode: 0644]
lib/chart/type/d3/d3-chart-element.co [new file with mode: 0644]
lib/chart/type/d3/d3-geo-element.co [moved from lib/chart/type/d3/d3-geo-chart-type.co with 100% similarity]
lib/chart/type/d3/d3-line-chart-type.co [deleted file]
lib/chart/type/d3/d3-line-element.co [new file with mode: 0644]
lib/chart/type/d3/index.co
lib/util/formatters.co
www/modules.yaml

index 2030a90..1aa4b9e 100644 (file)
@@ -38,6 +38,7 @@ class exports.ChartType extends ReadyEmitter
      * Register a new chart type.
      */
     @register = (Subclass) ->
+        console.log "ChartType.register(#Subclass)"
         KNOWN_CHART_TYPES[ Subclass::typeName ] = Subclass
     
     /**
@@ -53,6 +54,7 @@ class exports.ChartType extends ReadyEmitter
      * @returns {ChartType}
      */
     @create = (model, view) ->
+        console.log "ChartType.create(#model) ->", model
         return null unless Type = @lookup model
         new Type model, view
     
index a961cba..c5fd1e2 100644 (file)
@@ -1,7 +1,8 @@
 chart_type   = require 'kraken/chart/chart-type'
 chart_option = require 'kraken/chart/option'
 dygraphs     = require 'kraken/chart/type/dygraphs'
-d3_charts    = require 'kraken/chart/type/d3'
+d3_chart     = require 'kraken/chart/type/d3-chart'
+d3_elements  = require 'kraken/chart/type/d3'
 
 exports import chart_type import chart_option \
-        import dygraphs import d3_charts
+        import dygraphs import d3_chart import d3_elements
similarity index 84%
rename from lib/chart/type/d3/d3-chart-type.co
rename to lib/chart/type/d3-chart.co
index 8f73c54..99d0b6d 100644 (file)
@@ -5,6 +5,9 @@ ColorBrewer = require 'colorbrewer'
 } = require 'kraken/util'
 { ChartType,
 } = require 'kraken/chart/chart-type'
+{ D3ChartElement,
+} = require 'kraken/chart/type/d3/d3-chart-element'
+
 
 root = do -> this
 
@@ -28,11 +31,10 @@ class exports.D3ChartType extends ChartType
         legend   : '.graph-legend'
     
     
-    # constructor: function D3ChartType
-    -> super ...
-    
     
+    -> super ...
     
+        
     getData: ->
         @model.dataset.getColumns()
     
@@ -101,18 +103,24 @@ class exports.D3ChartType extends ChartType
         frame.select ".x.axis.time"
             .attr "transform", "translate(0,#{yScale.range()[0]})"
             .call xAxis        
-                
+             
+
+
+        # this is wrong. should work using d3 datajoins.
+        # i.e. use the enter/exit function to add/remove
+        # metrics from the graph
         for i,metric in @model.dataset.metrics.models
             # metric defined charttype
-            chartType = metric.get "chartType"            
+            chartElement = metric.get "chartElement"            
             # otherwise the graph defined charttype
             # FOR NOW take line as default
-            chartType or= 'd3-line' # @model.get "chartType"
+            chartElement ?= 'd3-line' # @model.get "chartType"
                     
 
-            # create d3 charttype and render it
-            model = ChartType.create chartType                
-            model.renderChartType metric, frame ,xScale, yScale
+            # create d3 chart element and render it
+            chEl = D3ChartElement.create chartElement     
+            console.log chEl         
+            chEl.renderChartElement metric, frame ,xScale, yScale
 
         
         svg
diff --git a/lib/chart/type/d3/d3-bar-chart-type.co b/lib/chart/type/d3/d3-bar-chart-type.co
deleted file mode 100644 (file)
index 0a257ff..0000000
+++ /dev/null
@@ -1,241 +0,0 @@
-d3 = require 'd3'
-
-{ _, op,
-} = require 'kraken/util'
-{ ChartType,
-} = require 'kraken/chart/chart-type'
-
-root = do -> this
-
-
-class exports.BarChartType extends ChartType
-    __bind__ : <[ determineSize ]>
-    SPEC_URL : '/schema/d3/d3-bar.json'
-    
-    # NOTE: ChartType.register() must come AFTER `typeName` declaration.
-    typeName : 'd3-bar'    
-    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 ...
-    
-    getData: ->
-        @model.dataset.getColumns()
-    
-    
-    transform: ->
-        dataset = @model.dataset
-        options = @model.getOptions() import @determineSize()
-        options import do
-            colors             : dataset.getColors()
-            labels             : dataset.getLabels()
-        options
-
-
-    renderChartType: (metric, svgEl ,xScale, yScale) ->        
-        
-        X = (d, i) -> xScale d[0]
-        Y = (d, i) -> yScale d[1]
-        
-
-        ### Render the line path
-        metricBars = root.metricBars = svgEl.append "g"
-            .attr "class", "metric bars "+metric.get 'label'
-                
-        data = d3.zip metric.getDateColumn(),metric.getData()                
-
-        ### Render Bars
-        barWidth = svgEl.attr('width')/data.length
-        barHeight = (d) ->  svgEl.attr('height')-Y(d)
-
-        metricBars.selectAll "bar"    
-            .data data
-            .enter().append "rect"
-                .attr "class", (d, i) ->  "metric bar #i"
-                .attr "x", X
-                .attr "y", Y
-                .attr "height", barHeight
-                .attr "width", -> barWidth                
-                .attr "fill", metric.get 'color'
-                .attr "stroke", "white"
-                .style "opacity", "0.4"
-                .style "z-index", -10
-
-
-        # adding event listeners
-        chT = this
-        metricBars.selectAll ".metric.bar"
-            .on "mouseover", (d, i) ->
-                
-                svgEl.append "text"
-                    .attr "class", "mf"
-                    .attr "dx", 50
-                    .attr "dy", 100   
-                    .style "font-size", "0px"                                
-                    .transition()
-                    .duration(800)                
-                        .text "Uh boy, the target would be:  "+chT.numberFormatter(d[1]).toString()
-                        .style "font-size", "25px"                    
-            .on "mouseout", (d, i) ->
-
-                svgEl.selectAll ".mf"
-                    .transition()
-                    .duration(300)
-                    .text "BUMMER!!!"
-                    .style "font-size", "0px"
-                    .remove()
-                
-
-
-        svgEl        
-    
-    renderChart: (data, viewport, options, lastChart) ->
-        ### Starting with http://bost.ocks.org/mike/chart/
-        
-        margin = {top: 20, right: 20, bottom: 20, left: 20}
-        width  = 760
-        height = 320
-        xScale = d3.time.scale()
-        yScale = d3.scale.linear()
-        
-        dates  = data[0]
-        cols   = data.slice(1)
-        
-        # Calculate extents using all the data points (but not dates)
-        # allValues = d3.merge @model.dataset.getDataColumns()
-        allValues = d3.merge cols
-        
-        
-        # Update the x-scale with the extents of the dates.
-        xScale
-            .domain d3.extent dates
-            .range [ 0, width - margin.left - margin.right ]
-        
-        # Update the y-scale with the extents of the data.
-        yScale
-            .domain d3.extent allValues
-            .range [ height - margin.top - margin.bottom, 0 ]
-        
-        # Select the svg element, if it exists.
-        svg = d3.select viewport.0 .selectAll "svg"
-            .data [cols]
-        
-        # ...Otherwise, create the skeletal chart.
-        enterFrame = svg.enter()
-            .append "svg" .append "g"
-                .attr "class", "frame"
-        enterFrame.append "g"
-            .attr "class", "x axis time"
-        
-        # Update chart dimensions.
-        svg .attr "width", width
-            .attr "height", height
-        frame = svg.select "g.frame"
-            .attr "transform", "translate(#{margin.left},#{margin.top})"
-        
-        # Update the x-axis.
-        xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(6, 0)
-        frame.select ".x.axis.time"
-            .attr "transform", "translate(0,#{yScale.range()[0]})"
-            .call xAxis
-                    
-        X = (d, i) -> xScale d[0]
-        Y = (d, i) -> yScale d[1]
-
-        ### Render Bars
-        barWidth = svg.attr('width')/dates.length
-        barHeight = (d) -> svg.attr('height')-Y(d)
-
-        bars = frame.selectAll "g.bars"
-            .data cols.map -> d3.zip dates, it
-        bars.enter().append "g"
-            .attr "class", (col, i)     ->  "metric bars #i"
-        bars.exit().remove()
-        
-        bars.selectAll ".bar"
-            .data op.first
-            .enter().append "rect"
-                .attr "class", "bar"
-                .attr "x", X
-                .attr "y", Y
-                .attr "height", barHeight
-                .attr "width", -> barWidth
-                # TODO grab color from graph spec
-                .attr "fill", "red" 
-                .attr "stroke", "white"
-                        
-        
-        ### Mouse Lens
-        lens = root.lens = frame.selectAll "g.lens"
-            .data [[]]
-        gLens = lens.enter().append "g"
-            .attr "class", "lens"
-            .style "z-index", 1e9
-        gInner = gLens.append "g"
-            .attr "transform", "translate(1.5em,0)"
-        gInner.append "circle"
-            .attr "r", "1.5em"
-            # .style "opacity", "0.4"
-            # .style "fill", "white"
-            .style "fill", "rgba(255, 255, 255, 0.4)"
-            .style "stroke", "white"
-            .style "stroke-width", "3px"
-        gInner.append "text"
-            .attr "y", "0.5em"
-            .attr "text-anchor", "middle"
-            .style "fill", "white"
-            .style "font", "12px Helvetica"
-            .style "font-weight", "bold"
-        
-
-        mf = frame.selectAll "g.mf"
-            .data ["mf"]
-            .enter().append "g"
-                .attr "class", "mf"                
-            .append "text"
-                .attr "class", "yoyo"
-                .attr "dx", 50
-                .attr "dy", 100
-
-
-
-        bars.selectAll ".bar"
-            .on "mouseover", (d, i) ->
-                el = root.el = el # DOM element of event
-                # {r,g,b} = color = d3.rgb options.colors[i]                            
-                mf
-                    .transition()
-                    .duration(300)
-                    .ease("exp")
-                        .text "Uh boy, the target would be:"+d[1]
-                        .style "font-size", "25px"
-                
-
-            .on "mouseout", (d, i) ->
-                mf
-                    .transition()
-                    .duration(1000)
-                    .text "BUMMER!!!"
-                    .style "font-size", "0px"
-
-                
-                
-                # {x:lineX, y:lineY} = root.pt = line.indexToPoint idx
-                # lens = frame.select "g.lens"                
-                #     .attr "transform", "translate(#lineX, #lineY)"
-                # lens.select "circle" .style "fill", "rgba(#r, #g, #b, 0.4)"
-                # lens.select "text" .text Y
-        
-        
-        
-        svg
diff --git a/lib/chart/type/d3/d3-bar-element.co b/lib/chart/type/d3/d3-bar-element.co
new file mode 100644 (file)
index 0000000..dcb300a
--- /dev/null
@@ -0,0 +1,78 @@
+d3 = require 'd3'
+
+{ _, op,
+} = require 'kraken/util'
+{ D3ChartElement    
+} = require 'kraken/chart/type/d3/d3-chart-element'
+
+_fmt = require 'kraken/util/formatters'
+
+root = do -> this
+
+class exports.BarChartType extends D3ChartElement
+    __bind__ : <[ ]>
+    SPEC_URL : '/schema/d3/d3-bar.json'
+    
+    # NOTE: D3ChartElement.register() must come AFTER `typeName` declaration.
+    chartElement : 'd3-bar'    
+    D3ChartElement.register this            
+
+    -> super ...
+
+    renderChartElement: (metric, svgEl ,xScale, yScale) ->        
+        
+        X = (d, i) -> xScale d[0]
+        Y = (d, i) -> yScale d[1]
+        
+
+        ### Render the line path
+        metricBars = root.metricBars = svgEl.append "g"
+            .attr "class", "metric bars "+metric.get 'label'
+                
+        data = d3.zip metric.getDateColumn(),metric.getData()                
+
+        ### Render Bars
+        barWidth = svgEl.attr('width')/data.length
+        barHeight = (d) ->  svgEl.attr('height')-Y(d)
+
+        metricBars.selectAll "bar"    
+            .data data
+            .enter().append "rect"
+                .attr "class", (d, i) ->  "metric bar #i"
+                .attr "x", X
+                .attr "y", Y
+                .attr "height", barHeight
+                .attr "width", -> barWidth                
+                .attr "fill", metric.get 'color'
+                .attr "stroke", "white"
+                .style "opacity", "0.4"
+                .style "z-index", -10
+
+
+        # adding event listeners
+        chT = this
+        metricBars.selectAll ".metric.bar"
+            .on "mouseover", (d, i) ->
+                
+                svgEl.append "text"
+                    .attr "class", "mf"
+                    .attr "dx", 50
+                    .attr "dy", 100   
+                    .style "font-size", "0px"                                
+                    .transition()
+                    .duration(800)                
+                        .text "Uh boy, the target would be:  "+_fmt.numberFormatter(d[1]).toString()
+                        .style "font-size", "25px"                    
+            .on "mouseout", (d, i) ->
+
+                svgEl.selectAll ".mf"
+                    .transition()
+                    .duration(300)
+                    .text "BUMMER!!!"
+                    .style "font-size", "0px"
+                    .remove()
+                
+
+
+        svgEl        
+    
diff --git a/lib/chart/type/d3/d3-chart-element.co b/lib/chart/type/d3/d3-chart-element.co
new file mode 100644 (file)
index 0000000..bc93ed1
--- /dev/null
@@ -0,0 +1,79 @@
+d3 = require 'd3'
+ColorBrewer = require 'colorbrewer'
+
+{ _, op,
+} = require 'kraken/util'
+{ ReadyEmitter,
+} = require 'kraken/util/event'
+# Base = require 'kraken/base/base'
+
+
+root = do -> this
+
+/**
+ * Map of known libraries by name.
+ * @type Object
+ */
+KNOWN_CHART_ELEMENTS = exports.KNOWN_CHART_ELEMENTS = {}
+
+class exports.D3ChartElement extends ReadyEmitter
+    __bind__ : <[ ]>
+    SPEC_URL : '/schema/d3/d3-chart.json'
+    
+    
+    ### Class Methods
+    
+    /**
+     * Register a new d3 element
+     */
+    @register = (Subclass) ->
+        console.log "D3ChartElement.register(#Subclass)"
+        KNOWN_CHART_ELEMENTS[ Subclass::chartElement ] = Subclass
+    
+    /**
+     * Look up a `charttype` by `typeName`.
+     */
+    @lookup = (name) ->
+        name = name.get('chartElement') if name instanceof Backbone.Model
+        KNOWN_CHART_ELEMENTS[name]
+    
+    /**
+     * Look up a chart type by name, returning a new instance
+     * with the given model (and, optionally, view).
+     * @returns {D3ChartElement}
+     */
+    @create = (name) ->
+        console.log "D3ChartElement.create(#name)"        
+        return null unless Type = @lookup name
+        
+        new Type 
+            
+    
+    () ->        
+        _.bindAll this, ...@__bind__ # TODO: roll up MRO
+        @loadSpec() unless @ready
+        super ...
+
+    
+
+    /**
+     * Load the corresponding chart specification, which includes
+     * info about valid options, along with their types and defaults.
+     */
+    loadSpec: ->
+        return this if @ready
+        proto = @constructor::
+        jQuery.ajax do
+            url     : @SPEC_URL
+            dataType : 'json'
+            success : (spec) ~>
+                proto.spec = spec
+                proto.options_ordered = spec
+                proto.options = _.synthesize spec, -> [it.name, it]
+                proto.ready = true
+                @triggerReady()
+            error: ~> console.error "Error loading #{@typeName} spec! #it"
+        this
+    
+    
+    
diff --git a/lib/chart/type/d3/d3-line-chart-type.co b/lib/chart/type/d3/d3-line-chart-type.co
deleted file mode 100644 (file)
index e0974f7..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-d3 = require 'd3'
-ColorBrewer = require 'colorbrewer'
-
-{ _, op,
-} = require 'kraken/util'
-{ ChartType,
-} = require 'kraken/chart/chart-type'
-
-root = do -> this
-
-
-class exports.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 ...
-    
-    
-    getData: ->
-        @model.dataset.getColumns()
-    
-    
-    transform: ->
-        dataset = @model.dataset
-        options = @model.getOptions() import @determineSize()
-        options import do
-            colors             : dataset.getColors()
-            labels             : dataset.getLabels()
-        options
-    
-    renderChartType: (metric, svgEl ,xScale, yScale) ->        
-        
-        X = (d, i) -> xScale d[0]
-        Y = (d, i) -> yScale d[1]
-        line = d3.svg.line().x(X).y(Y)
-
-        ### Render the line path
-        metricLine = root.metricLine = svgEl.append "g"
-            .attr "class", "g metric line "+metric.get 'label'
-                
-        data = d3.zip metric.getDateColumn(),metric.getData()                
-
-        metricLine.selectAll "path.line"
-            .data d3.zip data.slice(0,-1), data.slice(1)
-            .enter().append "path"
-                .attr "d", line
-                .attr "class", (d, i) -> "metric line segment #i"
-                .style "stroke", metric.get 'color'
-
-        
-        ### Mouse Lens
-        lens = root.lens = svgEl.selectAll "g.lens"
-            .data [[]]
-        gLens = lens.enter().append "g"
-            .attr "class", "lens"
-            .style "z-index", 1e9
-        gInner = gLens.append "g"
-            .attr "transform", "translate(1.5em,0)"
-        gInner.append "circle"
-            .attr "r", "1.5em"
-            # .style "opacity", "0.4"
-            # .style "fill", "white"
-            .style "fill", "rgba(255, 255, 255, 0.4)"
-            .style "stroke", "white"
-            .style "stroke-width", "3px"
-        gInner.append "text"
-            .attr "y", "0.5em"
-            .attr "text-anchor", "middle"
-            .style "fill", "black"
-            .style "font", "12px Helvetica"
-            .style "font-weight", "bold"
-
-        # event listeners
-        chT = this
-        metricLine.selectAll ".line.segment"
-            .on "mouseover", (d, i) ->
-                
-                {r,g,b} = color = d3.rgb metric.get 'color'                
-                lineX = (X(d[0])+X(d[1]))/2
-                lineY = (Y(d[0])+Y(d[1]))/2
-                                
-
-                lens = svgEl.select "g.lens"                
-                    .attr "transform", "translate(#lineX, #lineY)"
-                lens.select "circle" .style "fill", "rgba(#r, #g, #b, 0.4)"
-                lens.select "text" .text -> chT.numberFormatter(d[0][1]).toString()
-
-
-        svgEl        
-
-    
-    renderChart: (data, viewport, options, lastChart) ->
-        ### Starting with http://bost.ocks.org/mike/chart/
-        
-        margin = {top: 20, right: 20, bottom: 20, left: 20}
-        width  = 760
-        height = 320
-        xScale = d3.time.scale()
-        yScale = d3.scale.linear()
-        
-        dates  = data[0]
-        cols   = data.slice(1)
-        
-        # Calculate extents using all the data points (but not dates)
-        # allValues = d3.merge @model.dataset.getDataColumns()
-        allValues = d3.merge cols
-        
-        
-        # Update the x-scale with the extents of the dates.
-        xScale
-            .domain d3.extent dates
-            .range [ 0, width - margin.left - margin.right ]
-        
-        # Update the y-scale with the extents of the data.
-        yScale
-            .domain d3.extent allValues
-            .range [ height - margin.top - margin.bottom, 0 ]
-        
-        # Select the svg element, if it exists.
-        svg = d3.select viewport.0 .selectAll "svg"
-            .data [cols]
-        
-        # ...Otherwise, create the skeletal chart.
-        enterFrame = svg.enter()
-            .append "svg" .append "g"
-                .attr "class", "frame"
-        enterFrame.append "g"
-            .attr "class", "x axis time"
-        
-        # Update chart dimensions.
-        svg .attr "width", width
-            .attr "height", height
-        frame = svg.select "g.frame"
-            .attr "transform", "translate(#{margin.left},#{margin.top})"
-        
-        # Update the x-axis.
-        xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(6, 0)
-        frame.select ".x.axis.time"
-            .attr "transform", "translate(0,#{yScale.range()[0]})"
-            .call xAxis
-        
-        
-        ### Render the line paths
-        lines = root.lines = frame.selectAll "path.line"
-            .data cols.map -> d3.zip dates, it
-        lines.enter().append "path"
-            .attr "class", "metric line"
-        lines.exit().remove()
-        
-        X = (d, i) -> xScale d[0]
-        Y = (d, i) -> yScale d[1]
-        line = d3.svg.line().x(X).y(Y)
-        lines.attr "d", line
-            .attr "class", (col, i) -> "metric line metric#i"
-            .style "stroke", (col, i) -> options.colors[i]
-            .each (col, i) ->
-                {width} = bbox = @getBBox()
-                # Add line-to-data position conversions
-                @indexAtX = d3.scale.quantize()
-                    .domain [0, width]
-                    .range d3.range col.length
-                @indexToPoint = (idx) ->
-                    @pathSegList.getItem idx
-        
-        ### Render Points
-        points = frame.selectAll "g.points"
-            .data cols
-        points.enter().append "g"
-            .attr "class", (col, i)     ->  "points points#i"
-            .property "line", (col, i)  -> $(this).parent().find('path.metric.line')[i]
-        points.exit().remove()
-        
-        points.selectAll ".point"
-            .data op.first
-            .enter().append "circle"
-                .attr "class", "point"
-                .attr "r", "2px"
-                .property "line", -> $ this .parentsUntil('svg', 'g')[0].line
-                .style "fill", -> $ this.line .css 'stroke'
-                .attr "transform", (d, i) ->
-                    {x,y} = @line.indexToPoint i
-                    "translate(#x, #y)"
-        
-        
-        ### Mouse Lens
-        lens = root.lens = frame.selectAll "g.lens"
-            .data [[]]
-        gLens = lens.enter().append "g"
-            .attr "class", "lens"
-            .style "z-index", 1e9
-        gInner = gLens.append "g"
-            .attr "transform", "translate(1.5em,0)"
-        gInner.append "circle"
-            .attr "r", "1.5em"
-            # .style "opacity", "0.4"
-            # .style "fill", "white"
-            .style "fill", "rgba(255, 255, 255, 0.4)"
-            .style "stroke", "white"
-            .style "stroke-width", "3px"
-        gInner.append "text"
-            .attr "y", "0.5em"
-            .attr "text-anchor", "middle"
-            .style "fill", "white"
-            .style "font", "12px Helvetica"
-            .style "font-weight", "bold"
-        
-        lines.on "mouseover", (col, i) ->
-            line = root.line = this # DOM element of event
-            {r,g,b} = color = d3.rgb options.colors[i]
-            
-            # quantize mouse x-location to get for closest data-point (index into data array)
-            [x,y] = root.pos = d3.mouse line
-            idx = root.idx = line.indexAtX x
-            {x:lineX, y:lineY} = root.pt = line.indexToPoint idx
-            
-            lens = frame.select "g.lens"                
-                .attr "transform", "translate(#lineX, #lineY)"
-            lens.select "circle" .style "fill", "rgba(#r, #g, #b, 0.4)"
-            lens.select "text" .text -> col[idx][1]
-        
-        
-        points.on "mouseover", (d, i) ->
-            line = root.line = this.line # this is the DOM element of point
-            {r,g,b} = color = d3.rgb options.colors[i]
-                        
-            # quantize mouse x-location to get for closest data-point (index into data array)
-            [x,y] = root.pos = d3.mouse line
-            idx = root.idx = line.indexAtX x
-            {x:lineX, y:lineY} = root.pt = line.indexToPoint idx
-            
-            lens = frame.select "g.lens"                
-                .attr "transform", "translate(#lineX, #lineY)"
-            lens.select "circle" .style "fill", "rgba(#r, #g, #b, 0.4)"
-            lens.select "text" .text -> d[idx]
-
-        
-        svg
-    
-    
-    
diff --git a/lib/chart/type/d3/d3-line-element.co b/lib/chart/type/d3/d3-line-element.co
new file mode 100644 (file)
index 0000000..aad93d9
--- /dev/null
@@ -0,0 +1,84 @@
+d3 = require 'd3'
+ColorBrewer = require 'colorbrewer'
+
+{ _, op,
+} = require 'kraken/util'
+{ D3ChartElement    
+} = require 'kraken/chart/type/d3/d3-chart-element'
+
+_fmt = require 'kraken/util/formatters'
+
+root = do -> this
+
+class exports.LineChartElement extends D3ChartElement
+    __bind__ : <[ ]>
+    SPEC_URL : '/schema/d3/d3-line.json'
+    
+    # NOTE: D3ChartElement.register() must come AFTER `typeName` declaration.
+    chartElement : 'd3-line'
+    D3ChartElement.register this            
+    
+    -> super ...
+    
+    renderChartElement: (metric, svgEl ,xScale, yScale) ->        
+        
+        X = (d, i) -> xScale d[0]
+        Y = (d, i) -> yScale d[1]
+        line = d3.svg.line().x(X).y(Y)
+
+        ### Render the line path
+        metricLine = root.metricLine = svgEl.append "g"
+            .attr "class", "g metric line "+metric.get 'label'
+                
+        data = d3.zip metric.getDateColumn(),metric.getData()                
+
+        metricLine.selectAll "path.line"
+            .data d3.zip data.slice(0,-1), data.slice(1)
+            .enter().append "path"
+                .attr "d", line
+                .attr "class", (d, i) -> "metric line segment #i"
+                .style "stroke", metric.get 'color'
+
+        
+        ### Mouse Lens
+        lens = root.lens = svgEl.selectAll "g.lens"
+            .data [[]]
+        gLens = lens.enter().append "g"
+            .attr "class", "lens"
+            .style "z-index", 1e9
+        gInner = gLens.append "g"
+            .attr "transform", "translate(1.5em,0)"
+        gInner.append "circle"
+            .attr "r", "1.5em"
+            # .style "opacity", "0.4"
+            # .style "fill", "white"
+            .style "fill", "rgba(255, 255, 255, 0.4)"
+            .style "stroke", "white"
+            .style "stroke-width", "3px"
+        gInner.append "text"
+            .attr "y", "0.5em"
+            .attr "text-anchor", "middle"
+            .style "fill", "black"
+            .style "font", "12px Helvetica"
+            .style "font-weight", "bold"
+
+        # event listeners        
+        metricLine.selectAll ".line.segment"
+            .on "mouseover", (d, i) ->
+                
+                {r,g,b} = color = d3.rgb metric.get 'color'                
+                lineX = (X(d[0])+X(d[1]))/2
+                lineY = (Y(d[0])+Y(d[1]))/2
+                                
+
+                lens = svgEl.select "g.lens"                
+                    .attr "transform", "translate(#lineX, #lineY)"
+                lens.select "circle" .style "fill", "rgba(#r, #g, #b, 0.4)"
+                lens.select "text" .text -> _fmt.numberFormatter(d[0][1]).toString()
+
+        svgEl        
+
+    
+    
+    
+    
index 3baf40d..a18badd 100644 (file)
@@ -1,6 +1,6 @@
-d3chart = require 'kraken/chart/type/d3/d3-chart-type'
-line = require 'kraken/chart/type/d3/d3-line-chart-type'
-bar  = require 'kraken/chart/type/d3/d3-bar-chart-type'
-# geo  = require 'kraken/chart/type/d3/d3-geo-chart-type'
+d3chart = require 'kraken/chart/type/d3/d3-chart-element'
+line = require 'kraken/chart/type/d3/d3-line-element'
+bar  = require 'kraken/chart/type/d3/d3-bar-element'
+# geo  = require 'kraken/chart/type/d3/d3-geo-element'
 
 exports import line import bar import d3chart # import geo 
index b30650c..f2c3b95 100644 (file)
@@ -30,10 +30,17 @@ _fmt = do
      * 
      * @param {Number} n Number to format.
      * @param {Number} [digits=2] Number of digits after the decimal to always display.
-     * @returns {String} Formatted number.
+     * @param {Boolean} [abbrev=true] Expand number suffixes if false.
+     * @returns {Object} Formatted number parts.
      */
-    _numberFormatter: (n, digits=2) ->
-        for [suffix, d] of [['B', 1000000000], ['M', 1000000], ['K', 1000], ['', NaN]]
+    numberFormatter: (n, digits=2, abbrev=true) ->
+        suffixes = do
+            if abbrev
+                [['B', 1000000000], ['M', 1000000], ['K', 1000], ['', NaN]]
+            else
+                [['Billion', 1000000000], ['Million', 1000000], ['', NaN]]
+        
+        for [suffix, d] of suffixes
             break if isNaN d
             if n >= d
                 n = n / d
@@ -42,12 +49,9 @@ _fmt = do
         parts = s.split '.'
         whole = _.rchop parts[0], 3 .join ','
         fraction = '.' + parts.slice(1).join '.'
-        { n, digits, whole, fraction, suffix }
-    
-    
-    numberFormatter: (n, digits=2) ->
-        { whole, fraction, suffix } = _fmt._numberFormatter n, digits
-        "#whole#fraction#suffix"
+        { n, digits, whole, fraction, suffix, toString: -> 
+            "#{@whole}#{@fraction}#{if abbrev then '' else ' '}#{@suffix}"
+        }
     
     numberFormatterHTML: (n, digits=2) ->
         { whole, fraction, suffix } = _fmt._numberFormatter n, digits
@@ -61,4 +65,4 @@ _fmt = do
     
 
 
-exports = _fmt
+module.exports = exports =  _fmt
index 64c93c2..0f884a3 100644 (file)
@@ -80,10 +80,11 @@ dev:
                     - timeseries
                     - csv
                     - index
-                - op
+                - op            
                 - backbone
                 - parser
                 - cascade
+                - formatters
                 - index
             - base:
                 - scaffold:
@@ -112,11 +113,12 @@ dev:
                     - index
                 - type:
                     - d3:
-                        - d3-chart-type
-                        - d3-line-chart-type                        
-                        - d3-bar-chart-type
+                        - d3-chart-element
+                        - d3-line-element
+                        - d3-bar-element
                         # - d3-geo-chart-type
                         - index
+                    - d3-chart
                     - dygraphs
                     - index
                 - chart-type