绘制一个边长为15/20mm的矩形周长为2,将它绕其一边旋转一周,环绕此矩形周长为2,将它绕其一边旋转一周绘制一个边长为5/6mm的矩形周长为2,将它绕其一边旋转一周怎么画?

前言本节我们通过简单的矩形绘制学习如何实现无限画布准备工作在绘制前,我们需要矫正 canvas 的分辨率,使用 appState 保存 canvas 相关的信息。新建一个 index.jsx 文件,初始化代码如下:const appState = {offsetLeft: 0,offsetTop: 0, }; const Canvas = memo(() => {const canvasRef = useRef(null);const canvasContainer = useRef(null);useEffect(() => {const canvas = canvasRef.current;const context = canvas.getContext("2d");const { offsetWidth, offsetHeight, offsetLeft, offsetTop } = canvas;canvas.width = offsetWidth * window.devicePixelRatio;canvas.height = offsetHeight * window.devicePixelRatio;context.scale(window.devicePixelRatio, window.devicePixelRatio);appState.offsetLeft = offsetLeft;appState.offsetTop = offsetTop;}, []);return (
绘制canvas
); }); 绘制坐标轴为方便观察,首先在 canvas 上绘制一个坐标轴。新建一个 renderScene.js 文件,实现 drawAxis 方法:const drawAxis = (ctx) => {ctx.save();const rectH = 100; // 纵轴刻度间距const rectW = 100; // 横轴刻度间距const tickLength = 8; // 刻度线长度const canvas = ctx.canvas;ctx.translate(0, 0);ctx.strokeStyle = "red";ctx.fillStyle = "red";// 绘制横轴和纵轴ctx.save();ctx.beginPath();ctx.setLineDash([10, 10]);ctx.moveTo(0, 0);ctx.lineTo(0, canvas.height);ctx.moveTo(0, 0);ctx.lineTo(canvas.width, 0);ctx.stroke();ctx.restore();// 绘制横轴和纵轴刻度ctx.beginPath();ctx.lineWidth = 2;ctx.textBaseline = "middle";for (let i = 0; i < canvas.height / rectH; i++) {// 绘制纵轴刻度ctx.moveTo(0, i * rectH);ctx.lineTo(tickLength, i * rectH);ctx.font = "20px Arial";ctx.fillText(i, -25, i * rectH);}for (let i = 1; i < canvas.width / rectW; i++) {// 绘制横轴刻度ctx.moveTo(i * rectW, 0);ctx.lineTo(i * rectW, tickLength);ctx.font = "20px Arial";ctx.fillText(i, i * rectW - 5, -15);}ctx.stroke();ctx.restore(); }; const renderScene = (canvas) => {const context = canvas.getContext("2d");drawAxis(context); }; export default renderScene; 然后在 index.jsx 中引入 renderSceneuseEffect(() => {//...renderScene(canvas); }, []); 效果如下:绘制矩形屏幕坐标系转 canvas 坐标系在开始绘制矩形之前,我们先来看下屏幕坐标系如何转换成 canvas 坐标系。如下图所示,对于 canvas 上的任意一点,比如下面的 A 点。当我们点击事件位于 A 点时,我们可以获取到 A 点的屏幕坐标 (event.clientX, event.clientY)。那么 A 点的 canvas 坐标计算方式就是x = event.clientX - canvas.offsetLeft; y = event.clientY - canvas.offsetTop; 因此我们可以封装一个坐标系转换的工具方法viewportCoordsToSceneCoordsconst viewportCoordsToSceneCoords = ( { clientX, clientY },{ offsetLeft, offsetTop } ) => {const x = clientX - offsetLeft;const y = clientY - offsetTop;return { x, y }; }; 绘制矩形声明一个 elements 数组存放我们绘制的图形为 canvas 绑定一个onPointerDown={handleCanvasPointerDown}事件const handleCanvasPointerDown = (event) => {const origin = viewportCoordsToSceneCoords(event, appState);const pointerDownState = {origin,lastCoords: { ...origin },eventListeners: {onMove: null,onUp: null,},};const element = {x: pointerDownState.origin.x,y: pointerDownState.origin.y,width: 0,height: 0,strokeColor: "#000000",backgroundColor: "transparent",fillStyle: "hachure",strokeWidth: 1,strokeStyle: "solid",};appState.draggingElement = element;elements.push(element);const onPointerMove =onPointerMoveFromCanvasPointerDownHandler(pointerDownState);const onPointerUp = onPointerUpFromCanvasPointerDownHandler(pointerDownState);window.addEventListener("pointermove", onPointerMove);window.addEventListener("pointerup", onPointerUp);pointerDownState.eventListeners.onMove = onPointerMove;pointerDownState.eventListeners.onUp = onPointerUp; }; handleCanvasPointerDown事件主要做了以下几件事:调用viewportCoordsToSceneCoords方法将点击事件的屏幕坐标转换成 canvas 左标,并保存在 origin 中,这个也是我们绘制矩形的起点(即矩形的左上角的点)初始化一个 element 对象,这个 element 对象保存绘制矩形所需要的坐标信息以及颜色信息等将 element 对象添加到 elements 数组中,并保存在 appState.draggingElement 中,方便后续使用在 window 上注册pointermove和pointerup事件,其中pointermove事件用于计算鼠标移动的距离,计算矩形的宽度和高度。pointerup用于注销这两个事件,因为一旦鼠标离开,就说明绘制过程结束。onPointerUpFromCanvasPointerDownHandler实现如下:const onPointerUpFromCanvasPointerDownHandler =(pointerDownState) => (event) => {window.removeEventListener("pointermove",pointerDownState.eventListeners.onMove);window.removeEventListener("pointerup",pointerDownState.eventListeners.onUp);}; onPointerMoveFromCanvasPointerDownHandler事件逻辑如下:根据鼠标移动事件,计算当前点的 canvas 坐标计算矩形的宽高调用 renderScene 开始绘制const onPointerMoveFromCanvasPointerDownHandler =(pointerDownState) => (event) => {const pointerCoords = viewportCoordsToSceneCoords(event, appState);pointerDownState.lastCoords.x = pointerCoords.x;pointerDownState.lastCoords.y = pointerCoords.y;appState.draggingElement.width =pointerCoords.x - pointerDownState.origin.x;appState.draggingElement.height =pointerCoords.y - pointerDownState.origin.y;renderScene(canvasRef.current);}; renderScene新增 renderElements 方法const renderElements = (ctx) => {elements.forEach((ele) => {ctx.save();ctx.translate(ele.x, ele.y);ctx.strokeStyle = ele.strokeStyle;ctx.strokeColor = ele.strokeColor;ctx.strokeRect(0, 0, ele.width, ele.height);ctx.restore();}); }; const renderScene = (canvas) => {const context = canvas.getContext("2d");context.clearRect(0, 0, canvas.width, canvas.height);drawAxis(context);renderElements(context); }; 最终效果如下:现在,我们已经可以在画布上随意绘制矩形了。无限画布所谓无限画布,就是我们可以水平或者竖直方向滚动画布,并可以实现绘制。如下图,假设我们在 canvas 水平方向滚动了 scrollX,在竖直方向滚动了 scrollY 距离,那么我们原先的坐标系原点就从(0,0)的位置移动到了下图中的B点。对于滚动后的画布上面的任意一点,比如 A 点,A 点的坐标就变成了x = event.clientX - canvas.offsetLeft - scrollX; y = event.clientY - canvas.offsetTop - scrollY; 我们需要给 canvas 添加滚动事件onWheel={handleCanvasWheel},同时记录滚动距离。并重新绘制const handleCanvasWheel = (event) => {const { deltaX, deltaY } = event;appState.scrollX = appState.scrollX - deltaX;appState.scrollY = appState.scrollY - deltaY;renderScene(canvasRef.current, appState); }; 我们将滚动距离保存在appState中,并传入renderScene方法:const renderScene = (canvas, appState) => {const context = canvas.getContext("2d");context.clearRect(0, 0, canvas.width, canvas.height);drawAxis(context, appState);renderElements(context, appState); }; 由于坐标发生了改变,因此我们需要调整下 drawAxis 的逻辑。这里我绘制出了横轴和纵轴的正负刻度。const drawAxis = (ctx, { scrollX, scrollY }) => {ctx.save();const rectH = 100; // 纵轴刻度间距const rectW = 100; // 横轴刻度间距const tickLength = 8; // 刻度线长度const canvas = ctx.canvas;ctx.translate(scrollX, scrollY);ctx.strokeStyle = "red";ctx.fillStyle = "red";// 绘制横轴和纵轴ctx.save();ctx.beginPath();ctx.setLineDash([10, 10]);ctx.moveTo(0, -scrollY);ctx.lineTo(0, canvas.height - scrollY);ctx.moveTo(-scrollX, 0);ctx.lineTo(canvas.width - scrollX, 0);ctx.stroke();ctx.restore();// 绘制横轴和纵轴刻度ctx.beginPath();ctx.lineWidth = 2;ctx.textBaseline = "middle";for (let i = 0; i < scrollY / rectH; i++) {// 绘制纵轴负数刻度ctx.moveTo(0, -i * rectH);ctx.lineTo(tickLength, -i * rectH);ctx.font = "20px Arial";ctx.fillText(-i, -25, -i * rectH);}for (let i = 0; i < (canvas.height - scrollY) / rectH; i++) {// 绘制纵轴正数刻度ctx.moveTo(0, i * rectH);ctx.lineTo(tickLength, i * rectH);ctx.font = "20px Arial";ctx.fillText(i, -25, i * rectH);}for (let i = 1; i < scrollX / rectW; i++) {// 绘制横轴负数刻度ctx.moveTo(-i * rectW, 0);ctx.lineTo(-i * rectW, tickLength);ctx.font = "20px Arial";ctx.fillText(-i, -i * rectW - 10, -15);}for (let i = 1; i < (canvas.width - scrollX) / rectW; i++) {// 绘制横轴正数刻度ctx.moveTo(i * rectW, 0);ctx.lineTo(i * rectW, tickLength);ctx.font = "20px Arial";ctx.fillText(i, i * rectW - 5, -15);}ctx.stroke();ctx.restore(); }; 坐标轴效果如下:可以看出坐标轴的绘制和滚动距离完全对应的上。我们已经能够实现一个无限画布并且正确绘制坐标轴,但此时如果我们在上面绘制一个矩形就会发现,矩形的宽度和高度是正确的,同时矩形的原点,即 x,y 也是正确的,但是矩形绘制的位置并不对。这是因为我们这里矩形的位置是相对于移动后的坐标系。因此我们需要修改我们的 renderElement 方法const renderElements = (ctx, appState) => {elements.forEach((ele) => {ctx.save();ctx.translate(ele.x + appState.scrollX, ele.y + appState.scrollY);ctx.strokeStyle = ele.strokeStyle;ctx.strokeColor = ele.strokeColor;ctx.strokeRect(0, 0, ele.width, ele.height);ctx.restore();}); }; 修改后,我们就可以正常绘制矩形了至此,我们就可以实现 canvas 无限画布,并能够在上面绘制矩形。导出现在,我们希望能够将我们的画布导出成 png 图片。很快,我们就可以实现下面的代码: {const canvas = canvasRef.current;var a = document.createElement("a");a.href = canvas.toDataURL();a.download = "canvas.png";a.click();}} >导出PNG 但是我们导出的时候会发现只能导出视图内的图形,视图以外的图形(即画面中看不到的图形)无法导出,这显然不符合我们的需求我们需要计算elements中最小的 minX 和 minY,以及最大的 maxX 和 maxY,并重新创建一个画布绘制,然后在这个新的画布上绘制我们的图形,修改导出代码 {let minX = Infinity;let maxX = -Infinity;let minY = Infinity;let maxY = -Infinity;elements.forEach((element) => {const [x1, y1, x2, y2] = [element.x,element.y,element.x + element.width,element.y + element.height,];minX = Math.min(minX, x1);minY = Math.min(minY, y1);maxX = Math.max(maxX, x2);maxY = Math.max(maxY, y2);});const canvas = document.createElement("canvas");canvas.width = (maxX - minX + 20) * window.devicePixelRatio;canvas.height = (maxY - minY + 20) * window.devicePixelRatio;const context = canvas.getContext("2d");context.scale(window.devicePixelRatio, window.devicePixelRatio);renderScene(canvas, {...appState,scrollX: -minX + 10,scrollY: -minY + 10,});console.log("导出", elements);var a = document.createElement("a");a.href = canvas.toDataURL();a.download = "canvas.png";a.click();}} >导出PNG -minX + 10,scrollY: -minY + 10,});console.log("导出", elements);var a = document.createElement("a");a.href = canvas.toDataURL();a.download = "canvas.png";a.click();}} >导出PNG 可以看到,现在一切正常最后最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。 有需要的小伙伴,可以点击下方卡片领取,无偿分享
当您进行数据可视化时,您可能希望通过添加一些注释来突出显示绘图的特定区域。 在这篇文章中,我们将学习如何在 Python 中使用 matplotlib 制作的绘图上添加矩形。 我们将首先添加一个具有特定颜色的简单矩形,然后学习如何用选择的颜色填充矩形。 接下来我们还将看到一个添加文本来描述添加矩形的示例。 最后我们将看到如何在 python 中的 matplotlib 绘图上添加多个矩形。1. 在使用 matplotlib 制作的绘图上绘制矩形matplotlib 中的 patches 模块允许我们在绘图顶部添加矩形等形状。 让我们将 patches 加载为 mpatches。import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.patches as mpatches
penguins_data="https://raw.githubusercontent.com/datavizpyr/data/master/palmer_penguin_species.tsv"
# load penguns data with Pandas read_csv
df = pd.read_csv(penguins_data, sep="\t")
df.head()
plt.scatter(x=df.culmen_length_mm,
y=df.culmen_depth_mm)
plt.xlabel("Culmen Length (mm)",fontweight ='bold', size=14)
plt.ylabel("Culmen Depth (mm)", fontweight ='bold',size=14)
left, bottom, width, height = (31, 15, 14, 7)
rect=mpatches.Rectangle((left,bottom),width,height,
fill=False,
color="purple",
linewidth=2)
#facecolor="red")
plt.gca().add_patch(rect)
plt.show
2.在使用 matplotlib 制作的绘图上绘制填充颜色的矩形之前,我们绘制了一个简单的矩形。现在,让我们以几种不同的方式对其进行自定义。 首先,我们将用颜色填充矩形以更好地突出绘图的部分。我们可以通过不使用 fill=False 并使用 facecolor 指定颜色来用颜色填充矩形。 在这里,我们还指定填充的透明度级别。plt.scatter(x=df.culmen_length_mm,
y=df.culmen_depth_mm)
plt.xlabel("Culmen Length (mm)",fontweight ='bold', size=14)
plt.ylabel("Culmen Depth (mm)", fontweight ='bold',size=14)
left, bottom, width, height = (31, 15, 14, 7)
rect=mpatches.Rectangle((left,bottom),width,height,
#fill=False,
alpha=0.1,
facecolor="red")
plt.gca().add_patch(rect)
plt.show()
3.在使用 matplotlib 制作的绘图上为矩形添加文本注释我们可以使用 matplotlib 中的 text() 函数添加文本来描述矩形。 在这里,我们在绘制矩形后立即添加文本注释。plt.scatter(x=df.culmen_length_mm,
y=df.culmen_depth_mm)
plt.xlabel("Culmen Length (mm)",fontweight ='bold', size=14)
plt.ylabel("Culmen Depth (mm)", fontweight ='bold',size=14)
left, bottom, width, height = (31, 15, 14, 7)
rect=mpatches.Rectangle((left,bottom),width,height,
#fill=False,
alpha=0.1,
#color="purple",
#linewidth=2,
facecolor="red")
plt.gca().add_patch(rect)
# add text with text() function in matplotlib
plt.text(31, 21.3,'rectangle',fontsize=16, color="red", weight="bold")
plt.show()
4.在使用 matplotlib 制作的绘图上绘制多个矩形添加多个矩形以突出显示绘图的多个部分也很容易。 在这个例子中,我们两次使用相似的代码块来添加两个不同颜色的矩形。# make scatter plot
plt.scatter(x=df.culmen_length_mm,
y=df.culmen_depth_mm)
plt.xlabel("Culmen Length (mm)",fontweight ='bold', size=14)
plt.ylabel("Culmen Depth (mm)", fontweight ='bold',size=14)
# add first rectangle with patches
left, bottom, width, height = (31, 15, 14, 7)
rect=mpatches.Rectangle((left,bottom),width,height,
#fill=False,
alpha=0.1,
#color="purple",
#linewidth=2,
facecolor="red")
plt.gca().add_patch(rect)
# add second rectangle with patches
left, bottom, width, height = (48, 17.5, 7, 4)
rect=mpatches.Rectangle((left,bottom),width,height,
#fill=False,
alpha=0.1,
#color="purple",
#linewidth=2,
facecolor="green")
plt.gca().add_patch(rect)
plt.show()
参考资源[1] How To Draw a Rectangle on a Plot in Matplotlib?

我要回帖

更多关于 矩形周长为2,将它绕其一边旋转一周 的文章

 

随机推荐