UNPKG

g-chartframe

Version:
2,174 lines • 70.7 kB
function createCommonjsModule(fn, module) {
	return module = { exports: {} }, fn(module, module.exports), module.exports;
}

var saveSvgAsPng = createCommonjsModule(function (module, exports) {

(function () {
  var out$ = exports || typeof undefined != 'undefined'  || this || window;
  out$.default = out$;

  var xmlNs = 'http://www.w3.org/2000/xmlns/';
  var xhtmlNs = 'http://www.w3.org/1999/xhtml';
  var svgNs = 'http://www.w3.org/2000/svg';
  var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp "&#160;">]>';
  var urlRegex = /url\(["']?(.+?)["']?\)/;
  var fontFormats = {
    woff2: 'font/woff2',
    woff: 'font/woff',
    otf: 'application/x-font-opentype',
    ttf: 'application/x-font-ttf',
    eot: 'application/vnd.ms-fontobject',
    sfnt: 'application/font-sfnt',
    svg: 'image/svg+xml'
  };

  var isElement = function isElement(obj) {
    return obj instanceof HTMLElement || obj instanceof SVGElement;
  };
  var requireDomNode = function requireDomNode(el) {
    if (!isElement(el)) throw new Error('an HTMLElement or SVGElement is required; got ' + el);
  };
  var requireDomNodePromise = function requireDomNodePromise(el) {
    return new Promise(function (resolve, reject) {
      if (isElement(el)) resolve(el);else reject(new Error('an HTMLElement or SVGElement is required; got ' + el));
    });
  };
  var isExternal = function isExternal(url) {
    return url && url.lastIndexOf('http', 0) === 0 && url.lastIndexOf(window.location.host) === -1;
  };

  var getFontMimeTypeFromUrl = function getFontMimeTypeFromUrl(fontUrl) {
    var formats = Object.keys(fontFormats).filter(function (extension) {
      return fontUrl.indexOf('.' + extension) > 0;
    }).map(function (extension) {
      return fontFormats[extension];
    });
    if (formats) return formats[0];
    console.error('Unknown font format for ' + fontUrl + '. Fonts may not be working correctly.');
    return 'application/octet-stream';
  };

  var arrayBufferToBase64 = function arrayBufferToBase64(buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    for (var i = 0; i < bytes.byteLength; i++) {
      binary += String.fromCharCode(bytes[i]);
    }return window.btoa(binary);
  };

  var getDimension = function getDimension(el, clone, dim) {
    var v = el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim] || clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim)) || el.getBoundingClientRect()[dim] || parseInt(clone.style[dim]) || parseInt(window.getComputedStyle(el).getPropertyValue(dim));
    return typeof v === 'undefined' || v === null || isNaN(parseFloat(v)) ? 0 : v;
  };

  var getDimensions = function getDimensions(el, clone, width, height) {
    if (el.tagName === 'svg') return {
      width: width || getDimension(el, clone, 'width'),
      height: height || getDimension(el, clone, 'height')
    };else if (el.getBBox) {
      var _el$getBBox = el.getBBox(),
          x = _el$getBBox.x,
          y = _el$getBBox.y,
          _width = _el$getBBox.width,
          _height = _el$getBBox.height;

      return {
        width: x + _width,
        height: y + _height
      };
    }
  };

  var reEncode = function reEncode(data) {
    return decodeURIComponent(encodeURIComponent(data).replace(/%([0-9A-F]{2})/g, function (match, p1) {
      var c = String.fromCharCode('0x' + p1);
      return c === '%' ? '%25' : c;
    }));
  };

  var uriToBlob = function uriToBlob(uri) {
    var byteString = window.atob(uri.split(',')[1]);
    var mimeString = uri.split(',')[0].split(':')[1].split(';')[0];
    var buffer = new ArrayBuffer(byteString.length);
    var intArray = new Uint8Array(buffer);
    for (var i = 0; i < byteString.length; i++) {
      intArray[i] = byteString.charCodeAt(i);
    }
    return new Blob([buffer], { type: mimeString });
  };

  var query = function query(el, selector) {
    if (!selector) return;
    try {
      return el.querySelector(selector) || el.parentNode && el.parentNode.querySelector(selector);
    } catch (err) {
      console.warn('Invalid CSS selector "' + selector + '"', err);
    }
  };

  var detectCssFont = function detectCssFont(rule, href) {
    // Match CSS font-face rules to external links.
    // @font-face {
    //   src: local('Abel'), url(https://fonts.gstatic.com/s/abel/v6/UzN-iejR1VoXU2Oc-7LsbvesZW2xOQ-xsNqO47m55DA.woff2);
    // }
    var match = rule.cssText.match(urlRegex);
    var url = match && match[1] || '';
    if (!url || url.match(/^data:/) || url === 'about:blank') return;
    var fullUrl = url.startsWith('../') ? href + '/../' + url : url.startsWith('./') ? href + '/.' + url : url;
    return {
      text: rule.cssText,
      format: getFontMimeTypeFromUrl(fullUrl),
      url: fullUrl
    };
  };

  var inlineImages = function inlineImages(el) {
    return Promise.all(Array.from(el.querySelectorAll('image')).map(function (image) {
      var href = image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || image.getAttribute('href');
      if (!href) return Promise.resolve(null);
      if (isExternal(href)) {
        href += (href.indexOf('?') === -1 ? '?' : '&') + 't=' + new Date().valueOf();
      }
      return new Promise(function (resolve, reject) {
        var canvas = document.createElement('canvas');
        var img = new Image();
        img.crossOrigin = 'anonymous';
        img.src = href;
        img.onerror = function () {
          return reject(new Error('Could not load ' + href));
        };
        img.onload = function () {
          canvas.width = img.width;
          canvas.height = img.height;
          canvas.getContext('2d').drawImage(img, 0, 0);
          image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', canvas.toDataURL('image/png'));
          resolve(true);
        };
      });
    }));
  };

  var cachedFonts = {};
  var inlineFonts = function inlineFonts(fonts) {
    return Promise.all(fonts.map(function (font) {
      return new Promise(function (resolve, reject) {
        if (cachedFonts[font.url]) return resolve(cachedFonts[font.url]);

        var req = new XMLHttpRequest();
        req.addEventListener('load', function () {
          // TODO: it may also be worth it to wait until fonts are fully loaded before
          // attempting to rasterize them. (e.g. use https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet)
          var fontInBase64 = arrayBufferToBase64(req.response);
          var fontUri = font.text.replace(urlRegex, 'url("data:' + font.format + ';base64,' + fontInBase64 + '")') + '\n';
          cachedFonts[font.url] = fontUri;
          resolve(fontUri);
        });
        req.addEventListener('error', function (e) {
          console.warn('Failed to load font from: ' + font.url, e);
          cachedFonts[font.url] = null;
          resolve(null);
        });
        req.addEventListener('abort', function (e) {
          console.warn('Aborted loading font from: ' + font.url, e);
          resolve(null);
        });
        req.open('GET', font.url);
        req.responseType = 'arraybuffer';
        req.send();
      });
    })).then(function (fontCss) {
      return fontCss.filter(function (x) {
        return x;
      }).join('');
    });
  };

  var cachedRules = null;
  var styleSheetRules = function styleSheetRules() {
    if (cachedRules) return cachedRules;
    return cachedRules = Array.from(document.styleSheets).map(function (sheet) {
      try {
        return { rules: sheet.cssRules, href: sheet.href };
      } catch (e) {
        console.warn('Stylesheet could not be loaded: ' + sheet.href, e);
        return {};
      }
    });
  };

  var inlineCss = function inlineCss(el, options) {
    var _ref = options || {},
        selectorRemap = _ref.selectorRemap,
        modifyStyle = _ref.modifyStyle,
        modifyCss = _ref.modifyCss,
        fonts = _ref.fonts,
        excludeUnusedCss = _ref.excludeUnusedCss;

    var generateCss = modifyCss || function (selector, properties) {
      var sel = selectorRemap ? selectorRemap(selector) : selector;
      var props = modifyStyle ? modifyStyle(properties) : properties;
      return sel + '{' + props + '}\n';
    };
    var css = [];
    var detectFonts = typeof fonts === 'undefined';
    var fontList = fonts || [];
    styleSheetRules().forEach(function (_ref2) {
      var rules = _ref2.rules,
          href = _ref2.href;

      if (!rules) return;
      Array.from(rules).forEach(function (rule) {
        if (typeof rule.style != 'undefined') {
          if (query(el, rule.selectorText)) css.push(generateCss(rule.selectorText, rule.style.cssText));else if (detectFonts && rule.cssText.match(/^@font-face/)) {
            var font = detectCssFont(rule, href);
            if (font) fontList.push(font);
          } else if (!excludeUnusedCss) {
            css.push(rule.cssText);
          }
        }
      });
    });

    return inlineFonts(fontList).then(function (fontCss) {
      return css.join('\n') + fontCss;
    });
  };

  var downloadOptions = function downloadOptions() {
    if (!navigator.msSaveOrOpenBlob && !('download' in document.createElement('a'))) {
      return { popup: window.open() };
    }
  };

  out$.prepareSvg = function (el, options, done) {
    requireDomNode(el);

    var _ref3 = options || {},
        _ref3$left = _ref3.left,
        left = _ref3$left === undefined ? 0 : _ref3$left,
        _ref3$top = _ref3.top,
        top = _ref3$top === undefined ? 0 : _ref3$top,
        w = _ref3.width,
        h = _ref3.height,
        _ref3$scale = _ref3.scale,
        scale = _ref3$scale === undefined ? 1 : _ref3$scale,
        _ref3$responsive = _ref3.responsive,
        responsive = _ref3$responsive === undefined ? false : _ref3$responsive,
        _ref3$excludeCss = _ref3.excludeCss,
        excludeCss = _ref3$excludeCss === undefined ? false : _ref3$excludeCss;

    return inlineImages(el).then(function () {
      var clone = el.cloneNode(true);
      clone.style.backgroundColor = (options || {}).backgroundColor || el.style.backgroundColor;

      var _getDimensions = getDimensions(el, clone, w, h),
          width = _getDimensions.width,
          height = _getDimensions.height;

      if (el.tagName !== 'svg') {
        if (el.getBBox) {
          if (clone.getAttribute('transform') != null) {
            clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
          }
          var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
          svg.appendChild(clone);
          clone = svg;
        } else {
          console.error('Attempted to render non-SVG element', el);
          return;
        }
      }

      clone.setAttribute('version', '1.1');
      clone.setAttribute('viewBox', [left, top, width, height].join(' '));
      if (!clone.getAttribute('xmlns')) clone.setAttributeNS(xmlNs, 'xmlns', svgNs);
      if (!clone.getAttribute('xmlns:xlink')) clone.setAttributeNS(xmlNs, 'xmlns:xlink', 'http://www.w3.org/1999/xlink');

      if (responsive) {
        clone.removeAttribute('width');
        clone.removeAttribute('height');
        clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
      } else {
        clone.setAttribute('width', width * scale);
        clone.setAttribute('height', height * scale);
      }

      Array.from(clone.querySelectorAll('foreignObject > *')).forEach(function (foreignObject) {
        foreignObject.setAttributeNS(xmlNs, 'xmlns', foreignObject.tagName === 'svg' ? svgNs : xhtmlNs);
      });

      if (excludeCss) {
        var outer = document.createElement('div');
        outer.appendChild(clone);
        var src = outer.innerHTML;
        if (typeof done === 'function') done(src, width, height);else return { src: src, width: width, height: height };
      } else {
        return inlineCss(el, options).then(function (css) {
          var style = document.createElement('style');
          style.setAttribute('type', 'text/css');
          style.innerHTML = '<![CDATA[\n' + css + '\n]]>';

          var defs = document.createElement('defs');
          defs.appendChild(style);
          clone.insertBefore(defs, clone.firstChild);

          var outer = document.createElement('div');
          outer.appendChild(clone);
          var src = outer.innerHTML.replace(/NS\d+:href/gi, 'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');

          if (typeof done === 'function') done(src, width, height);else return { src: src, width: width, height: height };
        });
      }
    });
  };

  out$.svgAsDataUri = function (el, options, done) {
    requireDomNode(el);
    return out$.prepareSvg(el, options).then(function (_ref4) {
      var src = _ref4.src,
          width = _ref4.width,
          height = _ref4.height;

      var svgXml = 'data:image/svg+xml;base64,' + window.btoa(reEncode(doctype + src));
      if (typeof done === 'function') {
        done(svgXml, width, height);
      }
      return svgXml;
    });
  };

  out$.svgAsPngUri = function (el, options, done) {
    requireDomNode(el);

    var _ref5 = options || {},
        _ref5$encoderType = _ref5.encoderType,
        encoderType = _ref5$encoderType === undefined ? 'image/png' : _ref5$encoderType,
        _ref5$encoderOptions = _ref5.encoderOptions,
        encoderOptions = _ref5$encoderOptions === undefined ? 0.8 : _ref5$encoderOptions,
        canvg = _ref5.canvg;

    var convertToPng = function convertToPng(_ref6) {
      var src = _ref6.src,
          width = _ref6.width,
          height = _ref6.height;

      var canvas = document.createElement('canvas');
      var context = canvas.getContext('2d');
      var pixelRatio = window.devicePixelRatio || 1;

      canvas.width = width * pixelRatio;
      canvas.height = height * pixelRatio;
      canvas.style.width = canvas.width + 'px';
      canvas.style.height = canvas.height + 'px';
      context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);

      if (canvg) canvg(canvas, src);else context.drawImage(src, 0, 0);

      var png = void 0;
      try {
        png = canvas.toDataURL(encoderType, encoderOptions);
      } catch (e) {
        if (typeof SecurityError !== 'undefined' && e instanceof SecurityError || e.name === 'SecurityError') {
          console.error('Rendered SVG images cannot be downloaded in this browser.');
          return;
        } else throw e;
      }
      if (typeof done === 'function') done(png, canvas.width, canvas.height);
      return Promise.resolve(png);
    };

    if (canvg) return out$.prepareSvg(el, options).then(convertToPng);else return out$.svgAsDataUri(el, options).then(function (uri) {
      return new Promise(function (resolve, reject) {
        var image = new Image();
        image.onload = function () {
          return resolve(convertToPng({
            src: image,
            width: image.width,
            height: image.height
          }));
        };
        image.onerror = function () {
          reject('There was an error loading the data URI as an image on the following SVG\n' + window.atob(uri.slice(26)) + 'Open the following link to see browser\'s diagnosis\n' + uri);
        };
        image.src = uri;
      });
    });
  };

  out$.download = function (name, uri, options) {
    if (navigator.msSaveOrOpenBlob) navigator.msSaveOrOpenBlob(uriToBlob(uri), name);else {
      var saveLink = document.createElement('a');
      if ('download' in saveLink) {
        saveLink.download = name;
        saveLink.style.display = 'none';
        document.body.appendChild(saveLink);
        try {
          var blob = uriToBlob(uri);
          var url = URL.createObjectURL(blob);
          saveLink.href = url;
          saveLink.onclick = function () {
            return requestAnimationFrame(function () {
              return URL.revokeObjectURL(url);
            });
          };
        } catch (e) {
          console.error(e);
          console.warn('Error while getting object URL. Falling back to string URL.');
          saveLink.href = uri;
        }
        saveLink.click();
        document.body.removeChild(saveLink);
      } else if (options && options.popup) {
        options.popup.document.title = name;
        options.popup.location.replace(uri);
      }
    }
  };

  out$.saveSvg = function (el, name, options) {
    var downloadOpts = downloadOptions(); // don't inline, can't be async
    return requireDomNodePromise(el).then(function (el) {
      return out$.svgAsDataUri(el, options || {});
    }).then(function (uri) {
      return out$.download(name, uri, downloadOpts);
    });
  };

  out$.saveSvgAsPng = function (el, name, options) {
    var downloadOpts = downloadOptions(); // don't inline, can't be async
    return requireDomNodePromise(el).then(function (el) {
      return out$.svgAsPngUri(el, options || {});
    }).then(function (uri) {
      return out$.download(name, uri, downloadOpts);
    });
  };
})();
});
var saveSvgAsPng_1 = saveSvgAsPng.saveSvgAsPng;

