UNPKG

d3plus-shape

Version:

Fancy SVG shapes for visualizations

448 lines (400 loc) 16.5 kB
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 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 _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 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 _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } import { max, min, quantile } from "d3-array"; import { nest } from "d3-collection"; import { select as _select } from "d3-selection"; import { accessor, assign, BaseClass, configPrep, constant, merge, elem } from "d3plus-common"; import Circle from "./Circle"; import Rect from "./Rect"; import Whisker from "./Whisker"; var shapes = { Circle: Circle, Rect: Rect }; /** @class Box @extends BaseClass @desc Creates SVG box based on an array of data. */ var Box = /*#__PURE__*/ function (_BaseClass) { _inherits(Box, _BaseClass); /** @memberof Box @desc Invoked when creating a new class instance, and overrides any default parameters inherited from BaseClass. @private */ function Box() { var _this; _classCallCheck(this, Box); _this = _possibleConstructorReturn(this, _getPrototypeOf(Box).call(this)); _this._medianConfig = { fill: constant("black") }; _this._orient = accessor("orient", "vertical"); _this._outlier = accessor("outlier", "Circle"); _this._outlierConfig = { Circle: { r: accessor("r", 5) }, Rect: { height: function height(d, i) { return _this._orient(d, i) === "vertical" ? 5 : 20; }, width: function width(d, i) { return _this._orient(d, i) === "vertical" ? 20 : 5; } } }; _this._rectConfig = { fill: constant("white"), stroke: constant("black"), strokeWidth: constant(1) }; _this._rectWidth = constant(50); _this._whiskerConfig = {}; _this._whiskerMode = ["tukey", "tukey"]; _this._x = accessor("x", 250); _this._y = accessor("y", 250); return _this; } /** @memberof Box @desc Draws the Box. @param {Function} [*callback*] @chainable */ _createClass(Box, [{ key: "render", value: function render() { var _this2 = this; if (this._select === void 0) { this.select(_select("body").append("svg").style("width", "".concat(window.innerWidth, "px")).style("height", "".concat(window.innerHeight, "px")).style("display", "block").node()); } var outlierData = []; var filteredData = nest().key(function (d, i) { return _this2._orient(d, i) === "vertical" ? _this2._x(d, i) : _this2._y(d, i); }).entries(this._data).map(function (d) { d.data = merge(d.values); d.i = _this2._data.indexOf(d.values[0]); d.orient = _this2._orient(d.data, d.i); var values = d.values.map(d.orient === "vertical" ? _this2._y : _this2._x); values.sort(function (a, b) { return a - b; }); d.first = quantile(values, 0.25); d.median = quantile(values, 0.50); d.third = quantile(values, 0.75); var mode = _this2._whiskerMode; if (mode[0] === "tukey") { d.lowerLimit = d.first - (d.third - d.first) * 1.5; if (d.lowerLimit < min(values)) d.lowerLimit = min(values); } else if (mode[0] === "extent") d.lowerLimit = min(values);else if (typeof mode[0] === "number") d.lowerLimit = quantile(values, mode[0]); if (mode[1] === "tukey") { d.upperLimit = d.third + (d.third - d.first) * 1.5; if (d.upperLimit > max(values)) d.upperLimit = max(values); } else if (mode[1] === "extent") d.upperLimit = max(values);else if (typeof mode[1] === "number") d.upperLimit = quantile(values, mode[1]); var rectLength = d.third - d.first; // Compute values for vertical orientation. if (d.orient === "vertical") { d.height = rectLength; d.width = _this2._rectWidth(d.data, d.i); d.x = _this2._x(d.data, d.i); d.y = d.first + rectLength / 2; } else if (d.orient === "horizontal") { // Compute values for horizontal orientation. d.height = _this2._rectWidth(d.data, d.i); d.width = rectLength; d.x = d.first + rectLength / 2; d.y = _this2._y(d.data, d.i); } // Compute data for outliers. d.values.forEach(function (eachValue, index) { var value = d.orient === "vertical" ? _this2._y(eachValue, index) : _this2._x(eachValue, index); if (value < d.lowerLimit || value > d.upperLimit) { var dataObj = {}; dataObj.__d3plus__ = true; dataObj.data = eachValue; dataObj.i = index; dataObj.outlier = _this2._outlier(eachValue, index); if (d.orient === "vertical") { dataObj.x = d.x; dataObj.y = value; outlierData.push(dataObj); } else if (d.orient === "horizontal") { dataObj.y = d.y; dataObj.x = value; outlierData.push(dataObj); } } }); d.__d3plus__ = true; return d; }); // Draw box. this._box = new Rect().data(filteredData).x(function (d) { return d.x; }).y(function (d) { return d.y; }).select(elem("g.d3plus-Box", { parent: this._select }).node()).config(configPrep.bind(this)(this._rectConfig, "shape")).render(); // Draw median. this._median = new Rect().data(filteredData).x(function (d) { return d.orient === "vertical" ? d.x : d.median; }).y(function (d) { return d.orient === "vertical" ? d.median : d.y; }).height(function (d) { return d.orient === "vertical" ? 1 : d.height; }).width(function (d) { return d.orient === "vertical" ? d.width : 1; }).select(elem("g.d3plus-Box-Median", { parent: this._select }).node()).config(configPrep.bind(this)(this._medianConfig, "shape")).render(); // Draw 2 lines using Whisker class. // Construct coordinates for whisker startpoints and push it to the whiskerData. var whiskerData = []; filteredData.forEach(function (d, i) { var x = d.x; var y = d.y; var topLength = d.first - d.lowerLimit; var bottomLength = d.upperLimit - d.third; if (d.orient === "vertical") { var topY = y - d.height / 2; var bottomY = y + d.height / 2; whiskerData.push({ __d3plus__: true, data: d, i: i, x: x, y: topY, length: topLength, orient: "top" }, { __d3plus__: true, data: d, i: i, x: x, y: bottomY, length: bottomLength, orient: "bottom" }); } else if (d.orient === "horizontal") { var topX = x + d.width / 2; var bottomX = x - d.width / 2; whiskerData.push({ __d3plus__: true, data: d, i: i, x: topX, y: y, length: bottomLength, orient: "right" }, { __d3plus__: true, data: d, i: i, x: bottomX, y: y, length: topLength, orient: "left" }); } }); // Draw whiskers. this._whisker = new Whisker().data(whiskerData).select(elem("g.d3plus-Box-Whisker", { parent: this._select }).node()).config(configPrep.bind(this)(this._whiskerConfig, "shape")).render(); // Draw outliers. this._whiskerEndpoint = []; nest().key(function (d) { return d.outlier; }).entries(outlierData).forEach(function (shapeData) { var shapeName = shapeData.key; _this2._whiskerEndpoint.push(new shapes[shapeName]().data(shapeData.values).select(elem("g.d3plus-Box-Outlier-".concat(shapeName), { parent: _this2._select }).node()).config(configPrep.bind(_this2)(_this2._outlierConfig, "shape", shapeName)).render()); }); return this; } /** @memberof Box @desc Sets the highlight accessor to the Shape class's active function. @param {Function} [*value*] @chainable */ }, { key: "active", value: function active(_) { if (this._box) this._box.active(_); if (this._median) this._median.active(_); if (this._whisker) this._whisker.active(_); if (this._whiskerEndpoint) this._whiskerEndpoint.forEach(function (endPoint) { return endPoint.active(_); }); } /** @memberof Box @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. @param {Array} [*data* = []] @chainable */ }, { key: "data", value: function data(_) { return arguments.length ? (this._data = _, this) : this._data; } /** @memberof Box @desc Sets the highlight accessor to the Shape class's hover function. @param {Function} [*value*] @chainable */ }, { key: "hover", value: function hover(_) { if (this._box) this._box.hover(_); if (this._median) this._median.hover(_); if (this._whisker) this._whisker.hover(_); if (this._whiskerEndpoint) this._whiskerEndpoint.forEach(function (endPoint) { return endPoint.hover(_); }); } /** @memberof Box @desc If *value* is specified, sets the config method for median and returns the current class instance. @param {Object} [*value*] @chainable */ }, { key: "medianConfig", value: function medianConfig(_) { return arguments.length ? (this._medianConfig = assign(this._medianConfig, _), this) : this._medianConfig; } /** @memberof Box @desc If *value* is specified, sets the orientation to the specified value. If *value* is not specified, returns the current orientation. @param {Function|String} [*value* = "vertical"] Accepts "vertical" or "horizontal" @chainable */ }, { key: "orient", value: function orient(_) { return arguments.length ? (this._orient = typeof _ === "function" ? _ : constant(_), this) : this._orient; } /** @memberof Box @desc If *value* is specified, sets the outlier accessor to the specified function or string and returns the current class instance. @param {Function|String} @chainable */ }, { key: "outlier", value: function outlier(_) { return arguments.length ? (this._outlier = typeof _ === "function" ? _ : constant(_), this) : this._outlier; } /** @memberof Box @desc If *value* is specified, sets the config method for each outlier point and returns the current class instance. @param {Object} [*value*] @chainable */ }, { key: "outlierConfig", value: function outlierConfig(_) { return arguments.length ? (this._outlierConfig = assign(this._outlierConfig, _), this) : this._outlierConfig; } /** @memberof Box @desc If *value* is specified, sets the config method for rect shape and returns the current class instance. @param {Object} [*value*] @chainable */ }, { key: "rectConfig", value: function rectConfig(_) { return arguments.length ? (this._rectConfig = assign(this._rectConfig, _), this) : this._rectConfig; } /** @memberof Box @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: "rectWidth", value: function rectWidth(_) { return arguments.length ? (this._rectWidth = typeof _ === "function" ? _ : constant(_), this) : this._rectWidth; } /** @memberof Box @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 = _select(_), this) : this._select; } /** @memberof Box @desc If *value* is specified, sets the config method for whisker and returns the current class instance. @param {Object} [*value*] @chainable */ }, { key: "whiskerConfig", value: function whiskerConfig(_) { return arguments.length ? (this._whiskerConfig = assign(this._whiskerConfig, _), this) : this._whiskerConfig; } /** @memberof Box @desc Determines the value used for each whisker. Can be passed a single value to apply for both whiskers, or an Array of 2 values for the lower and upper whiskers (in that order). Accepted values are `"tukey"`, `"extent"`, or a Number representing a quantile. @param {String|Number|String[]|Number[]} [*value* = "tukey"] @chainable */ }, { key: "whiskerMode", value: function whiskerMode(_) { return arguments.length ? (this._whiskerMode = _ instanceof Array ? _ : [_, _], this) : this._whiskerMode; } /** @memberof Box @desc If *value* is specified, sets the x axis to the specified function or number and returns the current class instance. @param {Function|Number} [*value*] @chainable @example function(d) { return d.x; } */ }, { key: "x", value: function x(_) { return arguments.length ? (this._x = typeof _ === "function" ? _ : accessor(_), this) : this._x; } /** @memberof Box @desc If *value* is specified, sets the y axis to the specified function or number and returns the current class instance. @param {Function|Number} [*value*] @chainable @example function(d) { return d.y; } */ }, { key: "y", value: function y(_) { return arguments.length ? (this._y = typeof _ === "function" ? _ : accessor(_), this) : this._y; } }]); return Box; }(BaseClass); export { Box as default };