【问题标题】:Draw text at center of polygons在多边形中心绘制文本
【发布时间】:2022-12-10 06:33:45
【问题描述】:

我从数据库中获取了一组多边形。每个形状可以是三角形、矩形、正方形或任何多边形。

我想在每个多边形的中心绘制文本。字体大小必须根据每个多边形的大小是动态的。文本颜色应与线条颜色匹配。

来自数据库的示例:

这是我的代码:

var polygons = [
  {
    text: "ROI",    color: "#00ff00",
    jointLength: 5,    lineWidth: 3,
    X: [890, 893, 409, 21, 27],    Y: [658, 205, 199, 556, 659],
  },  {
    text: "Lane 3",    color: "#ff0000",
    jointLength: 4,    lineWidth: 3,
    X: [915, 911, 643, 879],    Y: [5, 682, 683, 2],
  },  {
    text: "Lane 4",    color: "#ff0000",
    jointLength: 4,    lineWidth: 3,
    X: [888, 656, 170, 701],    Y: [2, 680, 682, 1],
  },  {
    text: "Lane 5",    color: "#ff0000",
    jointLength: 5,    lineWidth: 3,
    X: [712, 182, 4, 4, 590],    Y: [1, 681, 682, 532, 1],
  },  {
    text: "Speed",    color: "#0000ff",
    jointLength: 4,    lineWidth: 3,
    X: [290, 911, 873, 5],    Y: [367, 357, 668, 664],
  }
];

polygons.forEach((polygon) => {
  const ctx = document.getElementById("canvas").getContext("2d");
  ctx.strokeStyle = polygon.color;
  ctx.lineWidth = polygon.lineWidth;
  ctx.beginPath();
  ctx.moveTo(polygon.X[0], polygon.Y[0]);
  for (let i = 1; i < polygon.jointLength; i++) {
    ctx.lineTo(polygon.X[i], polygon.Y[i]);
  }
  ctx.closePath();
  ctx.stroke();
});
&lt;canvas id="canvas" width=999 height=999&gt;&lt;/canvas&gt;

【问题讨论】:

  • 你能和我们分享一下多边形数组吗?
  • 哪个center
  • 哪个中心,Circumscribed 或 Centroid 并不重要

标签: javascript canvas html5-canvas


【解决方案1】:

主要逻辑说明:

  • 我用算术平均值公式计算的多边形中心
  • 我通过使用font-size = 300获取文本宽度计算的字体大小(但您可以根据需要更改第一个检查大小)然后检查text with是否大于2个最近点之间的最小距离(如果文本位于多边形的中心,我认为这是一个很好的限制)。如果是,那么我开始使用二进制搜索算法找到正确的font-size

由于这个逻辑,第二个多边形中的文本比它可以的要小,因为我们在顶部有 2 个点,它们彼此非常接近

有一个代码(打开整页以获得更好的可见性):

const polygons = [
  {
    text: "ROI",
    color: "red",
    jointLength: 5,
    lineWidth: 3,
    X: [890, 893, 409, 21, 27],
    Y: [658, 205, 199, 556, 659],
  },
  {
    text: "Lane 3",
    color: "blue",
    jointLength: 4,
    lineWidth: 3,
    X: [915, 911, 643, 879],
    Y: [5, 682, 683, 2],
  },
  {
    text: "Lane 4",
    color: "green",
    jointLength: 4,
    lineWidth: 3,
    X: [888, 656, 170, 701],
    Y: [2, 680, 682, 1],
  },
  {
    text: "Lane 5",
    color: "orange",
    jointLength: 5,
    lineWidth: 3,
    X: [712, 182, 4, 4, 590],
    Y: [1, 681, 682, 532, 1],
  },
  {
    text: "Speed",
    color: "purple",
    jointLength: 4,
    lineWidth: 3,
    X: [290, 911, 873, 5],
    Y: [367, 357, 668, 664],
  },
];

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 1000;

class Polygon {
  #ctx;
  #dots = [];
  #text;
  #color;
  #lineWidth;
  #dotsCount;
  