var xhtml = "http://www.w3.org/1999/xhtml";

var namespaces = {
  svg: "http://www.w3.org/2000/svg",
  xhtml: xhtml,
  xlink: "http://www.w3.org/1999/xlink",
  xml: "http://www.w3.org/XML/1998/namespace",
  xmlns: "http://www.w3.org/2000/xmlns/"
};

function namespace(name) {
  var prefix = name += "", i = prefix.indexOf(":");
  if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
  return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; // eslint-disable-line no-prototype-builtins
}

function creatorInherit(name) {
  return function() {
    var document = this.ownerDocument,
        uri = this.namespaceURI;
    return uri === xhtml && document.documentElement.namespaceURI === xhtml
        ? document.createElement(name)
        : document.createElementNS(uri, name);
  };
}

function creatorFixed(fullname) {
  return function() {
    return this.ownerDocument.createElementNS(fullname.space, fullname.local);
  };
}

function creator(name) {
  var fullname = namespace(name);
  return (fullname.local
      ? creatorFixed
      : creatorInherit)(fullname);
}

function none() {}

function selector(selector) {
  return selector == null ? none : function() {
    return this.querySelector(selector);
  };
}

function selection_select(select) {
  if (typeof select !== "function") select = selector(select);

  for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
      if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
        if ("__data__" in node) subnode.__data__ = node.__data__;
        subgroup[i] = subnode;
      }
    }
  }

  return new Selection(subgroups, this._parents);
}

