From: David Schoonover Date: Sun, 3 Jun 2012 12:53:20 +0000 (+0200) Subject: Adds AppView to start cleaning up -main.co files; Begins refactor of chart-type code... X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=4911701adeb6d02ebb688435c7f5fef7776ddf40;p=limn.git Adds AppView to start cleaning up -main.co files; Begins refactor of chart-type code; fixes new, broken changes from browserify. --- diff --git a/lib/app.co b/lib/app.co new file mode 100644 index 0000000..58e16ad --- /dev/null +++ b/lib/app.co @@ -0,0 +1,52 @@ +Backbone = require 'backbone' + +{ _, op, +} = require 'kraken/util' + + +/** + * @class Application view, automatically attaching to an existing element + * found at `appSelector`. + * @extends Backbone.View + */ +AppView = exports.AppView = Backbone.View.extend do # {{{ + appSelector : '#content .inner' + + + /** + * @constructor + */ + constructor: function AppView (options={}) + if typeof options is 'function' + @initialize = options + options = {} + else + @initialize = that if options.initialize + + @appSelector = that if options.appSelector + options.el or= jQuery @appSelector .0 + console.log "new #this", options + Backbone.View.call this, options + + jQuery ~> @render() + this + + /** + * Override to set up your app. This method may be passed + * as an option to the constructor. + */ + initialize: -> # stub + + /** + * Append subviews. + */ + render : -> + @$el.append @view.el if @view and not @view.$el.parent()?.length + + getClassName: -> + "#{@..name or @..displayName}" + + toString: -> + "#{@getClassName()}()" +# }}} + diff --git a/lib/base/asset-manager.co b/lib/base/asset-manager.co new file mode 100644 index 0000000..fa91012 --- /dev/null +++ b/lib/base/asset-manager.co @@ -0,0 +1,43 @@ +{ _, op, +} = require 'kraken/util' +{ ReadyEmitter, +} = require 'kraken/util/event' + + + + +class AssetManager extends ReadyEmitter + # Map from key/url to data. + assets : null + + + /** + * @constructor + */ + -> + super ... + @assets = {} + + + + + /** + * Load the corresponding chart specification, which includes + * info about valid options, along with their types and defaults. + */ + load: -> + return this if @ready + proto = @constructor:: + jQuery.ajax do + url : @SPEC_URL + success : (spec) ~> + proto.spec = spec + proto.options_ordered = spec + proto.options = _.synthesize spec, -> [it.name, it] + proto.ready = true + @emit 'ready', this + error: ~> console.error "Error loading #{@typeName} spec! #it" + this + + + diff --git a/lib/base/base-model.co b/lib/base/base-model.co index 5048a1d..260c398 100644 --- a/lib/base/base-model.co +++ b/lib/base/base-model.co @@ -87,14 +87,24 @@ BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{ * @param {Object} [opts={}] Options: * @param {Function} opts.start Function that starts the loading process. Always called with `this` as the context. * @param {String} [opts.startEvent='load'] Event to trigger before beginning the load. - * @param {String} [opts.completeEvent='load-success'] Event which signals loading has completed. - * @param {Boolean} [opts.force=false] If true, move forward with the load even if we're ready. - * @returns {this} + * @param {String} [opts.completeEvent='load-success'] Event which signals loading has completed successfully. + * @param {String} [opts.errorEvent='load-error'] Event which signals loading has completed but failed. + * @param {Boolean} [opts.force=false] If true, reset ready state if we're ready before proceeding. + * @param {Boolean} [opts.readyIfError=false] If true, move fire the ready event when loading completes, even if it failed. + * @returns {this} */ loader: (opts={}) -> - opts = { -force, startEvent:'load', completeEvent:'load-success', ...opts } + opts = { + -force + -readyIfError + startEvent : 'load' + completeEvent : 'load-success' + errorEvent : 'load-error' + ...opts + } @resetReady() if opts.force - return this if not opts.start or @loading or @ready + throw new Error('You must specify a `start` function to start loading!') unless opts.start + return this if @loading or @ready @wait() @loading = true @@ -102,11 +112,17 @@ BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{ # Register a handler for the post-load event that will run only once @once opts.completeEvent, ~> - console.log "#{this}.onLoadComplete()" + # console.log "#{this}.onLoadComplete()" @loading = false @unwait() # terminates the `load` wait @trigger 'load-success', this unless opts.completeEvent is 'load-success' @triggerReady() + @once opts.errorEvent, ~> + # console.log "#{this}.onLoadError()" + @loading = false + @unwait() # terminates the `load` wait + @trigger 'load-error', this unless opts.errorEvent is 'load-error' + @triggerReady() if opts.readyIfError # Finally, start the loading process opts.start.call this diff --git a/lib/chart/chart-type.co b/lib/chart/chart-type.co index 4da37a1..ad8fabc 100644 --- a/lib/chart/chart-type.co +++ b/lib/chart/chart-type.co @@ -151,7 +151,6 @@ class exports.ChartType extends ReadyEmitter withView : (@view) -> this - /** * Load the corresponding chart specification, which includes * info about valid options, along with their types and defaults. @@ -343,6 +342,28 @@ class exports.ChartType extends ReadyEmitter /** + * Determines chart viewport size. + * @return { width, height } + */ + determineSize: -> + modelW = width = @model.get 'width' + modelH = height = @model.get 'height' + return { width, height } unless @view.ready and width and height + + viewport = @getElementsForRole 'viewport' + + if width is 'auto' + Width = viewport.innerWidth() or 300 + width ?= modelW + + if height is 'auto' + height = viewport.innerHeight() or 320 + height ?= modelH + + { width, height } + + + /** * Transforms domain data and applies it to the chart library to * render or update the corresponding chart. * @@ -360,11 +381,12 @@ class exports.ChartType extends ReadyEmitter * Transforms the domain objects into a hash of derived values using * chart-type-specific keys. * - * @abstract + * Default implementation returns `model.getOptions()`. + * * @returns {Object} The derived data. */ transform: -> - ... + @model.getOptions() /** diff --git a/lib/chart/index.co b/lib/chart/index.co index 8cc50ae..5a02eb2 100644 --- a/lib/chart/index.co +++ b/lib/chart/index.co @@ -1,5 +1,5 @@ -chart = require 'kraken/chart/chart-type' -dygraphs = require 'kraken/chart/dygraphs' -models = require 'kraken/chart/chart-option-model' -views = require 'kraken/chart/chart-option-view' -exports import chart import dygraphs import models import views +chart_type = require 'kraken/chart/chart-type' +chart_option = require 'kraken/chart/option' +dygraphs = require 'kraken/chart/type/dygraphs' + +exports import chart_type import chart_option import dygraphs diff --git a/lib/chart/chart-option-model.co b/lib/chart/option/chart-option-model.co similarity index 100% rename from lib/chart/chart-option-model.co rename to lib/chart/option/chart-option-model.co diff --git a/lib/chart/chart-option-view.co b/lib/chart/option/chart-option-view.co similarity index 99% rename from lib/chart/chart-option-view.co rename to lib/chart/option/chart-option-view.co index 6c9e2a0..1b004e1 100644 --- a/lib/chart/chart-option-view.co +++ b/lib/chart/option/chart-option-view.co @@ -3,7 +3,7 @@ { BaseView, } = require 'kraken/base' { ChartOption, ChartOptionList, -} = require 'kraken/chart/chart-option-model' +} = require 'kraken/chart/option/chart-option-model' DEBOUNCE_RENDER = exports.DEBOUNCE_RENDER = 100ms diff --git a/lib/chart/option/index.co b/lib/chart/option/index.co new file mode 100644 index 0000000..5cdea4d --- /dev/null +++ b/lib/chart/option/index.co @@ -0,0 +1,4 @@ +model = require 'kraken/chart/option/chart-option-model' +view = require 'kraken/chart/option/chart-option-view' + +exports import model import view diff --git a/lib/chart/type/d3-line.co b/lib/chart/type/d3-line.co deleted file mode 100644 index e69de29..0000000 diff --git a/lib/chart/type/d3-bar.co b/lib/chart/type/d3/d3-bar-chart-type.co similarity index 100% rename from lib/chart/type/d3-bar.co rename to lib/chart/type/d3/d3-bar-chart-type.co diff --git a/lib/chart/type/d3/d3-geo-chart-type.co b/lib/chart/type/d3/d3-geo-chart-type.co new file mode 100644 index 0000000..4709aa0 --- /dev/null +++ b/lib/chart/type/d3/d3-geo-chart-type.co @@ -0,0 +1,182 @@ +ColorBrewer = require 'colorbrewer' + +{ _, op, +} = require 'kraken/util' +{ ChartType, +} = require 'kraken/chart' + +class GeoWorldChartType extends ChartType + __bind__ : <[ dygNumberFormatter dygNumberFormatterHTML ]> + SPEC_URL : '/schema/d3/d3-geo-world.json' + + # NOTE: ChartType.register() must come AFTER `typeName` declaration. + typeName : 'd3-geo-world' + 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: -> + options = @model.getOptions() import @determineSize() + # options.colors.palette = ["black", "red"] if options.colors.palette? + options.colors.scaleDomain = d3.extent if options.colors.scaleDomain? + options + + + getProjection : (type) -> + switch type + case 'mercator' 'albers' 'albersUsa' + d3.geo[type]() + case 'azimuthalOrtho' + d3.geo.azimuthal() + .mode 'orthographic' + case 'azimuthalStereo' + d3.geo.azimuthal() + .mode 'stereographic' + default + throw new Error "Invalid map projection type '#type'!" + + + renderChart: (data, viewport, options, lastChart) -> + {width, height} = options + + fill = @fill = (data, options) -> + d3.scale[ options.colors.scale ]() + .domain options.colors.scaleDomain + .range options.colors.palette + + quantize = @quantize = (data, options) -> + (d) -> + if data[d.properties.name]? + return fill data[d.properties.name].editors + else + # console.log 'Country '+d.properties.name+' not in data' + return fill "rgb(0,0,0)" + + projection = @projection = @getProjection(options.map.projection) + .scale width + .translate [width/2, height/2] + + path = d3.geo.path() + .projection projection + + move = -> + projection + .translate d3.event.translate + .scale d3.event.scale + feature.attr "d", path + + zoom = d3.behavior.zoom() + .translate projection.translate() + .scale projection.scale() + .scaleExtent [height,height*8] + .on "zoom", move + + + #### + + chart = d3.select viewport.0 + .append "svg:svg" + .attr "width", width + .attr "height", height + .append "svg:g" + .attr "transform", "translate(0,0)" + .call zoom + + # path objects + feature := map.selectAll ".feature" + + # rectangle + map.append "svg:rect" + .attr "class", "frame" + .attr "width", width + .attr "height", height + + + ### infobox + infobox := d3.select '#infobox' + + infobox.select '#ball' + .append "svg:svg" + .attr "width", "100%" + .attr "height", "20px" + .append "svg:rect" + .attr "width", "60%" + .attr "height", "20px" + .attr "fill", '#f40500' + + setInfoBox = (d) -> + name = d.properties.name + ae = 0 + e5 = 0 + e100 = 0 + + if data[name]? + ae = parseInt data[name].editors + e5 = parseInt data[name].editors5 + e100 = parseInt data[name].editors100 + + infobox.select '#country' .text name + infobox.select '#ae' .text ae + infobox.select '#e5' .text e5+" ("+(100.0*e5/ae).toPrecision(3)+"%)" + infobox.select '#e100' .text e100+" ("+(100.0*e100/ae).toPrecision(3)+"%)" + + xy = d3.svg.mouse this + infobox.style "left", xy[0]+'px' + infobox.style "top", xy[1]+'px' + infobox.style "display", "block" + + + worldmap = -> + d3.json do + "/data/geo/maps/world-countries.json" + (json) -> + feature := feature + .data json.features + .enter().append "svg:path" + .attr "class", "feature" + .attr "d", path + .attr "fill", quantize + .attr "id", (d) -> d.properties.name + .on "mouseover", setInfoBox + .on "mouseout", -> infobox.style "display", "none" + + + + + + +main = -> + jQuery.ajax do + url : "/data/geo/data/en_geo_editors.json" + dataType : 'json' + success : (res) -> + # result will be the returned JSON + data := res + + # delete & hide spinner + jQuery '.geo-spinner' .spin(false).hide() + + # load the world map + worldmap() + + # adding bootstrap tooltips + # $ '.page-header' .tooltip title:"for the header it works but is useless" + # $ '.feature' .tooltip title:"here it doesn't work" + + console.log 'Loaded geo coding map!' + error : (err) -> console.error err + + diff --git a/lib/chart/type/d3-geo.co b/lib/chart/type/d3/d3-line-chart-type.co similarity index 100% rename from lib/chart/type/d3-geo.co rename to lib/chart/type/d3/d3-line-chart-type.co diff --git a/lib/chart/type/d3/index.co b/lib/chart/type/d3/index.co new file mode 100644 index 0000000..3a488eb --- /dev/null +++ b/lib/chart/type/d3/index.co @@ -0,0 +1,5 @@ +bar = require 'kraken/chart/type/d3/d3-bar-chart-type' +geo = require 'kraken/chart/type/d3/d3-geo-chart-type' +line = require 'kraken/chart/type/d3/d3-line-chart-type' + +exports import bar import geo import line diff --git a/lib/chart/dygraphs.co b/lib/chart/type/dygraphs.co similarity index 98% rename from lib/chart/dygraphs.co rename to lib/chart/type/dygraphs.co index 49c3f86..7ae8e67 100644 --- a/lib/chart/dygraphs.co +++ b/lib/chart/type/dygraphs.co @@ -21,11 +21,6 @@ class exports.DygraphsChartType extends ChartType viewport : '.viewport' legend : '.graph-legend' - - - /** - * @constructor - */ -> super ... diff --git a/lib/graph/graph-model.co b/lib/graph/graph-model.co index 385ad7b..c5b59ce 100644 --- a/lib/graph/graph-model.co +++ b/lib/graph/graph-model.co @@ -60,19 +60,17 @@ Graph = exports.Graph = BaseModel.extend do # {{{ * Attribute defaults. */ defaults: -> - { - slug : '' - name : '' - desc : '' - notes : '' - # dataset : '/data/datasources/rc/rc_comscore_region_uv.csv' - # dataset : null - width : 'auto' - height : 320 - chartType : 'dygraphs' - parents : <[ root ]> - options : {} - } + slug : '' + name : '' + desc : '' + notes : '' + # dataset : '/data/datasources/rc/rc_comscore_region_uv.csv' + # dataset : null + width : 'auto' + height : 320 + chartType : 'dygraphs' + parents : <[ root ]> + options : {} url: -> "#{@urlRoot}/#{@get('slug')}.json" diff --git a/lib/main-edit.co b/lib/main-edit.co index 18e21cb..0ccad34 100644 --- a/lib/main-edit.co +++ b/lib/main-edit.co @@ -1,8 +1,12 @@ +{EventEmitter} = require 'events' + Seq = require 'seq' Backbone = require 'backbone' { _, op, } = require 'kraken/util' +{ AppView, +} = require 'kraken/app' { BaseView, BaseModel, BaseList, } = require 'kraken/base' { ChartType, @@ -49,12 +53,10 @@ main = -> # _.dump _.clone(data.options), 'data.options' - # Instantiate model & view - graph = root.graph = new Graph data, {+parse} - view = root.view = new GraphEditView do - model : graph - - $ '#content .inner' .append view.el + # Instantiate app with model & view + root.app = new AppView -> + @model = root.graph = new Graph data, {+parse} + @view = root.view = new GraphEditView {@model} # Load data files diff --git a/lib/server/server.co b/lib/server/server.co index 3dfc9ad..f7a4fc2 100755 --- a/lib/server/server.co +++ b/lib/server/server.co @@ -118,8 +118,9 @@ app.configure -> log_level : LOG_LEVEL app.use require('browserify') do mount : '/vendor/browserify.js' - require : <[ events seq ]> - cache : "#CWD/.cache/browserify/cache.json" + require : <[ seq events ]> + cache : false + # cache : "#CWD/.cache/browserify/cache.json" # Serve static files app.use express.static WWW diff --git a/package.co b/package.co index a341841..3b76b1c 100644 --- a/package.co +++ b/package.co @@ -34,7 +34,7 @@ devDependencies : 'uglify-js' : '>= 1.2.6' scripts : test:'expresso' -repository : type:'git', url:'git://git@less.ly:kraken-ui.git' +repository : type:'git', url:'git://less.ly/kraken-ui.git' engine : node:'>=0.6.2' license : 'MIT' diff --git a/www/css/geo-display.styl b/www/css/geo-display.styl index d4c20cc..b3195b3 100644 --- a/www/css/geo-display.styl +++ b/www/css/geo-display.styl @@ -39,7 +39,7 @@ section.geo // max-width 900px .frame - stroke #000 + stroke #333 fill none pointer-events all diff --git a/www/modules.yaml b/www/modules.yaml index aba31e8..8e40043 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -103,10 +103,14 @@ dev: - graph-list-view - index - chart: + - option: + - chart-option-model + - chart-option-view + - index + - type: + - dygraphs + - index - chart-type - - dygraphs - - chart-option-view - - chart-option-model - index - data: - metric-model @@ -122,6 +126,7 @@ dev: - dashboard-model - dashboard-view - index + - app # - suffix: .js # paths: diff --git a/www/schema/d3/d3-geo-world.yaml b/www/schema/d3/d3-geo-world.yaml new file mode 100644 index 0000000..03325c8 --- /dev/null +++ b/www/schema/d3/d3-geo-world.yaml @@ -0,0 +1,99 @@ +- 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). + +- name: zoom.min + tags: + - interactivity + - zoom + type: Float + default: 1.0 + desc: Limit to the amount the chart will zoom out, expressed as a multiplier + of the frame. By default, this is limited to show the whole map in the + frame. + +- name: zoom.max + tags: + - interactivity + - zoom + type: Float + default: 8.0 + desc: Limit to the amount the chart will zoom in, expressed as a multiplier + of the frame (8x by default). + +- name: colors.palette + tags: + - color + - axes + - standard + type: Array + default: [black, red] + desc: Array of colors to which values are mapped (based on their position + in `colors.scaleDomain`). + +- name: colors.scale + tags: + - color + - axes + - standard + type: enum + values: + - linear + - log + default: log + desc: Scale color differences in the map using a scale-transform (log-scale by + default). Options include: + - Linear scaling + - Logarithmic scaling + +- name: colors.scaleDomain + tags: + - color + - axes + type: Array + default: null + desc: Domain for scaling color differences. Uses the extent of the dataset + by default (and when `null`), meaning the smallest value will map to the first + color of the palette, and the largest value to the last color. + +- name: colors.missing + tags: + - standard + - color + - data + type: String + default: 'rgba(0,0,0,0)' + desc: Features without values are replaced with this color (transparent by default). + +- name: map.projection + tags: + - geo + - map + type: enum + values: + - mercator + - albers + - albersUsa + - azimuthalOrtho + - azimuthalStereo + default: mercator + desc: Projection for map-data (mercator by default). Options include: + - Spherical mercator projection + - Albers equal-area conic projection + - Composite Albers projection for the United States + - Orthographic Azimuthal projection + - Stereographic Azimuthal projection + +- name: map.definition + tags: + - geo + - map + type: String + default: "/data/geo/maps/world-countries.json" + desc: Path or URL to the `geoJSON` map definition data. +