UNPKG

d3plus-shape

Version:

Fancy SVG shapes for visualizations

1,362 lines (1,184 loc) 171 kB
/* d3plus-shape v0.16.11 Fancy SVG shapes for visualizations Copyright (c) 2019 D3plus - https://d3plus.org @license MIT */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-transition'), require('d3plus-common'), require('d3-array'), require('d3-color'), require('d3plus-color'), require('d3-shape'), require('d3plus-text'), require('d3-collection'), require('d3-interpolate-path'), require('d3-polygon')) : typeof define === 'function' && define.amd ? define('d3plus-shape', ['exports', 'd3-selection', 'd3-transition', 'd3plus-common', 'd3-array', 'd3-color', 'd3plus-color', 'd3-shape', 'd3plus-text', 'd3-collection', 'd3-interpolate-path', 'd3-polygon'], factory) : (global = global || self, factory(global.d3plus = {}, global.d3Selection, global.d3Transition, global.d3plusCommon, global.d3Array, global.d3Color, global.d3plusColor, global.paths, global.d3plusText, global.d3Collection, global.d3InterpolatePath, global.d3Polygon)); }(this, function (exports, d3Selection, d3Transition, d3plusCommon, d3Array, d3Color, d3plusColor, paths, d3plusText, d3Collection, d3InterpolatePath, d3Polygon) { 'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } /** @class Image @desc Creates SVG images based on an array of data. @example <caption>a sample row of data</caption> var data = {"url": "file.png", "width": "100", "height": "50"}; @example <caption>passed to the generator</caption> new Image().data([data]).render(); @example <caption>creates the following</caption> <image class="d3plus-Image" opacity="1" href="file.png" width="100" height="50" x="0" y="0"></image> @example <caption>this is shorthand for the following</caption> image().data([data])(); @example <caption>which also allows a post-draw callback function</caption> image().data([data])(function() { alert("draw complete!"); }) */ var Image = /*#__PURE__*/ function () { /** @memberof Image @desc Invoked when creating a new class instance, and sets any default parameters. @private */ function Image() { _classCallCheck(this, Image); this._duration = 600; this._height = d3plusCommon.accessor("height"); this._id = d3plusCommon.accessor("id"); this._pointerEvents = d3plusCommon.constant("auto"); this._select; this._url = d3plusCommon.accessor("url"); this._width = d3plusCommon.accessor("width"); this._x = d3plusCommon.accessor("x", 0); this._y = d3plusCommon.accessor("y", 0); } /** @memberof Image @desc Renders the current Image to the page. If a *callback* is specified, it will be called once the images are done drawing. @param {Function} [*callback*] @chainable */ _createClass(Image, [{ key: "render", value: function render(callback) { var _this = this; if (this._select === void 0) this.select(d3Selection.select("body").append("svg").style("width", "".concat(window.innerWidth, "px")).style("height", "".concat(window.innerHeight, "px")).style("display", "block").node()); var images = this._select.selectAll(".d3plus-Image").data(this._data, this._id); var enter = images.enter().append("image").attr("class", "d3plus-Image").attr("opacity", 0).attr("width", 0).attr("height", 0).attr("x", function (d, i) { return _this._x(d, i) + _this._width(d, i) / 2; }).attr("y", function (d, i) { return _this._y(d, i) + _this._height(d, i) / 2; }); var t = d3Transition.transition().duration(this._duration), that = this, update = enter.merge(images); update.attr("xlink:href", this._url).style("pointer-events", this._pointerEvents).transition(t).attr("opacity", 1).attr("width", function (d, i) { return _this._width(d, i); }).attr("height", function (d, i) { return _this._height(d, i); }).attr("x", function (d, i) { return _this._x(d, i); }).attr("y", function (d, i) { return _this._y(d, i); }).each(function (d, i) { var image = d3Selection.select(this), link = that._url(d, i); var fullAddress = link.indexOf("http://") === 0 || link.indexOf("https://") === 0; if (!fullAddress || link.indexOf(window.location.hostname) === 0) { var img = new Image(); img.src = link; img.crossOrigin = "Anonymous"; img.onload = function () { var canvas = document.createElement("canvas"); canvas.width = this.width; canvas.height = this.height; var context = canvas.getContext("2d"); context.drawImage(this, 0, 0); image.attr("xlink:href", canvas.toDataURL("image/png")); }; } }); images.exit().transition(t).attr("width", function (d, i) { return _this._width(d, i); }).attr("height", function (d, i) { return _this._height(d, i); }).attr("x", function (d, i) { return _this._x(d, i); }).attr("y", function (d, i) { return _this._y(d, i); }).attr("opacity", 0).remove(); if (callback) setTimeout(callback, this._duration + 100); return this; } /** @memberof Image @desc If *data* is specified, sets the data array to the specified array and returns the current class instance. If *data* is not specified, returns the current data array. An <image> tag will be drawn for each object in the array. @param {Array} [*data* = []] @chainable */ }, { key: "data", value: function data(_) { return arguments.length ? (this._data = _, this) : this._data; } /** @memberof Image @desc If *ms* is specified, sets the animation duration to the specified number and returns the current class instance. If *ms* is not specified, returns the current animation duration. @param {Number} [*ms* = 600] @chainable */ }, { key: "duration", value: function duration(_) { return arguments.length ? (this._duration = _, this) : this._duration; } /** @memberof Image @desc If *value* is specified, sets the height accessor to the specified function or number and returns the current class instance. @param {Function|Number} [*value*] @chainable @example function(d) { return d.height; } */ }, { key: "height", value: function height(_) { return arguments.length ? (this._height = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._height; } /** @memberof Image @desc If *value* is specified, sets the id accessor to the specified function and returns the current class instance. @param {Function} [*value*] @chainable @example function(d) { return d.id; } */ }, { key: "id", value: function id(_) { return arguments.length ? (this._id = _, this) : this._id; } /** @memberof Image @desc If *value* is specified, sets the pointer-events accessor to the specified function or string and returns the current class instance. @param {Function|String} [*value* = "auto"] @chainable */ }, { key: "pointerEvents", value: function pointerEvents(_) { return arguments.length ? (this._pointerEvents = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._pointerEvents; } /** @memberof Image @desc If *selector* is specified, sets the SVG container element to the specified d3 selector or DOM element and returns the current class instance. If *selector* is not specified, returns the current SVG container element. @param {String|HTMLElement} [*selector* = d3.select("body").append("svg")] @chainable */ }, { key: "select", value: function select(_) { return arguments.length ? (this._select = d3Selection.select(_), this) : this._select; } /** @memberof Image @desc If *value* is specified, sets the URL accessor to the specified function and returns the current class instance. @param {Function} [*value*] @chainable @example function(d) { return d.url; } */ }, { key: "url", value: function url(_) { return arguments.length ? (this._url = _, this) : this._url; } /** @memberof Image @desc If *value* is specified, sets the width accessor to the specified function or number and returns the current class instance. @param {Function|Number} [*value*] @chainable @example function(d) { return d.width; } */ }, { key: "width", value: function width(_) { return arguments.length ? (this._width = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._width; } /** @memberof Image @desc If *value* is specified, sets the x accessor to the specified function or number and returns the current class instance. @param {Function|Number} [*value*] @chainable @example function(d) { return d.x || 0; } */ }, { key: "x", value: function x(_) { return arguments.length ? (this._x = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._x; } /** @memberof Image @desc If *value* is specified, sets the y accessor to the specified function or number and returns the current class instance. @param {Function|Number} [*value*] @chainable @example function(d) { return d.y || 0; } */ }, { key: "y", value: function y(_) { return arguments.length ? (this._y = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._y; } }]); return Image; }(); /** @function pointDistanceSquared @desc Returns the squared euclidean distance between two points. @param {Array} p1 The first point, which should always be an `[x, y]` formatted Array. @param {Array} p2 The second point, which should always be an `[x, y]` formatted Array. @returns {Number} */ var pointDistanceSquared = (function (p1, p2) { var dx = p2[0] - p1[0], dy = p2[1] - p1[1]; return dx * dx + dy * dy; }); /** @function pointDistance @desc Calculates the pixel distance between two points. @param {Array} p1 The first point, which should always be an `[x, y]` formatted Array. @param {Array} p2 The second point, which should always be an `[x, y]` formatted Array. @returns {Number} */ var pointDistance = (function (p1, p2) { return Math.sqrt(pointDistanceSquared(p1, p2)); }); /** @class Shape @extends external:BaseClass @desc An abstracted class for generating shapes. */ var Shape = /*#__PURE__*/ function (_BaseClass) { _inherits(Shape, _BaseClass); /** @memberof Shape @desc Invoked when creating a new class instance, and sets any default parameters. @private */ function Shape() { var _this; var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "g"; _classCallCheck(this, Shape); _this = _possibleConstructorReturn(this, _getPrototypeOf(Shape).call(this)); _this._activeOpacity = 0.25; _this._activeStyle = { "stroke": function stroke(d, i) { var c = _this._fill(d, i); if (["transparent", "none"].includes(c)) c = _this._stroke(d, i); return d3Color.color(c).darker(1); }, "stroke-width": function strokeWidth(d, i) { var s = _this._strokeWidth(d, i) || 1; return s * 3; } }; _this._ariaLabel = d3plusCommon.constant(""); _this._backgroundImage = d3plusCommon.constant(false); _this._backgroundImageClass = new Image(); _this._data = []; _this._duration = 600; _this._fill = d3plusCommon.constant("black"); _this._fillOpacity = d3plusCommon.constant(1); _this._hoverOpacity = 0.5; _this._hoverStyle = { "stroke": function stroke(d, i) { var c = _this._fill(d, i); if (["transparent", "none"].includes(c)) c = _this._stroke(d, i); return d3Color.color(c).darker(0.5); }, "stroke-width": function strokeWidth(d, i) { var s = _this._strokeWidth(d, i) || 1; return s * 2; } }; _this._id = function (d, i) { return d.id !== void 0 ? d.id : i; }; _this._label = d3plusCommon.constant(false); _this._labelClass = new d3plusText.TextBox(); _this._labelConfig = { fontColor: function fontColor(d, i) { return d3plusColor.colorContrast(_this._fill(d, i)); }, fontSize: 12, padding: 5 }; _this._name = "Shape"; _this._opacity = d3plusCommon.constant(1); _this._pointerEvents = d3plusCommon.constant("visiblePainted"); _this._role = d3plusCommon.constant("presentation"); _this._rotate = d3plusCommon.constant(0); _this._rx = d3plusCommon.constant(0); _this._ry = d3plusCommon.constant(0); _this._scale = d3plusCommon.constant(1); _this._shapeRendering = d3plusCommon.constant("geometricPrecision"); _this._stroke = function (d, i) { return d3Color.color(_this._fill(d, i)).darker(1); }; _this._strokeDasharray = d3plusCommon.constant("0"); _this._strokeLinecap = d3plusCommon.constant("butt"); _this._strokeOpacity = d3plusCommon.constant(1); _this._strokeWidth = d3plusCommon.constant(0); _this._tagName = tagName; _this._textAnchor = d3plusCommon.constant("start"); _this._vectorEffect = d3plusCommon.constant("non-scaling-stroke"); _this._verticalAlign = d3plusCommon.constant("top"); _this._x = d3plusCommon.accessor("x", 0); _this._y = d3plusCommon.accessor("y", 0); return _this; } /** @memberof Shape @desc Given a specific data point and index, returns the aesthetic properties of the shape. @param {Object} *data point* @param {Number} *index* @private */ _createClass(Shape, [{ key: "_aes", value: function _aes() { return {}; } /** @memberof Shape @desc Adds event listeners to each shape group or hit area. @param {D3Selection} *update* The update cycle of the data binding. @private */ }, { key: "_applyEvents", value: function _applyEvents(handler) { var _this2 = this; var events = Object.keys(this._on); var _loop = function _loop(e) { handler.on(events[e], function (d, i) { if (!_this2._on[events[e]]) return; if (d.i !== void 0) i = d.i; if (d.nested && d.values) { var cursor = d3Selection.mouse(_this2._select.node()), values = d.values.map(function (d) { return pointDistance(cursor, [_this2._x(d, i), _this2._y(d, i)]); }); d = d.values[values.indexOf(d3Array.min(values))]; } _this2._on[events[e]].bind(_this2)(d, i); }); }; for (var e = 0; e < events.length; e++) { _loop(e); } } /** @memberof Shape @desc Provides the updated styling to the given shape elements. @param {HTMLElement} *elem* @param {Object} *style* @private */ }, { key: "_updateStyle", value: function _updateStyle(elem, style) { var that = this; if (elem.size() && elem.node().tagName === "g") elem = elem.selectAll("*"); /** @desc Determines whether a shape is a nested collection of data points, and uses the appropriate data and index for the given function context. @param {Object} *d* data point @param {Number} *i* index @private */ function styleLogic(d, i) { return typeof this !== "function" ? this : d.nested && d.key && d.values ? this(d.values[0], that._data.indexOf(d.values[0])) : this(d, i); } var styleObject = {}; for (var key in style) { if ({}.hasOwnProperty.call(style, key)) { styleObject[key] = styleLogic.bind(style[key]); } } elem.transition().duration(0).call(d3plusCommon.attrize, styleObject); } /** @memberof Shape @desc Provides the default styling to the shape elements. @param {HTMLElement} *elem* @private */ }, { key: "_applyStyle", value: function _applyStyle(elem) { var that = this; if (elem.size() && elem.node().tagName === "g") elem = elem.selectAll("*"); /** @desc Determines whether a shape is a nested collection of data points, and uses the appropriate data and index for the given function context. @param {Object} *d* data point @param {Number} *i* index @private */ function styleLogic(d, i) { return typeof this !== "function" ? this : d.nested && d.key && d.values ? this(d.values[0], that._data.indexOf(d.values[0])) : this(d, i); } elem.attr("fill", styleLogic.bind(this._fill)).attr("fill-opacity", styleLogic.bind(this._fillOpacity)).attr("rx", styleLogic.bind(this._rx)).attr("ry", styleLogic.bind(this._ry)).attr("stroke", styleLogic.bind(this._stroke)).attr("stroke-dasharray", styleLogic.bind(this._strokeDasharray)).attr("stroke-linecap", styleLogic.bind(this._strokeLinecap)).attr("stroke-opacity", styleLogic.bind(this._strokeOpacity)).attr("stroke-width", styleLogic.bind(this._strokeWidth)).attr("vector-effect", styleLogic.bind(this._vectorEffect)); } /** @memberof Shape @desc Calculates the transform for the group elements. @param {HTMLElement} *elem* @private */ }, { key: "_applyTransform", value: function _applyTransform(elem) { var _this3 = this; elem.attr("transform", function (d, i) { return "\n translate(".concat(d.__d3plusShape__ ? d.translate ? d.translate : "".concat(_this3._x(d.data, d.i), ",").concat(_this3._y(d.data, d.i)) : "".concat(_this3._x(d, i), ",").concat(_this3._y(d, i)), ")\n scale(").concat(d.__d3plusShape__ ? d.scale || _this3._scale(d.data, d.i) : _this3._scale(d, i), ")\n rotate(").concat(d.__d3plusShape__ ? d.rotate ? d.rotate : _this3._rotate(d.data || d, d.i) : _this3._rotate(d.data || d, d.i), ")"); }); } /** @memberof Shape @desc Checks for nested data and uses the appropriate variables for accessor functions. @param {HTMLElement} *elem* @private */ }, { key: "_nestWrapper", value: function _nestWrapper(method) { return function (d, i) { return method(d.__d3plusShape__ ? d.data : d, d.__d3plusShape__ ? d.i : i); }; } /** @memberof Shape @desc Modifies existing shapes to show active status. @private */ }, { key: "_renderActive", value: function _renderActive() { var that = this; this._group.selectAll(".d3plus-Shape, .d3plus-Image, .d3plus-textBox").each(function (d, i) { if (!d) d = {}; if (!d.parentNode) d.parentNode = this.parentNode; var parent = d.parentNode; if (d3Selection.select(this).classed("d3plus-textBox")) d = d.data; if (d.__d3plusShape__ || d.__d3plus__) { while (d && (d.__d3plusShape__ || d.__d3plus__)) { i = d.i; d = d.data; } } else i = that._data.indexOf(d); var group = !that._active || typeof that._active !== "function" || !that._active(d, i) ? parent : that._activeGroup.node(); if (group !== this.parentNode) { group.appendChild(this); if (this.className.baseVal.includes("d3plus-Shape")) { if (parent === group) d3Selection.select(this).call(that._applyStyle.bind(that));else d3Selection.select(this).call(that._updateStyle.bind(that, d3Selection.select(this), that._activeStyle)); } } }); // this._renderImage(); // this._renderLabels(); this._group.selectAll("g.d3plus-".concat(this._name, "-shape, g.d3plus-").concat(this._name, "-image, g.d3plus-").concat(this._name, "-text")).attr("opacity", this._hover ? this._hoverOpacity : this._active ? this._activeOpacity : 1); } /** @memberof Shape @desc Modifies existing shapes to show hover status. @private */ }, { key: "_renderHover", value: function _renderHover() { var that = this; this._group.selectAll("g.d3plus-".concat(this._name, "-shape, g.d3plus-").concat(this._name, "-image, g.d3plus-").concat(this._name, "-text, g.d3plus-").concat(this._name, "-hover")).selectAll(".d3plus-Shape, .d3plus-Image, .d3plus-textBox").each(function (d, i) { if (!d) d = {}; if (!d.parentNode) d.parentNode = this.parentNode; var parent = d.parentNode; if (d3Selection.select(this).classed("d3plus-textBox")) d = d.data; if (d.__d3plusShape__ || d.__d3plus__) { while (d && (d.__d3plusShape__ || d.__d3plus__)) { i = d.i; d = d.data; } } else i = that._data.indexOf(d); var group = !that._hover || typeof that._hover !== "function" || !that._hover(d, i) ? parent : that._hoverGroup.node(); if (group !== this.parentNode) group.appendChild(this); if (this.className.baseVal.includes("d3plus-Shape")) { if (parent === group) d3Selection.select(this).call(that._applyStyle.bind(that));else d3Selection.select(this).call(that._updateStyle.bind(that, d3Selection.select(this), that._hoverStyle)); } }); // this._renderImage(); // this._renderLabels(); this._group.selectAll("g.d3plus-".concat(this._name, "-shape, g.d3plus-").concat(this._name, "-image, g.d3plus-").concat(this._name, "-text")).attr("opacity", this._hover ? this._hoverOpacity : this._active ? this._activeOpacity : 1); } /** @memberof Shape @desc Adds background image to each shape group. @private */ }, { key: "_renderImage", value: function _renderImage() { var _this4 = this; var imageData = []; this._update.merge(this._enter).data().forEach(function (datum, i) { var aes = _this4._aes(datum, i); if (aes.r || aes.width && aes.height) { var d = datum; if (datum.nested && datum.key && datum.values) { d = datum.values[0]; i = _this4._data.indexOf(d); } var height = aes.r ? aes.r * 2 : aes.height, url = _this4._backgroundImage(d, i), width = aes.r ? aes.r * 2 : aes.width; if (url) { var x = d.__d3plusShape__ ? d.translate ? d.translate[0] : _this4._x(d.data, d.i) : _this4._x(d, i), y = d.__d3plusShape__ ? d.translate ? d.translate[1] : _this4._y(d.data, d.i) : _this4._y(d, i); if (aes.x) x += aes.x; if (aes.y) y += aes.y; if (d.__d3plusShape__) { d = d.data; i = d.i; } imageData.push({ __d3plus__: true, data: d, height: height, i: i, id: _this4._id(d, i), url: url, width: width, x: x + -width / 2, y: y + -height / 2 }); } } }); this._backgroundImageClass.data(imageData).duration(this._duration).pointerEvents("none").select(d3plusCommon.elem("g.d3plus-".concat(this._name, "-image"), { parent: this._group, update: { opacity: this._active ? this._activeOpacity : 1 } }).node()).render(); } /** @memberof Shape @desc Adds labels to each shape group. @private */ }, { key: "_renderLabels", value: function _renderLabels() { var _this5 = this; var labelData = []; this._update.merge(this._enter).data().forEach(function (datum, i) { var d = datum; if (datum.nested && datum.key && datum.values) { d = datum.values[0]; i = _this5._data.indexOf(d); } var labels = _this5._label(d, i); if (_this5._labelBounds && labels !== false && labels !== undefined && labels !== null) { var bounds = _this5._labelBounds(d, i, _this5._aes(datum, i)); if (bounds) { if (labels.constructor !== Array) labels = [labels]; var x = d.__d3plusShape__ ? d.translate ? d.translate[0] : _this5._x(d.data, d.i) : _this5._x(d, i), y = d.__d3plusShape__ ? d.translate ? d.translate[1] : _this5._y(d.data, d.i) : _this5._y(d, i); if (d.__d3plusShape__) { d = d.data; i = d.i; } for (var l = 0; l < labels.length; l++) { var b = bounds.constructor === Array ? bounds[l] : Object.assign({}, bounds); var rotate = _this5._rotate(d, i); var r = d.labelConfig && d.labelConfig.rotate ? d.labelConfig.rotate : bounds.angle !== undefined ? bounds.angle : 0; r += rotate; var rotateAnchor = rotate !== 0 ? [b.x * -1 || 0, b.y * -1 || 0] : [b.width / 2, b.height / 2]; labelData.push({ __d3plus__: true, data: d, height: b.height, l: l, id: "".concat(_this5._id(d, i), "_").concat(l), r: r, rotateAnchor: rotateAnchor, text: labels[l], width: b.width, x: x + b.x, y: y + b.y }); } } } }); this._labelClass.data(labelData).duration(this._duration).pointerEvents("none").rotate(function (d) { return d.__d3plus__ ? d.r : d.data.r; }).rotateAnchor(function (d) { return d.__d3plus__ ? d.rotateAnchor : d.data.rotateAnchor; }).select(d3plusCommon.elem("g.d3plus-".concat(this._name, "-text"), { parent: this._group, update: { opacity: this._active ? this._activeOpacity : 1 } }).node()).config(d3plusCommon.configPrep.bind(this)(this._labelConfig)).render(); } /** @memberof Shape @desc Renders the current Shape to the page. If a *callback* is specified, it will be called once the shapes are done drawing. @param {Function} [*callback*] @chainable */ }, { key: "render", value: function render(callback) { var _this6 = this; if (this._select === void 0) { this.select(d3Selection.select("body").append("svg").style("width", "".concat(window.innerWidth, "px")).style("height", "".concat(window.innerHeight, "px")).style("display", "block").node()); } this._transition = d3Transition.transition().duration(this._duration); var data = this._data, key = this._id; if (this._dataFilter) { data = this._dataFilter(data); if (data.key) key = data.key; } if (this._sort) { data = data.sort(function (a, b) { while (a.__d3plusShape__ || a.__d3plus__) { a = a.data; } while (b.__d3plusShape__ || b.__d3plus__) { b = b.data; } return _this6._sort(a, b); }); } d3Selection.selectAll("g.d3plus-".concat(this._name, "-hover > *, g.d3plus-").concat(this._name, "-active > *")).each(function (d) { if (d && d.parentNode) d.parentNode.appendChild(this);else this.parentNode.removeChild(this); }); // Makes the update state of the group selection accessible. this._group = d3plusCommon.elem("g.d3plus-".concat(this._name, "-group"), { parent: this._select }); var update = this._update = d3plusCommon.elem("g.d3plus-".concat(this._name, "-shape"), { parent: this._group, update: { opacity: this._active ? this._activeOpacity : 1 } }).selectAll(".d3plus-".concat(this._name)).data(data, key); // Orders and transforms the updating Shapes. update.order(); if (this._duration) { update.transition(this._transition).call(this._applyTransform.bind(this)); } else { update.call(this._applyTransform.bind(this)); } // Makes the enter state of the group selection accessible. var enter = this._enter = update.enter().append(this._tagName).attr("class", function (d, i) { return "d3plus-Shape d3plus-".concat(_this6._name, " d3plus-id-").concat(d3plusText.strip(_this6._nestWrapper(_this6._id)(d, i))); }).call(this._applyTransform.bind(this)).attr("aria-label", this._ariaLabel).attr("role", this._role).attr("opacity", this._nestWrapper(this._opacity)); var enterUpdate = enter.merge(update); var enterUpdateRender = enterUpdate.attr("shape-rendering", this._nestWrapper(this._shapeRendering)); if (this._duration) { enterUpdateRender = enterUpdateRender.attr("pointer-events", "none").transition(this._transition).transition().delay(100).attr("pointer-events", this._pointerEvents); } enterUpdateRender.attr("opacity", this._nestWrapper(this._opacity)); // Makes the exit state of the group selection accessible. var exit = this._exit = update.exit(); if (this._duration) exit.transition().delay(this._duration).remove();else exit.remove(); this._renderImage(); this._renderLabels(); this._hoverGroup = d3plusCommon.elem("g.d3plus-".concat(this._name, "-hover"), { parent: this._group }); this._activeGroup = d3plusCommon.elem("g.d3plus-".concat(this._name, "-active"), { parent: this._group }); var hitAreas = this._group.selectAll(".d3plus-HitArea").data(this._hitArea ? data : [], key); hitAreas.order().call(this._applyTransform.bind(this)); var isLine = this._name === "Line"; isLine && this._path.curve(paths["curve".concat(this._curve.charAt(0).toUpperCase()).concat(this._curve.slice(1))]).defined(this._defined).x(this._x).y(this._y); var hitEnter = hitAreas.enter().append(isLine ? "path" : "rect").attr("class", function (d, i) { return "d3plus-HitArea d3plus-id-".concat(d3plusText.strip(_this6._nestWrapper(_this6._id)(d, i))); }).attr("fill", "black").attr("stroke", "black").attr("pointer-events", "painted").attr("opacity", 0).call(this._applyTransform.bind(this)); var that = this; var hitUpdates = hitAreas.merge(hitEnter).each(function (d) { var i = that._data.indexOf(d); var h = that._hitArea(d, i, that._aes(d, i)); return h && !(that._name === "Line" && parseFloat(that._strokeWidth(d, i)) > 10) ? d3Selection.select(this).call(d3plusCommon.attrize, h) : d3Selection.select(this).remove(); }); hitAreas.exit().remove(); this._applyEvents(this._hitArea ? hitUpdates : enterUpdate); setTimeout(function () { if (_this6._active) _this6._renderActive();else if (_this6._hover) _this6._renderHover(); if (callback) callback(); }, this._duration + 100); return this; } /** @memberof Shape @desc If *value* is specified, sets the highlight accessor to the specified function and returns the current class instance. @param {Function} [*value*] @chainable */ }, { key: "active", value: function active(_) { if (!arguments.length || _ === undefined) return this._active; this._active = _; if (this._group) { // this._renderImage(); // this._renderLabels(); this._renderActive(); } return this; } /** @memberof Shape @desc When shapes are active, this is the opacity of any shape that is not active. @param {Number} *value* = 0.25 @chainable */ }, { key: "activeOpacity", value: function activeOpacity(_) { return arguments.length ? (this._activeOpacity = _, this) : this._activeOpacity; } /** @memberof Shape @desc The style to apply to active shapes. @param {Object} *value* @chainable */ }, { key: "activeStyle", value: function activeStyle(_) { return arguments.length ? (this._activeStyle = d3plusCommon.assign({}, this._activeStyle, _), this) : this._activeStyle; } /** @memberof Shape @desc If *value* is specified, sets the aria-label attribute to the specified function or string and returns the current class instance. @param {Function|String} *value* @chainable */ }, { key: "ariaLabel", value: function ariaLabel(_) { return _ !== undefined ? (this._ariaLabel = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._ariaLabel; } /** @memberof Shape @desc If *value* is specified, sets the background-image accessor to the specified function or string and returns the current class instance. @param {Function|String} [*value* = false] @chainable */ }, { key: "backgroundImage", value: function backgroundImage(_) { return arguments.length ? (this._backgroundImage = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._backgroundImage; } /** @memberof Shape @desc If *data* is specified, sets the data array to the specified array and returns the current class instance. If *data* is not specified, returns the current data array. A shape will be drawn for each object in the array. @param {Array} [*data* = []] @chainable */ }, { key: "data", value: function data(_) { return arguments.length ? (this._data = _, this) : this._data; } /** @memberof Shape @desc If *ms* is specified, sets the animation duration to the specified number and returns the current class instance. If *ms* is not specified, returns the current animation duration. @param {Number} [*ms* = 600] @chainable */ }, { key: "duration", value: function duration(_) { return arguments.length ? (this._duration = _, this) : this._duration; } /** @memberof Shape @desc If *value* is specified, sets the fill accessor to the specified function or string and returns the current class instance. @param {Function|String} [*value* = "black"] @chainable */ }, { key: "fill", value: function fill(_) { return arguments.length ? (this._fill = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._fill; } /** @memberof Shape @desc Defines the "fill-opacity" attribute for the shapes. @param {Function|Number} [*value* = 1] @chainable */ }, { key: "fillOpacity", value: function fillOpacity(_) { return arguments.length ? (this._fillOpacity = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._fillOpacity; } /** @memberof Shape @desc If *value* is specified, sets the highlight accessor to the specified function and returns the current class instance. @param {Function} [*value*] @chainable */ }, { key: "hover", value: function hover(_) { if (!arguments.length || _ === void 0) return this._hover; this._hover = _; if (this._group) { // this._renderImage(); // this._renderLabels(); this._renderHover(); } return this; } /** @memberof Shape @desc The style to apply to hovered shapes. @param {Object} *value* @chainable */ }, { key: "hoverStyle", value: function hoverStyle(_) { return arguments.length ? (this._hoverStyle = d3plusCommon.assign({}, this._hoverStyle, _), this) : this._hoverStyle; } /** @memberof Shape @desc If *value* is specified, sets the hover opacity to the specified function and returns the current class instance. @param {Number} [*value* = 0.5] @chainable */ }, { key: "hoverOpacity", value: function hoverOpacity(_) { return arguments.length ? (this._hoverOpacity = _, this) : this._hoverOpacity; } /** @memberof Shape @desc If *bounds* is specified, sets the mouse hit area to the specified function and returns the current class instance. If *bounds* is not specified, returns the current mouse hit area accessor. @param {Function} [*bounds*] The given function is passed the data point, index, and internally defined properties of the shape and should return an object containing the following values: `width`, `height`, `x`, `y`. @chainable @example function(d, i, shape) { return { "width": shape.width, "height": shape.height, "x": -shape.width / 2, "y": -shape.height / 2 }; } */ }, { key: "hitArea", value: function hitArea(_) { return arguments.length ? (this._hitArea = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._hitArea; } /** @memberof Shape @desc If *value* is specified, sets the id accessor to the specified function and returns the current class instance. @param {Function} [*value*] @chainable */ }, { key: "id", value: function id(_) { return arguments.length ? (this._id = _, this) : this._id; } /** @memberof Shape @desc If *value* is specified, sets the label accessor to the specified function or string and returns the current class instance. @param {Function|String|Array} [*value*] @chainable */ }, { key: "label", value: function label(_) { return arguments.length ? (this._label = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._label; } /** @memberof Shape @desc If *bounds* is specified, sets the label bounds to the specified function and returns the current class instance. If *bounds* is not specified, returns the current inner bounds accessor. @param {Function} [*bounds*] The given function is passed the data point, index, and internally defined properties of the shape and should return an object containing the following values: `width`, `height`, `x`, `y`. If an array is returned from the function, each value will be used in conjunction with each label. @chainable @example function(d, i, shape) { return { "width": shape.width, "height": shape.height, "x": -shape.width / 2, "y": -shape.height / 2 }; } */ }, { key: "labelBounds", value: function labelBounds(_) { return arguments.length ? (this._labelBounds = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._labelBounds; } /** @memberof Shape @desc A pass-through to the config method of the TextBox class used to create a shape's labels. @param {Object} [*value*] @chainable */ }, { key: "labelConfig", value: function labelConfig(_) { return arguments.length ? (this._labelConfig = d3plusCommon.assign(this._labelConfig, _), this) : this._labelConfig; } /** @memberof Shape @desc If *value* is specified, sets the opacity accessor to the specified function or number and returns the current class instance. @param {Number} [*value* = 1] @chainable */ }, { key: "opacity", value: function opacity(_) { return arguments.length ? (this._opacity = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._opacity; } /** @memberof Shape @desc If *value* is specified, sets the pointerEvents accessor to the specified function or string and returns the current class instance. @param {String} [*value*] @chainable */ }, { key: "pointerEvents", value: function pointerEvents(_) { return arguments.length ? (this._pointerEvents = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._pointerEvents; } /** @memberof Shape @desc If *value* is specified, sets the role attribute to the specified function or string and returns the current class instance. @param {Function|String} *value* @chainable */ }, { key: "role", value: function role(_) { return _ !== undefined ? (this._role = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._role; } /** @memberof Shape @desc If *value* is specified, sets the rotate accessor to the specified function or number and returns the current class instance. @param {Function|Number} [*value* = 0] @chainable */ }, { key: "rotate", value: function rotate(_) { return arguments.length ? (this._rotate = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._rotate; } /** @memberof Shape @desc Defines the "rx" attribute for the shapes. @param {Function|Number} [*value* = 0] @chainable */ }, { key: "rx", value: function rx(_) { return arguments.length ? (this._rx = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._rx; } /** @memberof Shape @desc Defines the "rx" attribute for the shapes. @param {Function|Number} [*value* = 0] @chainable */ }, { key: "ry", value: function ry(_) { return arguments.length ? (this._ry = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._ry; } /** @memberof Shape @desc If *value* is specified, sets the scale accessor to the specified function or string and returns the current class instance. @param {Function|Number} [*value* = 1] @chainable */ }, { key: "scale", value: function scale(_) { return arguments.length ? (this._scale = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._scale; } /** @memberof Shape @desc If *selector* is specified, sets the SVG container element to the specified d3 selector or DOM element and returns the current class instance. If *selector* is not specified, returns the current SVG container element. @param {String|HTMLElement} [*selector* = d3.select("body").append("svg")] @chainable */ }, { key: "select", value: function select(_) { return arguments.length ? (this._select = d3Selection.select(_), this) : this._select; } /** @memberof Shape @desc If *value* is specified, sets the shape-rendering accessor to the specified function or string and returns the current class instance. @param {Function|String} [*value* = "geometricPrecision"] @chainable @example function(d) { return d.x; } */ }, { key: "shapeRendering", value: function shapeRendering(_) { return arguments.length ? (this._shapeRendering = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._shapeRendering; } /** @memberof Shape @desc If *value* is specified, sets the sort comparator to the specified function and returns the current class instance. @param {false|Function} [*value* = []] @chainable */ }, { key: "sort", value: function sort(_) { return arguments.length ? (this._sort = _, this) : this._sort; } /** @memberof Shape @desc If *value* is specified, sets the stroke accessor to the specified function or string and returns the current class instance. @param {Function|String} [*value* = "black"] @chainable */ }, { key: "stroke", value: function stroke(_) { return arguments.length ? (this._stroke = typeof _ === "function" ? _ : d3plusCommon.constant(_), this) : this._stroke; } /**