  constructor(ctx, data) {
    this.#ctx = ctx;
    this.#text = data.text;
    this.#color = data.color;
    this.#lineWidth = data.lineWidth;
    this.#dotsCount = data.jointLength;
    
    for (let i = 0; i < this.#dotsCount; ++ i) {
      this.#dots.push({x: data.X[i], y: data.Y[i]})
    }
  }
  
  #getCenterCoords() {
    const x = this.#dots.reduce((sum, dot) => sum += dot.x, 0) / this.#dotsCount;
    const y = this.#dots.reduce((sum, dot) => sum += dot.y, 0) / this.#dotsCount;
    
    return {x, y};
  }
  
  #distance = (dot1, dot2) => Math.sqrt((dot1.x - dot2.x) ** 2 + (dot1.y - dot2.y) ** 2);
  
  #getMinimalDistanceBetweenDots() {
    let minDist = Infinity;
    
    for (let i = 0; i < this.#dotsCount; ++i) {
      const dot1 = this.#dots[i];
      
      for (let j = i + 1; j < this.#dotsCount; ++j) {
        const dot2 = this.#dots[j];
        const dist = this.#distance(dot1, dot2);
        
        if (dist < minDist) minDist = dist;
      }
    }
    
    return minDist;
  }
  
  #getTextSize() {
    const minAvailableWidth = this.#getMinimalDistanceBetweenDots();
    
    let rightBound = 300;
    let leftBound = 0;
    let fontSize = rightBound;

    while (rightBound - leftBound > 1) {
    
      fontSize = Math.round((leftBound + rightBound) / 2);
      this.#ctx.font = `${fontSize}px verdana`;
      const textSize = this.#ctx.measureText(this.#text).width;
      
      if (textSize > minAvailableWidth) {
        rightBound = fontSize;
        continue;
      }
      
      if (textSize < minAvailableWidth) {
        leftBound = fontSize;
        continue;
      }
      
      if (textSize === minAvailableWidth) {
        break;
      }
    }
    
    return fontSize;
  }
  
  draw() {
    const path = new Path2D();
    const firstDot = this.#dots[0];
    const center = this.#getCenterCoords();
  
    this.#dots.forEach(dot => path.lineTo(dot.x, dot.y));

    path.lineTo(firstDot.x, firstDot.y);

    this.#ctx.strokeStyle = this.#color;
    this.#ctx.lineWidth = this.#lineWidth;
    this.#ctx.lineCap = 'round';
    this.#ctx.lineJoin = 'round';
    this.#ctx.stroke(path);
    
    this.#ctx.font = `${this.#getTextSize()}px verdana`;
    this.#ctx.fillStyle = this.#color;
    this.#ctx.textAlign = 'center'; 
    this.#ctx.fillText(this.#text, center.x, center.y);
  }
}

polygons.forEach((polygon) => new Polygon(ctx, polygon).draw());
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

  • 字体上的奇怪行为......乍一看,ROI 字体应该和 Speed 一样大
  • @HelderSepulveda 是的,我知道。这是我为第二个(蓝色)多边形描述的问题。但我不知道任何多边形中文本限制的最佳选择。如果您知道更好的解决方案,请分享 :) 我很乐意学习新东西)
  • 这个问题的答案非常好(+10),OP 甚至没有尝试绘制文本......但值得指出的是,minAvailableWidth 应该在绘制文本的部分计算,我们可以在@上看到987654326@ 多边形在顶部很窄,因此文本非常小,确定字体大小的循环也是一种蛮力方法,可以通过二进制搜索显着改进
  • @HelderSepulveda 我试着实施你的建议。现在好点了吗?)
  • 是的,我看到了新的getTextSize,这是一个了不起的改进
猜你喜欢
  • 2013-11-10
  • 1970-01-01
  • 2011-10-23
  • 1970-01-01
  • 2014-02-11
  • 1970-01-01
  • 2015-03-19
  • 2011-12-30
相关资源
最近更新 更多