function array(x) {
  return typeof x === "object" && "length" in x
    ? x // Array, TypedArray, NodeList, array-like
    : Array.from(x); // Map, Set, iterable, string, or anything else
}

function empty() {
  return [];
}

function selectorAll(selector) {
  return selector == null ? empty : function() {
    return this.querySelectorAll(selector);
  };
}

function arrayAll(select) {
  return function() {
    var group = select.apply(this, arguments);
    return group == null ? [] : array(group);
  };
}

function selection_selectAll(select) {
  if (typeof select === "function") select = arrayAll(select);
  else select = selectorAll(select);

  for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
      if (node = group[i]) {
        subgroups.push(select.call(node, node.__data__, i, group));
        parents.push(node);
      }
    }
  }

  return new Selection(subgroups, parents);
}

function matcher(selector) {
  return function() {
    return this.matches(selector);
  };
}

function childMatcher(selector) {
  return function(node) {
    return node.matches(selector);
  };
}

var find = Array.prototype.find;

function childFind(match) {
  return function() {
    return find.call(this.children, match);
  };
}

function childFirst() {
  return this.firstElementChild;
}

function selection_selectChild(match) {
  return this.select(match == null ? childFirst
      : childFind(typeof match === "function" ? match : childMatcher(match)));
}

var filter = Array.prototype.filter;

function children() {
  return this.children;
}

function childrenFilter(match) {
  return function() {
    return filter.call(this.children, match);
  };
}

function selection_selectChildren(match) {
  return this.selectAll(match == null ? children
      : childrenFilter(typeof match === "function" ? match : childMatcher(match)));
}

function selection_filter(match) {
  if (typeof match !== "function") match = matcher(match);

  for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
      if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
        subgroup.push(node);
      }
    }
  }

  return new Selection(subgroups, this._parents);
}

function sparse(update) {
  return new Array(update.length);
}

function selection_enter() {
  return new Selection(this._enter || this._groups.map(sparse), this._parents);
}

function EnterNode(parent, datum) {
  this.ownerDocument = parent.ownerDocument;
  this.namespaceURI = parent.namespaceURI;
  this._next = null;
  this._parent = parent;
  this.__data__ = datum;
}

EnterNode.prototype = {
  constructor: EnterNode,
  appendChild: function(child) { return this._parent.insertBefore(child, this._next); },
  insertBefore: function(child, next) { return this._parent.insertBefore(child, next); },
  querySelector: function(selector) { return this._parent.querySelector(selector); },
  querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); }
};

function constant(x) {
  return function() {
    return x;
  };
}

function bindIndex(parent, group, enter, update, exit, data) {
  var i = 0,
      node,
      groupLength = group.length,
      dataLength = data.length;

  // Put any non-null nodes that fit into update.
  // Put any null nodes into enter.
  // Put any remaining data into enter.
  for (; i < dataLength; ++i) {
    if (node = group[i]) {
      node.__data__ = data[i];
      update[i] = node;
    } else {
      enter[i] = new EnterNode(parent, data[i]);
    }
  }

  // Put any non-null nodes that don’t fit into exit.
  for (; i < groupLength; ++i) {
    if (node = group[i]) {
      exit[i] = node;
    }
  }
}

function bindKey(parent, group, enter, update, exit, data, key) {
  var i,
      node,
      nodeByKeyValue = new Map,
      groupLength = group.length,
      dataLength = data.length,
      keyValues = new Array(groupLength),
      keyValue;

  // Compute the key for each node.
  // If multiple nodes have the same key, the duplicates are added to exit.
  for (i = 0; i < groupLength; ++i) {
    if (node = group[i]) {
      keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + "";
      if (nodeByKeyValue.has(keyValue)) {
        exit[i] = node;
      } else {
        nodeByKeyValue.set(keyValue, node);
      }
    }
  }

  // Compute the key for each datum.
  // If there a node associated with this key, join and add it to update.
  // If there is not (or the key is a duplicate), add it to enter.
  for (i = 0; i < dataLength; ++i) {
    keyValue = key.call(parent, data[i], i, data) + "";
    if (node = nodeByKeyValue.get(keyValue)) {
      update[i] = node;
      node.__data__ = data[i];
      nodeByKeyValue.delete(keyValue);
    } else {
      enter[i] = new EnterNode(parent, data[i]);
    }
  }

  // Add any remaining nodes that were not bound to data to exit.
  for (i = 0; i < groupLength; ++i) {
    if ((node = group[i]) && (nodeByKeyValue.get(keyValues[i]) === node)) {
      exit[i] = node;
    }
  }
}

function datum(node) {
  return node.__data__;
}

function selection_data(value, key) {
  if (!arguments.length) return Array.from(this, datum);

  var bind = key ? bindKey : bindIndex,
      parents = this._parents,
      groups = this._groups;

  if (typeof value !== "function") value = constant(value);

  for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
    var parent = parents[j],
        group = groups[j],
        groupLength = group.length,
        data = array(value.call(parent, parent && parent.__data__, j, parents)),
        dataLength = data.length,
        enterGroup = enter[j] = new Array(dataLength),
        updateGroup = update[j] = new Array(dataLength),
        exitGroup = exit[j] = new Array(groupLength);

    bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);

    // Now connect the enter nodes to their following update node, such that
    // appendChild can insert the materialized enter node before this node,
    // rather than at the end of the parent node.
    for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
      if (previous = enterGroup[i0]) {
        if (i0 >= i1) i1 = i0 + 1;
        while (!(next = updateGroup[i1]) && ++i1 < dataLength);
        previous._next = next || null;
      }
    }
  }

  update = new Selection(update, parents);
  update._enter = enter;
  update._exit = exit;
  return update;
}

function selection_exit() {
  return new Selection(this._exit || this._groups.map(sparse), this._parents);
}

function selection_join(onenter, onupdate, onexit) {
  var enter = this.enter(), update = this, exit = this.exit();
  enter = typeof onenter === "function" ? onenter(enter) : enter.append(onenter + "");
  if (onupdate != null) update = onupdate(update);
  if (onexit == null) exit.remove(); else onexit(exit);
  return enter && update ? enter.merge(update).order() : update;
}

function selection_merge(selection) {
  if (!(selection instanceof Selection)) throw new Error("invalid merge");

  for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
    for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
      if (node = group0[i] || group1[i]) {
        merge[i] = node;
      }
    }
  }

  for (; j < m0; ++j) {
    merges[j] = groups0[j];
  }

  return new Selection(merges, this._parents);
}

function selection_order() {

  for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {
    for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
      if (node = group[i]) {
        if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);
        next = node;
      }
    }
  }

  return this;
}

function selection_sort(compare) {
  if (!compare) compare = ascending;

  function compareNode(a, b) {
    return a && b ? compare(a.__data__, b.__data__) : !a - !b;
  }

  for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
    for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
      if (node = group[i]) {
        sortgroup[i] = node;
      }
    }
    sortgroup.sort(compareNode);
  }

  return new Selection(sortgroups, this._parents).order();
}

function ascending(a, b) {
  return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}

function selection_call() {
  var callback = arguments[0];
  arguments[0] = this;
  callback.apply(null, arguments);
  return this;
}

function selection_nodes() {
  return Array.from(this);
}

function selection_node() {

  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
      var node = group[i];
      if (node) return node;
    }
  }

  return null;
}

function selection_size() {
  let size = 0;
  for (const node of this) ++size; // eslint-disable-line no-unused-vars
  return size;
}

function selection_empty() {
  return !this.node();
}

function selection_each(callback) {

  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
      if (node = group[i]) callback.call(node, node.__data__, i, group);
    }
  }

  return this;
}

function attrRemove(name) {
  return function() {
    this.removeAttribute(name);
  };
}

function attrRemoveNS(fullname) {
  return function() {
    this.removeAttributeNS(fullname.space, fullname.local);
  };
}

function attrConstant(name, value) {
  return function() {
    this.setAttribute(name, value);
  };
}

function attrConstantNS(fullname, value) {
  return function() {
    this.setAttributeNS(fullname.space, fullname.local, value);
  };
}

function attrFunction(name, value) {
  return function() {
    var v = value.apply(this, arguments);
    if (v == null) this.removeAttribute(name);
    else this.setAttribute(name, v);
  };
}

function attrFunctionNS(fullname, value) {
  return function() {
    var v = value.apply(this, arguments);
    if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
    else this.setAttributeNS(fullname.space, fullname.local, v);
  };
}

