@metacell/geppetto-meta-ui
Version:
React components from geppetto-meta to create neuroscience applications and visualize data.
468 lines (463 loc) • 15.4 kB
JavaScript
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
// import * as d3 from "d3";
var d3 = require("d3");
import * as util from "../../utilities";
export var Chord = /*#__PURE__*/function () {
function Chord(calculateNodeDegrees) {
_classCallCheck(this, Chord);
this.calculateNodeDegrees = calculateNodeDegrees;
}
return _createClass(Chord, [{
key: "draw",
value: function draw(context) {
if (this.calculateNodeDegrees !== null) {
context.calculateNodeDegrees(this.calculateNodeDegrees);
}
var matrix = this.generateChordMatrix(context);
var pi = Math.PI;
var halfpi = pi / 2;
var dr = 15;
var innerRadius = Math.min(context.width, context.height) * .41,
outerRadius = innerRadius * 1.05;
// if no custom colors set then use d3 provided color scheme
var fill = context.nodeColormap.range ? context.nodeColormap : d3.scaleOrdinal(d3.schemeCategory20);
var svg = context.svg.append("g").attr("transform", "translate(" + context.width / 2 + "," + context.height / 2 + ")");
var chord = this.layout_chord().padding(.05).matrix(matrix);
chord.groups().forEach(function (g, i) {
g.id = context.dataset.nodeTypes[i];
});
var slice = svg.append("g").selectAll("path").data(chord.groups).enter().append("path").style("fill", function (d) {
return fill(d.id);
}).style("stroke", function (d) {
return fill(d.id);
}).attr("d", d3.arc().innerRadius(innerRadius).outerRadius(outerRadius)).on("mouseover", fade_over(.1)).on("mouseout", fade_out(1));
slice.append("title").text(function (d) {
return d.id;
});
var svg_asymChord = function svg_asymChord() {
var source = function source(d) {
return d.source;
},
target = function target(d) {
return d.target;
},
radius = function radius(d) {
return d.radius;
},
startAngle = function startAngle(d) {
return d.startAngle;
},
endAngle = function endAngle(d) {
return d.endAngle;
};
function chord(d, i) {
var s = subgroup(this, source, d, i),
t = subgroup(this, target, d, i);
// var [s_p0s, s_p1s] = shift_radius(s)
var shifted = shift_radius(t);
var t_p0s = shifted[0];
var t_p1s = shifted[1];
return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t_p0s) + arc(t.r - dr, t_p1s, t.a1 - t.a0) + curve(t.r - dr, t_p1s, s.r, s.p0)) + "Z";
}
function shift_radius(d) {
return [[d.p0[0] - dr * Math.cos(d.a0), d.p0[1] - dr * Math.sin(d.a0)], [d.p1[0] - dr * Math.cos(d.a1), d.p1[1] - dr * Math.sin(d.a1)]];
}
function subgroup(self, f, d, i) {
var subgroup = f.call(self, d, i),
r = radius.call(self, subgroup, i),
a0 = startAngle.call(self, subgroup, i) - halfpi,
a1 = endAngle.call(self, subgroup, i) - halfpi;
return {
r: r,
a0: a0,
a1: a1,
p0: [r * Math.cos(a0), r * Math.sin(a0)],
p1: [r * Math.cos(a1), r * Math.sin(a1)]
};
}
function equals(a, b) {
return a.a0 === b.a0 && a.a1 === b.a1;
}
function arc(r, p, a) {
return "A" + r + "," + r + " 0 " + +(a > pi) + ",1 " + p;
}
function curve(r0, p0, r1, p1) {
return "Q 0,0 " + p1;
}
function constant(x) {
return function () {
return x;
};
}
chord.radius = function (v) {
if (!arguments.length) {
return radius;
}
radius = typeof x === "function" ? v : constant(v);
return chord;
};
chord.source = function (v) {
if (!arguments.length) {
return source;
}
source = typeof x === "function" ? v : constant(v);
return chord;
};
chord.target = function (v) {
if (!arguments.length) {
return target;
}
target = typeof x === "function" ? v : constant(v);
return chord;
};
chord.startAngle = function (v) {
if (!arguments.length) {
return startAngle;
}
startAngle = typeof x === "function" ? v : constant(v);
return chord;
};
chord.endAngle = function (v) {
if (!arguments.length) {
return endAngle;
}
endAngle = typeof x === "function" ? v : constant(v);
return chord;
};
return chord;
};
svg.append("g").attr("class", "chord").selectAll("path").data(chord.chords).enter().append("path").attr("d", function (d, i) {
return svg_asymChord().radius(innerRadius)(d, i);
}).style("fill", function (d) {
return fill(context.dataset.nodeTypes[d.target.index]);
}).style("opacity", 1);
function fade_over(opacity) {
return function (g, i) {
var filter = function filter(d) {
return d.source.index !== i && d.target.index !== i;
};
if (d3.event.shiftKey) {
filter = function filter(d) {
return d.target.index !== i;
};
} else if (d3.event.ctrlKey) {
filter = function filter(d) {
return d.source.index !== i;
};
}
svg.selectAll(".chord path").filter(filter).transition().style("opacity", opacity);
};
}
function fade_out(opacity) {
return function (g, i) {
svg.selectAll(".chord path").transition().style("opacity", opacity);
};
}
}
}, {
key: "generateChordMatrix",
value: function generateChordMatrix(context) {
var matrix = [];
var type2type = {};
// {type_i: {postType_j: counts, ...}, ...}
var typesZeros = util.map(context.dataset.nodeTypes, function (type) {
return [type, 0];
});
context.dataset.nodeTypes.forEach(function (type) {
var initCounts = util._object(typesZeros);
var linksFromType = util.filter(context.dataset.links, function (link) {
return context.dataset.nodes[link.source].type === type;
});
type2type[type] = util._extend(initCounts, util.countBy(linksFromType, function (link) {
return context.dataset.nodes[link.target].type;
}));
});
util.countBy(context.dataset.nodes, function (node) {
return node.type;
});
/*
* //unconnected nodes of all types
* var discNodes = util.filter(context.dataset.nodes, function (node) {
* return node.degree == 0
* });
* var numDiscByType = util.countBy(discNodes, function (node) {
* return node.type
* });
*/
context.dataset.nodeTypes.forEach(function (type, idx, nodeTypes) {
var numConn = [];
nodeTypes.forEach(function (innerType) {
/*
* normalization should be optional
* numConn.push[type2type.innerType] / numNodesOfType[type];
*/
numConn.push(type2type[type][innerType]);
});
// numConn.push(numDiscByType[type] ? numDiscByType[type] : 0);
matrix.push(numConn);
});
/*
* // row of zeros for unconnected nodes
* var zeroes = [];
* for (var i = 0; i <= context.dataset.nodeTypes.length; i++) {
* zeroes.push(0)
* }
* matrix.push(zeroes);
*/
return matrix;
}
}, {
key: "layout_chord",
value: function layout_chord() {
var chord = {},
chords,
groups,
matrix,
n,
padding = 0,
sortGroups,
sortSubgroups,
sortChords;
var pi = Math.PI;
var tau = 2 * pi;
function arrayRotate(arr, count) {
count = count % arr.length;
if (count < 0) {
arr.unshift.apply(arr, arr.splice(count));
} else {
arr.push.apply(arr, arr.splice(0, count));
}
return arr;
}
function relayout() {
var a1;
var a0;
var v;
var dj;
var di;
var subgroups = {},
groupSums = [],
groupSumsT = new Array(n).fill(0),
groupIndex = d3.range(n),
subgroupIndex = [],
k,
x,
y,
x0,
i,
j;
chords = [];
groups = [];
k = 0;
i = -1;
while (++i < n) {
x = 0;
j = -1;
while (++j < n) {
x += matrix[i][j];
groupSumsT[j] += matrix[i][j];
}
groupSums.push(x);
subgroupIndex.push(arrayRotate(d3.range(n).reverse(), -i));
k += x;
}
k += d3.sum(groupSumsT);
if (sortGroups) {
groupIndex.sort(function (a, b) {
return sortGroups(groupSums[a], groupSums[b]);
});
}
if (sortSubgroups) {
subgroupIndex.forEach(function (d, i) {
d.sort(function (a, b) {
return sortSubgroups(matrix[i][a], matrix[i][b]);
});
});
}
k = (tau - padding * n) / k;
x = 0;
y = 0;
i = -1;
while (++i < n) {
j = -1;
y += k * (groupSums[i] + matrix[i][i]);
x0 = x;
while (++j < n) {
di = groupIndex[i];
dj = subgroupIndex[di][j];
if (di !== dj) {
// pre arcs
v = matrix[di][dj];
a0 = x;
x += v * k;
a1 = x;
subgroups[di + "-" + dj + "_pre"] = {
index: di,
subindex: dj,
startAngle: a0,
endAngle: a1,
value: v
};
// post arcs
var w = matrix[dj][di];
var a0post = y;
y += w * k;
var a1post = y;
subgroups[dj + "-" + di + "_post"] = {
index: di,
subindex: dj,
startAngle: a0post,
endAngle: a1post,
value: w
};
} else {
// i->i conn
v = matrix[i][i];
a0 = x0 + (groupSums[i] - v) * k;
a1 = a0 + 2 * k * v;
// x += k * v;
subgroups[i + "-" + i + "_pre"] = subgroups[i + "-" + i + "_post"] = {
index: i,
subindex: i,
startAngle: a0,
endAngle: a1,
value: v
};
}
}
x += k * (groupSumsT[di] + matrix[di][di]) + padding;
y += padding;
}
// slices
i = -1;
x = 0;
x0 = 0;
while (++i < n) {
x0 = x;
di = groupIndex[i];
dj = subgroupIndex[di][j];
x += k * (groupSums[di] + groupSumsT[di]);
groups[di] = {
index: di,
startAngle: x0,
endAngle: x,
value: (x - x0) / k
};
x += padding;
}
i = -1;
while (++i < n) {
j = -1;
while (++j < n) {
var source = subgroups[i + "-" + j + "_pre"],
target = subgroups[i + "-" + j + "_post"];
if (source.value || target.value) {
chords.push({
source: source,
target: target
});
}
}
}
if (sortChords) {
resort();
}
}
function resort() {
chords.sort(function (a, b) {
return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
});
}
chord.matrix = function (x) {
if (!arguments.length) {
return matrix;
}
n = (matrix = x) && matrix.length;
chords = groups = null;
return chord;
};
chord.padding = function (x) {
if (!arguments.length) {
return padding;
}
padding = x;
chords = groups = null;
return chord;
};
chord.sortGroups = function (x) {
if (!arguments.length) {
return sortGroups;
}
sortGroups = x;
chords = groups = null;
return chord;
};
chord.sortSubgroups = function (x) {
if (!arguments.length) {
return sortSubgroups;
}
sortSubgroups = x;
chords = null;
return chord;
};
chord.sortChords = function (x) {
if (!arguments.length) {
return sortChords;
}
sortChords = x;
if (chords) {
resort();
}
return chord;
};
chord.chords = function () {
if (!chords) {
relayout();
}
return chords;
};
chord.groups = function () {
if (!groups) {
relayout();
}
return groups;
};
return chord;
}
}, {
key: "getLegends",
value: function getLegends(context) {
var nodeTypeScale = context.nodeColormap.range ? context.nodeColormap : d3.scaleOrdinal(d3.schemeCategory20);
return [{
id: 'legend',
colorScale: nodeTypeScale,
title: "Populations"
}];
}
}, {
key: "getName",
value: function getName() {
return "Chord";
}
}, {
key: "hasTooltip",
value: function hasTooltip() {
return false;
}
}, {
key: "hasToggle",
value: function hasToggle() {
return true;
}
}, {
key: "hasSelect",
value: function hasSelect() {
return false;
}
}]);
}();