/** * @upsetjs/react * https://github.com/upsetjs/upsetjs * * Copyright (c) 2021 Samuel Gratzl */ import { generateCombinations, ISetCombinations, ISetLike, ISets, ISet, ISetCombination, GenerateSetCombinationsOptions, ISetOverlapFunction, generateDistinctOverlapFunction, } from '@upsetjs/model'; import { generateId, noGuessPossible } from '../../utils'; import type { ITextCircle, ITextArcSlice, IVennDiagramLayoutGenerator, ITextEllipse } from '../layout/interfaces'; import type { VennDiagramSizeInfo } from './deriveVennSizeDependent'; import { areCombinations } from '../../derive/deriveDataDependent'; export interface VennDiagramDataInfo { id: string; format(v: number): string; sets: { d: readonly { v: ISet; l: ITextCircle | ITextEllipse; key: string; offset: { x: number; y: number } }[]; v: ISets; format(v: number): string; }; cs: { d: readonly { v: ISetCombination; l: ITextArcSlice; key: string }[]; v: ISetCombinations; has(v: ISetCombination, s: ISet): boolean; }; toKey(s: ISetLike): string; toElemKey?(e: T): string; overlapGuesser: ISetOverlapFunction; } export default function deriveVennDataDependent( sets: ISets, combinations: ISetCombinations | GenerateSetCombinationsOptions, size: VennDiagramSizeInfo, layout: IVennDiagramLayoutGenerator, format: (v: number) => string, toKey: (s: ISetLike) => string, toElemKey?: (e: T) => string, id?: string, setLabelOffsets?: readonly { x: number; y: number }[] ): VennDiagramDataInfo { const ss = sets.length > layout.maxSets ? sets.slice(0, layout.maxSets) : sets; const { cs, setKeys, csKeys } = calculateCombinations(ss, toKey, combinations); const l = layout.compute(ss, cs, size.area.w, size.area.h); return { id: id ? id : generateId(), sets: { d: l.sets.map((l, i) => ({ v: ss[i], l, key: setKeys[i], offset: setLabelOffsets != null && i < setLabelOffsets.length ? setLabelOffsets[i] : { x: 0, y: 0 }, })), v: ss, format, }, format, cs: { d: l.intersections.map((l, i) => ({ v: cs[i], l, key: csKeys[i] })), v: cs, has: (v, s) => { const sk = toKey(s); return Array.from(v.sets).some((ss) => toKey(ss) === sk); }, }, toKey, toElemKey, overlapGuesser: generateDistinctOverlapFunction(cs, noGuessPossible, toKey), }; } export function calculateCombinations( ss: ISets, toKey: (s: ISetLike) => string, combinations: GenerateSetCombinationsOptions | ISetCombinations, options: GenerateSetCombinationsOptions = { min: 1 } ) { const setKeys = ss.map(toKey); let cs: ISetCombinations = []; if (areCombinations(combinations)) { const given = new Map(combinations.map((c) => [Array.from(c.sets).map(toKey).sort().join('#'), c])); const helperSets = ss.map((s) => ({ type: 'set' as const, cardinality: 0, elems: [], name: s.name, s, })); // generate dummy ones and map to given data cs = generateCombinations( helperSets, Object.assign( { type: 'distinctIntersection', empty: true, order: ['degree:asc', 'group:asc'], }, options ) ).map((c) => { const key = Array.from(c.sets) .map((s) => toKey((s as unknown as { s: ISet }).s)) .sort() .join('#'); if (given.has(key)) { return given.get(key)!; } // generate a dummy one return { name: c.name, cardinality: 0, degree: c.degree, elems: [], sets: new Set(Array.from(c.sets).map((s) => (s as unknown as { s: ISet }).s)), type: 'distinctIntersection', } as ISetCombination; }); } else { cs = generateCombinations( ss, Object.assign( { type: 'distinctIntersection', empty: true, order: ['degree:asc', 'group:asc'], }, options, combinations ?? {} ) ); } const csKeys = cs.map(toKey); return { cs, setKeys, csKeys }; }