d3plus-shape
Version:
Fancy SVG shapes for visualizations
1,362 lines (1,184 loc) • 171 kB
JavaScript
/*
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;
}
/**