topola
Version:
Topola – online genealogy visualization
235 lines (234 loc) • 12.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FancyChart = void 0;
var d3_array_1 = require("d3-array");
var chart_util_1 = require("./chart-util");
var descendant_chart_1 = require("./descendant-chart");
/** Returns an SVG line definition for a tree branch between two points. */
function branch(x1, y1, x2, y2) {
var yMid = y2 + 110;
if (x2 > x1 + 100) {
return "\n M ".concat(x1 + 10, " ").concat(y1, "\n C ").concat(x1 + 10, " ").concat(yMid + 25, "\n ").concat(x1 + 45, " ").concat(yMid + 10, "\n ").concat((x1 + x2) / 2, " ").concat(yMid + 5, "\n ").concat(x2 - 45, " ").concat(yMid, "\n ").concat(x2 + 2, " ").concat(yMid - 25, "\n ").concat(x2 + 2, " ").concat(y2, "\n L ").concat(x2 - 2, " ").concat(y2, "\n C ").concat(x2 - 2, " ").concat(yMid - 25, "\n ").concat(x2 - 45, " ").concat(yMid - 10, "\n ").concat((x1 + x2) / 2, " ").concat(yMid - 5, "\n ").concat(x1 + 45, " ").concat(yMid, "\n ").concat(x1 - 10, " ").concat(yMid + 25, "\n ").concat(x1 - 10, " ").concat(y1);
}
if (x2 < x1 - 100) {
return "\n M ".concat(x1 - 10, " ").concat(y1, "\n C ").concat(x1 - 10, " ").concat(yMid + 25, "\n ").concat(x1 - 45, " ").concat(yMid + 10, "\n ").concat((x1 + x2) / 2, " ").concat(yMid + 5, "\n ").concat(x2 + 45, " ").concat(yMid, "\n ").concat(x2 - 2, " ").concat(yMid - 25, "\n ").concat(x2 - 2, " ").concat(y2, "\n L ").concat(x2 + 2, " ").concat(y2, "\n C ").concat(x2 + 2, " ").concat(yMid - 25, "\n ").concat(x2 + 45, " ").concat(yMid - 10, "\n ").concat((x1 + x2) / 2, " ").concat(yMid - 5, "\n ").concat(x1 - 45, " ").concat(yMid, "\n ").concat(x1 + 10, " ").concat(yMid + 25, "\n ").concat(x1 + 10, " ").concat(y1);
}
return "\n M ".concat(x1 + 10, " ").concat(y1, "\n C ").concat(x1 + 10, " ").concat(yMid + 25, "\n ").concat(x2 + 2, " ").concat(yMid - 25, "\n ").concat(x2 + 2, " ").concat(y2, "\n L ").concat(x2 - 2, " ").concat(y2, "\n C ").concat(x2 - 2, " ").concat(yMid - 25, "\n ").concat(x1 - 10, " ").concat(yMid + 25, "\n ").concat(x1 - 10, " ").concat(y1);
}
/** Renders a fancy descendants tree chart.
*
* It draws stylized leaves (colored circles behind individuals), organic branch paths,
* and a decorative tree trunk at the base of the tree.
* The layout uses D3 data binding to allow smooth updates and re-rendering.
*/
var FancyChart = /** @class */ (function () {
function FancyChart(options) {
this.options = options;
this.util = new chart_util_1.ChartUtil(options);
}
/** Creates a path from parent to the child node (vertical layout). */
FancyChart.prototype.linkVertical = function (s, d) {
var sAnchor = this.options.renderer.getFamilyAnchor(s.data);
var dAnchor = s.id === d.data.spouseParentNodeId
? this.options.renderer.getSpouseAnchor(d.data)
: this.options.renderer.getIndiAnchor(d.data);
var _a = [s.x + sAnchor[0], s.y + sAnchor[1]], sx = _a[0], sy = _a[1];
var _b = [d.x + dAnchor[0], d.y + dAnchor[1]], dx = _b[0], dy = _b[1];
return branch(dx, dy, sx, sy);
};
FancyChart.prototype.linkAdditionalMarriage = function (node) {
var nodeIndex = node.parent.children.findIndex(function (n) { return n.id === node.id; });
// Assert nodeIndex > 0.
var siblingNode = node.parent.children[nodeIndex - 1];
var sAnchor = this.options.renderer.getIndiAnchor(node.data);
var dAnchor = this.options.renderer.getIndiAnchor(siblingNode.data);
var _a = [node.x + sAnchor[0], node.y + sAnchor[1]], sx = _a[0], sy = _a[1];
var _b = [siblingNode.x + dAnchor[0], siblingNode.y + dAnchor[1]], dx = _b[0], dy = _b[1];
return "M ".concat(sx, ", ").concat(sy + 2, "\n L ").concat(dx, ", ").concat(dy + 10, "\n ").concat(dx, ", ").concat(dy - 10, "\n ").concat(sx, ", ").concat(sy - 2);
};
/**
* Renders the two-tone background blocks (cyan "sky" above, green "grass" below).
* Keeps them at the back of the SVG container by inserting them before any other elements.
*/
FancyChart.prototype.renderBackground = function (chartInfo, svg) {
var rectsData = [
{
y: -chartInfo.origin[1],
height: chartInfo.origin[1],
fill: '#cff',
},
{
y: 0,
height: chartInfo.size[1] - chartInfo.origin[1],
fill: '#494',
},
];
var bounds = svg
.select('g')
.selectAll('rect.fancy-bg')
.data(rectsData);
bounds
.enter()
.insert('rect', function () {
return this.firstChild;
})
.attr('class', 'fancy-bg')
.merge(bounds)
.attr('x', -chartInfo.origin[0])
.attr('y', function (d) { return d.y; })
.attr('width', chartInfo.size[0])
.attr('height', function (d) { return d.height; })
.attr('fill', function (d) { return d.fill; });
bounds.exit().remove();
};
/**
* Renders stylized circular green backdrops ("leaves") behind all descendant family tree nodes.
* A radial gradient is created (and reused) to give the outer ring of nodes a soft glowing edge.
*/
FancyChart.prototype.renderLeaves = function (nodes, svg) {
var gradient = svg.select('g').select('radialGradient#gradient');
if (gradient.empty()) {
gradient = svg
.select('g')
.append('radialGradient')
.attr('id', 'gradient');
gradient.append('stop').attr('offset', '0%').attr('stop-color', '#8f8');
gradient
.append('stop')
.attr('offset', '80%')
.attr('stop-color', '#8f8')
.attr('stop-opacity', 0.5);
gradient
.append('stop')
.attr('offset', '100%')
.attr('stop-color', '#8f8')
.attr('stop-opacity', 0);
}
var backgroundNodes = nodes.filter(function (n) { return n.parent && n.parent.id !== descendant_chart_1.DUMMY_ROOT_NODE_ID; });
var minGeneration = (0, d3_array_1.min)(backgroundNodes, function (node) { return node.data.generation; }) || 0;
var sizeFunction = function (node) {
return 280 - 180 / Math.sqrt(1 + node.data.generation - minGeneration);
};
{
var boundNodes = svg
.select('g')
.selectAll('g.background-node')
.data(backgroundNodes, function (d) { return d.id; });
var enter = boundNodes.enter().append('g').attr('class', 'background-node');
enter
.merge(boundNodes)
.attr('transform', function (node) {
return "translate(".concat(node.x - node.data.width / 2, ", ").concat(node.y - node.data.height / 2, ")");
});
var background = enter.append('g').attr('class', 'background');
background
.append('circle')
.attr('class', 'background')
.attr('r', sizeFunction)
.attr('cx', function (node) { return node.data.width / 2; })
.attr('cy', function (node) { return node.data.height / 2; })
.style('fill', '#493');
boundNodes.exit().remove();
}
{
var boundNodes = svg
.select('g')
.selectAll('g.background2-node')
.data(backgroundNodes, function (d) { return d.id; });
var enter = boundNodes.enter().append('g').attr('class', 'background2-node');
enter
.merge(boundNodes)
.attr('transform', function (node) {
return "translate(".concat(node.x - node.data.width / 2, ", ").concat(node.y - node.data.height / 2, ")");
});
var background = enter.append('g').attr('class', 'background2');
background
.append('circle')
.attr('class', 'background')
.attr('r', sizeFunction)
.attr('cx', function (node) { return node.data.width / 2; })
.attr('cy', function (node) { return node.data.height / 2; })
.style('fill', 'url(#gradient)');
boundNodes.exit().remove();
}
};
/**
* Renders the connection paths (branches) between family nodes.
* Uses organic Bezier curves styled as tree branches rather than standard block lines.
*/
FancyChart.prototype.renderLinks = function (nodes, svg) {
var _this = this;
var link = function (parent, child) {
if (child.data.additionalMarriage) {
return _this.linkAdditionalMarriage(child);
}
return _this.linkVertical(child, parent);
};
var links = nodes.filter(function (n) { return !!n.parent; });
var boundLinks = svg
.select('g')
.selectAll('path.branch')
.data(links, chart_util_1.linkId);
var enter = boundLinks
.enter()
.append('path')
.attr('class', function (node) {
return node.data.additionalMarriage ? 'branch additional-marriage' : 'branch';
});
enter
.merge(boundLinks)
.attr('d', function (node) { return link(node.parent, node); });
boundLinks.exit().remove();
};
/**
* Renders a decorative tree trunk at the base of the root node.
*/
FancyChart.prototype.renderTreeTrunk = function (nodes, svg) {
var trunkNodes = nodes.filter(function (n) { return !n.parent || n.parent.id === descendant_chart_1.DUMMY_ROOT_NODE_ID; });
var boundTrunks = svg
.select('g')
.selectAll('g.trunk')
.data(trunkNodes, function (d) { return d.id; });
var enter = boundTrunks
.enter()
.append('g')
.attr('class', 'trunk');
enter
.append('path')
.attr('d', "\n M 10 20\n L 10 40\n C 10 60 10 90 40 90\n L -40 90\n C -10 90 -10 60 -10 40\n L -10 20");
enter
.merge(boundTrunks)
.attr('transform', function (node) { return "translate(".concat(node.x, ", ").concat(node.y, ")"); });
boundTrunks.exit().remove();
};
/**
* Lays out, scales, and draws the fancy descendant chart.
*/
FancyChart.prototype.render = function () {
var nodes = (0, descendant_chart_1.layOutDescendants)(this.options, {
flipVertically: true,
vSpacing: 100,
});
var info = (0, chart_util_1.getChartInfo)(nodes);
info.origin[0] += 150;
info.origin[1] += 150;
info.size[0] += 300;
info.size[1] += 250;
var svg = this.util.getSvgForRendering();
if (svg.select('style.fancy-style').empty()) {
svg
.append('style')
.attr('class', 'fancy-style')
.text("\n .branch, .trunk {\n fill: #632;\n stroke: #632;\n }");
}
this.renderBackground(info, svg);
this.renderLeaves(nodes, svg);
this.renderLinks(nodes, svg);
this.renderTreeTrunk(nodes, svg);
this.util.renderNodes(nodes, svg);
this.util.updateSvgDimensions(info);
return Object.assign(info, { animationPromise: Promise.resolve() });
};
return FancyChart;
}());
exports.FancyChart = FancyChart;