import _ from 'lodash'; import { forwardRef, useRef } from 'react'; import { LayoutFn, Measure, withBluefishFn } from '../../bluefish'; import { NewBBox } from '../../NewBBox'; import { Align } from '../Align'; import { Connector } from '../Connector'; import { Group } from '../Group'; import { Rect } from '../Rect'; import { Ref } from '../Ref'; import { Text } from '../Text'; import labelLayout from './LabelLayout'; // 8-bit representation of anchors const TOP = 0x0, MIDDLE = 0x4, BOTTOM = 0x8, LEFT = 0x0, CENTER = 0x1, RIGHT = 0x2; // Mapping from text anchor to number representation const anchorCode = { 'top-left': TOP + LEFT, top: TOP + CENTER, 'top-right': TOP + RIGHT, left: MIDDLE + LEFT, middle: MIDDLE + CENTER, right: MIDDLE + RIGHT, 'bottom-left': BOTTOM + LEFT, bottom: BOTTOM + CENTER, 'bottom-right': BOTTOM + RIGHT, }; export const Output = ['x', 'y', 'opacity', 'align', 'baseline'] as const; export const Anchors = [ 'top-left', 'left', 'bottom-left', 'top', 'bottom', 'top-right', 'right', 'bottom-right', ] as const; export type PointLabelProps = { texts: { label: any; ref: any }[]; compare: ((a: any, b: any) => number) | undefined; offset: number[]; anchor: readonly (keyof typeof anchorCode)[]; avoidElements: any[]; avoidRefElements: boolean; padding: number; }; export const PointLabel = forwardRef(function PointLabel({ texts }: PointLabelProps, ref: any) { return ( {texts.map((text) => ( <> {text.label} ))} ); }); export const PointLabelAux = LayoutFn( (props): Measure => { return (measurables, constraints) => { // const [_label, ref] = measurables; // const [labelBBox, refBBox] = measurables.map((m) => m.measure(constraints)); // const refDomRef: SVGElement | null = ref.domRef; // generalize this to the whole array // https://stackoverflow.com/a/44656332 let i = -1; const [labels, refs] = _.partition(measurables, (_) => i++ % 2); const labelBBoxes = labels.map((m) => m.measure(constraints)); const refBBoxes = refs.map((m) => m.measure(constraints)); const refDomRefs: (SVGElement | null | undefined)[] = refs.map((m) => m.domRef); // early return if we don't have refs yet if (refDomRefs.some((refDomRef) => refDomRef === null || refDomRef === undefined)) { return { /* width: 0, height: 0 */ }; } // if (refDomRef === null || refDomRef === undefined) { // return { // width: 0, // height: 0, // }; // } labelLayout({ // labels and anchor points texts: _.zipWith(labelBBoxes, refDomRefs, (labelBBox, refDomRef) => ({ label: labelBBox, ref: refDomRef as SVGElement /* early return guarantees this */, })), // canvas size (provided by parent in Bluefish) size: [constraints.width!, constraints.height!], // optional sorting function to determine label layout priority order compare: undefined, // label offset from anchor point offset: [1], // offset orientation (e.g. 'top-left') anchor: Anchors, // optional list of elements to avoid (like a line mark) avoidElements: [], // whether or not we should avoid the anchor points (circle1, circle2, circle3) avoidRefElements: true, // padding around canvas to allow labels to be partially offscreen padding: 0, }); // bbox should just be a union over all children (this should be the default case somewhere...) // const left = Math.min(fromX!, toX!); // const top = Math.min(fromY!, toY!); // const right = Math.max(fromX!, toX!); // const bottom = Math.max(fromY!, toY!); // const left = Math.min(labelBBox.left!, refBBox.left!); // const top = Math.min(labelBBox.top!, refBBox.top!); // const right = Math.max(labelBBox.right!, refBBox.right!); // const bottom = Math.max(labelBBox.bottom!, refBBox.bottom!); const left = Math.min( ...labelBBoxes.map((labelBBox) => labelBBox.left!), ...refBBoxes.map((refBBox) => refBBox.left!), ); const top = Math.min( ...labelBBoxes.map((labelBBox) => labelBBox.top!), ...refBBoxes.map((refBBox) => refBBox.top!), ); const right = Math.max( ...labelBBoxes.map((labelBBox) => labelBBox.right!), ...refBBoxes.map((refBBox) => refBBox.right!), ); const bottom = Math.max( ...labelBBoxes.map((labelBBox) => labelBBox.bottom!), ...refBBoxes.map((refBBox) => refBBox.bottom!), ); // TODO: annoying problem where these values don't actually get propagated? const width = right - left; const height = bottom - top; return { left, top, right, bottom, width, height, }; }; }, // (props: PointLabelProps & { $bbox?: Partial }) => { // const { $bbox, ...rest } = props; // return ( // // ); // }, );