_.bindAll this, ...@__bind__ if @__bind__.length
     
     serialize: (v) ->
-        if v!?
-            v = ''
-        else if _.isBoolean v
+        # if v!?
+        #     v = ''
+        if _.isBoolean v
             v =  Number v
         else if _.isObject v
             v = JSON.stringify v
     
     /**
      * Like `.toJSON()` in that it should return a plain object with no functions,
-     * but for the purpose of `.toKVPairs()`, allowing you to customize the values
+     * but for the purpose of `.toKV()`, allowing you to customize the values
      * included and keys used.
      * @returns {Object}
      */
-    toKVObject: ->
+    toKVPairs: ->
         kvo = _.collapseObject @toJSON()
         for k, v in kvo
             kvo[k] = @serialize v
      * a query string or a POST body.
      * @returns {String}
      */
-    toKVPairs: (item_delim='&', kv_delim='=') ->
-        _.toKVPairs @toKVObject(), item_delim, kv_delim
+    toKV: (item_delim='&', kv_delim='=') ->
+        _.toKV @toKVPairs(), item_delim, kv_delim
+    
+    /**
+     * @returns {String} URL identifying this model.
+     */
+    toURL: (item_delim='&', kv_delim='=') ->
+        "?#{@toKV()}"
     
     toString: -> "#{@ctorName}(id=#{@id})"
 
      * @param {String|Object} o Serialized KV-pairs (or a plain object).
      * @returns {BaseModel} An instance of this model.
      */
-    fromKVPairs: (o, item_delim='&', kv_delim='=') ->
-        o   = _.fromKVPairs o, item_delim, kv_delim if typeof o is 'string'
+    fromKV: (o, item_delim='&', kv_delim='=') ->
+        o   = _.fromKV o, item_delim, kv_delim if typeof o is 'string'
         Cls = if typeof this is 'function' then this else this.constructor
         new Cls _.uncollapseObject o
 
 BaseList = exports.BaseList = Backbone.Collection.extend do # {{{
     ctorName : 'BaseList'
     
-    toKVObject: ->
+    toKVPairs: ->
         _.collapseObject @toJSON()
     
-    toKVPairs: (item_delim='&', kv_delim='=') ->
-        _.toKVPairs @toKVObject(), item_delim, kv_delim
+    toKV: (item_delim='&', kv_delim='=') ->
+        _.toKV @toKVPairs(), item_delim, kv_delim
+    
+    toURL: (item_delim='&', kv_delim='=') ->
+        "?#{@toKV ...}"
     
     toString: -> "#{@ctorName}(length=#{@length})"
 # }}}
 
         # Ignore functions/callbacks and, ahem, hidden tags.
         type = @get 'type', '' .toLowerCase()
         tags = @get 'tags', []
-        if _.str.include(type, 'function') or _.intersection tags, IGNORED_TAGS .length
+        if _.str.include(type, 'function') or _.intersection(tags, IGNORED_TAGS).length
             @set 'ignore', true
     
     
 GraphOptionList = exports.GraphOptionList = FieldList.extend do # {{{
     ctorName   : 'GraphOptionList'
     model      : GraphOption
+    
+    /**
+     * Override to omit defaults from URL.
+     */
+    toKVPairs: ->
+         _.collapseObject @values {-keepDefaults, +serialize}
+    
 # }}}
 
 
         view.on 'change:collapse render', @render
         view
     
-    toKVPairs: ->
-        @collection.toKVPairs ...
+    toKV: ->
+        @collection.toKV ...
     
 # }}}
 
 
 } = require 'kraken/vis'
 
 root = this
-CHART_OPTIONS_SPEC = []
-ROOT_VIS_DATA      = {}
-ROOT_VIS_OPTIONS   = {}
+CHART_OPTIONS_SPEC    = []
+CHART_DEFAULT_OPTIONS = {}
+ROOT_VIS_DATA         = {}
+ROOT_VIS_OPTIONS      = {}
 
 
 # Create the Graph Scaffold
 main = ->
+    opts = root.CHART_DEFAULT_OPTIONS = {}
+    for opt of root.CHART_OPTIONS_SPEC
+        opts[opt.name] = opt.default
+    
     # TODO: create a preset manager
     # Remove chart options from data so we don't have to deepcopy
     ROOT_VIS_OPTIONS := delete root.ROOT_VIS_DATA.options
     
     # Bind to URL changes
     History.Adapter.bind window, 'statechange', ->
