Blazor入门-svg绘制-碰撞检测和图形坐标调整

上一篇:
Blazor入门-简单svg绘制+导出图像_blazor 画图-CSDN博客
https://blog.csdn.net/pxy7896/article/details/139003443

注意:本文只给出思路和框架,对于具体的计算细节,考虑到日后会写入软件著作权和专利文书,因此不会展示。


问题

上一篇的结尾提到Label重叠问题。事实上,这个问题很严重。

比如我手边这个文件,按照上一篇的方法绘制之后,会变成:(箭头是新画法,也是简单的三角函数,不展开)
在这里插入图片描述
这种重叠不仅让画面变得不美观、杂乱,更是影响了用户的阅读体验。

有的文件直接无法识别:
在这里插入图片描述
这是因为:

  • 某些元件的起终位置有重叠
  • 在某些区域中,元件彼此相隔太近,因此计算出的坐标彼此间距极小,显示到前台就会重叠

解决思路

处理起终位置重叠的元件

首先是对元件做排序,然后识别出有重叠的元件。

目前我采取一种傻瓜式的解决方案,流程是:

  1. 对元件做排序(可以按先起点后终点或者按中点升序排序),获取排序后的列表 L1
  2. 将可以放置元件的区域分成 N 层(实际是 N 层同心圆环),对于 L1 中每个特征,使用下面的分层方法:
    2.1 将第一个特征 f 0 f_0 f0 放入第一层;
    2.2 对于后续特征 f i f_i fi(起始数值分别为 f i s f_{is} fis f i e f_{ie} fie):遍历当前层的空闲位置,如果对于某个空闲位置 p i p_i pi(起始数值分别为 p i s p_{is} pis p i e p_{ie} pie ),那么满足 f i s > p i s , f i e < p i e f_{is} > p_{is}, f_{ie} < p_{ie} fis>pis,fie<pie ,即可放入当前层,修改当前层的对象列表;
    2.3 如果当前层无法放置 f i f_i fi,那么新建一个层,将 f i f_i fi 直接放入;
    2.4 循环直到遍历结束

在循环中,可以为每个元件记录所属层编号,层数直接与计算坐标时的半径关联。

代码只是简单的三角函数,不会给出。

处理效果

在这里插入图片描述
在这里插入图片描述

处理相隔很近但不重叠的元件

这个主要包含两块:

  1. 碰撞检测:即判断两个label是否有重合
  2. 调整label的位置

碰撞检测

如果有很多对象,需要进行比较高效的碰撞检测,可以考虑 Quadtree (四叉树) 算法。这个算法将空间分为4个象限,可以快速检测碰撞。这里当然可以使用,不过考虑到我的元素没那么多,就使用最简单的碰撞检测函数:

bool isLabelColliding(Rectangle r1, Rectangle r2) {
    double x1 = r1.X;
    double y1 = r1.Y;
    double w1 = r1.Width;
    double x2 = r2.X;
    double y2 = r2.Y; 
    double w2 = r2.Width;
    return x1 < x2 + w2 && 
        x1 + w1 > x2 &&
        y1 < y2 + Constants.charHeight &&
        y1 + Constants.charHeight > y2;
}

其中Rectangle是我定义的一个类,包含四个double类型的变量 X Y Width Height,分别表示矩形左下角顶点的坐标、宽度和高度。其中高度其实是固定的,因为我假设label都只有一行。

另外,因为我对行高留了一点缓冲量,同时可以忽略框本身轻微的重叠,所以我另外写了一个放松版检测函数:

bool isLabelCollidingSlack(Rectangle r1, Rectangle r2, double xo, double yo)
{
    double x1 = r1.X;
    double y1 = r1.Y;
    double w1 = r1.Width;
    double x2 = r2.X;
    double y2 = r2.Y;
    double w2 = r2.Width;
    return x1 + xo < x2 + w2 &&
        x1 + w1 > x2 + xo &&
        y1 + yo < y2 + Constants.charHeight &&
        y1 + Constants.charHeight > y2 + yo;
}

其中 xo 和 yo 表示 x 和 y 方向上放松的程度,可以是字的宽度或高度乘一个合适的系数。这个系数就来自实际数据中获得的经验了。

坐标调整

