      1 /**
      2  * @license
      3  * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
      4  * MIT-licenced: https://opensource.org/licenses/MIT
      5  */
      6
      7 /*global Dygraph:false */
      8
      9 "use strict";
     10
     11 /**
     12 Current bits of jankiness:
     13 - Uses dygraph.layout_ to get the parsed annotations.
     14 - Uses dygraph.plotter_.area
     15
     16 It would be nice if the plugin didn't require so much special support inside
     17 the core dygraphs classes, but annotations involve quite a bit of parsing and
     18 layout.
     19
     20 TODO(danvk): cache DOM elements.
     21 */
     22
     23 var annotations = function() {
     24   this.annotations_ = [];
     25 };
     26
     27 annotations.prototype.toString = function() {
     28   return "Annotations Plugin";
     29 };
     30
     31 annotations.prototype.activate = function(g) {
     32   return {
     33     clearChart: this.clearChart,
     34     didDrawChart: this.didDrawChart
     35   };
     36 };
     37
     38 annotations.prototype.detachLabels = function() {
     39   for (var i = 0; i < this.annotations_.length; i++) {
     40     var a = this.annotations_[i];
     41     if (a.parentNode) a.parentNode.removeChild(a);
     42     this.annotations_[i] = null;
     43   }
     44   this.annotations_ = [];
     45 };
     46
     47 annotations.prototype.clearChart = function(e) {
     48   this.detachLabels();
     49 };
     50
     51 annotations.prototype.didDrawChart = function(e) {
     52   var g = e.dygraph;
     53
     54   // Early out in the (common) case of zero annotations.
     55   var points = g.layout_.annotated_points;
     56   if (!points || points.length === 0) return;
     57
     58   var containerDiv = e.canvas.parentNode;
     59
     60   var bindEvt = function(eventName, classEventName, pt) {
     61     return function(annotation_event) {
     62       var a = pt.annotation;
     63       if (a.hasOwnProperty(eventName)) {
     64         a[eventName](a, pt, g, annotation_event);
     65       } else if (g.getOption(classEventName)) {
     66         g.getOption(classEventName)(a, pt, g, annotation_event );
     67       }
     68     };
     69   };
     70
     71   // Add the annotations one-by-one.
     72   var area = e.dygraph.getArea();
     73
     74   // x-coord to sum of previous annotation's heights (used for stacking).
     75   var xToUsedHeight = {};
     76
     77   for (var i = 0; i < points.length; i++) {
     78     var p = points[i];
     79     if (p.canvasx < area.x || p.canvasx > area.x + area.w ||
     80         p.canvasy < area.y || p.canvasy > area.y + area.h) {
     81       continue;
     82     }
     83
     84     var a = p.annotation;
     85     var tick_height = 6;
     86     if (a.hasOwnProperty("tickHeight")) {
     87       tick_height = a.tickHeight;
     88     }
     89
     90     // TODO: deprecate axisLabelFontSize in favor of CSS
     91     var div = document.createElement("div");
     92     div.style['fontSize'] = g.getOption('axisLabelFontSize') + "px";
     93     var className = 'dygraph-annotation';
     94     if (!a.hasOwnProperty('icon')) {
     95       // camelCase class names are deprecated.
     96       className += ' dygraphDefaultAnnotation dygraph-default-annotation';
     97     }
     98     if (a.hasOwnProperty('cssClass')) {
     99       className += " " + a.cssClass;
    100     }
    101     div.className = className;
    102
    103     var width = a.hasOwnProperty('width') ? a.width : 16;
    104     var height = a.hasOwnProperty('height') ? a.height : 16;
    105     if (a.hasOwnProperty('icon')) {
    106       var img = document.createElement("img");
    107       img.src = a.icon;
    108       img.width = width;
    109       img.height = height;
    110       div.appendChild(img);
    111     } else if (p.annotation.hasOwnProperty('shortText')) {
    112       div.appendChild(document.createTextNode(p.annotation.shortText));
    113     }
    114     var left = p.canvasx - width / 2;
    115     div.style.left = left + "px";
    116     var divTop = 0;
    117     if (a.attachAtBottom) {
    118       var y = (area.y + area.h - height - tick_height);
    119       if (xToUsedHeight[left]) {
    120         y -= xToUsedHeight[left];
    121       } else {
    122         xToUsedHeight[left] = 0;
    123       }
    124       xToUsedHeight[left] += (tick_height + height);
    125       divTop = y;
    126     } else {
    127       divTop = p.canvasy - height - tick_height;
    128     }
    129     div.style.top = divTop + "px";
    130     div.style.width = width + "px";
    131     div.style.height = height + "px";
    132     div.title = p.annotation.text;
    133     div.style.color = g.colorsMap_[p.name];
    134     div.style.borderColor = g.colorsMap_[p.name];
    135     a.div = div;
    136
    137     g.addAndTrackEvent(div, 'click',
    138         bindEvt('clickHandler', 'annotationClickHandler', p, this));
    139     g.addAndTrackEvent(div, 'mouseover',
    140         bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this));
    141     g.addAndTrackEvent(div, 'mouseout',
    142         bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this));
    143     g.addAndTrackEvent(div, 'dblclick',
    144         bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this));
    145
    146     containerDiv.appendChild(div);
    147     this.annotations_.push(div);
    148
    149     var ctx = e.drawingContext;
    150     ctx.save();
    151     ctx.strokeStyle = a.hasOwnProperty('tickColor') ? a.tickColor : g.colorsMap_[p.name];
    152     ctx.lineWidth = a.hasOwnProperty('tickWidth') ? a.tickWidth : g.getOption('strokeWidth');
    153     ctx.beginPath();
    154     if (!a.attachAtBottom) {
    155       ctx.moveTo(p.canvasx, p.canvasy);
    156       ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height);
    157     } else {
    158       var y = divTop + height;
    159       ctx.moveTo(p.canvasx, y);
    160       ctx.lineTo(p.canvasx, y + tick_height);
    161     }
    162     ctx.closePath();
    163     ctx.stroke();
    164     ctx.restore();
    165   }
    166 };
    167
    168 annotations.prototype.destroy = function() {
    169   this.detachLabels();
    170 };
    171
    172 export default annotations;
    173