D3.js - 调整文本大小以适应任何多边形
D3.js - Resize text to fit any polygon
如何调整文本大小以适应 D3js 中的任何给定多边形?
我需要如图所示的东西:
我发现类似 topics 但没有可用的分辨率:同样 old/deprecated/examples 不工作。
这个问题本质上归结为在多边形内找到一个最大的矩形,在这种情况下与水平轴对齐并具有固定的纵横比,这由文本给出。
以高效的方式找到这个矩形不是一件容易的事,但是有可用的算法。例如d3plus-library. The details of this algorithm (which finds a good but not an optimal rectangle) are described in this blog post.
中的largestRect
方法
利用矩形的坐标,您可以变换文本,使其包含在矩形中,i。 e.
- 转换到矩形的左下角并
- 按矩形宽度与文本宽度的比例缩放。
如果您不想在您的依赖列表中添加额外的库,并且您正在考虑的多边形(几乎)是凸的并且不是高度不规则的,您可以尝试自己找到一个“令人满意的矩形”。下面,我对以多边形质心为中心的矩形进行了二进制搜索。在每次迭代中,我使用 d3-polygon 的 d3.polygonContains
方法检查四个角是否在多边形内部。生成的矩形为绿色以供比较。当然,这只是一个起点。
const dim = 500;
const svg = d3.select("svg").attr("width", dim).attr("height", dim);
const text = svg.append("text").attr("x", 0).attr("y", 0);
const polygon = svg.append("polygon").attr("fill", "none").attr("stroke", "blue");
const rectangle = svg.append("polygon").attr("fill", "none").attr("stroke", "red");
const rectangle2 = svg.append("polygon").attr("fill", "none").attr("stroke", "green");
d3.select("input").on("change", fitText);
d3.select("button").on("click", drawPolygon);
// Draw random polygon
function drawPolygon() {
const num_points = 3 + Math.ceil(7 * Math.random());
points = [];
for (let i = 0; i < num_points; i++) {
const angle = 2 * Math.PI / num_points * (i + 0.1 + 0.8 * Math.random());
const radius = dim / 2 * (0.1 + 0.9 * Math.random());
points.push([
radius * Math.cos(angle) + dim / 2,
radius * Math.sin(angle) + dim / 2,
])
}
polygon.attr("points", points.map(d => d.join()).join(' '));
fitText();
}
function fitText() {
// Set text to input value and reset transform.
text.text(d3.select("input").property("value")).attr("transform", null);
// Get dimensions of text
const text_dimensions = text.node().getBoundingClientRect();
const ratio = text_dimensions.width / text_dimensions.height;
// Find largest rectangle
const rect = d3plus.largestRect(points, {angle: 0, aspectRatio: ratio}).points;
// transform text
const scale = (rect[1][0] - rect[0][0]) / text_dimensions.width;
text.attr("transform", `translate(${rect[3][0]},${rect[3][1]}) scale(${scale})`);
rectangle.attr("points", rect.map(d => d.join()).join(' '));
// alternative
const rect2 = satisfyingRect(ratio);
rectangle2.attr("points", rect2.map(d => d.join()).join(' '));
}
function satisfyingRect(ratio) {
// center rectangle around centroid
const centroid = d3.polygonCentroid(points);
let minWidth = 0;
let maxWidth = d3.max(points, d => d[0]) - d3.min(points, d => d[0]);
let rect;
for (let i = 0; i < 20; i++) {
const width = 0.5 * (maxWidth + minWidth);
rect = [
[centroid[0] - width, centroid[1] - width / ratio],
[centroid[0] + width, centroid[1] - width / ratio],
[centroid[0] + width, centroid[1] + width / ratio],
[centroid[0] - width, centroid[1] + width / ratio]
]
if (rect.every(d => d3.polygonContains(points, d)))
minWidth = width;
else
maxWidth = width;
}
return rect;
}
let points;
drawPolygon();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3plus-shape@1"></script>
<div>
<input type="text" value="lorem ipsum dolor">
<button>New polygon</button>
</div>
<svg></svg>
如何调整文本大小以适应 D3js 中的任何给定多边形?
我需要如图所示的东西:
我发现类似 topics 但没有可用的分辨率:同样 old/deprecated/examples 不工作。
这个问题本质上归结为在多边形内找到一个最大的矩形,在这种情况下与水平轴对齐并具有固定的纵横比,这由文本给出。
以高效的方式找到这个矩形不是一件容易的事,但是有可用的算法。例如d3plus-library. The details of this algorithm (which finds a good but not an optimal rectangle) are described in this blog post.
中的largestRect
方法
利用矩形的坐标,您可以变换文本,使其包含在矩形中,i。 e.
- 转换到矩形的左下角并
- 按矩形宽度与文本宽度的比例缩放。
如果您不想在您的依赖列表中添加额外的库,并且您正在考虑的多边形(几乎)是凸的并且不是高度不规则的,您可以尝试自己找到一个“令人满意的矩形”。下面,我对以多边形质心为中心的矩形进行了二进制搜索。在每次迭代中,我使用 d3-polygon 的 d3.polygonContains
方法检查四个角是否在多边形内部。生成的矩形为绿色以供比较。当然,这只是一个起点。
const dim = 500;
const svg = d3.select("svg").attr("width", dim).attr("height", dim);
const text = svg.append("text").attr("x", 0).attr("y", 0);
const polygon = svg.append("polygon").attr("fill", "none").attr("stroke", "blue");
const rectangle = svg.append("polygon").attr("fill", "none").attr("stroke", "red");
const rectangle2 = svg.append("polygon").attr("fill", "none").attr("stroke", "green");
d3.select("input").on("change", fitText);
d3.select("button").on("click", drawPolygon);
// Draw random polygon
function drawPolygon() {
const num_points = 3 + Math.ceil(7 * Math.random());
points = [];
for (let i = 0; i < num_points; i++) {
const angle = 2 * Math.PI / num_points * (i + 0.1 + 0.8 * Math.random());
const radius = dim / 2 * (0.1 + 0.9 * Math.random());
points.push([
radius * Math.cos(angle) + dim / 2,
radius * Math.sin(angle) + dim / 2,
])
}
polygon.attr("points", points.map(d => d.join()).join(' '));
fitText();
}
function fitText() {
// Set text to input value and reset transform.
text.text(d3.select("input").property("value")).attr("transform", null);
// Get dimensions of text
const text_dimensions = text.node().getBoundingClientRect();
const ratio = text_dimensions.width / text_dimensions.height;
// Find largest rectangle
const rect = d3plus.largestRect(points, {angle: 0, aspectRatio: ratio}).points;
// transform text
const scale = (rect[1][0] - rect[0][0]) / text_dimensions.width;
text.attr("transform", `translate(${rect[3][0]},${rect[3][1]}) scale(${scale})`);
rectangle.attr("points", rect.map(d => d.join()).join(' '));
// alternative
const rect2 = satisfyingRect(ratio);
rectangle2.attr("points", rect2.map(d => d.join()).join(' '));
}
function satisfyingRect(ratio) {
// center rectangle around centroid
const centroid = d3.polygonCentroid(points);
let minWidth = 0;
let maxWidth = d3.max(points, d => d[0]) - d3.min(points, d => d[0]);
let rect;
for (let i = 0; i < 20; i++) {
const width = 0.5 * (maxWidth + minWidth);
rect = [
[centroid[0] - width, centroid[1] - width / ratio],
[centroid[0] + width, centroid[1] - width / ratio],
[centroid[0] + width, centroid[1] + width / ratio],
[centroid[0] - width, centroid[1] + width / ratio]
]
if (rect.every(d => d3.polygonContains(points, d)))
minWidth = width;
else
maxWidth = width;
}
return rect;
}
let points;
drawPolygon();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3plus-shape@1"></script>
<div>
<input type="text" value="lorem ipsum dolor">
<button>New polygon</button>
</div>
<svg></svg>