function selection_attr(name, value) {
  var fullname = namespace(name);

  if (arguments.length < 2) {
    var node = this.node();
    return fullname.local
        ? node.getAttributeNS(fullname.space, fullname.local)
        : node.getAttribute(fullname);
  }

  return this.each((value == null
      ? (fullname.local ? attrRemoveNS : attrRemove) : (typeof value === "function"
      ? (fullname.local ? attrFunctionNS : attrFunction)
      : (fullname.local ? attrConstantNS : attrConstant)))(fullname, value));
}

function defaultView(node) {
  return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node
      || (node.document && node) // node is a Window
      || node.defaultView; // node is a Document
}

function styleRemove(name) {
  return function() {
    this.style.removeProperty(name);
  };
}

function styleConstant(name, value, priority) {
  return function() {
    this.style.setProperty(name, value, priority);
  };
}

function styleFunction(name, value, priority) {
  return function() {
    var v = value.apply(this, arguments);
    if (v == null) this.style.removeProperty(name);
    else this.style.setProperty(name, v, priority);
  };
}

function selection_style(name, value, priority) {
  return arguments.length > 1
      ? this.each((value == null
            ? styleRemove : typeof value === "function"
            ? styleFunction
            : styleConstant)(name, value, priority == null ? "" : priority))
      : styleValue(this.node(), name);
}

function styleValue(node, name) {
  return node.style.getPropertyValue(name)
      || defaultView(node).getComputedStyle(node, null).getPropertyValue(name);
}

function propertyRemove(name) {
  return function() {
    delete this[name];
  };
}

function propertyConstant(name, value) {
  return function() {
    this[name] = value;
  };
}

function propertyFunction(name, value) {
  return function() {
    var v = value.apply(this, arguments);
    if (v == null) delete this[name];
    else this[name] = v;
  };
}

function selection_property(name, value) {
  return arguments.length > 1
      ? this.each((value == null
          ? propertyRemove : typeof value === "function"
          ? propertyFunction
          : propertyConstant)(name, value))
      : this.node()[name];
}

function classArray(string) {
  return string.trim().split(/^|\s+/);
}

function classList(node) {
  return node.classList || new ClassList(node);
}

function ClassList(node) {
  this._node = node;
  this._names = classArray(node.getAttribute("class") || "");
}

ClassList.prototype = {
  add: function(name) {
    var i = this._names.indexOf(name);
    if (i < 0) {
      this._names.push(name);
      this._node.setAttribute("class", this._names.join(" "));
    }
  },
  remove: function(name) {
    var i = this._names.indexOf(name);
    if (i >= 0) {
      this._names.splice(i, 1);
      this._node.setAttribute("class", this._names.join(" "));
    }
  },
  contains: function(name) {
    return this._names.indexOf(name) >= 0;
  }
};

function classedAdd(node, names) {
  var list = classList(node), i = -1, n = names.length;
  while (++i < n) list.add(names[i]);
}

function classedRemove(node, names) {
  var list = classList(node), i = -1, n = names.length;
  while (++i < n) list.remove(names[i]);
}

function classedTrue(names) {
  return function() {
    classedAdd(this, names);
  };
}

function classedFalse(names) {
  return function() {
    classedRemove(this, names);
  };
}

function classedFunction(names, value) {
  return function() {
    (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names);
  };
}

function selection_classed(name, value) {
  var names = classArray(name + "");

  if (arguments.length < 2) {
    var list = classList(this.node()), i = -1, n = names.length;
    while (++i < n) if (!list.contains(names[i])) return false;
    return true;
  }

  return this.each((typeof value === "function"
      ? classedFunction : value
      ? classedTrue
      : classedFalse)(names, value));
}

function textRemove() {
  this.textContent = "";
}

function textConstant(value) {
  return function() {
    this.textContent = value;
  };
}

function textFunction(value) {
  return function() {
    var v = value.apply(this, arguments);
    this.textContent = v == null ? "" : v;
  };
}

function selection_text(value) {
  return arguments.length
      ? this.each(value == null
          ? textRemove : (typeof value === "function"
          ? textFunction
          : textConstant)(value))
      : this.node().textContent;
}

function htmlRemove() {
  this.innerHTML = "";
}

function htmlConstant(value) {
  return function() {
    this.innerHTML = value;
  };
}

function htmlFunction(value) {
  return function() {
    var v = value.apply(this, arguments);
    this.innerHTML = v == null ? "" : v;
  };
}

function selection_html(value) {
  return arguments.length
      ? this.each(value == null
          ? htmlRemove : (typeof value === "function"
          ? htmlFunction
          : htmlConstant)(value))
      : this.node().innerHTML;
}

function raise() {
  if (this.nextSibling) this.parentNode.appendChild(this);
}

function selection_raise() {
  return this.each(raise);
}

function lower() {
  if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
}

function selection_lower() {
  return this.each(lower);
}

function selection_append(name) {
  var create = typeof name === "function" ? name : creator(name);
  return this.select(function() {
    return this.appendChild(create.apply(this, arguments));
  });
}

function constantNull() {
  return null;
}

function selection_insert(name, before) {
  var create = typeof name === "function" ? name : creator(name),
      select = before == null ? constantNull : typeof before === "function" ? before : selector(before);
  return this.select(function() {
    return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
  });
}

function remove() {
  var parent = this.parentNode;
  if (parent) parent.removeChild(this);
}

function selection_remove() {
  return this.each(remove);
}

function selection_cloneShallow() {
  var clone = this.cloneNode(false), parent = this.parentNode;
  return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
}

function selection_cloneDeep() {
  var clone = this.cloneNode(true), parent = this.parentNode;
  return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
}

function selection_clone(deep) {
  return this.select(deep ? selection_cloneDeep : selection_cloneShallow);
}

function selection_datum(value) {
  return arguments.length
      ? this.property("__data__", value)
      : this.node().__data__;
}

function contextListener(listener) {
  return function(event) {
    listener.call(this, event, this.__data__);
  };
}

function parseTypenames(typenames) {
  return typenames.trim().split(/^|\s+/).map(function(t) {
    var name = "", i = t.indexOf(".");
    if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
    return {type: t, name: name};
  });
}

function onRemove(typename) {
  return function() {
    var on = this.__on;
    if (!on) return;
    for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
      if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
        this.removeEventListener(o.type, o.listener, o.options);
      } else {
        on[++i] = o;
      }
    }
    if (++i) on.length = i;
    else delete this.__on;
  };
}

function onAdd(typename, value, options) {
  return function() {
    var on = this.__on, o, listener = contextListener(value);
    if (on) for (var j = 0, m = on.length; j < m; ++j) {
      if ((o = on[j]).type === typename.type && o.name === typename.name) {
        this.removeEventListener(o.type, o.listener, o.options);
        this.addEventListener(o.type, o.listener = listener, o.options = options);
        o.value = value;
        return;
      }
    }
    this.addEventListener(typename.type, listener, options);
    o = {type: typename.type, name: typename.name, value: value, listener: listener, options: options};
    if (!on) this.__on = [o];
    else on.push(o);
  };
}

function selection_on(typename, value, options) {
  var typenames = parseTypenames(typename + ""), i, n = typenames.length, t;

  if (arguments.length < 2) {
    var on = this.node().__on;
    if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
      for (i = 0, o = on[j]; i < n; ++i) {
        if ((t = typenames[i]).type === o.type && t.name === o.name) {
          return o.value;
        }
      }
    }
    return;
  }

  on = value ? onAdd : onRemove;
  for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options));
  return this;
}

function dispatchEvent(node, type, params) {
  var window = defaultView(node),
      event = window.CustomEvent;

  if (typeof event === "function") {
    event = new event(type, params);
  } else {
    event = window.document.createEvent("Event");
    if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;
    else event.initEvent(type, false, false);
  }

  node.dispatchEvent(event);
}

function dispatchConstant(type, params) {
  return function() {
    return dispatchEvent(this, type, params);
  };
}

function dispatchFunction(type, params) {
  return function() {
    return dispatchEvent(this, type, params.apply(this, arguments));
  };
}

function selection_dispatch(type, params) {
  return this.each((typeof params === "function"
      ? dispatchFunction
      : dispatchConstant)(type, params));
}

function* selection_iterator() {
  for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
    for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
      if (node = group[i]) yield node;
    }
  }
}

var root = [null];

function Selection(groups, parents) {
  this._groups = groups;
  this._parents = parents;
}

function selection_selection() {
  return this;
}

