From 436ca8b66aabc5b3d7cd505608e11a2f945368ae Mon Sep 17 00:00:00 2001 From: declerambaul Date: Tue, 26 Jun 2012 18:00:09 +0200 Subject: [PATCH] D3 charttypes now render an individual metric, which allows to combine different types in the same graph, e.g. having both bars and lines in a graph. --- data/graphs/d3-test.json | 146 +++++++++++++++++++++++++++++++ lib/chart/type/d3/d3-bar-chart-type.co | 58 ++++++++++++ lib/chart/type/d3/d3-chart-type.co | 121 +++++++++++++++++++++++++ lib/chart/type/d3/d3-line-chart-type.co | 62 +++++++++++++- lib/chart/type/d3/index.co | 5 +- lib/data/metric-model.co | 2 + www/d3-test.jade | 39 ++++++++ www/modules.yaml | 5 +- www/schema/d3/d3-chart.yaml | 9 ++ 9 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 data/graphs/d3-test.json create mode 100644 lib/chart/type/d3/d3-chart-type.co create mode 100644 www/d3-test.jade create mode 100644 www/schema/d3/d3-chart.yaml diff --git a/data/graphs/d3-test.json b/data/graphs/d3-test.json new file mode 100644 index 0000000..eef06a1 --- /dev/null +++ b/data/graphs/d3-test.json @@ -0,0 +1,146 @@ +{ + "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": "d3-test", + "name": "", + "desc": "", + "notes": "", + "width": "auto", + "height": 320, + "parents": ["root"], + "data": { + "palette": null, + "lines": [], + "metrics": [{ + "index": 0, + "label": "Total", + "type": "int", + "timespan": { + "start": null, + "end": null, + "step": null + }, + "disabled": false, + "source_id": "rc_page_requests_mobile_target", + "source_col": 1, + "color": "#00b2ff", + "visible": true, + "format_value": null, + "format_axis": null, + "transforms": [], + "scale": 1 + }, { + "index": 1, + "label": "Target", + "type": "int", + "timespan": { + "start": null, + "end": null, + "step": null + }, + "disabled": false, + "source_id": "rc_page_requests_mobile_target", + "source_col": 2, + "color": "rgb(213,62,79)", + "visible": true, + "format_value": null, + "format_axis": null, + "transforms": [], + "scale": 1, + "chartType": "d3-bar" + }] + }, + "callout": { + "enabled": true, + "metric_idx": 0, + "label": "" + }, + "chartType": "d3-chart", + "id": "d3-test" +} diff --git a/lib/chart/type/d3/d3-bar-chart-type.co b/lib/chart/type/d3/d3-bar-chart-type.co index 3bcb596..0a257ff 100644 --- a/lib/chart/type/d3/d3-bar-chart-type.co +++ b/lib/chart/type/d3/d3-bar-chart-type.co @@ -40,6 +40,64 @@ class exports.BarChartType extends ChartType colors : dataset.getColors() labels : dataset.getLabels() options + + + renderChartType: (metric, svgEl ,xScale, yScale) -> + + X = (d, i) -> xScale d[0] + Y = (d, i) -> yScale d[1] + + + ### Render the line path + metricBars = root.metricBars = svgEl.append "g" + .attr "class", "metric bars "+metric.get 'label' + + data = d3.zip metric.getDateColumn(),metric.getData() + + ### Render Bars + barWidth = svgEl.attr('width')/data.length + barHeight = (d) -> svgEl.attr('height')-Y(d) + + metricBars.selectAll "bar" + .data data + .enter().append "rect" + .attr "class", (d, i) -> "metric bar #i" + .attr "x", X + .attr "y", Y + .attr "height", barHeight + .attr "width", -> barWidth + .attr "fill", metric.get 'color' + .attr "stroke", "white" + .style "opacity", "0.4" + .style "z-index", -10 + + + # adding event listeners + chT = this + metricBars.selectAll ".metric.bar" + .on "mouseover", (d, i) -> + + svgEl.append "text" + .attr "class", "mf" + .attr "dx", 50 + .attr "dy", 100 + .style "font-size", "0px" + .transition() + .duration(800) + .text "Uh boy, the target would be: "+chT.numberFormatter(d[1]).toString() + .style "font-size", "25px" + .on "mouseout", (d, i) -> + + svgEl.selectAll ".mf" + .transition() + .duration(300) + .text "BUMMER!!!" + .style "font-size", "0px" + .remove() + + + + svgEl renderChart: (data, viewport, options, lastChart) -> ### Starting with http://bost.ocks.org/mike/chart/ diff --git a/lib/chart/type/d3/d3-chart-type.co b/lib/chart/type/d3/d3-chart-type.co new file mode 100644 index 0000000..8f73c54 --- /dev/null +++ b/lib/chart/type/d3/d3-chart-type.co @@ -0,0 +1,121 @@ +d3 = require 'd3' +ColorBrewer = require 'colorbrewer' + +{ _, op, +} = require 'kraken/util' +{ ChartType, +} = require 'kraken/chart/chart-type' + +root = do -> this + + +class exports.D3ChartType extends ChartType + __bind__ : <[ determineSize ]> + SPEC_URL : '/schema/d3/d3-chart.json' + + # NOTE: ChartType.register() must come AFTER `typeName` declaration. + typeName : 'd3-chart' + 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' + + + # constructor: function D3ChartType + -> super ... + + + + getData: -> + @model.dataset.getColumns() + + + transform: -> + dataset = @model.dataset + options = @model.getOptions() import @determineSize() + options import do + colors : dataset.getColors() + labels : dataset.getLabels() + options + + + renderChart: (data, viewport, options, lastChart) -> + ### Starting with http://bost.ocks.org/mike/chart/ + + margin = {top: 20, right: 20, bottom: 20, left: 20} + width = 760 + height = 320 + xScale = d3.time.scale() + yScale = d3.scale.linear() + + dates = data[0] + cols = data.slice(1) + + # Calculate extents using all the data points (but not dates) + # allValues = d3.merge @model.dataset.getDataColumns() + allValues = d3.merge cols + + # Update the x-scale with the extents of the dates. + xScale + .domain d3.extent dates + .range [ 0, width - margin.left - margin.right ] + + # Update the y-scale with the extents of the data. + yScale + .domain d3.extent allValues + .range [ height - margin.top - margin.bottom, 0 ] + + # Select the svg element, if it exists. + svg = d3.select viewport.0 .selectAll "svg" + .data [cols] + + # ...Otherwise, create the skeletal chart. + enterFrame = svg.enter() + .append "svg" .append "g" + .attr "class", "frame" + + # Update chart dimensions. + svg .attr "width", width + .attr "height", height + + frame = svg.select "g.frame" + .attr "transform", "translate(#{margin.left},#{margin.top})" + .attr "width", width - margin.left - margin.right + .attr "height", height - margin.top - margin.bottom + + + # x-axis. + # TODO move axis to separate chart-type + enterFrame.append "g" + .attr "class", "x axis time" + + + xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(6, 0) + frame.select ".x.axis.time" + .attr "transform", "translate(0,#{yScale.range()[0]})" + .call xAxis + + for i,metric in @model.dataset.metrics.models + # metric defined charttype + chartType = metric.get "chartType" + # otherwise the graph defined charttype + # FOR NOW take line as default + chartType or= 'd3-line' # @model.get "chartType" + + + # create d3 charttype and render it + model = ChartType.create chartType + model.renderChartType metric, frame ,xScale, yScale + + + svg + + + diff --git a/lib/chart/type/d3/d3-line-chart-type.co b/lib/chart/type/d3/d3-line-chart-type.co index 6c67ead..e0974f7 100644 --- a/lib/chart/type/d3/d3-line-chart-type.co +++ b/lib/chart/type/d3/d3-line-chart-type.co @@ -28,11 +28,9 @@ class exports.LineChartType extends ChartType legend : '.graph-legend' - -> super ... - getData: -> @model.dataset.getColumns() @@ -45,6 +43,66 @@ class exports.LineChartType extends ChartType labels : dataset.getLabels() options + renderChartType: (metric, svgEl ,xScale, yScale) -> + + X = (d, i) -> xScale d[0] + Y = (d, i) -> yScale d[1] + line = d3.svg.line().x(X).y(Y) + + ### Render the line path + metricLine = root.metricLine = svgEl.append "g" + .attr "class", "g metric line "+metric.get 'label' + + data = d3.zip metric.getDateColumn(),metric.getData() + + metricLine.selectAll "path.line" + .data d3.zip data.slice(0,-1), data.slice(1) + .enter().append "path" + .attr "d", line + .attr "class", (d, i) -> "metric line segment #i" + .style "stroke", metric.get 'color' + + + ### Mouse Lens + lens = root.lens = svgEl.selectAll "g.lens" + .data [[]] + gLens = lens.enter().append "g" + .attr "class", "lens" + .style "z-index", 1e9 + gInner = gLens.append "g" + .attr "transform", "translate(1.5em,0)" + gInner.append "circle" + .attr "r", "1.5em" + # .style "opacity", "0.4" + # .style "fill", "white" + .style "fill", "rgba(255, 255, 255, 0.4)" + .style "stroke", "white" + .style "stroke-width", "3px" + gInner.append "text" + .attr "y", "0.5em" + .attr "text-anchor", "middle" + .style "fill", "black" + .style "font", "12px Helvetica" + .style "font-weight", "bold" + + # event listeners + chT = this + metricLine.selectAll ".line.segment" + .on "mouseover", (d, i) -> + + {r,g,b} = color = d3.rgb metric.get 'color' + lineX = (X(d[0])+X(d[1]))/2 + lineY = (Y(d[0])+Y(d[1]))/2 + + + lens = svgEl.select "g.lens" + .attr "transform", "translate(#lineX, #lineY)" + lens.select "circle" .style "fill", "rgba(#r, #g, #b, 0.4)" + lens.select "text" .text -> chT.numberFormatter(d[0][1]).toString() + + + svgEl + renderChart: (data, viewport, options, lastChart) -> ### Starting with http://bost.ocks.org/mike/chart/ diff --git a/lib/chart/type/d3/index.co b/lib/chart/type/d3/index.co index 06db1d2..3baf40d 100644 --- a/lib/chart/type/d3/index.co +++ b/lib/chart/type/d3/index.co @@ -1,5 +1,6 @@ +d3chart = require 'kraken/chart/type/d3/d3-chart-type' line = require 'kraken/chart/type/d3/d3-line-chart-type' -# geo = require 'kraken/chart/type/d3/d3-geo-chart-type' bar = require 'kraken/chart/type/d3/d3-bar-chart-type' +# geo = require 'kraken/chart/type/d3/d3-geo-chart-type' -exports import line import bar # import geo +exports import line import bar import d3chart # import geo diff --git a/lib/data/metric-model.co b/lib/data/metric-model.co index e270c82..605f1b8 100644 --- a/lib/data/metric-model.co +++ b/lib/data/metric-model.co @@ -43,6 +43,8 @@ Metric = exports.Metric = BaseModel.extend do # {{{ transforms : [] scale : 1.0 + + chartType : null diff --git a/www/d3-test.jade b/www/d3-test.jade new file mode 100644 index 0000000..968e48f --- /dev/null +++ b/www/d3-test.jade @@ -0,0 +1,39 @@ +extends layout + +block title + title Edit Graph | Limn + +append styles + mixin css('/vendor/bootstrap-colorpicker/css/colorpicker.css') + mixin css('/vendor/bootstrap-datepicker/css/datepicker.css') + mixin css('graph.css') + // new for SVG + mixin css('chart.css') + mixin css('data.css') + mixin css('isotope.css') + +block main-scripts + script + root = this + + // import all the things, ugly JS-style + d3 = require('d3') + Seq = require('seq') + Backbone = require('backbone') + util = require('kraken/util'); _ = util._; op = util.op + AppView = require('kraken/app').AppView + base = require('kraken/base'); BaseView = base.BaseView; BaseModel = base.BaseModel; BaseList = base.BaseList + ChartType = require('kraken/chart').ChartType + data = require('kraken/data'); DataSource = data.DataSource; DataSourceList = data.DataSourceList + _graph = require('kraken/graph'); Graph = _graph.Graph; GraphList = _graph.GraphList; GraphEditView = _graph.GraphEditView + //LineChartType = require('kraken/chart/type/d3/d3-line-chart-type') + + // run on DOM-ready + jQuery(function(){ + root.app = new AppView(function(){ + this.model = root.graph = new Graph({ id:'d3-test' }, { parse:true }) + this.view = root.view = new GraphEditView({ model:this.model }) + }); + }); + + diff --git a/www/modules.yaml b/www/modules.yaml index b74eba7..64c93c2 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -112,9 +112,10 @@ dev: - index - type: - d3: - - d3-line-chart-type - # - d3-geo-chart-type + - d3-chart-type + - d3-line-chart-type - d3-bar-chart-type + # - d3-geo-chart-type - index - dygraphs - index diff --git a/www/schema/d3/d3-chart.yaml b/www/schema/d3/d3-chart.yaml new file mode 100644 index 0000000..88a3564 --- /dev/null +++ b/www/schema/d3/d3-chart.yaml @@ -0,0 +1,9 @@ +- 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). + -- 1.7.0.4