{"version":3,"file":"layout.cjs","sources":["../../src/notes/layout.ts"],"sourcesContent":["import { createContext, ReactNode, useContext } from \"react\";\nimport { StatefulComponent } from \"@macrostrat/ui-components\";\nimport h from \"../hyper\";\n\nimport { hasSpan } from \"./utils\";\nimport { FlexibleNode, Force, Node, Renderer } from \"./label-primitives\";\nimport {\n  ColumnContext,\n  ColumnCtx,\n  ColumnDivision,\n  ColumnLayoutProvider,\n} from \"../context\";\nimport {\n  AgeRangeRelationship,\n  compareAgeRanges,\n} from \"@macrostrat/stratigraphy-utils\";\n\nconst NoteLayoutContext = createContext(null);\n\nconst buildColumnIndex = function () {\n  /*\n   * Find out where on the X axis arrows,\n   * etc. should plot to aviod overlaps\n   */\n  const heightTracker = [];\n  return function (note) {\n    let colIx = 0;\n    // Get column that note should render in\n    const nPossibleCols = heightTracker.length + 1;\n    for (\n      let column = 0, end = nPossibleCols, asc = 0 <= end;\n      asc ? column <= end : column >= end;\n      asc ? column++ : column--\n    ) {\n      if (heightTracker[column] == null) {\n        heightTracker[column] = note.height;\n      }\n      if (heightTracker[column] < note.height) {\n        const hy = note.top_height || note.height;\n        heightTracker[column] = hy;\n        colIx = column;\n        break;\n      }\n    }\n    return colIx;\n  };\n};\n\nfunction withinDomain(scale) {\n  const scaleDomain = scale.domain();\n  const d1: [number, number] = [\n    Math.min(...scaleDomain),\n    Math.max(...scaleDomain),\n  ];\n  return (d) => {\n    const noteRange: [number, number] = [d.height, d.top_height ?? d.height];\n    const rel = compareAgeRanges(d1, noteRange);\n\n    return rel !== AgeRangeRelationship.Disjoint;\n  };\n}\n\ninterface NoteLayoutProviderProps {\n  notes: any[];\n  width: number;\n  paddingLeft: number;\n  noteComponent: any;\n  forceOptions: object;\n  children?: ReactNode;\n}\n\ninterface NoteLayoutState {\n  notes?: any[];\n  elementHeights?: object;\n  columnIndex?: object;\n  nodes?: object;\n  updateHeight?: Function;\n  generatePath: Function;\n  createNodeForNote?: Function;\n  noteComponent?: any;\n  renderer?: typeof Renderer;\n}\n\nexport interface NoteLayoutCtx {\n  renderer: typeof Renderer;\n  paddingLeft: number;\n  scale: Function;\n  width: number;\n  updateHeight: Function;\n  generatePath: Function;\n  columnIndex?: any;\n  nodes?: any;\n}\n\nclass NoteLayoutProvider extends StatefulComponent<\n  NoteLayoutProviderProps,\n  NoteLayoutState\n> {\n  static contextType = ColumnContext;\n  static defaultProps = {\n    paddingLeft: 60,\n    estimatedTextHeight(note, width) {\n      const txt = note.note || \"\";\n      return 12;\n    },\n  };\n  declare context: ColumnCtx<ColumnDivision>;\n  _previousContext: ColumnCtx<ColumnDivision>;\n  _rendererIndex: object;\n\n  constructor(props) {\n    super(props);\n    this.computeContextValue = this.computeContextValue.bind(this);\n    this.savedRendererForWidth = this.savedRendererForWidth.bind(this);\n    this.generatePath = this.generatePath.bind(this);\n    this.createNodeForNote = this.createNodeForNote.bind(this);\n    this.computeForceLayout = this.computeForceLayout.bind(this);\n    this.updateHeight = this.updateHeight.bind(this);\n    this.updateNotes = this.updateNotes.bind(this);\n    this.componentDidMount = this.componentDidMount.bind(this);\n    this.componentDidUpdate = this.componentDidUpdate.bind(this);\n    // State is very minimal to start\n    const { noteComponent } = this.props;\n    this.state = {\n      notes: [],\n      elementHeights: {},\n      columnIndex: {},\n      nodes: {},\n      generatePath: this.generatePath,\n      createNodeForNote: this.createNodeForNote,\n      noteComponent,\n    };\n  }\n\n  render() {\n    const { children, width } = this.props;\n    return h(\n      NoteLayoutContext.Provider,\n      { value: this.state },\n      h(ColumnLayoutProvider, { width }, children),\n    );\n  }\n\n  computeContextValue() {\n    const { width, paddingLeft } = this.props;\n    // Clamp notes to within scale boundaries\n    // (we could turn this off if desired)\n    const { scaleClamped: scale } = this.context;\n\n    const forwardedValues = {\n      // Forwarded values from column context\n      // There may be a more elegant way to do this\n      paddingLeft,\n      scale,\n      width,\n    };\n\n    // Compute force layout\n    const renderer = new Renderer({\n      direction: \"right\",\n      layerGap: paddingLeft,\n      nodeHeight: 5,\n    });\n\n    return this.setState({\n      renderer,\n      updateHeight: this.updateHeight,\n      generatePath: this.generatePath,\n      ...forwardedValues,\n    });\n  }\n\n  savedRendererForWidth(width) {\n    if (this._rendererIndex == null) {\n      this._rendererIndex = {};\n    }\n    if (this._rendererIndex[width] == null) {\n      this._rendererIndex[width] = new Renderer({\n        direction: \"right\",\n        layerGap: width,\n        nodeHeight: 5,\n      });\n    }\n    return this._rendererIndex[width];\n  }\n\n  generatePath(node, pixelOffset) {\n    const { paddingLeft } = this.props;\n    const renderer = this.savedRendererForWidth(paddingLeft - pixelOffset);\n    try {\n      return renderer.generatePath(node);\n    } catch (err) {\n      return null;\n    }\n  }\n\n  createNodeForNote(note) {\n    const { notes, elementHeights } = this.state;\n    let { scaleClamped: scale } = this.context;\n    const { id: noteID } = note;\n    const pixelHeight = elementHeights[noteID] || 10;\n    const padding = 5;\n    let noteHeight = scale(note.height);\n    if (hasSpan(note)) {\n      const upperHeight = scale(note.top_height);\n      const harr: [number, number] = [\n        noteHeight - padding,\n        upperHeight + padding,\n      ];\n      if (harr[0] - harr[1] > 0) {\n        return new FlexibleNode(harr, pixelHeight);\n      }\n      noteHeight = (harr[0] + harr[1]) / 2;\n    }\n    return new Node(noteHeight, pixelHeight);\n  }\n\n  computeForceLayout(prevProps, prevState) {\n    let { notes, nodes, elementHeights } = this.state;\n    const { pixelHeight } = this.context;\n    const { width, paddingLeft, forceOptions } = this.props;\n\n    if (notes.length === 0) {\n      return;\n    }\n    // Something is wrong...\n    //return if elementHeights.length < notes.length\n    // Return if we've already computed nodes\n    const v1 = Object.keys(nodes).length === notes.length;\n    if (prevState == null) {\n      prevState = {};\n    }\n    const v2 = elementHeights === prevState.elementHeights || [];\n    if (v1 && v2) {\n      return;\n    }\n\n    const force = new Force({\n      minPos: 0,\n      maxPos: pixelHeight,\n      nodeSpacing: 0,\n      ...forceOptions,\n    });\n\n    const dataNodes = notes.map(this.createNodeForNote);\n\n    force.nodes(dataNodes).compute();\n    const _nodes = force.nodes() ?? [];\n    const nodesObj = {};\n    for (let i = 0; i < _nodes.length; i++) {\n      const node = _nodes[i];\n      const note = notes[i];\n      nodesObj[note.id] = node;\n    }\n\n    return this.updateState({ nodes: { $set: nodesObj } });\n  }\n\n  updateHeight(id, height) {\n    if (height == null) {\n      return;\n    }\n    const { elementHeights } = this.state;\n    elementHeights[id] = height;\n    return this.updateState({ elementHeights: { $set: elementHeights } });\n  }\n\n  updateNotes() {\n    // We received a new set of notes from props\n    const { scaleClamped } = this.context;\n    const notes = this.props.notes\n      .filter(withinDomain(scaleClamped))\n      .sort((a, b) => a.height - b.height);\n    const columnIndex = notes.map(buildColumnIndex());\n    return this.setState({ notes, columnIndex });\n  }\n\n  /*\n   * Lifecycle methods\n   */\n  componentDidMount() {\n    this._previousContext = null;\n    this.updateNotes();\n    return this.computeContextValue();\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    if (this.props.notes !== prevProps.notes) {\n      this.updateNotes();\n    }\n\n    // Update note component\n    const { noteComponent } = this.props;\n    if (noteComponent !== prevProps.noteComponent) {\n      this.setState({ noteComponent });\n    }\n    this.computeForceLayout.call(prevProps, prevState);\n    if (this.props.notes === prevProps.notes) {\n      return;\n    }\n    if (this.context === this._previousContext) {\n      return;\n    }\n    this.computeContextValue();\n    return (this._previousContext = this.context);\n  }\n}\n\nfunction NoteRect(props) {\n  let { padding, width, ...rest } = props;\n  if (padding == null) {\n    padding = 5;\n  }\n  const { pixelHeight } = useContext(ColumnContext);\n  if (width == null) {\n    ({ width } = useContext(NoteLayoutContext));\n  }\n  if (isNaN(width)) {\n    return null;\n  }\n\n  return h(\"rect\", {\n    width: width + 2 * padding,\n    height: pixelHeight,\n    transform: `translate(${-padding},${-padding})`,\n    ...rest,\n  });\n}\n\nconst NoteUnderlay = function ({ ...rest }) {\n  return h(NoteRect, {\n    className: \"underlay\",\n    ...rest,\n  });\n};\n\nexport function useNoteLayout() {\n  const ctx = useContext(NoteLayoutContext);\n  if (ctx == null) {\n    throw new Error(\"useNoteLayout must be used within a NoteLayoutProvider\");\n  }\n  return ctx;\n}\n\nexport { NoteLayoutContext, NoteLayoutProvider, NoteRect, NoteUnderlay };\n"],"names":["createContext","column","compareAgeRanges","AgeRangeRelationship","StatefulComponent","ColumnContext","h","ColumnLayoutProvider","Renderer","hasSpan","FlexibleNode","Node","Force","useContext"],"mappings":"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"}