Add simple tree graph mode #4
							
								
								
									
										31
									
								
								geneit_app/src/utils/render_utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								geneit_app/src/utils/render_utils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					let canvas: HTMLCanvasElement = document.createElement("canvas");
 | 
				
			||||||
 | 
					let charLen: Map<string, Map<string, number>> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {String} text The text to be rendered.
 | 
				
			||||||
 | 
					 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function computeTextWidth(text: string, font: string) {
 | 
				
			||||||
 | 
					  // re-use canvas object for better performance
 | 
				
			||||||
 | 
					  const context = canvas.getContext("2d")!;
 | 
				
			||||||
 | 
					  context.font = font;
 | 
				
			||||||
 | 
					  const metrics = context.measureText(text);
 | 
				
			||||||
 | 
					  return metrics.width;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getTextWidth(text: string, font: string) {
 | 
				
			||||||
 | 
					  if (!charLen.has(font)) charLen.set(font, new Map());
 | 
				
			||||||
 | 
					  const ref = charLen.get(font)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let size = 0;
 | 
				
			||||||
 | 
					  for (const c of text) {
 | 
				
			||||||
 | 
					    if (!ref.has(c)) ref.set(c, computeTextWidth(c, font));
 | 
				
			||||||
 | 
					    size += ref.get(c)!;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  console.log(text, size);
 | 
				
			||||||
 | 
					  return size;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,18 +2,20 @@ import React from "react";
 | 
				
			|||||||
import { Couple } from "../../api/CoupleApi";
 | 
					import { Couple } from "../../api/CoupleApi";
 | 
				
			||||||
import { Member } from "../../api/MemberApi";
 | 
					import { Member } from "../../api/MemberApi";
 | 
				
			||||||
import { FamilyTreeNode } from "../../utils/family_tree";
 | 
					import { FamilyTreeNode } from "../../utils/family_tree";
 | 
				
			||||||
 | 
					import { getTextWidth } from "../../utils/render_utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const FACE_WIDTH = 60;
 | 
					const FACE_WIDTH = 60;
 | 
				
			||||||
const FACE_HEIGHT = 70;
 | 
					const FACE_HEIGHT = 70;
 | 
				
			||||||
 | 
					const FACE_TEXT_SPACING = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CARD_HEIGHT = 110;
 | 
					const CARD_HEIGHT = 103;
 | 
				
			||||||
const SPOUSE_SPACING = 10;
 | 
					const SPOUSE_SPACING = 10;
 | 
				
			||||||
const CARD_SPACING = 20;
 | 
					const CARD_SPACING = 20;
 | 
				
			||||||
const SIBLINGS_SPACING = 20;
 | 
					const SIBLINGS_SPACING = 20;
 | 
				
			||||||
const LEVEL_SPACING = 20;
 | 
					const LEVEL_SPACING = 25;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const NAME_CHAR_W = 7.1428;
 | 
					const NAME_FONT = "13px Roboto";
 | 
				
			||||||
const BIRTH_CHAR_W = 5;
 | 
					const BIRTH_FONT = "10px Roboto";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface SimpleTreeSpouseInfo {
 | 
					interface SimpleTreeSpouseInfo {
 | 
				
			||||||
  member: Member;
 | 
					  member: Member;
 | 
				
			||||||
@@ -31,8 +33,8 @@ interface SimpleTreeNode {
 | 
				
			|||||||
 * Get the width of a member card
 | 
					 * Get the width of a member card
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function memberCardWidth(m: Member): number {
 | 
					function memberCardWidth(m: Member): number {
 | 
				
			||||||
  const nameWidth = m.fullName.length * NAME_CHAR_W;
 | 
					  const nameWidth = getTextWidth(m.fullName, NAME_FONT);
 | 
				
			||||||
  const birthDeathWidth = m.displayBirthDeath.length * BIRTH_CHAR_W;
 | 
					  const birthDeathWidth = getTextWidth(m.displayBirthDeath, BIRTH_FONT);
 | 
				
			||||||
  return Math.max(FACE_WIDTH, nameWidth, birthDeathWidth);
 | 
					  return Math.max(FACE_WIDTH, nameWidth, birthDeathWidth);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -170,7 +172,7 @@ function NodeArea(p: {
 | 
				
			|||||||
            node={n}
 | 
					            node={n}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        downXOffset += n.width;
 | 
					        downXOffset += n.width + SIBLINGS_SPACING;
 | 
				
			||||||
        return el;
 | 
					        return el;
 | 
				
			||||||
      })}
 | 
					      })}
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
@@ -203,18 +205,20 @@ function MemberCard(p: {
 | 
				
			|||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Member text */}
 | 
					      {/* Member text */}
 | 
				
			||||||
      <text y={FACE_HEIGHT}>
 | 
					      <text y={FACE_HEIGHT + FACE_TEXT_SPACING}>
 | 
				
			||||||
        <tspan
 | 
					        <tspan
 | 
				
			||||||
          x={center(w, p.member.fullName.length * NAME_CHAR_W)}
 | 
					          x={center(w, getTextWidth(p.member.fullName, NAME_FONT))}
 | 
				
			||||||
          dy="14"
 | 
					          dy="14"
 | 
				
			||||||
          font-size="13"
 | 
					          font-size="13"
 | 
				
			||||||
 | 
					          fontFamily="Roboto"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {p.member.fullName}
 | 
					          {p.member.fullName}
 | 
				
			||||||
        </tspan>
 | 
					        </tspan>
 | 
				
			||||||
        <tspan
 | 
					        <tspan
 | 
				
			||||||
          x={center(w, p.member.displayBirthDeath.length * BIRTH_CHAR_W)}
 | 
					          x={center(w, getTextWidth(p.member.displayBirthDeath, BIRTH_FONT))}
 | 
				
			||||||
          dy="14"
 | 
					          dy="14"
 | 
				
			||||||
          font-size="10"
 | 
					          font-size="10"
 | 
				
			||||||
 | 
					          fontFamily="Roboto"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {p.member.displayBirthDeath}
 | 
					          {p.member.displayBirthDeath}
 | 
				
			||||||
        </tspan>
 | 
					        </tspan>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user