效果图:
按每个标签出现的频次大小渲染出不同比例大小的圆,渲染的圆的宽度区间为 [40, 160] ,其中的文字的大小区间为 [12, 30] ,圆的位置随机摆放且不重叠。
根据已知条件可得出,标签中频次最高的对应圆的宽度(直径)为160px、字号为30px,频次最低的圆的宽度(直径)为40px、字号为12px,那么最终问题的关键就是如何计算出其他标签频次对应的圆的大小?
假设标签频次中的最大值为max,最小值为min。可画出如下图:
那么想要计算出current对应的宽度和字号,需要先计算出橙色这段线占整段线的比例,再根据比例计算这段线代表的宽度大小和字号大小。计算过程如下:
1、求比例:percent = (current-min) / (max-min)
2、根据比例求该段线对应的宽度大小,percent * (160-40)
3、同理求对应的字体大小,percent * (30-12)
4、那么current对应的字体和宽度为12 + percent * (30-12), 30 + percent * (160-40)
根据上述计算过程可以写出如下函数:
const getSize = (cur: number, min: number, max: number, minFontSize: number, maxFontSize: number, minWidth: number, maxWidth: number) => {
const countRange = max - min
const curRange = cur - min
const fontSizeRange = maxFontSize - minFontSize
const widthRange = maxWidth - minWidth
// 考虑min,max相同的情况
return {
fontSize: countRange > 0 ? minFontSize + curRange / countRange * fontSizeRange : maxFontSize,
width: countRange > 0 ? minWidth + curRange / countRange * widthRange : maxWidth
}
}
接下来思考:如何将圆随机摆放在矩形中,且保证圆不重叠,已知矩形的rectangleWidth和rectangleHeight,圆的直径width。
1、采用相对/绝对定位来放置圆,只需计算圆在矩形中的left, top值。
2、圆的位置随机,但是圆要保证在矩形区域内,那么left值的取值范围可以为[0, rectangleWidth],同时考虑圆的大小,区间最终为[0, rectangleWidth - width]
3、top值的取值范围可以为[0, rectangleHeight],同时考虑圆的大小,区间最终为[0, rectangleHeight - height]
// width, fontSize 可根据上述的getSize计算得出
interface Label {
count: number;
label: string;
width: number;
fontSize: number;
left: number;
top: number;
}
const labelData: Array<Label> = []
// 设置圆的left和top值
const setPosition = () => {
labelData.forEach((v) => {
v.left = Math.random() * (rectangleWidth - v.width)
v.top = Math.random() * (rectangleHeight - v.width)
})
}
考虑圆不重叠的情况优化上述代码,圆重叠即圆出现相交的情况,如何判断圆是否相交,根据数学公式: ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 < r 1 + r 2 \sqrt{(x1-x2)^ 2 + (y1-y2)^2} \quad < r1 + r2 (x1−x2)2+(y1−y2)2<r1+r2该公式成立则两圆相交,其中(x1,y1)为圆1的中心坐标,(x2,y2)为圆2的中心坐标,r1、r2分别为两个圆的半径。那么我们需要记录每次画的圆的中心坐标和半径,绘制圆之前先找出之前绘画的圆有没有与之相交的,如果有,则重新获取left、top的值
const circles: Array<{x: number, y: number, radius: number}> = []
const setPosition = () => {
labelData.forEach((v) => {
const radius = v.width / 2
let left: number, top: number, x: number, y: number;
// do while 判断之前会先do一次,如果找到相交圆,则继续执行do
do {
left = Math.random() * (rectangleWidth - v.width)
top = Math.random() * (rectangleHeight - v.width)
x = left + radius
y = top + radius
} while (
circles.some(circle => Math.sqrt(Math.pow(circle.x - x, 2) + Math.pow(circle.y - y, 2)) < radius + circle.radius)
)
v.left = left
v.top = top
circles.push({x, y, radius})
})
}
这里其实还需要考虑到适配的问题,如果矩形区域被缩放,那么文字和圆的大小也应该进行缩放,我这里采用的是scale的方式,计算当前屏幕相对于1920(我们开发的参考尺寸是1920*1080)的比例,然后将对应的maxFontSize * scale ,maxWidth * scale,最小的尺寸就不需要再考虑啦,已经很小啦,当然如果最大尺寸被缩放到比最小的都小的时候就直接取最小尺寸吧!!