d3plus-shape
Version:
Fancy SVG shapes for visualizations
448 lines (400 loc) • 16.5 kB
JavaScript
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 };