--- /dev/null
+What data? Agreement to focus on extant reportcard metrics:
+* Visitors
+ ** Mostly comScore data: Unique Visitors, Reach % -- otto is on it; says "super easy" to convert to dygraph format
+ ** Referrer data? otto says it's in the CSVs & totes easy
+* Mobile
+ *** where is ezachte's processed data used to make the pie chart? (otto will email)
+ *** dietz will parse HTML to extract mobile data from http://stats.wikimedia.org/EN/TablesPageViewsMonthlyMobile.htm
+ *** otto will aggregate from ezachte's CSVs after dealing with visitors
+* Editors
+ ** fabz has x6 datasets -- see docs/graph.md for more details
+* Wiki Content
+ ** otto will aggregate last if there is time
+* Pageviews
+ * Mostly done and in data dir already (otto)
+
+UI -- dsc
+* Add fields for graph metadata
+* Cascading graph option presets
+* Add option for a "Benchmark Value" on the graph
+* Datasource UI
+ ** Sources Selector: list datasets via YAML metadata descriptor files
+ ** Metric Selector: select metrics out of a dataset
+ ** Date range selector?
+ ** Configure Metric for Graph: choose color, label, formatting, and transform (low-pri -- can hardcode for now)
+* Graph Edit UI
+ ** Tabbed main container
+ ** Hide callback options
+ ** Redraw button
+ ** Add new tags + tag for "common"/"important" options
+ ** List tags to toggle all matching options
+ ** (work beyond here is low-pri)
+ *** Typeahead filter box for names/tags
+ *** Sort by Importance, Tag, Lexical
+ *** Collapse/uncollapse all
+ *** Reset all options to defaults
+ *** Outline modified options
+
+Other Business
+* Need to write documentation about how to use this and gather feedback
+* dsc to email Moeller and figure out schedule
+* otto to email ezachte to figure out source for mobile graphs on stats.wikimedia.org
+
+
+
--- /dev/null
+
+chart: title, titleHeight,
+
+axis-appearance
+
+x-axis: xlabel, xLabelHeight, drawXAxis, xAxisHeight, xAxisLabelWidth, xValueParser, drawXGrid,
+
+y-axis: ylabel, y2label, yLabelHeight, avoidMinZero, drawYAxis, includeZero, logscale, yAxisLabelWidth, valueRange, drawYGrid, labelsKMB, digitsAfterDecimal, labelsKMG2, maxNumberWidth, sigFigs,
+
+axes: axis, axisLabelColor, axisLabelFontSize, axisLabelFormatter, axisLabelWidith, axisLineColor, axisLineWidth, axisTickSize, pixelPerLabel, ticker, dateWindow
+
+line: connectSeparatedPoints, drawPoints, pointSize, strokePattern, strokeWidth, colorSaturation, colorValue, colors,
+
+graph: fillGraph, stackedGraph, stepPlot, panEdgeFraction, gridLineColor, gridLineWidth, highlightCircleSize, rangeSelectorHeight, rangeSelectorPlotFillColor, rangeSelectorPlotStrokeColor, showRangeSelector, rightGap, timingName, visibility
+
+legend: labelsDiv, labelsDivStyles, labelsDivWidth, labelsSeparateLines, labelsShowZeroValues, legend, showLabelsOnHighlight,
+
+callback: pointClickCallback, annotationClickHandler, annotationMouseOutHandler, annotationMouseOverHandler, valueFormatter, clickCallback, drawCallback, highlightCallback, underlayCallback, unhighlightCallback, zoomCallback,
+
+statistics: sigma, wilsonInterval, fillAlpha, customBars, errorBars, fractions, rollPeriod, showRoller,
+
+data: delimiter, displayAnnotations,
+
+interactivity: animatedZooms, hideOverlayOnMouseOut, isZoomedIgnoreProgrammaticZoom
# todo
-- [graph options]
- - special 'auto' value for width & height to fit to page-size
- - non-lib option: redraw on page resize
- - hide callbacks & handlers
+- cascading presets
+- base preset
+- pass label element
+- tags -> hidden detection: callbacks, deprecated, type=function
+- title element to replace title option
-## features
-- Bookmarkable Graphs
- - Load graph from URL
- - Update URL when graph changes
-- Ability to merge & filter datasets
-- Discoverable & Self-Describing Data Sources
- - ToC endpoint
- - Metadata endpoint for each datasource/dataset
- - (Both could be static files -- "endpoint" just means a convention to the URLs)
-- Persist graph settings as a preset on the server
-- Dashboard Configuration
- - list of graphs w/ configuration identifiers
+##### ohai #####
+
+[Other Business]
+- Need to write documentation about how to use this and gather feedback
+- **dsc** to email Moeller and figure out schedule
+- **otto** to email ezachte to figure out source for mobile graphs on stats.wikimedia.org
## ux
+- Add fields for graph metadata (name, desc, slug, w/h, dataset)
+- Cascading graph option presets
+- Add option for a "Benchmark Value" on the graph
+- [Datasource UI]
+ - Sources Selector: list datasets via YAML metadata descriptor files
+ - Metric Selector: select metrics out of a dataset
+ - Date range selector?
+ - Configure Metric for Graph: choose color, label, formatting, and transform (low-pri -- can hardcode for now)
+- [Graph Edit UI]
+ - Tabbed main container
+ - Hide callback options
+ - Redraw button
+ - Add new tags + tag for "common"/"important" options
+ - List tags to toggle all matching options
+ - (work beyond here is low-pri)
+ - non-lib option: redraw graph on page resize
+ - Typeahead filter box for names/tags
+ - Sort by Importance, Tag, Lexical
+ - Collapse/uncollapse all
+ - Reset all options to defaults
+ - Outline modified options
+
+### later
+
- Filter/Sort attributes, datasets, etc
- Improve graph color defaults
- Stacked bar charts seem to be something a lot of us are into, but dygraphs won't be doing that. (tho d3 does)
- Outline all changed options
+## features
+
+- Ability to merge & filter datasets
+- Discoverable & Self-Describing Data Sources
+ - ToC endpoint
+ - Metadata endpoint for each datasource/dataset
+ - (Both could be static files -- "endpoint" just means a convention to the URLs)
+- Persist graph settings as a preset on the server
+- Dashboard Configuration
+ - list of graphs w/ configuration identifiers
+
+
## notes
ctorName : 'BaseModel'
initialize: ->
- _.bindAll this, ...@__bind__ if @__bind__.length
@__super__ = @constructor.__super__
+ _.bindAll this, ...@__bind__ if @__bind__.length
+
+ serialize: (v) ->
+ if v!?
+ v = ''
+ else if _.isBoolean v
+ v = Number v
+ else if _.isObject v
+ v = JSON.stringify v
+ String v
/**
* Like `.toJSON()` in that it should return a plain object with no functions,
* @returns {Object}
*/
toKVObject: ->
- _.collapseObject @toJSON()
+ kvo = _.collapseObject @toJSON()
+ for k, v in kvo
+ kvo[k] = @serialize v
+ kvo
/**
* Serialize the model into a `www-form-encoded` string suitable for use as
_.bindAll this, ...@__bind__ if @__bind__.length
@__super__ = @constructor.__super__
+ @build()
@model.view = this
@$el.data { @model, view:this }
@model.on 'change', @render, this
{ Field, FieldList, FieldView, Scaffold,
} = require 'kraken/scaffold'
+IGNORED_TAGS = exports.IGNORED_TAGS = <[ callback deprecated debugging ]>
+
class exports.TagSet extends Array
# Notify Tag indexer of category when created, to ensure all category-tags
# get indices with colors :P
KNOWN_TAGS.update @getCategory()
+
+ # 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
+ @set 'ignore', true
# Wrapper to ensure @set('tags') is called, as tags.push()
ctorName : 'GraphOptionsScaffold'
tagName : 'form'
className : 'options scaffold'
+ template : require 'kraken/template/graph-scaffold'
collectionType : GraphOptionList
subviewType : GraphOptionView
+ $fields : '.fields'
# GraphView will set this
ready : false
# console.log "#this.render() -> .isotope()"
@__super__.render ...
return this unless @ready
- @$el.isotope do
+ @$el.find '.options.control-group' .isotope do
itemSelector : '.field.option'
layoutMode : 'masonry'
masonry : columnWidth : 10
+Seq = require 'seq'
{ _, op,
} = require 'kraken/util'
{ BaseView, BaseModel, BaseList,
{ VisView, VisModel,
} = require 'kraken/vis'
-root = do -> this
+root = this
+CHART_OPTIONS_SPEC = []
+ROOT_VIS_DATA = {}
+ROOT_VIS_OPTIONS = {}
+
# Create the Graph Scaffold
main = ->
+ # 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!', String(root.location)
- graph = root.graph = new VisView do
- graph_spec : CHART_OPTIONS_SPEC
# If we got querystring args, apply them to the graph
- if root.location?.search.slice 1
- g = _.uncollapseObject _.fromKVPairs that
- # yarr, have to do options separately or everything goes to shit
- options = delete g.options
- graph.model.set g, {+silent}
- graph.chartOptions options, {+silent}
+ data = {}
+ if String(root.location).split '?' .1
+ data = _.uncollapseObject _.fromKVPairs that.replace('#', '%23')
- # Flush all changes once the DOM settles
- _.defer do
- -> graph.scaffold.invoke 'change'
- 50
+ # # yarr, have to do options separately or everything goes to shit
+ # options = delete data.options
+ # graph.model.set data, {+silent}
+ # graph.chartOptions options, {+silent}
+ # # Flush all changes once the DOM settles
+ # _.delay do
+ # ->
+ # graph.change()
+ # # graph.scaffold.invoke 'change'
+ # 50
+
+
+ vis = root.vis = new VisModel data
+ graph = root.graph = new VisView do
+ graph_spec : root.CHART_OPTIONS_SPEC
+ model : vis
$ '#content .inner' .append graph.el
-jQuery.ajax do
- url : '/graph/dygraph-options.json',
- dataType : 'json'
- success : (data) ->
- root.CHART_OPTIONS_SPEC = data
- jQuery main
- error : (err) -> console.error err
+
+# Load data files
+Seq([ <[ CHART_OPTIONS_SPEC /graph/dygraph-options.json ]>,
+ <[ ROOT_VIS_DATA /graph/root ]>
+])
+.parEach_ (next, [key, url]) ->
+ jQuery.ajax do
+ url : url,
+ dataType : 'json'
+ success : (data) ->
+ root[key] = data
+ next.ok()
+ error : (err) -> console.error err
+.seq ->
+ console.log 'All data loaded!'
+ jQuery main
initialize: ->
+ _.bindAll this, ...(_.functions this .filter -> _.startsWith(it, 'parse'))
@set 'value', @get('default'), {+silent} if not @has 'value'
# console.log "#this.initialize!"
# @on 'all', (evt) ~> console.log "#this.trigger(#evt)"
type = _ (type or @get 'type').toLowerCase()
for t of <[ Integer Float Boolean Object Array Function ]>
if type.startsWith t.toLowerCase()
- # console.log "parse#t ->", @["parse#t"] unless @["parse#t"]
return @["parse#t"]
@parseString
parseFunction: (fn) ->
if fn and _.startswith String(fn), 'function'
- try eval "(#fn)" catch err
+ try eval "(#fn)" catch err then null
else
null
/* * * Serializers * * */
serializeValue: ->
- v = @getValue()
- if v!?
- v = ''
- else if _.isBoolean v
- v = Number v
- else if _.isArray(v) or _.isObject(v)
- v = JSON.stringify v
- String v
+ @serialize @getValue()
toJSON: ->
{id:@id} import do
_.clone(@attributes) import { value:@getValue(), def:@get('default') }
toKVObject: ->
- { "#{@id}": @serializeValue() }
+ { "#{@id}":@serializeValue() }
toString: -> "(#{@id}: #{@serializeValue()})"
# }}}
* Collects a map of fields to their values, excluding those set to `null` or their default.
* @returns {Object}
*/
- values: (keepDefaults=false) ->
+ values: (opts={}) ->
+ opts = {-keepDefaults, -serialize} import opts
_.synthesize do
- if keepDefaults then @models else @models.filter -> not it.isDefault()
- -> [ it.get('name'), it.getValue() ]
+ 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()
toKVObject: ->
- _.collapseObject @toJSON()
+ _.collapseObject @values false, true
toKVPairs: (item_delim='&', kv_delim='=') ->
_.toKVPairs @toKVObject(), item_delim, kv_delim
@trigger 'update', this
render: ->
- return @remove() if @model.get 'hidden', false
+ return @remove() if @model.get 'ignore', false
return BaseView::render ... if @template
name = @model.get 'name'
addOne: (field) ->
# console.log "[S] #this.addOne!", @__super__
+ fields = if @$fields then @$el.find that else @$el
_.remove @subviews, field.view if field.view
# avoid duplicating event propagation
SubviewType = @subviewType
view = new SubviewType model:field
@subviews.push view
- @$el.append view.render().el unless field.get 'hidden'
+ fields.append view.render().el unless field.get 'ignore'
view.on 'update', @change.bind(this, field)
@render()
fs = require 'fs'
path = require 'path'
+{existsSync:exists} = path
{exec, spawn} = require 'child_process'
_ = require 'underscore'
# Allow "spoofing" HTTP methods that IE doesn't support
app.use express.methodOverride()
+ # Route to the web services
+ app.use app.router
+
# Transparently recompile modules that have changed
app.use compiler do
enabled : <[ coco jade-browser stylus yaml ]>
app.use express.static VAR
app.use express.static STATIC
- # Route to the web services
- app.use app.router
-
# Serve directory listings
app.use express.directory WWW<