如何在Konva中创建一个带有动态文本定位的垂直箭头文本标注?

huangapple go评论66阅读模式
英文:

How do I Create a Vertical Arrow with Text Annotation with Dynamic Text Positioning in Konva?

问题

我尝试通过Konva和react-konva在用户界面中绘制一些几何形状。其中一个基本元素是带有标注文本的垂直箭头,用于显示形状的尺寸,如下所示:

如何在Konva中创建一个带有动态文本定位的垂直箭头文本标注?

它是由以下代码片段创建的,这是我的第一次尝试实现它:

import {Stage, Layer, Group, Rect, Arrow, Text} from 'react-konva';

function AnnotatedVerticalArrow({x, y0, y1, text})
{
  return (
    <Group>
      <Arrow
        points={[x, y0, x, y1]}
        pointerAtBeginning={true}
        pointerAtEnding={true}
        fill="black"
        stroke="black"
      />  
      <Text
        x={x - 35} 
        y={(y1 - y0) / 2}
        text={text}
        fontSize={12}
      />  
    </Group>
  )
}

function App() {
  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <AnnotatedVerticalArrow
          x={50}
          y0={0}
          y1={100}
          text="2.54"
        />
      </Layer>
    </Stage>
  );  
}

export default App;

然而,我不知道如何正确定位文本,这种实现有几个问题:

  1. 文本标签的居中不正确,因为文本标签的原点不在文本的中心,而在角落。为了找到它的中心,需要文本的尺寸。

  2. 箭头和文本标签之间的间距是针对字体大小为12的硬编码值。更改字体大小、使用较短文本或较长文本会由于过多或不足的间距而破坏用户界面。

我该如何计算Text的尺寸并在渲染时使用其尺寸?

我曾看到一个关于GitHub仓库konvajs/react-konva中文本大小计算的广泛讨论,建议的解决方案是在componentDidMount()中计算大小,然后使用大小来更改元素的状态并强制重新渲染。但示例代码需要更新,因为它是为React类编写的,而不是React钩子。该示例也不清楚如何计算不同字体大小的尺寸。还有人提出使用measureText,但有评论者声称结果存在错误。还有另一位开发人员提出了一个极其复杂的解决方案,它的工作原理不明显。

英文:

I'm trying to draw some geometric shapes in the user interface via Konva and react-konva. One basic element is a vertical arrow with annotated text to show the dimension of a shape, as shown below:

如何在Konva中创建一个带有动态文本定位的垂直箭头文本标注?

It's created by the following code snippet, which was my first attempt at implementing it.

import {Stage, Layer, Group, Rect, Arrow, Text} from &#39;react-konva&#39;;

function AnnotatedVerticalArrow({x, y0, y1, text})
{
  return (
    &lt;Group&gt;
      &lt;Arrow
        points={[x, y0, x, y1]}
        pointerAtBeginning={true}
        pointerAtEnding={true}
        fill=&quot;black&quot;
        stroke=&quot;black&quot;
      /&gt;  
      &lt;Text
        x={x - 35} 
        y={(y1 - y0) / 2}
        text={text}
        fontSize={12}
      /&gt;  
    &lt;/Group&gt;
  )
}

function App() {
  return (
    &lt;Stage width={window.innerWidth} height={window.innerHeight}&gt;
      &lt;Layer&gt;
        &lt;AnnotatedVerticalArrow
          x={50}
          y0={0}
          y1={100}
          text=&quot;2.54&quot;
        /&gt;
      &lt;/Layer&gt;
    &lt;/Stage&gt;
  );  
}

export default App;

However, I don't know how to correctly position the text, and this implementation has several problems:

  1. The text label is not centered properly, as the origin of the text label is not the center of the text, but at the corner. In order to find its center, the dimension of the text is required.

  2. The spacing between the arrow and the text label is hardcoded for a font with size 12. Changing font size, using shorter text, or using longer text breaks the user interface due to excessive or insufficient spacing.

How do I calculate the size of a Text and use its dimension during rendering?

I've seen an extensive discussion about text size calculation at GitHub repository konvajs/react-konva, the suggested solution is calculating the size in componentDidMount(), then using the size to change the state of the element and force a rerender. But the example code needs an update since it's written for React classes, not React hooks. The example is also unclear about how to calculate the dimension with different font sizes. Another proposed using measureText, but one commenter claimed the result was buggy. Yet another developer suggested an extremely complicated workaround, how it works is not obvious.

答案1

得分: 1