Selection.prototype = {
  constructor: Selection,
  select: selection_select,
  selectAll: selection_selectAll,
  selectChild: selection_selectChild,
  selectChildren: selection_selectChildren,
  filter: selection_filter,
  data: selection_data,
  enter: selection_enter,
  exit: selection_exit,
  join: selection_join,
  merge: selection_merge,
  selection: selection_selection,
  order: selection_order,
  sort: selection_sort,
  call: selection_call,
  nodes: selection_nodes,
  node: selection_node,
  size: selection_size,
  empty: selection_empty,
  each: selection_each,
  attr: selection_attr,
  style: selection_style,
  property: selection_property,
  classed: selection_classed,
  text: selection_text,
  html: selection_html,
  raise: selection_raise,
  lower: selection_lower,
  append: selection_append,
  insert: selection_insert,
  remove: selection_remove,
  clone: selection_clone,
  datum: selection_datum,
  on: selection_on,
  dispatch: selection_dispatch,
  [Symbol.iterator]: selection_iterator
};

function select(selector) {
  return typeof selector === "string"
      ? new Selection([[document.querySelector(selector)]], [document.documentElement])
      : new Selection([[selector]], root);
}

function chartFrame(configObject) {
  var autoPosition = false;
  var a11yDesc = "A graphic by the Financial Times";
  var a11yPlotPresentation = true;
  var a11yTitle = "A chart";
  var backgroundColor;
  var containerClass = "g-chartframe";
  var copyright = "© FT";
  var copyrightStyle = false;
  var goalposts = false; // goalpost is the bit at the top and bottom of pritn charts

  var blackbar = false; // blackbar the short black bar above web graphics

  var whitebar = false; // whitebar the short white bar above social graphics

  var fullYear = false;
  var showDownloadPngButtons = true;
  var graphicHeight = 400;
  var graphicWidth = 500;
  var plot;
  var plotAdjuster = 0;
  var rem = 18;
  var subtitle = "some supporting information, units perhaps";
  var subtitleLineHeight = 20;
  var subtitleStyle = {};
  var source = "Source: research";
  var sourceLineHeight = 16;
  var sourcePlotYOffset = 46;
  var sourceStyle = {};
  var title = "Title: A description of the charts purpose";
  var titleLineHeight = 32;
  var titleStyle = {};
  var watermarkLocation = "icons.svg#ft-logo";
  var watermarkMarkup = "";
  var watermarkOffsetX = 40;
  var watermarkOffsetY = 0;
  var watermarkWidth = 124;
  var watermarkHeight = 10;
  var units = "px";
  var margin = {
    top: 100,
    left: 1,
    bottom: 20,
    right: 20
  };
  var subtitlePosition = {
    x: 1,
    y: 67
  };
  var sourcePosition = {
    x: 1
  };
  var titlePosition = {
    x: 1,
    y: 30
  };
  var convertFrom = {
    mm: function mm(x) {
      return x * 2.83464480558843;
    },
    px: function px(x) {
      return x;
    }
  };
  var custom = {};
  /* istanbul ignore next This is already well tested. */

  function attributeStyle(parent, style) {
    Object.keys(style).forEach(function (attribute) {
      parent.attr(attribute, style[attribute]);
    });
  }

  function frame(p) {
    // overall graphic properties
    p.attr("class", containerClass).attr("font-family", "MetricWeb,sans-serif");
    p.attr("role", "img");
    /* istanbul ignore next This is already well tested. */

    if (p.node().nodeName.toLowerCase() === "svg") {
      p.attr("width", graphicWidth).attr("height", graphicHeight).attr("viewBox", ["0 0", graphicWidth, graphicHeight].join(" "));
      /* istanbul ignore next This is already well tested. */

      if (a11yTitle !== false || title !== false) {
        p.append("title").text(a11yTitle || title).attr("id", "".concat(containerClass, "__chart-a11y-title"));
        p.attr("aria-labelledby", "".concat(containerClass, "__chart-a11y-title"));
      }

      if (a11yDesc !== false) {
        p.append("desc").text(a11yDesc).attr("id", "".concat(containerClass, "__chart-a11y-desc"));
        p.attr("aria-labelledby", "".concat(p.attr("aria-labelledby") ? "".concat(p.attr("aria-labelledby"), " ") : "").concat(containerClass, "__chart-a11y-desc"));
      }
    } // background


    if (backgroundColor !== undefined && !p.select("#chart-background").size()) {
      // @TODO remove second guard; see #62.
      p.append("rect").attr("role", "presentation").attr("id", "chart-background").attr("class", "chart-background").attr("x", 0).attr("y", 0).attr("width", graphicWidth).attr("height", graphicHeight).attr("fill", backgroundColor);
    } // 'blackbar' (the short black bar above web graphics) @TODO remove second guard; see #62.


    if (blackbar && !p.selectAll("rect.black-bar").size()) {
      p.append("rect").attr("class", "black-bar").attr("width", 60).attr("height", 4).style("fill", blackbar);
    }

    if (whitebar && !p.selectAll("rect.white-bar").size()) {
      p.append("rect").attr("class", "white-bar").attr("width", 60).attr("height", 4).style("fill", whitebar).attr("transform", "translate(".concat(margin.left, ",").concat(margin.left, ")"));
    } // 'goalposts' (the bit at the top and the bottom of print charts) @TODO remove second guard; see #62.


    if (goalposts && !p.selectAll("path.chart-goalposts").size()) {
      var goalpostPaths = ["M 0, ".concat(graphicHeight, " L ").concat(graphicWidth, ", ").concat(graphicHeight), "M 0, 15 L 0, 0 L ".concat(graphicWidth, ", 0 L ").concat(graphicWidth, ", 15")];
      p.append("path").attr("class", "chart-goalposts").attr("d", goalpostPaths).attr("stroke-width", 0.3).attr("fill", "none").attr("stroke", goalposts);
    }
    /* istanbul ignore next This is already well tested. */


    var titleLineCount = title ? title.split("|").length : 0;
    /* istanbul ignore next This is already well tested. */

    var subtitleLineCount = subtitle ? subtitle.split("|").length : 0;
    /* istanbul ignore next This is already well tested. */

    var sourceLineCount = source ? source.split("|").length : 0; // title; @TODO remove existence guard see #62

    if (title && !p.select("text.chart-title").size()) {
      p.append("text").attr("class", "chart-title").attr("id", "".concat(containerClass, "title")).call(function (titleText) {
        titleText.selectAll("tspan").data(title.split("|")).enter().append("tspan").text(function (d) {
          return d;
        }).attr("y", function (d, i) {
          return titlePosition.y + i * titleLineHeight;
        }).attr("x", titlePosition.x).call(attributeStyle, titleStyle);
      });
    } // @TODO remove existence guard see #62

    /* istanbul ignore next This is already well tested. */


    if (subtitle && !p.select("text.chart-subtitle").size()) {
      // subtitle
      p.append("text").attr("id", "".concat(containerClass, "subtitle")).attr("class", "chart-subtitle").call(function (subtitleText) {
        subtitleText.selectAll("tspan").data(subtitle.split("|")).enter().append("tspan").text(function (d) {
          return d;
        }).attr("id", "".concat(containerClass, "subtitle")).attr("y", function (d, i) {
          if (titleLineCount > 1) {
            return titlePosition.y + titleLineCount * titleLineHeight + subtitleLineHeight * i;
          }

          return subtitlePosition.y + i * subtitleLineHeight;
        }).attr("x", subtitlePosition.x).call(attributeStyle, subtitleStyle);
      });
    } // source; @TODO remove second existence check see #62

    /* istanbul ignore next This is already well tested. */


    if (source && !p.selectAll("text.chart-source").size()) {
      p.append("text").attr("class", "chart-source").attr("id", "".concat(containerClass, "source")).call(function (sourceText) {
        sourceText.selectAll("tspan").data(source.split("|")).enter().append("tspan").text(function (d) {
          return d;
        }).attr("id", "".concat(containerClass, "source")).attr("y", function (d, i) {
          /* istanbul ignore next I don't know how to test this. */
          if (sourcePosition.y) {
            return sourcePosition.y + i * sourceLineHeight;
          }

          return graphicHeight - (margin.bottom - sourcePlotYOffset) + sourceLineHeight * 1.5 + i * sourceLineHeight;
        }).attr("x", sourcePosition.x).call(attributeStyle, sourceStyle);
      });
    } // copyright


    if (copyrightStyle && !p.selectAll("text.chart-copyright").size()) {
      p.append("text").attr("class", "chart-copyright").append("tspan").text(copyright).attr("x", sourcePosition.x).attr("y", function () {
        /* istanbul ignore next I don't know how to test this. */
        if (sourceLineCount > 1) {
          return graphicHeight - (margin.bottom - sourcePlotYOffset) + sourceLineHeight * 1.125 + sourceLineCount * sourceLineHeight * 1.2;
        }

        return graphicHeight - (margin.bottom - sourcePlotYOffset) + sourceLineHeight * 2.5;
      }).call(attributeStyle, copyrightStyle);
    } // TODO figure out a way to improve this autoPosition stuff, needs ot be configurable so we don't have to reference specific classes


    if (autoPosition && (containerClass === "ft-printgraphic" || containerClass === "ft-socialgraphic" || containerClass === "ft-videographic")) {
      margin.top = titlePosition.y + titleLineCount * titleLineHeight + subtitleLineCount * subtitleLineHeight + rem / 3;
    } else if (autoPosition) {
      margin.top = titlePosition.y + titleLineCount * titleLineHeight + subtitleLineCount * subtitleLineHeight + 28 - plotAdjuster;
    } // watermark; @TODO remove existence check (#62)


    if (watermarkMarkup && !p.selectAll("g.chart-watermark").size() && p.node().ownerDocument.doctype.name !== "svg") {
      p.append("g").attr("class", "chart-watermark").html(watermarkMarkup) // This needs to be .text() to work in pure SVG context
      .attr("role", "presentation").attr("transform", "translate(".concat(graphicWidth - watermarkWidth - watermarkOffsetX, ",").concat(graphicHeight - watermarkHeight - watermarkOffsetY, ") scale(1) "));
    } // plot area (where you put the chart itself)

    /* istanbul ignore next I don't know how to test this. */


    if (!p.selectAll("g.chart-plot").size()) {
      plot = p.append("g").attr("class", "chart-plot");
    } else {
      plot = p.select("g.chart-plot");
    }

    plot.attr("transform", "translate(".concat(margin.left, ",").concat(margin.top, ")"));
    /* istanbul ignore next This is already well tested. */

    if (a11yPlotPresentation) {
      plot.attr("role", "presentation"); // include this extra role if a11yPlotPresentation
    }
    /* istanbul ignore next */


    if (showDownloadPngButtons && p.node().ownerDocument.doctype.name !== "svg") {
      var parent;

      if (p.node().nodeName.toLowerCase() === "svg") {
        parent = select(p.node().parentNode);
      } else {
        parent = select(p.node());
      } // Prevent this from being rendered twice; @TODO remove check (#62)


      if (!parent.selectAll(".button-holder").size()) {
        var holder = parent.append("div").attr("class", "button-holder");
        holder.append("button").attr("class", "save-png-button save-png-button__1x").text("Save as .png").style("float", "left").style("opacity", 0.6).on("click", function () {
          return savePNG(p, 1);
        });
        holder.append("button").attr("class", "save-png-button save-png-button__2x").style("float", "left").style("opacity", 0.6).text("Save as double size .png").on("click", function () {
          return savePNG(p, 2);
        });
      }
    }
  } // Setters and getters


  frame.a11y = function () {
    var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
        newTitle = _ref.title,
        newDesc = _ref.desc;

    if (newTitle !== undefined) a11yTitle = newTitle;
    if (newDesc !== undefined) a11yDesc = newDesc;

    if (newTitle === undefined && newDesc === undefined) {
      return {
        title: a11yTitle,
        desc: a11yDesc
      };
    }

    return frame;
  };

  frame.a11yDesc = function (x) {
    if (x === undefined) return a11yDesc;
    a11yDesc = x;
    return frame;
  };

  frame.a11yPlotPresentation = function (x) {
    if (x === undefined) return a11yPlotPresentation;
    a11yPlotPresentation = x;
    return frame;
  };

  frame.a11yTitle = function (x) {
    if (x === undefined) return a11yTitle;
    a11yTitle = x;
    return frame;
  };

  frame.autoPosition = function (x) {
    if (x === undefined) return autoPosition;
    autoPosition = x;
    return frame;
  };

  frame.backgroundColor = function (x) {
    if (x === undefined) return backgroundColor;
    backgroundColor = x;
    return frame;
  };

  frame.backgroundColour = function () {
    console.error("gChartframe.backgroundColour() is deprecated and will be removed next version.");
    console.error("Please use gChartframe.backgroundColor() instead.");
    return frame.backgroundColor.apply(frame, arguments);
  };

  frame.blackbar = function (x) {
    if (x === undefined) return blackbar;
    blackbar = x;
    return frame;
  };

  frame.containerClass = function (x) {
    if (x === undefined) return containerClass;
    containerClass = x;
    return frame;
  };

  frame.copyright = function (x) {
    if (x === undefined) return copyright;
    copyright = x;
    return frame;
  };

  frame.copyrightStyle = function (x) {
    if (x === undefined) return copyrightStyle;
    copyrightStyle = x;
    return frame;
  };

  frame.dimension = function () {
    return {
      width: graphicWidth - (margin.left + margin.right),
      height: graphicHeight - (margin.top + margin.bottom)
    };
  };

  frame.extend = function (key, value) {
    custom[key] = value;

    frame[key] = function (d) {
      if (d === undefined) return custom[key];
      custom[key] = d;
      return frame;
    };

    return frame;
  };

  frame.fullYear = function (x) {
    if (x === undefined) return fullYear;
    fullYear = x;
    return frame;
  };

  frame.goalposts = function (x) {
    if (x === undefined) return goalposts;
    goalposts = x;
    return frame;
  };

  frame.height = function (x) {
    if (x === undefined) return graphicHeight;
    graphicHeight = convertFrom[units](x);
    return frame;
  };

  frame.margin = function (x) {
    if (x === undefined) return margin;
    Object.keys(x).forEach(function (k) {
      margin[k] = x[k];
    });
    return frame;
  };

  frame.plot = function () {
    return plot;
  };

  frame.plotAdjuster = function (x) {
    if (x === undefined) return plotAdjuster;
    plotAdjuster = x;
    return frame;
  };

  frame.rem = function (x) {
    if (x === undefined) return rem;
    rem = x;
    return frame;
  };

  frame.showDownloadPngButtons = function (d) {
    if (typeof d === "undefined") return showDownloadPngButtons;
    showDownloadPngButtons = d;
    return frame;
  };

  frame.source = function (x) {
    if (x === undefined) return source;
    source = x;
    return frame;
  };

  frame.sourceLineHeight = function (x) {
    if (x === undefined) return sourceLineHeight;
    sourceLineHeight = x;
    return frame;
  };

  frame.sourcePlotYOffset = function (x) {
    if (x === undefined) return sourcePlotYOffset;
    sourcePlotYOffset = x;
    return frame;
  };

  frame.sourceStyle = function (x) {
    if (x === undefined) return sourceStyle;
    sourceStyle = x;
    return frame;
  };

  frame.sourceX = function (x) {
    if (x === undefined) return sourcePosition.x;
    sourcePosition.x = x;
    return frame;
  };

  frame.sourceY = function (x) {
    if (x === undefined) return sourcePosition.y;
    sourcePosition.y = x;
    return frame;
  };

  frame.subtitle = function (x) {
    if (x === undefined) return subtitle;
    subtitle = x;
    return frame;
  };

  frame.subtitleLineHeight = function (x) {
    if (x === undefined) return subtitleLineHeight;
    subtitleLineHeight = x;
    return frame;
  };

  frame.subtitleStyle = function (x) {
    if (x === undefined) return subtitleStyle;
    subtitleStyle = x;
    return frame;
  };

  frame.subtitleX = function (x) {
    if (x === undefined) return subtitlePosition.x;
    subtitlePosition.x = x;
    return frame;
  };

  frame.subtitleY = function (x) {
    if (x === undefined) return subtitlePosition.y;
    subtitlePosition.y = x;
    return frame;
  };

  frame.title = function (x) {
    if (x === undefined) return title;
    title = x;
    return frame;
  };

  frame.titleStyle = function (x) {
    if (x === undefined) return titleStyle;
    titleStyle = x;
    return frame;
  };

  frame.titleLineHeight = function (x) {
    if (x === undefined) return titleLineHeight;
    titleLineHeight = x;
    return frame;
  };

  frame.titleX = function (x) {
    if (x === undefined) return titlePosition.x;
    titlePosition.x = x;
    return frame;
  };

  frame.titleY = function (x) {
    if (x === undefined) return titlePosition.y;
    titlePosition.y = x;
    return frame;
  };

  frame.units = function (x) {
    if (x === undefined) return units;
    units = x;
    return frame;
  };

  frame.watermark = function (x) {
    if (x === undefined) return watermarkMarkup;
    watermarkLocation = "";
    watermarkMarkup = x;
    return frame;
  };

  frame.watermarkOffsetY = function (x) {
    if (x === undefined) return watermarkOffsetY;
    watermarkOffsetY = x;
    return frame;
  };

  frame.watermarkOffsetX = function (x) {
    if (x === undefined) return watermarkOffsetX;
    watermarkOffsetX = x;
    return frame;
  };

  frame.watermarkLocation = function (x) {
    if (x === undefined) return watermarkLocation;
    watermarkMarkup = "";
    watermarkLocation = x;
    return frame;
  };

  frame.watermarkWidth = function (x) {
    if (x === undefined) return watermarkWidth;
    watermarkWidth = x;
    return frame;
  };

  frame.watermarkHeight = function (x) {
    if (x === undefined) return watermarkHeight;
    watermarkHeight = x;
    return frame;
  };

  frame.whitebar = function (x) {
    if (x === undefined) return whitebar;
    whitebar = x;
    return frame;
  };

  frame.width = function (x) {
    if (!x) return graphicWidth;
    graphicWidth = convertFrom[units](x);
    return frame;
  };

  frame.attrs = function (x) {
    if (x === undefined) {
      return Object.assign({}, {
        a11yDesc: a11yDesc,
        a11yPlotPresentation: a11yPlotPresentation,
        a11yTitle: a11yTitle,
        autoPosition: autoPosition,
        containerClass: containerClass,
        copyright: copyright,
        copyrightStyle: copyrightStyle,
        blackbar: blackbar,
        backgroundColor: backgroundColor,
        goalposts: goalposts,
        graphicHeight: graphicHeight,
        graphicWidth: graphicWidth,
        margin: margin,
        plot: plot,
        plotAdjuster: plotAdjuster,
        rem: rem,
        subtitle: subtitle,
        subtitleLineHeight: subtitleLineHeight,
        subtitlePosition: subtitlePosition,
        subtitleStyle: subtitleStyle,
        source: source,
        sourceLineHeight: sourceLineHeight,
        sourcePosition: sourcePosition,
        sourceStyle: sourceStyle,
        title: title,
        titleLineHeight: titleLineHeight,
        titlePosition: titlePosition,
        titleStyle: titleStyle,
        watermarkLocation: watermarkLocation,
        watermarkMarkup: watermarkMarkup,
        watermarkOffsetX: watermarkOffsetX,
        watermarkOffsetY: watermarkOffsetY,
        watermarkHeight: watermarkHeight,
        watermarkWidth: watermarkWidth,
        whitebar: whitebar,
        units: units
      }, custom);
    }

    Object.keys(x).forEach(function (setterName) {
      var value = x[setterName];
      /* istanbul ignore next I don't know why this won't cover. */

      if (isFunction(frame[setterName])) {
        frame[setterName](value);
      }
    });
    return frame;
  };

  if (configObject !== undefined) {
    frame.attrs(configObject);
  }

  return frame;
}

