diff --git a/README.md b/README.md index b3a419c..09c2f27 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # relatives-tree -A tiny library (~3.02 KB gz) for calculating specific JSON data to family tree nodes and connectors. +A tiny library (~3.01 KB gz) for calculating specific JSON data to family tree nodes and connectors. 🖥 [Here is a demo](https://sanichkotikov.github.io/react-family-tree-example/) app with React rendering. diff --git a/src/connectors/children.ts b/src/connectors/children.ts index 784e0d6..4b44abc 100644 --- a/src/connectors/children.ts +++ b/src/connectors/children.ts @@ -1,67 +1,63 @@ +import { getParentsX, withType } from '../utils/family'; import { getUnitX, nodeCount, nodeIds } from '../utils/units'; -import { withType } from '../utils/family'; -import { inAscOrder, max, min, withId } from '../utils'; -import { SIZE } from '../constants'; -import { Connector, Family, FamilyType } from '../types'; +import { inAscOrder, max, min, withId, withIds } from '../utils'; +import { HALF_SIZE, NODES_IN_COUPLE, SIZE } from '../constants'; +import { Connector, Family, FamilyType, Unit } from '../types'; -// TODO refactor -export const children = (families: Family[]): Connector[] => { - const connectors: Connector[] = []; +export const children = (families: readonly Family[]): readonly Connector[] => ( + families + .filter(withType(FamilyType.root, FamilyType.child)) + .reduce((connectors, family) => { + const parent: Unit | undefined = family.parents[0]; - families.filter(withType(FamilyType.root, FamilyType.child)).forEach(family => { - let pX = 0; - const mY = family.Y + (family.parents.length ? SIZE : 0); - - if (family.parents.length === 1) { - const pUnit = family.parents[0]; - pX = getUnitX(family, pUnit) + nodeCount(pUnit); // TODO + const pX = getParentsX(family, parent); + const mY = family.Y + (parent ? SIZE : 0); // from parent(s) to child - if (pUnit.nodes.every(node => !!node.children.length)) { - const pY = family.Y + 1; + if (parent && parent.nodes.every(node => !!node.children.length)) { + const pY = family.Y + HALF_SIZE; connectors.push([pX, pY, pX, mY]); } - } - const parentIds = family.parents.map(nodeIds).flat(); + const parentIds = family.parents.map(nodeIds).flat(); + const positions: number[] = []; - const cXs: number[] = []; + family.children.forEach(unit => { + const left = getUnitX(family, unit) + HALF_SIZE; - family.children.forEach(cUnit => { - const cX = getUnitX(family, cUnit) + 1; + // from child to parent(s) + unit.nodes.forEach((node, index) => { + if (node.parents.some(withIds(parentIds))) { + const nX = left + (index * SIZE); + positions.push(nX); + connectors.push([nX, mY, nX, mY + HALF_SIZE]); + } + }); - // from child to parent(s) - cUnit.nodes.forEach((node, index) => { - if (node.parents.find(rel => parentIds.indexOf(rel.id) !== -1)) { - const nX = cX + (index * 2); - cXs.push(nX); - connectors.push([nX, mY, nX, mY + 1]); + // between child and child's spouse + if (nodeCount(unit) === NODES_IN_COUPLE) { + connectors.push([left, mY + HALF_SIZE, left + SIZE, mY + HALF_SIZE]); } - }); - // between child and child's spouse - if (nodeCount(cUnit) === 2) { - connectors.push([cX, mY + 1, cX + 2, mY + 1]); - } - else if (nodeCount(cUnit) === 1 && cUnit.nodes[0].spouses.length) { - family.children.forEach(nUnit => { - if (nUnit.nodes.findIndex(withId(cUnit.nodes[0].spouses[0].id)) !== -1) { - const xX = [cX, getUnitX(family, nUnit) + 1].sort(inAscOrder); - connectors.push([xX[0], mY + 1, xX[1], mY + 1]); - } - }); - } - }); + // between child and child's side spouse + else if (nodeCount(unit) === 1 && unit.nodes[0].spouses.length) { + family.children.forEach(nUnit => { + if (nUnit.nodes.some(withId(unit.nodes[0].spouses[0].id))) { + const xX = [left, getUnitX(family, nUnit) + HALF_SIZE].sort(inAscOrder); + connectors.push([xX[0], mY + HALF_SIZE, xX[1], mY + HALF_SIZE]); + } + }); + } + }); - if (cXs.length > 1) { // horizontal above children - connectors.push([min(cXs), mY, max(cXs), mY]); - } - else if (cXs.length === 1 && pX !== cXs[0]) { + if (positions.length > 1) + connectors.push([min(positions), mY, max(positions), mY]); + // horizontal between parent(s) and child - connectors.push([Math.min(pX, cXs[0]), mY, Math.max(pX, cXs[0]), mY]); - } - }); + else if (positions.length === 1 && pX !== positions[0]) + connectors.push([Math.min(pX, positions[0]), mY, Math.max(pX, positions[0]), mY]); - return connectors; -}; + return connectors; + }, []) +); diff --git a/src/connectors/middle.ts b/src/connectors/middle.ts index 082e20c..c8d51b2 100644 --- a/src/connectors/middle.ts +++ b/src/connectors/middle.ts @@ -1,7 +1,7 @@ import { inAscOrder, withId } from '../utils'; import { getUnitX, nodeCount } from '../utils/units'; import { withType } from '../utils/family'; -import { HALF_SIZE, SIZE } from '../constants'; +import { HALF_SIZE, NODES_IN_COUPLE, SIZE } from '../constants'; import { Connector, Family, FamilyType, Unit } from '../types'; const calcConnectors = (family: Family, families: readonly Family[]) => ( @@ -9,7 +9,7 @@ const calcConnectors = (family: Family, families: readonly Family[]) => ( const pX = getUnitX(family, unit) + HALF_SIZE; const pY = family.Y + HALF_SIZE; - if (nodeCount(unit) === 2) { + if (nodeCount(unit) === NODES_IN_COUPLE) { connectors.push([pX, pY, pX + SIZE, pY]); } // TODO: update and refactor diff --git a/src/connectors/parents.ts b/src/connectors/parents.ts index 96c2816..4c4c93f 100644 --- a/src/connectors/parents.ts +++ b/src/connectors/parents.ts @@ -1,7 +1,7 @@ import { prop, withIds } from '../utils'; import { getUnitX, nodeCount } from '../utils/units'; -import { withType } from '../utils/family'; -import { HALF_SIZE, SIZE } from '../constants'; +import { getParentsX, withType } from '../utils/family'; +import { HALF_SIZE, NODES_IN_COUPLE, SIZE } from '../constants'; import { Connector, Family, FamilyType, Unit } from '../types'; const getChildIDs = (unit: Unit): readonly string[] => ( @@ -10,12 +10,12 @@ const getChildIDs = (unit: Unit): readonly string[] => ( const calcConnectors = (family: Family) => ( (connectors: Connector[], unit: Unit) => { - const pX = getUnitX(family, unit) + nodeCount(unit); + const pX = getParentsX(family, unit); const pY = family.Y + HALF_SIZE; const mY = family.Y + SIZE; // between parents - if (nodeCount(unit) === 2) connectors.push([pX - HALF_SIZE, pY, pX + HALF_SIZE, pY]); + if (nodeCount(unit) === NODES_IN_COUPLE) connectors.push([pX - HALF_SIZE, pY, pX + HALF_SIZE, pY]); // from parent(s) to child connectors.push([pX, pY, pX, mY]); diff --git a/src/constants.ts b/src/constants.ts index b2884f8..f771914 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1,4 @@ export const SIZE = 2; export const HALF_SIZE = SIZE / 2; + +export const NODES_IN_COUPLE = 2; diff --git a/src/middle/create.ts b/src/middle/create.ts index 4ec08c6..306a308 100644 --- a/src/middle/create.ts +++ b/src/middle/create.ts @@ -6,6 +6,7 @@ import { setDefaultUnitShift } from '../utils/setDefaultUnitShift'; import { prop, withRelType } from '../utils'; import { newFamily } from '../utils/family'; import { unitsToNodes } from '../utils/units'; +import { NODES_IN_COUPLE } from '../constants'; import { Family, FamilyType, Node, RelType } from '../types'; import { correctOverlaps } from './correctOverlaps'; @@ -39,7 +40,7 @@ export const createBloodFamilies = (store: Store): readonly Family[] => { const mainFamily = createFamily(store.root.parents.map(prop('id')), FamilyType.root, true); const parents = unitsToNodes(mainFamily.parents); - if (parents.length === 2) { + if (parents.length === NODES_IN_COUPLE) { const { left, right } = getSpouseNodesFunc(store)(parents); return [ diff --git a/src/store.ts b/src/store.ts index 31b959e..a5edebb 100644 --- a/src/store.ts +++ b/src/store.ts @@ -2,6 +2,7 @@ import { toMap, withId } from './utils'; import { withType } from './utils/family'; import { Family, FamilyType, Node } from './types'; +// TODO think about refactoring class Store { private nextId: number; diff --git a/src/utils/family.ts b/src/utils/family.ts index 65282ff..4b982a4 100644 --- a/src/utils/family.ts +++ b/src/utils/family.ts @@ -1,7 +1,7 @@ import { SIZE } from '../constants'; import { Family, FamilyType, Unit } from '../types'; import { max } from './index'; -import { nodeCount, rightSide } from './units'; +import { getUnitX, nodeCount, rightSide } from './units'; export const newFamily = (id: number, type: FamilyType, main = false): Family => ({ id, @@ -23,3 +23,7 @@ export const heightOf = (family: Family): number => [ export const rightOf = (family: Family): number => family.X + widthOf(family); export const bottomOf = (family: Family) => family.Y + heightOf(family); export const unitNodesCount = (units: readonly Unit[]): number => units.reduce((acc, b) => acc + nodeCount(b), 0); + +export const getParentsX = (family: Family, unit: Unit | undefined): number => { + return unit ? getUnitX(family, unit) + nodeCount(unit) : 0; +}; diff --git a/src/utils/getExtendedNodes.ts b/src/utils/getExtendedNodes.ts index f364b6c..9f547f5 100644 --- a/src/utils/getExtendedNodes.ts +++ b/src/utils/getExtendedNodes.ts @@ -1,13 +1,14 @@ import { getUnitX } from './units'; import { hasHiddenRelatives } from './hasHiddenRelatives'; +import { SIZE } from '../constants'; import { ExtNode, Family, FamilyType, Node, Unit } from '../types'; const extendNode = (family: Family) => ( (unit: Unit) => ( unit.nodes.map((node: Node, idx: number) => ({ ...node, - top: family.Y + (unit.child && !!family.parents.length ? 2 : 0), - left: getUnitX(family, unit) + (idx * 2), + top: family.Y + (unit.child && !!family.parents.length ? SIZE : 0), + left: getUnitX(family, unit) + (idx * SIZE), hasSubTree: hasHiddenRelatives(family, node), })) ) diff --git a/src/utils/getSpouseNodesFunc.ts b/src/utils/getSpouseNodesFunc.ts index 8fb6cf8..52da5af 100644 --- a/src/utils/getSpouseNodesFunc.ts +++ b/src/utils/getSpouseNodesFunc.ts @@ -1,9 +1,8 @@ import Store from '../store'; +import { NODES_IN_COUPLE } from '../constants'; import { Node, Relation, RelType } from '../types'; import { byGender, relToNode, withRelType } from './index'; -const NODES_IN_COUPLE = 2; - type SpousesNodes = { left: readonly Node[]; middle: readonly Node[];