topola
Version:
Topola – online genealogy visualization
437 lines (436 loc) • 20.7 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DetailedRenderer = void 0;
exports.getLength = getLength;
var d3_selection_1 = require("d3-selection");
var _1 = require(".");
var date_format_1 = require("./date-format");
var d3_array_1 = require("d3-array");
require("d3-transition");
var composite_renderer_1 = require("./composite-renderer");
var INDI_MIN_HEIGHT = 44;
var INDI_MIN_WIDTH = 64;
var IMAGE_WIDTH = 70;
/** Minimum box height when an image is present. */
var IMAGE_HEIGHT = 90;
var DETAILS_HEIGHT = 14;
var ANIMATION_DELAY_MS = 200;
var ANIMATION_DURATION_MS = 500;
var textLengthCache = new Map();
/** Calculates the length of the given text in pixels when rendered. */
function getLength(text, textClass) {
var cacheKey = "".concat(text, "|").concat(textClass);
if (textLengthCache.has(cacheKey)) {
return textLengthCache.get(cacheKey);
}
var g = (0, d3_selection_1.select)('svg').append('g').attr('class', 'detailed node');
var x = g.append('text').attr('class', textClass).text(text);
var length = x.node().getComputedTextLength();
g.remove();
textLengthCache.set(cacheKey, length);
return length;
}
var SEX_SYMBOLS = new Map([
['F', '\u2640'],
['M', '\u2642'],
]);
/**
* Renders some details about a person such as date and place of birth
* and death.
*/
var DetailedRenderer = /** @class */ (function (_super) {
__extends(DetailedRenderer, _super);
function DetailedRenderer(options) {
var _this = _super.call(this, options) || this;
_this.options = options;
return _this;
}
DetailedRenderer.prototype.getColoringClass = function () {
switch (this.options.colors) {
case _1.ChartColors.NO_COLOR:
return 'nocolor';
case _1.ChartColors.COLOR_BY_SEX:
return 'bysex';
default:
return 'bygeneration';
}
};
/** Extracts lines of details for a person. */
DetailedRenderer.prototype.getIndiDetails = function (indi) {
var detailsList = [];
var birthDate = indi.getBirthDate() &&
(0, date_format_1.formatDateOrRange)(indi.getBirthDate(), this.options.locale);
var birthPlace = indi.getBirthPlace();
var deathDate = indi.getDeathDate() &&
(0, date_format_1.formatDateOrRange)(indi.getDeathDate(), this.options.locale);
var deathPlace = indi.getDeathPlace();
if (birthDate) {
detailsList.push({ symbol: '', text: birthDate });
}
if (birthPlace) {
detailsList.push({ symbol: '', text: birthPlace });
}
if (birthDate || birthPlace) {
detailsList[0].symbol = '*';
}
var listIndex = detailsList.length;
if (deathDate) {
detailsList.push({ symbol: '', text: deathDate });
}
if (deathPlace) {
detailsList.push({ symbol: '', text: deathPlace });
}
if (deathDate || deathPlace) {
detailsList[listIndex].symbol = '+';
}
else if (indi.isConfirmedDeath()) {
detailsList.push({ symbol: '+', text: '' });
}
return detailsList;
};
/** Extracts lines of details for a family. */
DetailedRenderer.prototype.getFamDetails = function (fam) {
var detailsList = [];
var marriageDate = fam.getMarriageDate() &&
(0, date_format_1.formatDateOrRange)(fam.getMarriageDate(), this.options.locale);
var marriagePlace = fam.getMarriagePlace();
if (marriageDate) {
detailsList.push({ symbol: '', text: marriageDate });
}
if (marriagePlace) {
detailsList.push({ symbol: '', text: marriagePlace });
}
if (marriageDate || marriagePlace) {
detailsList[0].symbol = '\u26AD';
}
return detailsList;
};
DetailedRenderer.prototype.getPreferredIndiSize = function (id) {
var indi = this.options.data.getIndi(id);
var details = this.getIndiDetails(indi);
var idAndSexHeight = indi.showId() || indi.showSex() ? DETAILS_HEIGHT : 0;
var height = (0, d3_array_1.max)([
INDI_MIN_HEIGHT + details.length * DETAILS_HEIGHT + idAndSexHeight,
indi.getImageUrl() ? IMAGE_HEIGHT : 0,
]);
var maxDetailsWidth = (0, d3_array_1.max)(details.map(function (x) { return getLength(x.text, 'details'); }));
var width = (0, d3_array_1.max)([
maxDetailsWidth + 22,
getLength(indi.getFirstName() || '', 'name') + 8,
getLength(indi.getLastName() || '', 'name') + 8,
getLength(id, 'id') + 32,
INDI_MIN_WIDTH,
]) + (indi.getImageUrl() ? IMAGE_WIDTH : 0);
return [width, height];
};
DetailedRenderer.prototype.getPreferredFamSize = function (id) {
var fam = this.options.data.getFam(id);
var details = this.getFamDetails(fam);
if (!details.length) {
return [0, 0];
}
var height = 10 + details.length * DETAILS_HEIGHT;
var maxDetailsWidth = (0, d3_array_1.max)(details.map(function (x) { return getLength(x.text, 'details'); }));
var width = maxDetailsWidth + 22;
return [width, height];
};
DetailedRenderer.prototype.render = function (enter, update) {
var _this = this;
enter = enter.append('g').attr('class', 'detailed');
update = update.select('g');
var indiUpdate = enter
.merge(update)
.selectAll('g.indi')
.data(function (node) {
var result = [];
var famXOffset = !_this.options.horizontal && node.data.family
? (0, d3_array_1.max)([-(0, composite_renderer_1.getFamPositionVertical)(node.data), 0])
: 0;
var famYOffset = _this.options.horizontal && node.data.family
? (0, d3_array_1.max)([-(0, composite_renderer_1.getFamPositionHorizontal)(node.data), 0])
: 0;
if (node.data.indi) {
result.push({
indi: node.data.indi,
generation: node.data.generation,
xOffset: famXOffset,
yOffset: 0,
});
}
if (node.data.spouse) {
result.push({
indi: node.data.spouse,
generation: node.data.generation,
xOffset: !_this.options.horizontal && node.data.indi
? node.data.indi.width + famXOffset
: 0,
yOffset: _this.options.horizontal && node.data.indi
? node.data.indi.height + famYOffset
: 0,
});
}
return result;
}, function (data) { return data.indi.id; });
var indiEnter = indiUpdate
.enter()
.append('g')
.attr('class', 'indi');
this.transition(indiEnter.merge(indiUpdate)).attr('transform', function (node) { return "translate(".concat(node.xOffset, ", ").concat(node.yOffset, ")"); });
this.renderIndi(indiEnter, indiUpdate);
var familyEnter = enter
.select(function (node) {
return node.data.family ? this : null;
})
.append('g')
.attr('class', 'family');
var familyUpdate = update
.select(function (node) {
return node.data.family ? this : null;
})
.select('g.family');
this.transition(familyEnter.merge(familyUpdate)).attr('transform', function (node) {
return _this.getFamTransform(node.data);
});
this.renderFamily(familyEnter, familyUpdate);
};
DetailedRenderer.prototype.getCss = function () {
return "\n.detailed text {\n font-family: Montserrat, verdana, arial, sans-serif;\n fill: black;\n}\n\n.detailed .name {\n font-size: 12px;\n font-weight: bold;\n}\n\n.link {\n fill: none;\n stroke: #000;\n stroke-width: 1px;\n}\n\n.additional-marriage {\n stroke-dasharray: 2;\n}\n\n.detailed rect {\n stroke: black;\n}\n\n.detailed {\n stroke-width: 2px;\n}\n\n.detailed .details {\n font-size: 10px;\n}\n\n.detailed .id {\n font-size: 10px;\n font-style: italic;\n}\n\n.detailed rect.nocolor {\n fill: #ffffff;\n}\n\n.detailed rect.bysex {\n fill: #eeeeee;\n}\n\n.detailed rect.bysex.male {\n fill: #dbffff;\n}\n\n.detailed rect.bysex.female {\n fill: #ffdbed;\n}\n\n.detailed rect.bygeneration {\n fill: #ffffdd;\n}\n\n.generation-11 .detailed rect.bygeneration, .generation1 .detailed rect.bygeneration {\n fill: #edffdb;\n}\n\n.generation-10 .detailed rect.bygeneration, .generation2 .detailed rect.bygeneration {\n fill: #dbffdb;\n}\n\n.generation-9 .detailed rect.bygeneration, .generation3 .detailed rect.bygeneration {\n fill: #dbffed;\n}\n\n.generation-8 .detailed rect.bygeneration, .generation4 .detailed rect.bygeneration {\n fill: #dbffff;\n}\n\n.generation-7 .detailed rect.bygeneration, .generation5 .detailed rect.bygeneration {\n fill: #dbedff;\n}\n\n.generation-6 .detailed rect.bygeneration, .generation6 .detailed rect.bygeneration {\n fill: #dbdbff;\n}\n\n.generation-5 .detailed rect.bygeneration, .generation7 .detailed rect.bygeneration {\n fill: #eddbff;\n}\n\n.generation-4 .detailed rect.bygeneration, .generation8 .detailed rect.bygeneration {\n fill: #ffdbff;\n}\n\n.generation-3 .detailed rect.bygeneration, .generation9 .detailed rect.bygeneration {\n fill: #ffdbed;\n}\n\n.generation-2 .detailed rect.bygeneration, .generation10 .detailed rect.bygeneration {\n fill: #ffdbdb;\n}\n\n.generation-1 .detailed rect.bygeneration, .generation11 .detailed rect.bygeneration {\n fill: #ffeddb;\n}";
};
DetailedRenderer.prototype.transition = function (selection) {
return this.options.animate
? selection
.transition()
.delay(ANIMATION_DELAY_MS)
.duration(ANIMATION_DURATION_MS)
: selection;
};
DetailedRenderer.prototype.getFamTransform = function (node) {
if (this.options.horizontal) {
return "translate(".concat((node.indi && node.indi.width) || node.spouse.width, ", ").concat((0, d3_array_1.max)([(0, composite_renderer_1.getFamPositionHorizontal)(node), 0]), ")");
}
return "translate(".concat((0, d3_array_1.max)([(0, composite_renderer_1.getFamPositionVertical)(node), 0]), ", ").concat((node.indi && node.indi.height) || node.spouse.height, ")");
};
DetailedRenderer.prototype.getSexClass = function (indiId) {
var _a;
var sex = (_a = this.options.data.getIndi(indiId)) === null || _a === void 0 ? void 0 : _a.getSex();
switch (sex) {
case 'M':
return 'male';
case 'F':
return 'female';
default:
return '';
}
};
DetailedRenderer.prototype.renderIndi = function (enter, update) {
var _this = this;
if (this.options.indiHrefFunc) {
enter = enter
.append('a')
.attr('href', function (data) { return _this.options.indiHrefFunc(data.indi.id); });
update = update.select('a');
}
if (this.options.indiCallback) {
enter.on('click', function (event, data) {
return _this.options.indiCallback({
id: data.indi.id,
generation: data.generation,
modifiers: {
shiftKey: event.shiftKey,
ctrlKey: event.ctrlKey,
altKey: event.altKey,
metaKey: event.metaKey,
},
});
});
}
// Background.
var background = enter
.append('rect')
.attr('rx', 5)
.attr('stroke-width', 0)
.attr('class', function (node) {
return "background ".concat(_this.getColoringClass(), " ").concat(_this.getSexClass(node.indi.id));
})
.merge(update.select('rect.background'));
var transition = this.transition(background);
transition.attr('width', function (node) { return node.indi.width; });
transition.attr('height', function (node) { return node.indi.height; });
// Clip path.
var getClipId = function (id) { return "clip-".concat(id); };
enter
.append('clipPath')
.attr('id', function (node) { return getClipId(node.indi.id); })
.append('rect')
.attr('rx', 5)
.merge(update.select('clipPath rect'))
.attr('width', function (node) { return node.indi.width; })
.attr('height', function (node) { return node.indi.height; });
var getIndi = function (data) {
return _this.options.data.getIndi(data.indi.id);
};
var getDetailsWidth = function (data) {
return data.indi.width - (getIndi(data).getImageUrl() ? IMAGE_WIDTH : 0);
};
// Name.
enter
.append('text')
.attr('text-anchor', 'middle')
.attr('class', 'name')
.attr('transform', function (node) { return "translate(".concat(getDetailsWidth(node) / 2, ", 17)"); })
.text(function (node) { return getIndi(node).getFirstName(); });
enter
.append('text')
.attr('text-anchor', 'middle')
.attr('class', 'name')
.attr('transform', function (node) { return "translate(".concat(getDetailsWidth(node) / 2, ", 33)"); })
.text(function (node) { return getIndi(node).getLastName(); });
// Extract details.
var details = new Map();
enter.each(function (node) {
var indi = getIndi(node);
var detailsList = _this.getIndiDetails(indi);
details.set(node.indi.id, detailsList);
});
var maxDetails = (0, d3_array_1.max)(Array.from(details.values(), function (v) { return v.length; }));
var _loop_1 = function (i) {
var lineGroup = enter.filter(function (data) { return details.get(data.indi.id).length > i; });
lineGroup
.append('text')
.attr('text-anchor', 'middle')
.attr('class', 'details')
.attr('transform', "translate(9, ".concat(49 + i * DETAILS_HEIGHT, ")"))
.text(function (data) { return details.get(data.indi.id)[i].symbol; });
lineGroup
.append('text')
.attr('class', 'details')
.attr('transform', "translate(15, ".concat(49 + i * DETAILS_HEIGHT, ")"))
.text(function (data) { return details.get(data.indi.id)[i].text; });
};
// Render details.
for (var i = 0; i < maxDetails; ++i) {
_loop_1(i);
}
// Render id.
var id = enter
.append('text')
.attr('class', 'id')
.text(function (data) { return (getIndi(data).showId() ? data.indi.id : ''); })
.merge(update.select('text.id'));
this.transition(id).attr('transform', function (data) { return "translate(9, ".concat(data.indi.height - 5, ")"); });
// Render sex.
var sex = enter
.append('text')
.attr('class', 'details sex')
.attr('text-anchor', 'end')
.text(function (data) {
var sexSymbol = SEX_SYMBOLS.get(getIndi(data).getSex() || '') || '';
return getIndi(data).showSex() ? sexSymbol : '';
})
.merge(update.select('text.sex'));
this.transition(sex).attr('transform', function (data) {
return "translate(".concat(getDetailsWidth(data) - 5, ", ").concat(data.indi.height - 5, ")");
});
// Image.
enter
.filter(function (data) { return !!getIndi(data).getImageUrl(); })
.append('image')
.attr('width', IMAGE_WIDTH)
.attr('height', function (data) { return data.indi.height; })
.attr('preserveAspectRatio', 'xMidYMin')
.attr('transform', function (data) { return "translate(".concat(data.indi.width - IMAGE_WIDTH, ", 0)"); })
.attr('clip-path', function (data) { return "url(#".concat(getClipId(data.indi.id), ")"); })
.attr('href', function (data) { return getIndi(data).getImageUrl(); });
// Border on top.
var border = enter
.append('rect')
.attr('rx', 5)
.attr('fill-opacity', 0)
.attr('class', 'border')
.merge(update.select('rect.border'));
var borderTransition = this.transition(border);
borderTransition.attr('width', function (data) { return data.indi.width; });
borderTransition.attr('height', function (data) { return data.indi.height; });
};
DetailedRenderer.prototype.renderFamily = function (enter, update) {
var _this = this;
if (this.options.famHrefFunc) {
enter = enter
.append('a')
.attr('href', function (node) {
return _this.options.famHrefFunc(node.data.family.id);
});
}
if (this.options.famCallback) {
enter.on('click', function (event, node) {
return _this.options.famCallback({
id: node.data.family.id,
generation: node.data.generation,
modifiers: {
shiftKey: event.shiftKey,
ctrlKey: event.ctrlKey,
altKey: event.altKey,
metaKey: event.metaKey,
},
});
});
}
// Extract details.
var details = new Map();
enter.each(function (node) {
var famId = node.data.family.id;
var fam = _this.options.data.getFam(famId);
var detailsList = _this.getFamDetails(fam);
details.set(famId, detailsList);
});
var maxDetails = (0, d3_array_1.max)(Array.from(details.values(), function (v) { return v.length; }));
// Box.
enter
.filter(function (node) {
var detail = details.get(node.data.family.id);
return 0 < detail.length;
})
.append('rect')
.attr('class', this.getColoringClass())
.attr('rx', 5)
.attr('ry', 5)
.attr('width', function (node) { return node.data.family.width; })
.attr('height', function (node) { return node.data.family.height; });
var _loop_2 = function (i) {
var lineGroup = enter.filter(function (node) { return details.get(node.data.family.id).length > i; });
lineGroup
.append('text')
.attr('text-anchor', 'middle')
.attr('class', 'details')
.attr('transform', "translate(9, ".concat(16 + i * DETAILS_HEIGHT, ")"))
.text(function (node) { return details.get(node.data.family.id)[i].symbol; });
lineGroup
.append('text')
.attr('text-anchor', 'start')
.attr('class', 'details')
.attr('transform', "translate(15, ".concat(16 + i * DETAILS_HEIGHT, ")"))
.text(function (node) { return details.get(node.data.family.id)[i].text; });
};
// Render details.
for (var i = 0; i < maxDetails; ++i) {
_loop_2(i);
}
};
return DetailedRenderer;
}(composite_renderer_1.CompositeRenderer));
exports.DetailedRenderer = DetailedRenderer;