2 Commits

Author SHA1 Message Date
21b64d9f43 Add SVG export
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-25 20:30:26 +02:00
54100d8e70 Found a proper way to colorize cards 2023-08-25 19:43:36 +02:00
2 changed files with 107 additions and 85 deletions

View File

@@ -1,15 +1,17 @@
import React from "react";
import f3, { f3Data } from "family-chart";
import "./family-chart.css";
import { FamilyTreeNode } from "../../utils/family_tree";
import { Member, fmtDate } from "../../api/MemberApi";
import { Couple } from "../../api/CoupleApi";
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
import { IconButton } from "@mui/material";
import { mdiXml } from "@mdi/js";
import Icon from "@mdi/react";
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
import { IconButton } from "@mui/material";
import f3, { f3Data } from "family-chart";
import { jsPDF } from "jspdf";
import React from "react";
import "svg2pdf.js";
import { getAllIndexes } from "../../utils/string_utils";
import { Couple } from "../../api/CoupleApi";
import { Member, fmtDate } from "../../api/MemberApi";
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
import { FamilyTreeNode } from "../../utils/family_tree";
import { downloadBlob } from "../../utils/files_utils";
import "./family-chart.css";
export function ComplexFamilyTree(p: {
tree: FamilyTreeNode;
@@ -24,21 +26,21 @@ export function ComplexFamilyTree(p: {
data: treeToF3Data(p.tree, p.isUp),
node_separation: 250,
level_separation: 150,
}),
view = f3.d3AnimationView({
});
const view = f3.d3AnimationView({
store,
cont: container,
}),
Card = f3.elements.Card({
});
const Card = f3.elements.Card({
store,
svg: view.svg,
card_dim: {
w: 210,
h: 118,
h: 120,
text_x: 5,
text_y: 70,
text_y: 75,
img_w: 60,
img_h: 60,
img_h: 70,
img_x: 5,
img_y: 5,
},
@@ -71,21 +73,33 @@ export function ComplexFamilyTree(p: {
link_break: false,
});
view.setCard(Card);
// Patch generated card
const PatchedCard: f3.F3CardBuilder = (p) => {
const res = Card(p);
// Patch card colors for PDF export
res
.querySelector(".card-male")
?.querySelector(".card-body-rect")
?.setAttribute("fill", "#add8e6");
res
.querySelector(".card-female")
?.querySelector(".card-body-rect")
?.setAttribute("fill", "#ffb6c1");
return res;
};
view.setCard(PatchedCard);
store.setOnUpdate((props) => view.update(props || {}));
store.update.tree({ initial: false, transition_time: 0 });
};
const exportPDF = async () => {
const doExport = async (onlySVG: boolean) => {
const docWidth = treeWidth(p.tree) * 65;
const docHeight = treeHeight(p.tree) * 60;
console.info(`Tree w=${treeWidth(p.tree)} h=${treeHeight(p.tree)}`);
const doc = new jsPDF({
orientation: "l",
format: [docHeight, docWidth],
});
// Clone the SVG to manipulate it
const container = document.createElement("div");
container.classList.add("f3", "f3-export");
@@ -104,11 +118,6 @@ export function ComplexFamilyTree(p: {
`<path class="link" fill="none" stroke="#000"`
);
dstSVG = dstSVG.replaceAll(
`class="card-body-rect"`,
`class="card-body-rect" fill="white"`
);
dstSVG = dstSVG.replaceAll(
`class="text-overflow-mask"`,
`class="text-overflow-mask" fill="transparent"`
@@ -123,29 +132,32 @@ export function ComplexFamilyTree(p: {
dstSVG = dstSVG.replaceAll("✝", " ");
let womanTiles = getAllIndexes(dstSVG, "card-female");
let count = 0;
for (let i of womanTiles) {
// Correct padding due to previous corrections
i += count++ * 2;
dstSVG =
dstSVG.substring(0, i) +
dstSVG.substring(i + 1).replace(`fill="white"`, `fill="#ffb6c1"`);
}
count = 0;
let manTiles = getAllIndexes(dstSVG, "card-male");
for (let i of manTiles) {
// Correct padding due to previous corrections
i += count++ * 2;
dstSVG =
dstSVG.substring(0, i) +
dstSVG.substring(i + 1).replace(`fill="white"`, `fill="#add8e6"`);
// Download in SVG format
if (onlySVG) {
// Fix background color (first rect background)
dstSVG = dstSVG.replace(
`fill="transparent"></rect>`,
`fill="white"></rect>`
);
const blob = new Blob([`<svg>${dstSVG}</svg>`], {
type: "image/svg+xml",
});
downloadBlob(blob, "ArbreGenealogique.svg");
return;
}
// Download in PDF format
//navigator.clipboard.writeText(dstSVG);
target.innerHTML = dstSVG;
const doc = new jsPDF({
orientation: "l",
format: [docHeight, docWidth],
});
await doc.svg(target, {
height: docHeight,
width: docWidth,
@@ -157,12 +169,19 @@ export function ComplexFamilyTree(p: {
doc.save("ArbreGenealogique.pdf");
};
const exportPDF = () => doExport(false);
const exportSVG = () => doExport(true);
return (
<div>
<div style={{ textAlign: "right" }}>
<IconButton onClick={exportPDF}>
<PictureAsPdfIcon />
</IconButton>
<IconButton onClick={exportSVG}>
<Icon path={mdiXml} size={1} />
</IconButton>
</div>
<div
style={{ width: "100%" }}

View File

@@ -86,7 +86,10 @@ declare module "family-chart" {
img_y: number;
};
card_display: ((data: f3Data) => string)[];
}) => (p: { node; d }) => HTMLElement;
}) => F3CardBuilder;
};
type F3CardBuilder = (p: { node; d }) => HTMLElement;
const elements: F3elements;
}