--- /dev/null
+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()}()"
+# }}}
+
--- /dev/null
+{ _, 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
+
+
+
* @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
# 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
withView : (@view) -> this
-
/**
* Load the corresponding chart specification, which includes
* info about valid options, along with their types and defaults.
/**
+ * 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.
*
* 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()
/**
-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
{ 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
--- /dev/null
+model = require 'kraken/chart/option/chart-option-model'
+view = require 'kraken/chart/option/chart-option-view'
+
+exports import model import view
--- /dev/null
+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
+
+