function isFunction(functionToCheck) {
  var getType = {};
  return functionToCheck && getType.toString.call(functionToCheck) === "[object Function]";
}

var classes = [".annotation", ".lines", ".highlights", ".axis path", ".axis text", ".axis line", ".axis", ".baseline", ".baseline line", ".legend", ".legend text", ".chart-goalposts", ".chart-title", ".chart-subtitle", ".chart-source", ".chart-copyright", ".chart-watermark", ".annotations-holder", ".lines highlighlines", ".highlights", ".annotation", ".annotations-holder line", ".annotations-holder text", ".line path", ".highlights rects"];
/* istanbul ignore next */

function savePNG(svg, scaleFactor) {
  svg.selectAll(classes.join(", ")).each(function inlineProps() {
    var element = this;
    var computedStyle = getComputedStyle(element, null); // loop through and compute inline svg styles

    for (var i = 0; i < computedStyle.length; i += 1) {
      var property = computedStyle.item(i);
      var value = computedStyle.getPropertyValue(property);
      element.style[property] = value;
    }
  });
  saveSvgAsPng_1(svg.node(), "".concat(svg.select("title").text().replace(/\s/g, "-").toLowerCase(), ".png"), {
    scale: scaleFactor
  });
}

function webFrameS(configObject) {
  var f = chartFrame().autoPosition(true).containerClass('ft-webgraphic-s').backgroundColor('#FFF1E0').blackbar('#000').width(300) // .watermark(watermarkPathDark)
  // .watermarkSize(80)
  // .watermarkOffset(-28)
  .margin({
    bottom: 90,
    right: 5,
    left: 15
  }).rem(14).plotAdjuster(0).titleStyle({
    'font-size': 20,
    'font-family': 'MetricWeb,sans-serif',
    'font-weight': 400,
    fill: '#000'
  }).titleY(32).titleLineHeight(24).subtitleLineHeight(20).subtitleStyle({
    'font-size': 18,
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).subtitleY(64).sourceLineHeight(12).sourcePlotYOffset(38).sourceStyle({
    'font-size': '12px',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).copyrightStyle({
    'font-size': '12px',
    'font-style': 'italic',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  });
  if (configObject !== undefined) f.attrs(configObject);
  return f;
}

function webFrameM(configObject) {
  var f = chartFrame().autoPosition(true).containerClass('ft-webgraphic-m').backgroundColor('#FFF1E0').blackbar('#000').width(700).height(500) // .watermark(watermarkPathDark)
  // .watermarkSize(80)
  // .watermarkOffset(-28)
  .margin({
    bottom: 104,
    right: 5,
    left: 20
  }).rem(16).plotAdjuster(4).titleY(32).titleStyle({
    'font-size': 24,
    'font-family': 'MetricWeb,sans-serif',
    'font-weight': 400,
    fill: '#000'
  }).titleLineHeight(28).subtitleLineHeight(20).subtitleStyle({
    'font-size': 18,
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).subtitleY(64).sourceLineHeight(16).sourcePlotYOffset(44).sourceStyle({
    'font-size': '14px',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).copyrightStyle({
    'font-size': '14px',
    'font-style': 'italic',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  });
  if (configObject !== undefined) f.attrs(configObject);
  return f;
}

function webFrameMDefault(configObject) {
  var f = chartFrame().autoPosition(true).containerClass('ft-webgraphic-m-default').backgroundColor('#FFF1E0').blackbar('#000').width(700).height(500) // .watermark(watermarkPathDark)
  // .watermarkSize(80)
  // .watermarkOffset(-28)
  .margin({
    bottom: 115,
    right: 5,
    left: 20
  }).rem(20).plotAdjuster(8).titleY(32).titleStyle({
    'font-size': 28,
    'font-family': 'MetricWeb,sans-serif',
    'font-weight': 400,
    fill: '#000'
  }).titleLineHeight(28).subtitleLineHeight(28).subtitleStyle({
    'font-size': 24,
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).subtitleY(68).sourceLineHeight(18).sourcePlotYOffset(34).sourceStyle({
    'font-size': '16px',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).copyrightStyle({
    'font-size': '14px',
    'font-style': 'italic',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  });
  if (configObject !== undefined) f.attrs(configObject);
  return f;
}

function webFrameL(configObject) {
  var f = chartFrame().autoPosition(true).containerClass('ft-webgraphic-l').backgroundColor('#FFF1E0').width(1180).height(700).blackbar('#000').fullYear(true) // .watermark(watermarkPathDark)
  // .watermarkSize(80)
  // .watermarkOffset(-28)
  .margin({
    bottom: 105,
    right: 5,
    left: 20
  }).rem(18).plotAdjuster(8).titleY(32).titleStyle({
    'font-size': 28,
    'font-family': 'MetricWeb,sans-serif',
    'font-weight': 400,
    fill: '#000'
  }).titleLineHeight(32).subtitleLineHeight(20).subtitleY(64).subtitleStyle({
    'font-size': 18,
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).sourceLineHeight(16).sourcePlotYOffset(44).sourceStyle({
    'font-size': '16px',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  }).copyrightStyle({
    'font-size': '16px',
    'font-style': 'italic',
    'font-family': 'MetricWeb,sans-serif',
    fill: '#66605C'
  });
  if (configObject !== undefined) f.attrs(configObject);
  return f;
}

function printFrame(configObject) {
  var f = chartFrame().containerClass('ft-printgraphic').autoPosition(true).backgroundColor('#FFF').goalposts('#000').units('mm').width(112.25) // these are after the units are set so they are converted from mm to px
  .height(68).margin({
    top: 40,
    left: 15,
    bottom: 35,
    right: 7
  }).rem(9.6).titleStyle({
    'font-size': '12px',
    fill: '#000000',
    'font-weight': '600',
    'font-family': 'MetricWeb,sans-serif'
  }).titleX(7).titleY(15).titleLineHeight(13).subtitleStyle({
    fill: '#000000',
    'font-size': '9.6px',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  }).subtitleLineHeight(10).subtitleX(7).subtitleY(27).sourceStyle({
    fill: '#000000',
    'font-size': '7.2px',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  }).sourceX(7).sourcePlotYOffset(18).sourceLineHeight(8);
  if (configObject !== undefined) f.attrs(configObject);
  return f;
}

var watermarkPathLight = '<path fill="#8e9095" id="logo" d="M3,8.5c0,0.7,0.2,0.9,1.5,0.9v0.4H0V9.4c0.9,0,1.2-0.1,1.2-0.8V1.4c0-0.8-0.3-0.8-1.2-0.8V0.2h6.1c0.8,0,1.1,0,1.4-0.1l0,2.7H7.2c-0.2-2-0.7-2.2-2.6-2.2H3v4h1.3c1.3,0,1.3-0.2,1.4-1.2h0.4v2.9H5.7c-0.1-1-0.2-1.2-1.4-1.2H3V8.5zM8.4,9.8V9.4c0.9,0,1.2-0.1,1.2-0.8V1.4c0-0.8-0.3-0.8-1.2-0.8V0.2h4.2v0.4c-0.9,0-1.2,0.1-1.2,0.8v7.1c0,0.8,0.3,0.8,1.2,0.8v0.4H8.4z M22,10l-6.6-8.2v6.7c0,0.8,0.5,0.9,1.5,0.9v0.4h-3.5V9.4c0.9,0,1.4-0.1,1.4-0.9V1.1c-0.4-0.4-0.7-0.5-1.4-0.5V0.2h3.1l5.1,6.5V1.5c0-0.8-0.5-0.9-1.5-0.9V0.2h3.5v0.4c-0.9,0-1.4,0.1-1.4,0.9V10H22z M26.7,9.8h-3.5V9.4c0.9,0,1.3-0.1,1.6-0.9L28,0.1h0.9l3.4,8.5c0.3,0.8,0.4,0.8,1.1,0.8v0.4h-4.1V9.4c1.2,0,1.4-0.1,1.1-0.8l-1-2.6h-3l-0.9,2.5c-0.3,0.8,0.2,0.9,1.2,0.9V9.8z M26.6,5.5h2.6L27.9,2L26.6,5.5z M42.6,10l-6.6-8.2v6.7c0,0.8,0.5,0.9,1.5,0.9v0.4H34V9.4c0.8,0,1.3-0.1,1.3-0.9V1.1c-0.5-0.4-0.8-0.5-1.6-0.5V0.2H37l5.1,6.5V1.5c0-0.8-0.5-0.9-1.5-0.9V0.2h3.5v0.4c-0.9,0-1.4,0.1-1.4,0.9V10H42.6z M52.4,0.1h0.2L52.7,3l-0.4,0c-0.2-1.7-1.1-2.5-2.7-2.5c-1.8,0-3.2,1.5-3.2,3.9c0,3,1.9,4.7,3.9,4.7c0.9,0,1.6-0.2,2.4-1.1L53,8.4c-0.6,0.9-1.8,1.6-3.5,1.6c-2.4,0-4.9-1.8-4.9-4.9c0-3,2.3-5.1,5-5.1c1.3,0,2,0.6,2.3,0.6C52.2,0.6,52.3,0.4,52.4,0.1z M53.8,9.8V9.4c0.9,0,1.2-0.1,1.2-0.8V1.4c0-0.8-0.3-0.8-1.2-0.8V0.2H58v0.4c-0.9,0-1.2,0.1-1.2,0.8v7.1c0,0.8,0.3,0.8,1.2,0.8v0.4H53.8z M61.9,9.8h-3.3V9.4c0.7,0,1.1-0.1,1.4-0.9l3.2-8.4h0.9l3.4,8.5c0.3,0.8,0.4,0.8,1.1,0.8v0.4h-4.1V9.4c1.2,0,1.4-0.1,1.1-0.8l-1-2.6h-3l-0.9,2.5c-0.3,0.8,0.2,0.9,1.2,0.9V9.8z M61.8,5.5h2.6L63,2L61.8,5.5z M69.3,9.8V9.4c0.9,0,1.2-0.1,1.2-0.8V1.4c0-0.8-0.3-0.8-1.2-0.8V0.2h4.1v0.4c-0.9,0-1.2,0.1-1.2,0.8v7.2c0,0.6,0.3,0.7,0.8,0.7h0.5c1.9,0,2.5-0.3,3-2.4L77,7l-0.3,2.9H69.3z M89.4,0.1l0,2.9h-0.4c-0.2-2.1-0.7-2.3-2.6-2.3H86v7.9c0,0.8,0.3,0.9,1.5,0.9v0.4h-4.8V9.4c1.2,0,1.5-0.1,1.5-0.9V0.6h-0.6c-1.9,0-2.3,0.3-2.6,2.3h-0.4l0-2.9c0.3,0,0.5,0.1,1.4,0.1h6C88.9,0.2,89.2,0.1,89.4,0.1z M90.4,9.8V9.4c0.9,0,1.2-0.1,1.2-0.8V1.4c0-0.8-0.3-0.8-1.2-0.8V0.2h4.2v0.4c-0.9,0-1.2,0.1-1.2,0.8v7.1c0,0.8,0.3,0.8,1.2,0.8v0.4H90.4z M107,0.2v0.4c-0.9,0-1.3,0-1.2,0.8l0.8,7.2c0.1,0.7,0.4,0.8,1.2,0.8v0.4h-4.1V9.4c0.9,0,1.2-0.1,1.1-0.8L104,1l-3.1,9h-0.1l-3-9l-0.7,7.5c-0.1,0.8,0.4,0.8,1.3,0.8v0.4h-3.2V9.4c0.9,0,1.2-0.1,1.2-0.8l0.7-7.2c0.1-0.8-0.3-0.8-1.2-0.8V0.2h3.4l2,6.6l2.2-6.6H107z M114.8,6.3h-0.4c-0.1-1-0.2-1.2-1.4-1.2h-1.5v3.5c0,0.6,0.3,0.7,0.8,0.7h0.8c1.9,0,2.5-0.3,3-2.4l0.4,0l-0.3,2.9h-7.6V9.4c0.9,0,1.2-0.1,1.2-0.8V1.4c0-0.8-0.3-0.8-1.2-0.8V0.2h7.3l0,2.4h-0.4c-0.2-1.7-0.7-1.9-2.6-1.9h-1.4v3.9h1.5c1.3,0,1.3-0.2,1.4-1.2h0.4V6.3z M122.5,7.8c0-0.9-0.6-1.3-1.5-1.9l-1.5-0.8c-1.2-0.6-1.8-1.3-1.8-2.5c0-1.5,1.3-2.7,3-2.7c1.2,0,1.8,0.6,2.1,0.6c0.2,0,0.3-0.1,0.4-0.4h0.3l0.1,2.7l-0.4,0c-0.2-1.3-1.1-2.3-2.5-2.3c-1,0-1.7,0.6-1.7,1.4c0,0.9,0.7,1.3,1.5,1.7l1.3,0.7c1.2,0.7,2.1,1.4,2.1,2.7c0,1.7-1.5,2.9-3.3,2.9c-1.3,0-1.9-0.6-2.3-0.6c-0.2,0-0.3,0.2-0.4,0.5h-0.3L117.4,7l0.4,0c0.3,1.8,1.5,2.5,2.8,2.5C121.6,9.5,122.5,9,122.5,7.8z"/>';
var watermarkPath = {
  light: watermarkPathLight
};

function socialFrame(configObject) {
  var f = chartFrame().autoPosition(true).containerClass('ft-socialgraphic').backgroundColor('#262a33').whitebar('#fff').width(612).height(612).watermark(watermarkPath.light).watermarkOffsetX(40).watermarkOffsetY(40).margin({
    left: 40,
    right: 40,
    bottom: 138,
    top: 140
  }).rem(24).titleX(40).titleY(80).titleLineHeight(32).titleStyle({
    'font-size': '30px',
    fill: '#ffffff',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  }).subtitleX(40).subtitleY(112).subtitleLineHeight(28).subtitleStyle({
    'font-size': '24px',
    fill: '#8e9095',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  }).sourceX(40).sourceLineHeight(20).sourcePlotYOffset(66).sourceStyle({
    'font-size': '20px',
    fill: '#8e9095',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  });
  if (configObject !== undefined) f.attrs(configObject);
  return f;
}

function videoFrame(configObject) {
  var f = chartFrame().autoPosition(true).backgroundColor('#262a33').containerClass('ft-videographic').width(1920).height(1080).margin({
    left: 207,
    right: 207,
    bottom: 210,
    top: 233
  }).rem(48).titleX(207).titleY(130).titleLineHeight(68).titleStyle({
    'font-size': '72px',
    fill: '#ffffff',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  }).subtitleX(207).subtitleY(200).subtitleLineHeight(48).subtitleStyle({
    'font-size': '48px',
    fill: '#8e9095',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  }).sourceX(207).sourcePlotYOffset(60).sourceLineHeight(38).sourceStyle({
    'font-size': '36px',
    fill: '#8e9095',
    'font-weight': 400,
    'font-family': 'MetricWeb,sans-serif'
  });
  if (configObject !== undefined) f.attrs(configObject);
  return f;
}

export { chartFrame as frame, printFrame, socialFrame, videoFrame, webFrameL, webFrameM, webFrameMDefault, webFrameS };