-        console.log 'StateChange!', String(root.location)
+        console.log 'StateChange!\n\n', String(root.location), '\n\n'
     
     
     # If we got querystring args, apply them to the graph
     data = {}
     if String(root.location).split '?' .1
-        data = _.uncollapseObject _.fromKVPairs that.replace('#', '%23')
+        data = _.uncollapseObject _.fromKV that.replace('#', '%23')
         
         # # yarr, have to do options separately or everything goes to shit
         # options = delete data.options
 
         {id:@id} import do
             _.clone(@attributes) import { value:@getValue(), def:@get('default') }
     
-    toKVObject: ->
+    toKVPairs: ->
         { "#{@id}":@serializeValue() }
     
     toString: -> "(#{@id}: #{@serializeValue()})"
      * @returns {Object}
      */
     values: (opts={}) ->
-        opts = {-keepDefaults, -serialize} import opts
+        opts = {+keepDefaults, -serialize} import opts
         _.synthesize do
             if opts.keepDefaults then @models else @models.filter -> not it.isDefault()
             -> [ it.get('name'), if opts.serialize then it.serializeValue() else it.getValue() ]
     
     toJSON: ->
-        @values()
+        @values {+keepDefaults, -serialize}
     
-    toKVObject: ->
-        _.collapseObject @values false, true
+    toKVPairs: ->
+        _.collapseObject @values {+keepDefaults, +serialize}
     
-    toKVPairs: (item_delim='&', kv_delim='=') ->
-        _.toKVPairs @toKVObject(), item_delim, kv_delim
+    toKV: (item_delim='&', kv_delim='=') ->
+        _.toKV @toKVPairs(), item_delim, kv_delim
+    
+    toURL: (item_delim='&', kv_delim='=') ->
+        "?#{@toKV ...}"
     
     toString: -> "#{@ctorName}(length=#{@length})"
 # }}}
 
     tagName   : 'div'
     className : 'field'
     
+    type : 'string'
+    
     events :
         'blur .value'   : 'update'
         'submit .value' : 'update'
     
     
-    # initialize: ->
-    #     # console.log "#this.initialize!"
-    #     BaseView::initialize ...
+    initialize: ->
+        # console.log "#this.initialize!"
+        BaseView::initialize ...
+        @type = @model.get('type', 'string').toLowerCase()
     
     update: ->
-        val     = @model.getParser() @$el.find('.value').val()
+        if @type is 'boolean'
+            val = !! @$el.find('.value').attr('checked')
+        else
+            val = @model.getParser() @$el.find('.value').val()
+        
         current = @model.getValue()
         return if _.isEqual val, current
         console.log "#this.update( #current -> #val )"
 
 
 # Proxy collection methods