我通过结合 回答问题 在渲染之前如何获取 React 组件的大小(高度/宽度) 和 GitHub 仓库 konvajs/react-konva 中的示例代码的方式找到了答案 Issue #6

解决方案如下:

  1. 使用 useState() 来表示文本的计算大小状态 calculatedTextSize,并将默认值初始化为 0
  2. 使用 useRef() 创建对 Text 组件的引用,命名为 textRef
  3. 将要返回的 Text 组件的 ref 属性设置为 textRef,以便此组件与引用关联,并在代码中访问它。
  4. 通过访问 calculatedTextSize.widthcalculatedTextSize.height 来计算 Text 的大小,就好像它已经计算完成一样。
  5. React.useLayoutEffect() 回调函数中执行大小计算。此函数在完成首次渲染后执行。在函数内部,调用 Text 的 measureSize() 方法以获得结果,然后通过 setCalculatedTextSize() 改变状态来传递结果。
  6. ...这将触发重新渲染,以便使用新的大小。

AnnotatedVerticalArrow() 的新实现如下:

import React from 'react';
import {Stage, Layer, Group, Rect, Arrow, Text} from 'react-konva';

function AnnotatedVerticalArrow({x, y0, y1, text}) {
  const textRef = React.useRef();
  const [calculatedTextSize, setCalculatedTextSize] =
    React.useState({width: 0, height: 0});

  React.useLayoutEffect(() => {
    if (textRef.current) {
      setCalculatedTextSize({
        width: textRef.current.measureSize(text).width,
        height: textRef.current.measureSize(text).height
      }); 
    }   
  }, [text]);  // "text" 必须包含在依赖数组中,否则其大小的重复更改将被忽略

  return (
    <Group>
      <Arrow
        points={[x, y0, x, y1]}
        pointerAtBeginning={true}
        pointerAtEnding={true}
        fill="black"
        stroke="black"
      />
      <Text
        ref={textRef}
        x={x - calculatedTextSize.width * 1.1}
        y={(y1 - y0) / 2 - calculatedTextSize.height / 2}
        text={text}
        fontSize={12}
      />  
    </Group>
  )
}

这仍然可以正常工作,即使 fontSize 已更改,因为 textRef 始终是我们在渲染代码路径中创建的 Text 组件的引用。

英文:

I've found the answer by combining the answer to the question How to get a react component's size (height/width) before render? and the example code in GitHub repo konvajs/react-konva's Issue #6.

The solution is to:

  1. Represent the calculated size of the text as a state, calculatedTextSize via useState(), initialize the default values to 0.
  2. Create a reference to the Text as textRef via useRef().
  3. Set the property ref of the Text we're returning to textRef, so this component is associated with the reference and becomes accessible in code.
  4. Calculate the size of the Text by accessing calculatedTextSize.width and calculatedTextSize.height, as if it's already calculated.
  5. Perform the size calculation in React.useLayoutEffect() callback. This function is executed when the first render pass is completed. Within the function, call the measureSize() method of the Text to obtain the result, then pass the result by changing the state via setCalculatedTextSize().
  6. ...which triggers a re-render, so the new size is used.

The new implementation of AnnotatedVerticalArrow() is:

import React from &#39;react&#39;;
import {Stage, Layer, Group, Rect, Arrow, Text} from &#39;react-konva&#39;;

function AnnotatedVerticalArrow({x, y0, y1, text})
{
  const textRef = React.useRef();
  const [calculatedTextSize, setCalculatedTextSize] =
    React.useState({width: 0, height: 0});

  React.useLayoutEffect(() =&gt; {
    if (textRef.current) {
      setCalculatedTextSize({
        width: textRef.current.measureSize(text).width,
        height: textRef.current.measureSize(text).height
      }); 
    }   
  }, [text]);  // &quot;text&quot; must be inside the dependency array
               // otherwise repeated changes to its size will be ignored

  return (
    &lt;Group&gt;
      &lt;Arrow
        points={[x, y0, x, y1]}
        pointerAtBeginning={true}
        pointerAtEnding={true}
        fill=&quot;black&quot;
        stroke=&quot;black&quot;
      /&gt;
      &lt;Text
        ref={textRef}
        x={x - calculatedTextSize.width * 1.1}
        y={(y1 - y0) / 2 - calculatedTextSize.height / 2}
        text={text}
        fontSize={12}
      /&gt;  
    &lt;/Group&gt;
  )
}

It remains working even if fontSize has been changed, since textRef is always a reference to the Text component that we've created in the rendering code path.

huangapple
  • 本文由 发表于 2023年3月4日 04:06:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75631452.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定