-{
- "id": "ohai",
- "slug": "ohai",
- "name": "ohai~",
- "desc": "A graph for the testing of great justice.",
- "notes": "",
- "width": "auto",
- "height": 250,
- "parents": ["root"],
- "dataset": "/data/datasources/rc/rc_page_requests.csv",
- "data": {
- "metrics": [{
- "source_id": "rc_active_editors_count",
- "source_col": 1,
- "label": "Total Active Editors",
- "color": "#E62F74"
- }, {
- "source_id": "rc_very_active_editors_count",
- "source_col": 1,
- "label": "Total Very Active Editors",
- "color": "#244792"
- }, {
- "source_id": "rc_edits_count",
- "source_col": 1,
- "label": "Total Edits",
- "color": "#FF6458"
- }]
- },
- "chartType": "dygraphs",
- "options": {
- "animatedZooms": true,
- "avoidMinZero": false,
- "axis": null,
- "axisLabelColor": "#666666",
- "axisLabelFontSize": 11,
- "axisLabelFormatter": null,
- "axisLabelWidth": 50,
- "axisLineColor": "#AAAAAA",
- "axisLineWidth": 0.3,
- "axisTickSize": 3,
- "colorSaturation": 1,
- "colorValue": 0.5,
- "colors": ["#FF0097", "#EF8158", "#83BB32", "#182B53", "#4596FF", "#553DC9", "#AD3238", "#00FFBC", "#F1D950"],
- "connectSeparatedPoints": false,
- "customBars": false,
- "dateWindow": null,
- "delimiter": ",",
- "digitsAfterDecimal": 2,
- "displayAnnotations": false,
- "drawPoints": true,
- "drawXAxis": true,
- "drawXGrid": true,
- "drawYAxis": true,
- "drawYGrid": true,
- "errorBars": false,
- "file": null,
- "fillAlpha": 0.15,
- "fillGraph": false,
- "fractions": false,
- "gridLineColor": "#D8D8D8",
- "gridLineWidth": 0.3,
- "hideOverlayOnMouseOut": false,
- "highlightCircleSize": 4,
- "includeZero": false,
- "interactionModel": null,
- "isZoomedIgnoreProgrammaticZoom": false,
- "labels": null,
- "labelsDiv": null,
- "labelsDivStyles": null,
- "labelsDivWidth": 250,
- "labelsKMB": true,
- "labelsKMG2": false,
- "labelsSeparateLines": true,
- "labelsShowZeroValues": true,
- "legend": "always",
- "logscale": true,
- "maxNumberWidth": 30,
- "panEdgeFraction": null,
- "pixelsPerLabel": null,
- "pixelsPerXLabel": null,
- "pixelsPerYLabel": null,
- "pointSize": 1,
- "rangeSelectorHeight": 40,
- "rangeSelectorPlotFillColor": "#A7B1C4",
- "rangeSelectorPlotStrokeColor": "#808FAB",
- "rightGap": 20,
- "rollPeriod": 1,
- "showLabelsOnHighlight": true,
- "showRangeSelector": false,
- "showRoller": false,
- "sigFigs": null,
- "sigma": 2,
- "stackedGraph": false,
- "stepPlot": false,
- "strokePattern": null,
- "strokeWidth": 4,
- "ticker": null,
- "title": null,
- "titleHeight": 18,
- "valueFormatter": null,
- "valueRange": null,
- "visibility": null,
- "wilsonInterval": true,
- "xAxisHeight": null,
- "xAxisLabelFormatter": null,
- "xAxisLabelWidth": 55,
- "xLabelHeight": 18,
- "xValueFormatter": null,
- "xValueParser": null,
- "xlabel": null,
- "y2label": null,
- "yAxisLabelFormatter": null,
- "yAxisLabelWidth": 50,
- "yLabelWidth": 18,
- "yValueFormatter": null,
- "ylabel": null
- }
-}
+{"options":{"animatedZooms":true,"avoidMinZero":false,"axis":null,"axisLabelColor":"#666666","axisLabelFontSize":11,"axisLabelFormatter":null,"axisLabelWidth":50,"axisLineColor":"#AAAAAA","axisLineWidth":0.3,"axisTickSize":3,"colorSaturation":1,"colorValue":0.5,"colors":["#FF0097","#EF8158","#83BB32","#182B53","#4596FF","#553DC9","#AD3238","#00FFBC","#F1D950"],"connectSeparatedPoints":false,"customBars":false,"dateWindow":null,"delimiter":",","digitsAfterDecimal":2,"displayAnnotations":false,"drawPoints":true,"drawXAxis":true,"drawXGrid":true,"drawYAxis":true,"drawYGrid":true,"errorBars":false,"file":null,"fillAlpha":0.15,"fillGraph":false,"fractions":false,"gridLineColor":"#D8D8D8","gridLineWidth":0.3,"hideOverlayOnMouseOut":false,"highlightCircleSize":4,"includeZero":false,"interactionModel":null,"isZoomedIgnoreProgrammaticZoom":false,"labels":null,"labelsDiv":null,"labelsDivStyles":null,"labelsDivWidth":250,"labelsKMB":true,"labelsKMG2":false,"labelsSeparateLines":true,"labelsShowZeroValues":true,"legend":"always","logscale":true,"maxNumberWidth":30,"panEdgeFraction":null,"pixelsPerLabel":null,"pixelsPerXLabel":null,"pixelsPerYLabel":null,"pointSize":1,"rangeSelectorHeight":40,"rangeSelectorPlotFillColor":"#A7B1C4","rangeSelectorPlotStrokeColor":"#808FAB","rightGap":20,"rollPeriod":1,"showLabelsOnHighlight":true,"showRangeSelector":false,"showRoller":false,"sigFigs":null,"sigma":2,"stackedGraph":false,"stepPlot":false,"strokePattern":null,"strokeWidth":4,"ticker":null,"title":null,"titleHeight":18,"valueFormatter":null,"valueRange":null,"visibility":null,"wilsonInterval":true,"xAxisHeight":null,"xAxisLabelFormatter":null,"xAxisLabelWidth":55,"xLabelHeight":18,"xValueFormatter":null,"xValueParser":null,"xlabel":null,"y2label":null,"yAxisLabelFormatter":null,"yAxisLabelWidth":50,"yLabelWidth":18,"yValueFormatter":null,"ylabel":null},"slug":"ohai","name":"ohai~","desc":"A graph for the testing of great justice.","notes":"","width":"auto","height":250,"chartType":"dygraphs","parents":["root"],"id":"ohai","data":{"palette":null,"lines":[],"id":"ohai","metrics":[{"index":0,"label":"Total Active Editors","type":"int","timespan":{"start":null,"end":null,"step":null},"disabled":false,"source_id":"rc_active_editors_count","source_col":1,"color":"#E62F74","visible":true,"format_value":null,"format_axis":null,"transforms":[],"scale":1},{"index":0,"label":"Total Very Active Editors","type":"int","timespan":{"start":null,"end":null,"step":null},"disabled":false,"source_id":"rc_very_active_editors_count","source_col":1,"color":"#244792","visible":true,"format_value":null,"format_axis":null,"transforms":[],"scale":1},{"index":0,"label":"","type":"int","timespan":{"start":null,"end":null,"step":null},"disabled":false,"source_id":"rc_new_article_count","source_col":1,"color":"#FF6458","visible":true,"format_value":null,"format_axis":null,"transforms":[],"scale":1,"source-url":"/data/datasources/rc/rc_very_active_editors_count.csv"},{"index":3,"label":"New Editors","type":"int","timespan":{"start":null,"end":null,"step":null},"disabled":false,"source_id":"rc_new_editors_count","source_col":1,"color":"rgb(253,174,97)","visible":true,"format_value":null,"format_axis":null,"transforms":[],"scale":1,"source-url":"/data/datasources/rc/rc_very_active_editors_count.csv"},{"index":4,"label":"","type":"int","timespan":{"start":null,"end":null,"step":null},"disabled":false,"source_id":"rc_binary_files","source_col":10,"color":"rgb(254,224,139)","visible":true,"format_value":null,"format_axis":null,"transforms":[],"scale":1}]},"dataset":"/data/datasources/rc/rc_page_requests.csv"}
\ No newline at end of file
@addSubview @dataset_view
.on 'add-metric', @onMetricsChanged, this
.on 'remove-metric', @onMetricsChanged, this
- .on 'edit-metric', @editMetric, this
+ .on 'select-metric', @selectMetric, this
@render()
@triggerReady()
.on 'metric-update', @onUpdateMetric, this
.on 'metric-change', @onUpdateMetric, this
@metric_views.push @addSubview view
+ @renderSubviews()
metric
removeMetric: (metric) ->
@removeSubview view
metric
- editMetric: (metric) ->
- console.log "#this.editMetric!", metric
+ selectMetric: (metric) ->
+ # console.log "#this.selectMetric!", metric
@metric_views.invoke 'hide'
@metric_edit_view = @metric_views.findByModel metric
@metric_edit_view?.show()
_.delay @onMetricsChanged, 10
onMetricsChanged: ->
+ return unless @dataset_view
oldMinHeight = parseInt @$el.css 'min-height'
newMinHeight = Math.max do
@dataset_view.$el.height()
metrics : []
- constructor: function DataSet
- BaseModel ...
+ constructor: function DataSet (attributes={}, opts)
+ @metrics = new MetricList attributes.metrics
+ BaseModel.call this, attributes, opts
initialize : ->
BaseModel::initialize ...
- @metrics = new MetricList @attributes.metrics
+ @set 'metrics', @metrics, {+silent}
@on 'change:metrics', @onMetricChange, this
-
load: (opts={}) ->
@resetReady() if opts.force
return this if @loading or @ready
@triggerReady()
this
+ # refreshSubModels: ->
+ # # @set 'metrics', @metrics.toJSON(), {+silent}
+ # @set 'metrics', _.pluck(@metrics.models, 'attributes'), {+silent}
+ # this
+
+ /**
+ * Override to handle the case where one of our rich sub-objects is attempted
+ * to be overridden with a native object.
+ */
+ set: (key, value, opts) ->
+ # return DataSet.__super__.set ... unless @metrics
+
+ if _.isObject(key) and key?
+ [values, opts] = [key, value]
+ else
+ values = { "#key": value }
+ opts or= {}
+
+ for key, value in values
+ continue unless key is 'metrics' and _.isArray value
+ @metrics.reset value
+ delete values[key]
+ unless opts.silent
+ DataSet.__super__.set.call this, 'metrics', value, {+silent}
+ DataSet.__super__.set.call this, 'metrics', @metrics, opts
+
+ DataSet.__super__.set.call this, values, opts
+
/* * * * TimeSeriesData interface * * * {{{ */
* @returns {Array<Array>} The reified dataset, materialized to a list of rows including timestamps.
*/
getData: ->
+ return [] unless @ready
_.zip ...@getColumns()
/**
* @returns {Array<Array>} List of all columns (including date column).
*/
getColumns: ->
- [ @getDateColumn() ].concat @getDataColumns()
+ return [] unless @ready
+ _.compact [ @getDateColumn() ].concat @getDataColumns()
/**
* @returns {Array<Date>} The date column.
*/
getDateColumn: ->
+ return [] unless @ready
dates = @metrics.onlyOk().invoke 'getDateColumn'
maxLen = _.max _.pluck dates, 'length'
_.find dates, -> it.length is maxLen
* @returns {Array<Array>} List of all columns except the date column.
*/
getDataColumns: ->
+ return [] unless @ready
@metrics.onlyOk().invoke 'getData'
/**
* @returns {Array<String>} List of column labels.
*/
getLabels: ->
+ return [] unless @ready
[ 'Date' ].concat @metrics.onlyOk().invoke 'getLabel'
- # }}}
-
getColors: ->
+ return [] unless @ready
@metrics.onlyOk().pluck 'color'
+ # }}}
+
+
newMetric: ->
index = @metrics.length
@metrics.add m = new Metric { index, color:ColorBrewer.Spectral[11][index] }
+ # @get 'metrics' .push m.attributes
+ # @trigger 'change:metrics', this, @metrics, 'metrics'
+ # @trigger 'change', this, @metrics, 'metrics'
m
onMetricChange: ->
console.log "#this.onMetricChange! ready=#{@ready}"
@resetReady()
- @metrics.reset @get 'metrics'
@load()
- # TODO: toJSON() must ensure columns in MetricList are ordered by index
+
+ # XXX: toJSON() must ensure columns in MetricList are ordered by index
# ...in theory, MetricList.comparator now does this
+ # toJSON: ->
+ # @refreshSubModels()
+ # json = DataSet.__super__.toJSON ...
+ # json.metrics = json.metrics.map -> it.toJSON?() or it
+ # json
# }}}
events:
'click .new-metric-button' : 'newMetric'
- 'click .metrics .dataset-metric' : 'editMetric'
+ 'click .metrics .dataset-metric' : 'selectMetric'
views_by_cid : {}
active_view : null
addMetric: (metric) ->
console.log "#this.addMetric!", metric
- if metric.view
- @removeSubview metric.view.remove()
+ if @views_by_cid[metric.cid]
+ @removeSubview that
delete @views_by_cid[metric.cid]
view = @addSubview new DataSetMetricView {model:metric, @graph_id}
@views_by_cid[metric.cid] = view
- @$ '.metrics' .append view.render().el
-
- # @render()
+ # @$ '.metrics' .append view.render().el
@trigger 'add-metric', metric, view, this
+ @render()
view
removeMetric: (metric) ->
console.log "#this.removeMetric!", metric
- if view = metric.view
- @removeSubview view.remove()
+ if view = @views_by_cid[metric.cid]
+ @removeSubview view
delete @views_by_cid[metric.cid]
@trigger 'remove-metric', metric, view, this
- metric.view
+ view
addAllMetrics: ->
console.log "#this.addAllMetrics! --> #{@model.metrics}"
@model.metrics.each @addMetric, this
this
- editMetric: (metric) ->
- # console.log "#this.editMetric!", metric
+ selectMetric: (metric) ->
if metric instanceof [jQuery.Event, Event]
metric = $ metric.currentTarget .data 'model'
view = @active_view = @views_by_cid[metric.cid]
- console.log "#this.editMetric!", metric
+ # console.log "#this.selectMetric!", metric
@$ '.metrics .dataset-metric' .removeClass 'metric-active'
view.$el.addClass 'metric-active'
view.$el.find '.activity-arrow' .css 'font-size', 2+view.$el.height()
- @trigger 'edit-metric', metric, view, this
+ @trigger 'select-metric', metric, view, this
this
'disabled' if m.disabled,
]).map( -> "metric-#it" ).join ' '
source :
- if m.source_id and m.source_col_name
- "#{m.source_id}.#{m.source_col_name}"
+ if m.source_id and m.source_col
+ "#{m.source_id}[#{m.source_col}]"
else
'No source'
timespan :
update: ->
MetricEditView.__super__.update ...
+ @$ '.metric-label' .attr 'placeholder', @model.getPlaceholderLabel()
# Update the color picker
@$ '.color-swatch'
onSourceMetricChange: (metric) ->
console.log "#this.onSourceMetricChange!", metric
+ @$ '.metric-label' .attr 'placeholder', @model.getPlaceholderLabel()
@trigger 'metric-change', @model, this
this
getPlaceholderLabel: ->
col = @get 'source_col'
- name = "#{@source.get 'shortName'}, #{@source.getColumnName col}" if @source and col > 0
+ name = "#{@source.get 'shortName'}, #{@source.getColumnName col}" if @source and col >= 0
name or @NEW_METRIC_LABEL
getSourceColumnName: ->
load: (opts={}) ->
- source_id = @get 'source_id'
+ source_id = @get 'source_id'
@resetReady() if opts.force or @source?.id is not source_id
- return this if not source_id or @loading or @ready
+ return this if @loading or @ready
+
+ unless source_id and @get('source_col') >= 0
+ return @triggerReady()
console.log "#this.load()..."
@updateId()
### Graph Data UI
- @data_view = @addSubview new DataView { model:@model.get('data'), graph_id:@id }
+ @data_view = @addSubview new DataView { model:@model.dataset, graph_id:@id }
@data_view
.on 'start-waiting', @wait, this
.on 'stop-waiting', @unwait, this
valueFormatter : @numberFormatterHTML
# console.log "#this.render!", dataset
- _.dump options, 'options'
+ # _.dump options, 'options'
# Always rerender the chart to sidestep the case where we need to push defaults into
# dygraphs to reset the current option state.
# @renderDetails()
# @attachSubviews()
# _.invoke @subviews, 'render'
- BaseView::render ...
+ GraphEditView.__super__.render ...
@renderChart()
# @updateURL()
@trigger 'render', this
* Update the page URL using HTML5 History API
*/
updateURL: ->
- data = @toJSON()
+ json = @toJSON()
title = "#{@model.get('name') or 'New Graph'} | Edit Graph | GraphKit"
url = @toURL('edit')
- # console.log 'History.pushState', JSON.stringify(data), title, url
- History.pushState data, title, url
+ # console.log 'History.pushState', JSON.stringify(json), title, url
+ History.pushState json, title, url
### }}}
# \toptions: #{JSON.stringify options}
# \t^opts: #{JSON.stringify _.intersection _.keys(changes), _.keys(options)}
# """
- @chart?.updateOptions file:that if changes?.dataset
+ # @chart?.updateOptions file:that if changes?.dataset
@chartOptions options, {+silent} if changes?.options
onScaffoldChange: (scaffold, value, key, field) ->
onDetailsSubmit: ->
console.log "#this.onDetailsSubmit!"
- data = @$ 'form.graph-details' .formData()
- @model.set data
+ details = @$ 'form.graph-details' .formData()
+ @model.set details
false
onOptionsSubmit: ->
# Insert submodels in place of JSON
@dataset = new DataSet {id:@id, ...@get 'data'}
+ # .on 'change', @onDataSetChange, this
@set 'data', @dataset, {+silent}
@trigger 'init', this
next.ok()
success : @unwaitAnd (model, res) ~>
# console.log "#{this}.fetch() --> success!", res
+ # Update the DataSet model with the new values
@dataset.set @get 'data'
- @trigger 'change:data', this, @dataset, 'data'
- @trigger 'change', this, @dataset, 'data'
+ @set 'data', @dataset, {+silent}
next.ok res
# Load Parents...
Seq @dataset.metrics.models
.parEach_ (next, metric) ->
metric.once 'ready', next.ok .load()
- .parEach_ (next, metric) ->
- metric.source
- .on 'load-data-success', next.ok .loadData()
+ .parEach_ (next, metric) ~>
+ unless metric.source
+ console.warn "#{this}.loadData() -- Skipping metric #metric with invalid source!", metric
+ return next.ok()
+ metric.source.on 'load-data-success', next.ok .loadData()
.seq ~>
console.log "#{this}.loadData() complete!"
@loading = false
@triggerReady 'dataReady', 'data-ready'
this
+ onDataSetChange: ->
+ console.log "#this.onDataSetChange!"
+ @set 'data', @dataset, {+silent}
### Accessors
if _.startsWith key, 'options.'
@getOption key.slice(8)
else
- (@..__super__ or BaseModel::).get.call this, key
+ Graph.__super__.get.call this, key
set: (key, value, opts) ->
values = { "#key": value }
values = @parse values
- setter = (@..__super__ or BaseModel::).set
+ setter = Graph.__super__.set
# Merge options in, firing granulated change events
if values.options
- ### Chart Option Accessors ###
+ ### Chart Option Accessors {{{
hasOption: (key) ->
@getOption(key) is void
options
-
- ### Serialization
+ # }}}
+ ### Serialization {{{
parse: (data) ->
data = JSON.parse data if typeof data is 'string'
toJSON: (opts={}) ->
opts = {+keepDefaults, +keepUnchanged} import opts
# use jQuery's deep-copy implementation -- XXX: Deep-copy no longer necessary thanks to @getOptions()
- # json = $.extend true, {}, @attributes
json = _.clone(@attributes) import { options:@getOptions(opts) }
+ # { data: ...json }
toKVPairs: (opts={}) ->
toPermalink: ->
"#{root.location.protocol}//#{window.location.host}#{@toLink()}"
+ # }}}
+
### Graph Cache for parent-lookup
new ModelCache Graph
input.metric-color(type='hidden', id="#{graph_id}_metric", name="color", value=color)
.color-swatch.input-append.color(data-color="#{color}", data-color-format="hex")
input.metric-color(type='hidden', id="#{graph_id}_metric_color", name="color", value=color)
- span.add-on: i(style="background-color: #{color};")
+ span.add-on: i(style="background-color: #{color};", title="#{color}")
input.metric-label(type='text', id="#{graph_id}_metric_label", name='label', placeholder='#{placeholder_label}', value=label)