-<[ get at pluck invoke values toJSON toKVObject toKVPairs ]>
+<[ get at pluck invoke values toJSON toKVPairs toKV toURL ]>
     .forEach (methodname) ->
         Scaffold::[methodname] = -> @collection[methodname].apply @collection, arguments
 
 
                                 input.span3.slug(type='text', 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.
                         .half.control-group
-                            label.dataset.control-label(for='dataset') Data Set
-                            .controls
-                                input.span3.dataset(type='text', 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.
                     .row-fluid
                         .half.control-group
                             label.width.control-label(for='width') Width
                             .controls: input.span1.height(type='text', id='height', name='height', value=height)
                 
                 .graph-data-pane.tab-pane(id="graph-#{graph_id}-data")
+                    .row-fluid
+                        label.dataset.control-label(for='dataset') Data Set
+                        .controls
+                            input.span3.dataset(type='text', 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-#{graph_id}-options")
                 
 
     
     
     
-    toKVPairs: (o, item_delim='&', kv_delim='=') ->
+    toKV: (o, item_delim='&', kv_delim='=') ->
         _.reduce do
             o
             (acc, v, k) ->
             []
         .join item_delim
     
-    fromKVPairs: (qs, item_delim='&', kv_delim='=') ->
+    fromKV: (qs, item_delim='&', kv_delim='=') ->
         _.reduce do
             qs.split item_delim
             (acc, pair) ->
 
 # {crc32}   = require 'kraken/util/crc'
 
 
+## Debug
+_.dump = (o, label='dump') ->
+    if not _.isArray(o) and _.isObject(o)
+        console.group label
+        for k, v in o
+            console.log "#k:", v
+        console.groupEnd()
+    else
+        console.log label, o
+    o
+
+
 exports import { root, _, op, backbone }
 # exports import { root, _, op, HashSet, BitString, crc32, }
 
     encode : -> it and $ "<div>#it</div>" .html().replace /"/g, '"'
     decode : -> it and $ "<div>#it</div>" .text()
     
-    
 
 
  * other settings for both its content and presentation.
  */
 VisModel = exports.VisModel = BaseModel.extend do # {{{
+    IGNORE_OPTIONS : <[ width height timingName ]>
     ctorName    : 'VisModel'
     urlRoot     : '/graph'
     idAttribute : 'slug'
     
     
+    
     initialize : ->
         BaseModel::initialize ...
         name = @get 'name'
     
     
     defaults: ->
-        {
+        ({
             slug    : ''
             name    : ''
             desc    : ''
             # presets : []
             width   : 'auto'
             height  : 320
-            options : {} import root.ROOT_VIS_OPTIONS
-        } import root.ROOT_VIS_DATA
+        } import root.ROOT_VIS_DATA) import { options:_.clone root.ROOT_VIS_OPTIONS }
     
     
     parse: (data) ->
     
     setOption: (key, value, opts={}) ->
         options = @get 'options', {}
-        options[key] = value
-        @set 'options', options, opts
-        @trigger "change:options:#key", this, value, key, opts unless opts.silent
+        unless _.contains @IGNORE_OPTIONS, key
+            options[key] = value
+            @set 'options', options, opts
+            @trigger "change:options:#key", this, value, key, opts unless opts.silent
+        this
     
     unsetOption: (key, opts={}) ->
         options = @get 'options', {}
         delete options[key]
         @set 'options', options, opts
         @trigger "change:options:#key", this, value, key, opts unless opts.silent
-    
-    
-    
-    toString: -> "#{@ctorName}(id=#{@id}, name=#{@get 'name'}, dataset=#{@get 'dataset'})"
+        this
+    
+    
+    ### URL Serialization
+    
+    toJSON: (options={}) ->
+        options = {+keepDefaults} import options
+        
+        json = _.clone(@attributes) import { options:_.clone(@attributes.options) }
+        return json if options.keepDefaults
+        
+        defs = root.CHART_DEFAULT_OPTIONS
+        opts = json.options
+        for k, v in opts
+            delete opts[k] if v is defs[k]
+        json
+    
+    
+    toKVPairs: ->
+        kvo = @toJSON()
+        # console.group 'toKVPairs'
+        # console.log '[IN]', JSON.stringify kvo
+        opts = kvo.options = _.clone kvo.options
+        for k, rootVal in root.ROOT_VIS_OPTIONS
+            v = opts[k]
+            # console.log "  [#k] rootVal:", rootVal, "===", v, "?", _.isEqual(rootVal, v) unless _.isEqual(rootVal, v)
+            if _.isEqual rootVal, v
+                delete opts[k]
+            else
+                opts[k] = @serialize v
+        # console.log '[OUT]', JSON.stringify kvo
+        # console.groupEnd()
+        _.collapseObject kvo
+    
+    
+    toString: -> "#{@ctorName}(id=#{@id})"
 
 # }}}
 
 
  * - Graph metadata, such as name, description, slug
  */
 VisView = exports.VisView = BaseView.extend do # {{{
+    FILTER_CHART_OPTIONS : <[ file labels visibility colors dateWindow  ticker timingName axisLabelFormatter valueFormatter xAxisLabelFormatter yAxisLabelFormatter xValueFormatter yValueFormatter xValueParser ]>
     __bind__  : <[ resizeViewport render renderAll onReady formatter axisFormatter ]>
     ctorName  : 'VisView'
     tagName   : 'section'
         BaseView::initialize ...
         # console.log "#this.initialize!"
         
-        for name of <[ resizeViewport render renderAll ]>
+        for name of <[ render renderAll ]>
             @[name] = _.debounce @[name], DEBOUNCE_RENDER
         
         # Resize graph on window resize
-        $ root .on 'resize', @resizeViewport
+        $ root .on 'resize', _.debounce(@resizeViewport, DEBOUNCE_RENDER)
         
         @id = _.domize 'graph', (@model.id or @model.cid)
         
         @model.on 'change',  @render, this
         @model.on 'change:dataset', ~>
             changes = @model.changedAttributes()
-            console.log 'VisModel.changed(dataset) ->', JSON.stringify changes
+            console.log 'VisModel.changed( dataset ) ->', JSON.stringify changes
             @chart.updateOptions file:that if changes?.dataset
         @model.on 'change:options', ~>
             changes = @model.changedAttributes()
-            console.log 'VisModel.changed(options) ->', JSON.stringify changes
+            console.log 'VisModel.changed( options ) ->', JSON.stringify changes
             @chartOptions that, {+silent} if changes?.options
         
         @viewport = @$el.find '.viewport'
         @scaffold.collection.reset that if o.graph_spec
         
         @scaffold.on 'change', (scaffold, value, key, field) ~>
-            # console.log "scaffold.change!", value, key, field
-            @model.setOption key, value, {+silent} unless field.isDefault()
+            console.log "scaffold.change!", key, value, field
+            @model.setOption key, value, {+silent} #unless field.isDefault()
         
         options = @model.get 'options', {}
         @chartOptions options, {+silent}
                 options.get(k)?.setValue v, opts
             this
         else
-            options.values() # TODO: pull this from model (must clone), sort out events
+            opts = @model.toJSON({ -keepDefaults })?.options or {}
+            for k of @FILTER_CHART_OPTIONS
+                # console.log "filter #k?", not opts[k]
+                if k in opts and not opts[k]
+                    delete opts[k]
+            opts
     
     
     /**
      * @return { width, height }
      */
     resizeViewport: ->
-        return this unless @ready
+        modelW = width = @model.get 'width'
+        modelH = height = @model.get 'height'
+        return { width, height } unless @ready
         
         # Remove old style, as it confuses dygraph after options update
         @viewport.attr 'style', ''
         label = @$el.find '.graph-label'
         
-        if (width = @model.get 'width')  is 'auto'
+        if width is 'auto'
             vpWidth = @viewport.innerWidth()
             labelW = label.outerWidth()
             width = vpWidth - labelW - 10 - (vpWidth - label.position().left - labelW)
-        if (height = @model.get 'height') is 'auto'
+        width ?= modelW
+        if height is 'auto'
             height = @viewport.innerHeight()
+        height ?= modelH
+        
         size = { width, height }
         @viewport.css size
         # console.log 'resizeViewport!', JSON.stringify(size), @viewport
     
     render: ->
         return this unless @ready
-        console.log "#this.render!"
         
         size = @resizeViewport()
-        options = @chartOptions() import size
+        options = @chartOptions() #import size
         options import do
             labelsDiv          : @$el.find '.graph-label' .0
             # axisLabelFormatter : @axisFormatter
             # valueFormatter     : @formatter
-        
-        # @chart?.destroy()
-        unless @chart
-            @chart = new Dygraph do
-                @viewport.0
-                @model.get 'dataset'
-                options
-        else
-            @chart.updateOptions options
-            @chart.resize size
+        dataset = @model.get 'dataset'
+        
+        console.log "#this.render!", dataset
+        _.dump options, 'options'
+        
+        @chart?.destroy()
+        @chart = new Dygraph do
+            @viewport.0
+            dataset
+            options
+        
+        # unless @chart
+        #     @chart = new Dygraph do
+        #         @viewport.0
+        #         dataset
+        #         options
+        # else
+        #     @chart.updateOptions options
+        #     @chart.resize size
         
         # path = String(root.location?.path or '/')
         data  = @toJSON()
         title = @model.get('name', root.document?.title or '')
-        url   = "?"+@toKVPairs()
+        url   = @toURL()
         # console.log 'History.pushState', JSON.stringify(data), title, url
         History.pushState data, title, url
         
     toJSON: ->
         @model.toJSON()
     
-    toKVPairs: ->
-        @model.toKVPairs.apply @model, arguments
+    toKV: ->
+        @model.toKV.apply @model, arguments
+    
+    toURL: ->
+        @model.toURL()
     
     toString: -> "#{@ctorName}(#{@model})"
 # }}}