--- /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
+
+
--- /dev/null
+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
viewport : '.viewport'
legend : '.graph-legend'
-
-
- /**
- * @constructor
- */
-> super ...
* 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"
+{EventEmitter} = require 'events'
+
Seq = require 'seq'
Backbone = require 'backbone'
{ _, op,
} = require 'kraken/util'
+{ AppView,
+} = require 'kraken/app'
{ BaseView, BaseModel, BaseList,
} = require 'kraken/base'
{ ChartType,
# _.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
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
'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'
// max-width 900px
.frame
- stroke #000
+ stroke #333
fill none
pointer-events all
- 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
- dashboard-model
- dashboard-view
- index
+ - app
# - suffix: .js
# paths:
--- /dev/null
+- 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.
+