这个其实是最麻烦的一部分,因为盲目的调整可能引入新的碰撞。比如如果 A 和 B 碰撞,则将 B 向下移动一段距离,那么可能原本不重叠的 A 和 C 碰撞了。如果采用调整-碰撞检测-再调整,则需要考虑终止条件,以免超时。

我试过几种调整方式,比如:(当然最后都放弃了,因为调整效果不佳)

  1. 设置一个基础值 b ,然后对于每个label,给一个随机数作为 offset ,计算label位置时,半径是 r = b + o f f s e t r = b + offset r=b+offset,角度仍然使用元件中点( ( s t a r t + e n d ) / 2 (start + end) / 2 (start+end)/2)在圆上偏移的角度;
  2. 1中偏移的角度也可以再加偏移量;
  3. 在1的基础上,进行二次碰撞检测,对于仍然碰撞的,在 x 和 y 上增加一点偏移;
  4. 在3的基础上,偏移采用正负随机;
  5. 在3的基础上,对于偏移量的符号,应该根据label所在圆上的位置进行判断:将圆像坐标轴一样十字分割成四等分,向上需要-y,向下需要+y,向左-x,向右+x;
  6. 使用一个数组记录每个label与其他label碰撞的次数,碰撞次数为0则不需要处理,碰撞次数越多越需要远离圆心。因此不使用随机数和偏移量,而是固定半径,通过碰撞次数的多少调整 x 和 y 上的偏移量;
  7. 偏移量可以采用爬坡的方式,因为svg图是有边界的,超出边界后label是看不到的。爬坡的意思是偏移量先递增,到了边界再递减或者直接归到下边界值。

还有别的方法没记录。以上的方法也可以组合。但是效果仍然是不好的,因为太随机了,反而使得label的位置无法控制,而且调整后仍然可能再次引入碰撞。

最终的方法我不能写哈哈哈哈,是真的在保密。

我只能说用到了上述某两种方法的组合,而且做了两次碰撞检测(有用到放松版碰撞检测函数)。基本在第二次碰撞检测的时候就只剩很少很少 label 了(比如1~2个),然后我用了新的放置方式:将label以弧形的状态放置在圆环内侧,比如下图这样。(当然,其实也考虑了弧形放置的碰撞问题,那个就更复杂一点)
在这里插入图片描述
具体的过程是:

  • 将元件按照中点升序排序
  • 计算元件 label 内容的长度,与箭头中心弧线的长度做比较,如果能放下,说明 label 可以放在元件内部,那后续也不需要考虑碰撞问题;
  • 如果不能放在内部,先根据中点和原始的坐标计算方法,计算label的坐标和宽度
  • 将上述计算后的 label 进行碰撞检测,经过一些处理方法调整坐标,再进行第二轮碰撞检测
  • 如果还有碰撞,将产生碰撞的 label 以弧线的形态放入圆内部

涉及的svg技术

如何将文字写到弧线上

思路:将内容绑定到一条Path上。需要计算 path 的 d 属性,同时需要给该 path 指明 id 。对于blazor,只要在循环里绑定即可。
在这里插入图片描述
上图对应的html代码如下所示。其中 d 的计算与前篇文章中内弧和外弧的计算思路一致,先计算各种数据,然后再拼接。

<text font-size="16" fill="black" >
	<path id="p1" d="M 763.9369985579203 297.9295177580384 A 245 245 36.01448997965174 0 1 838.7345961997676 424.9473653915278 z" />
    <textPath href="#p1">Hello everyone!</textPath>
</text>

文字/label的书写方向

<text>中有一个属性是direction,值为"rtl"表示从右向左书写,"ltr"则相反。

矩形绘制中的坐标转换

在前面进行碰撞检测时,为了直观,我给所有label加了一些防撞框(或者说边界框),如下图所示:
在这里插入图片描述
需要注意的是,前面我的碰撞检测函数,是以左下角为顶点,向右延长width,向上延长height这样的方式构建边界框,再进行检测的。但是在svg绘制rect时,它是向下延长height构建的,所以需要给原本的坐标减去字高(假设只有一行),否则构建的框如下图所示:
在这里插入图片描述
同时,考虑到左右半圆,label中文字的书写方式是不同的,因为处理 rect 的 x 坐标也需要减去相应的宽度。

最后,rect的一种写法是:

<rect x="xx" y="xx" width="xx" height="xx" fill="none" stroke="red" stroke-width="1" />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值