/*
 * Raphael Line Chart 1.3 - Raphael Line Charts plugin
 *
 * Copyright (c) 2021 Sagie Maoz (http://sagie.maoz.info)
 * Dual-licensed under the GPL (http://www.opensource.org/licenses/gpl-3.0)
 * and the MIT (http://www.opensource.org/licenses/mit-license) licenses.
 */
(function() {

// modified version of Raphael.fn.drawGrid() from the Raphael Analytics example
Raphael.fn.drawGrid = function(x, y, width, height, x_step, x_size, y_count, y_size) {
  var path,
    rowHeight,
    columnWidth;

  // frame border
  path = ["M", Math.round(x) + 0.5, Math.round(y) + 0.5,
      "L", Math.round(x + width) + 0.5, Math.round(y) + 0.5,
      Math.round(x + width) + 0.5, Math.round(y + height) + 0.5,
      Math.round(x) + 0.5, Math.round(y + height) + 0.5,
      Math.round(x) + 0.5, Math.round(y) + 0.5];

  // horizontal lines
  rowHeight = Math.ceil(height / y_count);
  for (var i = 0; i < y_count; i++) {
    path = path.concat(["M", Math.round(x) + 0.5, Math.round(y + i * rowHeight) + 0.5,
              "H", Math.round(x + width) + 0.5]);
  }

  // vertical lines
  if (!x_step) {
    x_step = 1;
    x_size = 10;
  }
  columnWidth = Math.ceil(width / x_size);
  for (i = 0; i < x_size; i+= x_step) {
    path = path.concat(["M", Math.round(x + i * (columnWidth + 1.1)) + 0.5, Math.round(y) + 0.5,
              "V", Math.round(y + height) + 0.5]);
  }

  return this.path(path.join(","));
};

Raphael.fn.popup = function (X, Y, set, pos, ret) {
    var pos = String(pos || "top-middle").split("-");
        pos[1] = pos[1] || "middle",
        tokenRegex = /\{([^\}]+)\}/g,
        objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties

        replacer = function (all, key, obj) {
            var res = obj;
            key.replace(objNotationRegex,
            function (all, name, quote, quotedName, isFunc) {
                name = name || quotedName;
                if (res) {
                    if (name in res) {
                        res = res[name];
                    }
                    typeof res == "function" && isFunc && (res = res());
                }
            });
            res = (res == null || res == obj ? all : res) + "";
            return res;
        },

        fill = function (str, obj) {
            return String(str).replace(tokenRegex,
            function (all, key) {
                return replacer(all, key, obj);
            });
        },

        r = 5,
        bb = set.getBBox(),
        w = Math.round(bb.width),
        h = Math.round(bb.height),
        x = Math.round(bb.x) - r,
        y = Math.round(bb.y) - r,
        gap = Math.min(h / 2, w / 2, 10),
        shapes = {
            top: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
            bottom: "M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
            right: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z",
            left: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z"
        },
        offset = {
            hx0: X - (x + r + w - gap * 2),
            hx1: X - (x + r + w / 2 - gap),
            hx2: X - (x + r + gap),
            vhy: Y - (y + r + h + r + gap),
            "^hy": Y - (y - gap)
        },
        mask = [{
            x: x + r,
            y: y,
            w: w,
            w4: w / 4,
            h4: h / 4,
            right: 0,
            left: w - gap * 2,
            bottom: 0,
            top: h - gap * 2,
            r: r,
            h: h,
            gap: gap
        },
        {
            x: x + r,
            y: y,
            w: w,
            w4: w / 4,
            h4: h / 4,
            left: w / 2 - gap,
            right: w / 2 - gap,
            top: h / 2 - gap,
            bottom: h / 2 - gap,
            r: r,
            h: h,
            gap: gap
        },
        {
            x: x + r,
            y: y,
            w: w,
            w4: w / 4,
            h4: h / 4,
            left: 0,
            right: w - gap * 2,
            top: 0,
            bottom: h - gap * 2,
            r: r,
            h: h,
            gap: gap
        }][pos[1] == "middle" ? 1 : (pos[1] == "top" || pos[1] == "left") * 2];
        var dx = 0,
            dy = 0,
            out = this.path(fill(shapes[pos[0]], mask)).insertBefore(set);

        switch (pos[0]) {
            case "top":
                dx = X - (x + r + mask.left + gap);
                dy = Y - (y + r + h + r + gap);
            break;
            case "bottom":
                dx = X - (x + r + mask.left + gap);
                dy = Y - (y - gap);
            break;
            case "left":
                dx = X - (x + r + w + r + gap);
                dy = Y - (y + r + mask.top + gap);
            break;
            case "right":
                dx = X - (x - gap);
                dy = Y - (y + r + mask.top + gap);
            break;
        }
        out.translate(dx, dy);
        if (ret) {
            ret = out.attr("path");
            out.remove();
            return {
                path: ret,
                dx: dx,
                dy: dy
            };
        }
        set.translate(dx, dy);
        return out;
};


Raphael.fn.lineChart = function(method) {

  // public methods
  var methods = {
    init: function(options) {

      this.lineChart.settings = helpers.extend(true, {}, this.lineChart.defaults, options);
      this.customAttributes.lineChart = {};

      //TODO either data or data_holder is a must
      // let's go
      var element = this,
        o = this.customAttributes.lineChart,
        settings = this.lineChart.settings,
        width = settings.width,
        height = settings.height,

        table = helpers.getTable(element, o, settings.data, settings.data_holder),
        size = table.labels.length,

        max = Math.max.apply(Math, table.data),

        YLabelWidth = element.text(-50, -50, max).attr(settings.text.axis_labels).getBBox().width,
        gutter = {
          top: settings.gutter.top,
          right: settings.gutter.right,
          bottom: settings.gutter.bottom,
          left: settings.gutter.left + YLabelWidth
        },
        blanket = element.set(),

        X = (width - gutter.left) / size,
        Y, p, bgpp, x, y;

      // renders weird if we only have a max of 1 and multiple steps
      if (max < 2 && settings.y_labels_count)
        settings.y_labels_count = 1;

      // fix max. value
      if (settings.y_labels_count && max > settings.y_labels_count*10) {
        p = 25*settings.y_labels_count;
        max = Math.ceil(max/p)*p;
      }
      Y = max ? ((height - gutter.bottom - gutter.top) / max) : 0;

      o.gutter = gutter;
      o.labelGutter = settings.gutter.left;

      o.size = size;
      o.dots = [];
      o.rects = [];
      o.info = [];
      //TODO allow customizing
      o.path = element.path().attr({
        stroke: settings.colors.master,
        "stroke-width": 4,
        "stroke-linejoin": "round"
      });
      // chart area
      //TODO allow customizing
      if (settings.show_area) {
        o.bgp = element.path().attr({
          stroke: "none",
          opacity: 0.3,
          fill: settings.colors.master
        });
      }
      else {
        o.bgp = element.path().attr({
          stroke: "none",
          opacity: 0,
          fill: settings.colors.master
        }).hide();
      }

      // draw background grid
      if (!o.gridDrawn && settings.no_grid === false) {
        var grid = element.drawGrid(
          gutter.left + X * 0.5 + 0.5,
          gutter.top + 0.5,
          width - gutter.left - X,
          height - gutter.top - gutter.bottom,
          settings.x_labels_step,
          size,
          settings.y_labels_count,
          max
        ).attr({ stroke: "#eaeaea" });

        grid.toBack();
      }
      o.gridDrawn = true;

      // draw x axis labels
      o.XLabels = [];
      if (settings.x_labels_step) {
        helpers.drawXLabels(element, table.labels);
      }

      // draw y axis labels
      o.YLabels = [];
      if (settings.y_labels_count) {
        helpers.drawYLabels(element, max);
      }

      // prepare popup
      o.label = element.set();
      //TODO ??
      o.label.push(element.text(60, 12, "24 hits").attr(settings.text.popup_line1));
      o.label.push(element.text(60, 27, "22 September 2008").attr(settings.text.popup_line2).attr({
        fill: settings.colors.master
      }));
      o.label.hide();

      //TODO allow customizing
      o.frame = element.popup(100, 100, o.label, "right").attr({
        fill: "#ffffff",
        stroke: "#666",
        "stroke-width": 2,
        "fill-opacity": 0.8
      }).hide();

      for (var i = 0, ii = size; i < ii; i++) {
        var dot, rect;

        // calculate current x, y
        y = Math.round(height - gutter.bottom - Y * table.data[i]) || 0;
        x = Math.round(gutter.left + X * (i + 0.5));

        if (!i) {
          p = ["M", x, y, "C", x, y];
          bgpp = ["M", gutter.left + X * 0.5, height - gutter.bottom, "L", x, y, "C", x, y];
        }
        else if (i < ii - 1) {
          var Y0 = Math.round(height - gutter.bottom - Y * table.data[i - 1]),
            X0 = Math.round(gutter.left + X * (i - 0.5)),
            Y2 = Math.round(height - gutter.bottom - Y * table.data[i + 1]),
            X2 = Math.round(gutter.left + X * (i + 1.5)),
            a = helpers.getAnchors(X0, Y0, x, y, X2, Y2);
          p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
          bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
        }

        if(!settings.no_dot)
        {
          //TODO allow customizing all of these
          dot = element.circle(x, y, 4).attr({
            fill: "#ffffff",
            stroke: settings.colors.master,
            "stroke-width": 2
          });
          if (y === 0) {
            dot.attr({
              opacity: 0
            });
          }

          o.dots.push(dot);

          blanket.push(element.rect(gutter.left + X * i, 0, X, height - gutter.bottom).attr({
            stroke: "none",
            fill: "#fff",
            opacity: 0
          }));
          rect = blanket[blanket.length - 1];

          o.rects.push(rect);

          o.info.push({
            x: x,
            y: y,
            data: table.data[i],
            label: table.labels[i],
            line1: table.lines1[i],
            line2: table.lines2[i]
          });
          helpers.bindHoverEvent(this, i, dot, rect, o.frame, o.label);
        }
      }

      p = p.concat([x, y, x, y]);
      bgpp = bgpp.concat([x, y, x, y, "L", x, height - gutter.bottom, "z"]);
      o.path.attr({
        path: p
      });
      o.bgp.attr({
        path: bgpp
      });
      o.frame.toFront();
      o.label[0].toFront();
      o.label[1].toFront();
      blanket.toFront();
    },

    setDataIndex: function(index) {
      var o = this.customAttributes.lineChart;

      if (index < o.dataArray.data.length) {
        var one = {
          labels: o.dataArray.labels,
          data: o.dataArray.data[index],
          lines1: o.dataArray.lines1[index],
          lines2: o.dataArray.lines2[index]
        };
        return this.lineChart('setData', one);
      }
      else
      {
        return helpers.error('Data index out of range.');
      }
    },

    setDataHolder: function(holder) {
      var table = helpers.loadTableData(this, holder);
      if (table) {
        return this.lineChart('setData', table);
      }
      else {
        return helpers.error('No data holder element supplied.');
      }
    },

    // Caution: this would only work for the same number of records
    setData: function(table) {
      var element = this,
        settings = this.lineChart.settings,
        o = this.customAttributes.lineChart,
        width = settings.width,
        height = settings.height,
        gutter = o.gutter,

        X = (width - gutter.left) / table.labels.length,
        max = Math.max.apply(Math, table.data),
        Y, p, bgpp;

      table = helpers.getTable(element, o, table);

      if (table.labels.length != o.size) {
        return helpers.error('New data source has to be of same size');
      }

      // fix max. value
      if (settings.y_labels_count && max > settings.y_labels_count*10) {
        p = 25*settings.y_labels_count;
        max = Math.ceil(max/p)*p;
      }
      Y = max ? ((height - gutter.bottom - gutter.top) / max) : 0;

      for (var i = 0, ii = table.labels.length; i < ii; i++) {
        var dot = o.dots[i],
          rect = o.rects[i];

        // calculate current x, y
        y = Math.round(height - gutter.bottom - Y * table.data[i]) || 0;
        x = Math.round(gutter.left + X * (i + 0.5));

        if (!i) {
          p = ["M", x, y, "C", x, y];
          bgpp = ["M", gutter.left + X * 0.5, height - gutter.bottom, "L", x, y, "C", x, y];
        }
        else if (i < ii - 1) {
          var Y0 = Math.round(height - gutter.bottom - Y * table.data[i - 1]),
            X0 = Math.round(gutter.left + X * (i - 0.5)),
            Y2 = Math.round(height - gutter.bottom - Y * table.data[i + 1]),
            X2 = Math.round(gutter.left + X * (i + 1.5)),
            a = helpers.getAnchors(X0, Y0, x, y, X2, Y2);

          p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
          bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
        }

        dot.animate({cy: y},
          settings.animation.speed,
          settings.animation.easing);

        // new popup data

        o.info[i] = {
          x: x,
          y: y,
          data: table.data[i],
          label: table.labels[i],
          line1: table.lines1[i],
          line2: table.lines2[i]
        };
      }

      // animate paths
      p = p.concat([x, y, x, y]);
      bgpp = bgpp.concat([x, y, x, y, "L", x, height - gutter.bottom, "z"]);
      o.path.animate({path: p},
          settings.animation.speed,
          settings.animation.easing);
      o.bgp.animate({path: bgpp},
          settings.animation.speed,
          settings.animation.easing);
      // update x axis labels
      if (settings.x_labels_step) {
        helpers.drawXLabels(element, table.labels);
      }

      // draw y axis labels
      if (settings.y_labels_count) {
        helpers.drawYLabels(element, max);
      }
    }

  };

  // private methods
  var helpers = {
    extend: function () { // copied from jQuery source
      // copy reference to target object
      var target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false,
        options, name, src, copy;

      // Handle a deep copy situation
      if (typeof target === "boolean") {
        deep = target;
        target = arguments[1] || {};
        // skip the boolean and the target
        i = 2;
      }

      // Handle case when target is a string or something (possible in deep copy)
      if (typeof target !== "object" && typeof target !== "function") {
        target = {};
      }

      // extend myself if only one argument is passed
      if (length === i) {
        target = this;
        --i;
      }

      for (; i < length; i++) {
        // Only deal with non-null/undefined values
        if ((options = arguments[i]) !== null) {
          // Extend the base object
          for (name in options) {
            if (options.hasOwnProperty(name))
            {
              src = target[name];
              copy = options[name];

              // Prevent never-ending loop
              if (target === copy) {
                continue;
              }

              // Recurse if we're merging object literal values or arrays
              if (deep && copy && (copy.constructor == Object || copy.constructor == Array)) {
                var clone = src && (src.constructor == Object || src.constructor == Array) ? src : copy.constructor == Array ? [] : {};

                // Never move original objects, clone them
                target[name] = helpers.extend(deep, clone, copy);

                // Don't bring in undefined values
              } else if (copy !== undefined) {
                target[name] = copy;
              }
            }
          }
        }
      }

      // Return the modified object
      return target;
    },

    getAnchors: function(p1x, p1y, p2x, p2y, p3x, p3y) {
      var l1 = (p2x - p1x) / 2,
        l2 = (p3x - p2x) / 2,
        a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
        b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
        dx1 = dy1 = dx2 = dy2 = 0,
        alpha;

      a = p1y < p2y ? Math.PI - a: a;
      b = p3y < p2y ? Math.PI - b: b;
      alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2;
      dx1 = l1 * Math.sin(alpha + a);
      dx2 = l2 * Math.sin(alpha + b);
      if (p1y != p2y) {
        dy1 = l1 * Math.cos(alpha + a);
      }
        if (p2y != p3y) {
        dy2 = l2 * Math.cos(alpha + b);
      }

      return {
        x1: p2x - dx1,
        y1: p2y + dy1,
        x2: p2x + dx2,
        y2: p2y + dy2
      };
    },

    getTable: function(elm, o, obj, table_elm) {
      var settings = elm.lineChart.settings,
          i = settings.data_index;
      if (obj) {
        // handle multiple data rows
        if (obj.data[i].constructor == Array) {
          o.dataArray = obj;
          var one = {};
          one.labels = obj.labels;
          one.data = obj.data[i];
          one.lines1 = obj.lines1[i];
          one.lines2 = obj.lines2[i];
          return one;
        }
        else {
          return obj;
        }
      }
      else {
        return helpers.loadTableData(elm, table_elm);
      }
    },

    loadTableData: function(elm, table_elm) {
      var settings = elm.lineChart.settings,
      table = (table_elm && table_elm.constructor == String) ? document.getElementById(table_elm) : table_elm,
        res = {
          labels: [],
          data: [],
          lines1: [],
          lines2: []
        },
      tds = {},
      curr, td, i, j;

      if (table) {
        // find elements to collect
        for (i=0; i < table.childNodes.length; i++, curr = null, td = 'td') {
          if (table.childNodes[i].tagName == 'TFOOT') {
          curr = 'labels';
          td = 'th';
          }
          else if (table.childNodes[i].tagName == 'TBODY') {
          if (table.childNodes[i].className == settings.table_classes.data) {
            curr = 'data';
          }
          else if (table.childNodes[i].className == settings.table_classes.line1) {
            curr = 'lines1';
          }
          else if (table.childNodes[i].className == settings.table_classes.line2) {
            curr = 'lines2';
          }
          }

          if (curr) {
          tds[curr] = table.childNodes[i].getElementsByTagName(td);
          }
        }

        // populate res
        if (tds.labels && tds.data && tds.lines1 && tds.lines2) {
          for (j=0; j < tds.labels.length; j++) {
              res.labels.push(tds.labels[j].innerHTML);
          }
          for (j=0; j < tds.data.length; j++) {
            res.data.push(tds.data[j].innerHTML);
          }
          for (j=0; j < tds.lines1.length; j++) {
            res.lines1.push(tds.lines1[j].innerHTML);
          }
          for (j=0; j < tds.lines2.length; j++) {
            res.lines2.push(tds.lines2[j].innerHTML);
          }
        }
        return res;
      } else {
        return false;
      }
    },

    bindHoverEvent: function(elm, i, dot, rect, frame, label) {
      var settings = elm.lineChart.settings,
        o = elm.customAttributes.lineChart,
        f_in = function() {
          var side = "right",
            info = o.info[i],
            x = info.x,
            y = info.y;

          window.clearTimeout(elm.leave_timer);
          if (x + frame.getBBox().width > settings.width) {
            side = "left";
          }
          var ppp = elm.popup(x, y, label, side, 1);
          var anim = Raphael.animation({
            path: ppp.path,
            transform: ["t", ppp.dx, ppp.dy]
          }, 200 * o.is_label_visible);

          lx = label[0].transform()[0][1] + ppp.dx;
          ly = label[0].transform()[0][2] + ppp.dy;
          frame.show().stop().animate(anim);
          label[0].attr({
            text: info.line1
          }).show().stop().animateWith(frame, anim, {
            transform: ["t", lx, ly]
          }, 200 * o.is_label_visible);

          label[1].attr({
            text: info.line2
          }).show().stop().animateWith(frame, anim, {
            transform: ["t", lx, ly]
          }, 200 * o.is_label_visible);
          frame.toFront();
          label[0].toFront();
          label[1].toFront();
          this.toFront();
          dot.attr("r", 6);
          o.is_label_visible = true;
        },
        f_out = function() {
          dot.attr("r", 4);

          elm.leave_timer = window.setTimeout(function() {
            frame.hide();
            label[0].hide();
            label[1].hide();
            o.is_label_visible = false;
          },
          1);
        }

      rect.hover(f_in, f_out);
    },

    drawXLabels: function(elm, labels) {
      var settings = elm.lineChart.settings,
        o = elm.customAttributes.lineChart,
        x = (settings.width - o.gutter.left) / o.size,
        y = settings.height - o.gutter.bottom + 18,
        step = settings.x_labels_step,
        style = settings.text.axis_labels,
        i;

      // reset old labels
      if (o.XLabels.length) {
        for (i = 0; i < o.XLabels.length; i++) {
          o.XLabels[i].remove();
        }
      }

      o.XLabels = [];
      for (i = 0; i < o.size; i++) {
        var label_x = Math.round(o.gutter.left + x * (i + 0.5));

        if (i % step === 0) {
          var l = elm.text(label_x, y, labels[i])
                .attr(style).toBack();
          o.XLabels.push(l);
        }
      }
    },

    drawYLabels: function(elm, max) {
      var settings = elm.lineChart.settings,
        o = elm.customAttributes.lineChart,
        x = o.labelGutter,
        y = o.gutter.bottom,
        top = o.gutter.top,
        height = settings.height,
        count = settings.y_labels_count,
        step = Math.round(max / count),
        labelHeight = (height - top - y) / count;
        
      // reset old labels
      if (o.YLabels.length) {
        for (var i = 0; i < o.YLabels.length; i++) {
          o.YLabels[i].remove();
        }
      }


      o.YLabels = [];
      for (var j = 0; j <= count; j++) {
        var txt = (j * (max/count)),
          l;

        l = elm
              .text(x, height - y - (j*labelHeight),
                    settings.y_labels_format(txt, max, count))
              .attr(settings.text.axis_labels);
        o.YLabels.push(l);

      }
    },

    error: function(message) {
      if (console && console.error) {
        console.error('lineChart Error: ' + message);
      }
      return false;
    }
  };

  // go go go!
  if (methods[method]) {
    return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  } else if (typeof method === 'object' || !method) {
    return methods.init.apply(this, arguments);
  } else {
    return helpers.error('Method "' + method + '" not found.');
  }
};

Raphael.fn.lineChart.defaults = {
  data_holder: null,    // table element holding the data to display
  data: null,        // alternatively, supply a data object
  data_index: 0,    // index of initial data array to use
  width: 500,        // chart width
  height: 250,      // chart height
  gutter: {        // gutter dimensions
    top: 20,
    right: 0,
    bottom: 50,
    left: 30
  },
  show_area: false,    // whether to fill the area below the line
  no_dot: false,      // no dot on the graph
  no_grid: false,      // whether to display background grid
  x_labels_step: false,  // X axis: either false or a step integer
  y_labels_count: false,  // Y axis: either false or a labels count
  y_labels_format: function(txt, max, count) { // function to handle display of Y labels
    if (max > count) {
      txt = Math.floor(txt) + '';
    }
    else {
      txt = txt + '';
    }
    txt = txt.replace(/\.(\d{3})\d*/, '.$1');
    txt = txt.replace(/(\d{1,3})(?=(?:\d{3})+$)/g,"$1,");
    return txt;
  },
  animation: {      // animation (on data source change) settings
    speed: 600,
    easing: "backOut"
  },
  colors: {        // color settings
    master: '#01A8F0',
    line1: '#000000',
    line2: '#01A8F0'
  },
  text: {          // text style settings
    axis_labels: {
      font: '10px Helvetica, Arial',
      fill: "#000000"
    },
    popup_line1: {
      font: 'bold 11px Helvetica, Arial',
      fill: "#000000"
    },
    popup_line2: {
      font: 'bold 10px Helvetica, Arial',
      fill: "#000000"
    }
  },
  table_classes: {
	data: 'data',
	line1: 'line1',
	line2: 'line2'
  }
};

})();
