d3plus-network
Version:
Javascript network visualizations built upon d3 modules.
344 lines (332 loc) • 15.9 kB
JavaScript
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _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, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _get() { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get.bind(); } 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(arguments.length < 3 ? target : receiver); } return desc.value; }; } return _get.apply(this, arguments); }
function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }
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 } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } 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 _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
/**
@external Viz
@see https://github.com/d3plus/d3plus-viz#Viz
*/
import { nest } from "d3-collection";
import { sankey, sankeyCenter, sankeyJustify, sankeyLeft, sankeyLinkHorizontal, sankeyRight } from "d3-sankey";
var sankeyAligns = {
center: sankeyCenter,
justify: sankeyJustify,
left: sankeyLeft,
right: sankeyRight
};
import { accessor, assign, configPrep, constant, elem } from "d3plus-common";
import { Path } from "d3plus-shape";
import * as shapes from "d3plus-shape";
import { addToQueue, Viz } from "d3plus-viz";
/**
@class Sankey
@extends external:Viz
@desc Creates a sankey visualization based on a defined set of nodes and links. [Click here](http://d3plus.org/examples/d3plus-network/sankey-diagram/) for help getting started using the Sankey class.
*/
var Sankey = /*#__PURE__*/function (_Viz) {
_inherits(Sankey, _Viz);
var _super = _createSuper(Sankey);
/**
@memberof Sankey
@desc Invoked when creating a new class instance, and sets any default parameters.
@private
*/
function Sankey() {
var _this;
_classCallCheck(this, Sankey);
_this = _super.call(this);
_this._nodeId = accessor("id");
_this._links = accessor("links");
_this._linksSource = "source";
_this._linksTarget = "target";
_this._noDataMessage = false;
_this._nodes = accessor("nodes");
_this._nodeAlign = sankeyAligns.justify;
_this._nodePadding = 8;
_this._nodeWidth = 30;
_this._on.mouseenter = function () {};
_this._on["mouseleave.shape"] = function () {
_this.hover(false);
};
var defaultMouseMove = _this._on["mousemove.shape"];
_this._on["mousemove.shape"] = function (d, i, x, event) {
defaultMouseMove(d, i, x, event);
if (_this._focus && _this._focus === d.id) {
_this.hover(false);
_this._on.mouseenter.bind(_assertThisInitialized(_this))(d, i, x, event);
_this._focus = undefined;
} else {
var id = _this._nodeId(d, i),
node = _this._nodeLookup[id],
nodeLookup = Object.keys(_this._nodeLookup).reduce(function (all, item) {
all[_this._nodeLookup[item]] = !isNaN(item) ? parseInt(item, 10) : item;
return all;
}, {});
var links = _this._linkLookup[node];
var filterIds = [id];
links.forEach(function (l) {
filterIds.push(nodeLookup[l]);
});
_this.hover(function (h, x) {
if (h.source && h.target) {
return h.source.id === id || h.target.id === id;
} else {
return filterIds.includes(_this._nodeId(h, x));
}
});
}
};
_this._path = sankeyLinkHorizontal();
_this._sankey = sankey();
_this._shape = constant("Rect");
_this._shapeConfig = assign(_this._shapeConfig, {
Path: {
fill: "none",
hoverStyle: {
"stroke-width": function strokeWidth(d) {
return Math.max(1, Math.abs(d.source.y1 - d.source.y0) * (d.value / d.source.value) - 2);
}
},
label: false,
stroke: "#DBDBDB",
strokeOpacity: 0.5,
strokeWidth: function strokeWidth(d) {
return Math.max(1, Math.abs(d.source.y1 - d.source.y0) * (d.value / d.source.value) - 2);
}
},
Rect: {}
});
_this._value = constant(1);
return _this;
}
/**
Extends the draw behavior of the abstract Viz class.
@private
*/
_createClass(Sankey, [{
key: "_draw",
value: function _draw(callback) {
var _this2 = this;
_get(_getPrototypeOf(Sankey.prototype), "_draw", this).call(this, callback);
var height = this._height - this._margin.top - this._margin.bottom,
width = this._width - this._margin.left - this._margin.right;
var _nodes = Array.isArray(this._nodes) ? this._nodes : this._links.reduce(function (all, d) {
if (!all.includes(d[_this2._linksSource])) all.push(d[_this2._linksSource]);
if (!all.includes(d[_this2._linksTarget])) all.push(d[_this2._linksTarget]);
return all;
}, []).map(function (id) {
return {
id: id
};
});
var nodes = _nodes.map(function (n, i) {
return {
__d3plus__: true,
data: n,
i: i,
id: _this2._nodeId(n, i),
node: n,
shape: "Rect"
};
});
var nodeLookup = this._nodeLookup = nodes.reduce(function (obj, d, i) {
obj[d.id] = i;
return obj;
}, {});
var links = this._links.map(function (link, i) {
var check = [_this2._linksSource, _this2._linksTarget];
var linkLookup = check.reduce(function (result, item) {
result[item] = nodeLookup[link[item]];
return result;
}, {});
return {
source: linkLookup[_this2._linksSource],
target: linkLookup[_this2._linksTarget],
value: _this2._value(link, i)
};
});
this._linkLookup = links.reduce(function (obj, d) {
if (!obj[d.source]) obj[d.source] = [];
obj[d.source].push(d.target);
if (!obj[d.target]) obj[d.target] = [];
obj[d.target].push(d.source);
return obj;
}, {});
var transform = "translate(".concat(this._margin.left, ", ").concat(this._margin.top, ")");
this._sankey.nodeAlign(this._nodeAlign).nodePadding(this._nodePadding).nodeWidth(this._nodeWidth).nodes(nodes).links(links).size([width, height])();
this._shapes.push(new Path().config(this._shapeConfig.Path).data(links).d(this._path).select(elem("g.d3plus-Links", {
parent: this._select,
enter: {
transform: transform
},
update: {
transform: transform
}
}).node()).render());
nest().key(function (d) {
return d.shape;
}).entries(nodes).forEach(function (d) {
_this2._shapes.push(new shapes[d.key]().data(d.values).height(function (d) {
return d.y1 - d.y0;
}).width(function (d) {
return d.x1 - d.x0;
}).x(function (d) {
return (d.x1 + d.x0) / 2;
}).y(function (d) {
return (d.y1 + d.y0) / 2;
}).select(elem("g.d3plus-sankey-nodes", {
parent: _this2._select,
enter: {
transform: transform
},
update: {
transform: transform
}
}).node()).config(configPrep.bind(_this2)(_this2._shapeConfig, "shape", d.key)).render());
});
return this;
}
/**
@memberof Sankey
@desc If *value* is specified, sets the hover method to the specified function and returns the current class instance.
@param {Function} [*value*]
@chainable
*/
}, {
key: "hover",
value: function hover(_) {
this._hover = _;
this._shapes.forEach(function (s) {
return s.hover(_);
});
if (this._legend) this._legendClass.hover(_);
return this;
}
/**
@memberof Sankey
@desc A predefined *Array* of edges that connect each object passed to the [node](#Sankey.node) method. The `source` and `target` keys in each link need to map to the nodes in one of one way:
1. A *String* value matching the `id` of the node.
The value passed should be an *Array* of data. An optional formatting function can be passed as a second argument to this method. This custom function will be passed the data that has been loaded, as long as there are no errors. This function should return the final links *Array*.
@param {Array} *links* = []
@chainable
*/
}, {
key: "links",
value: function links(_, f) {
if (arguments.length) {
addToQueue.bind(this)(_, f, "links");
return this;
}
return this._links;
}
/**
@memberof Sankey
@desc The key inside of each link Object that references the source node.
@param {String} [*value* = "source"]
@chainable
*/
}, {
key: "linksSource",
value: function linksSource(_) {
return arguments.length ? (this._linksSource = _, this) : this._linksSource;
}
/**
@memberof Sankey
@desc The key inside of each link Object that references the target node.
@param {String} [*value* = "target"]
@chainable
*/
}, {
key: "linksTarget",
value: function linksTarget(_) {
return arguments.length ? (this._linksTarget = _, this) : this._linksTarget;
}
/**
@memberof Sankey
@desc Sets the nodeAlign property of the sankey layout, which can either be "left", "right", "center", or "justify".
@param {Function|String} [*value* = "justify"]
@chainable
*/
}, {
key: "nodeAlign",
value: function nodeAlign(_) {
return arguments.length ? (this._nodeAlign = typeof _ === "function" ? _ : sankeyAligns[_], this) : this._nodeAlign;
}
/**
@memberof Sankey
@desc If *value* is specified, sets the node id accessor(s) to the specified array of values and returns the current class instance. If *value* is not specified, returns the current node group accessor.
@param {String} [*value* = "id"]
@chainable
*/
}, {
key: "nodeId",
value: function nodeId(_) {
return arguments.length ? (this._nodeId = typeof _ === "function" ? _ : accessor(_), this) : this._nodeId;
}
/**
@memberof Sankey
@desc The list of nodes to be used for drawing the network. The value passed must be an *Array* of data.
Additionally, a custom formatting function can be passed as a second argument to this method. This custom function will be passed the data that has been loaded, as long as there are no errors. This function should return the final node *Array*.
@param {Array} *nodes* = []
@chainable
*/
}, {
key: "nodes",
value: function nodes(_, f) {
if (arguments.length) {
addToQueue.bind(this)(_, f, "nodes");
return this;
}
return this._nodes;
}
/**
@memberof Sankey
@desc If *value* is specified, sets the padding of the node and returns the current class instance. If *value* is not specified, returns the current nodePadding. By default, the nodePadding size is 8.
@param {Number} [*value* = 8]
@chainable
*/
}, {
key: "nodePadding",
value: function nodePadding(_) {
return arguments.length ? (this._nodePadding = _, this) : this._nodePadding;
}
/**
@memberof Sankey
@desc If *value* is specified, sets the width of the node and returns the current class instance. If *value* is not specified, returns the current nodeWidth. By default, the nodeWidth size is 30.
@param {Number} [*value* = 30]
@chainable
*/
}, {
key: "nodeWidth",
value: function nodeWidth(_) {
return arguments.length ? (this._nodeWidth = _, this) : this._nodeWidth;
}
/**
@memberof Sankey
@desc If *value* is specified, sets the width of the links and returns the current class instance. If *value* is not specified, returns the current value accessor.
@param {Function|Number} *value*
@example
function value(d) {
return d.value;
}
*/
}, {
key: "value",
value: function value(_) {
return arguments.length ? (this._value = typeof _ === "function" ? _ : accessor(_), this) : this._value;
}
}]);
return Sankey;
}(Viz);
export { Sankey as default };