【问题标题】:SVG step chart with rounded corners带圆角的 SVG 阶梯图
【发布时间】:2020-06-14 19:09:21
【问题描述】:

如何创建具有平滑圆角的阶梯图,如下例所示?

Graph example

拐角半径取决于步骤之间的坐标差异。

我使用 d3.js 链接并尝试创建自定义 SVG 曲线,但无法正确实现。

我会很感激任何想法。也许有一些库?

UPD:@exaneta 的折线代码

<polyline points="10,40 15,45 30,70 35,75" fill="none" stroke="black" stroke-width="1"></polyline>

【问题讨论】:

  • 如果我在 svg 画布上有一条多段线连接点,我可以使用 javascript 计算圆角的二次贝塞尔曲线的坐标。如果这是您需要的,请编辑您的问题并使用折线添加一些 svg 代码。
  • @enxaneta,说实话,我真的不明白这是如何工作的。我试图用折线描述必要的图形(连接线)。你可以在问题中看到它。我怎样才能为这样的折线圆角?
  • 对于每条线段,您必须计算线段结束前 N 个点的 X、Y 坐标。然后从下一行开始沿N个单位。然后用贝塞尔曲线(即Q 路径命令)或圆弧(A 路径命令)连接这两个点。

标签: javascript html d3.js svg charts


【解决方案1】:

正如我所评论的,您可以使用 javascript 计算圆角的二次贝塞尔曲线的坐标。贝塞尔曲线的控制点是折线的点。然后,您需要将 Bézier 的起点和终点作为距折线上的控制点 r 的一个点。 获得新路径的所有点后,您可以使用它们来构建 thePathd 属性的值。

请确保r 变量的大小合理。

let polypoints = poly.getAttribute("points");
let r = 5; // distance for the curvature

function getPoints(poly) {
  // poly is the polygon's element d attribute
  let polyPoints = poly
    .replace(/(\r?\n|\r|\t)+/g, "")
    .replace(/\-/g, " -")
    .split(/[\s,]+/);
  polyPoints = removeEmptyElements(polyPoints);
  let points = [];
  for (let i = 0; i < polyPoints.length; i += 2) {
    let temp = [Number(polyPoints[i]), Number(polyPoints[i + 1])];
    points.push(temp);
  }
  return points;////[[10, 40],[15, 45],[30, 70],[35, 75]]
}

function getAngle(c, l) {
  let delta_x = l.x - c.x;
  let delta_y = l.y - c.y;
  let a = Math.atan2(delta_y, delta_x);
  return a; //in radians;
}

function removeEmptyElements(array) {
  for (let i = 0; i < array.length; i++) {
    if (array[i] == "") {
      array.splice(i, 1);
    }
  }
  return array;
}

function polygonWithRoundedCorners(poly, r) {
  let points = getPoints(poly);
  //move to the first point
  let d = `M${points[0][0]},${points[0][1]}`;
  
  for (let i = 1; i < points.length - 1; i++) {
    let previous = i - 1;
    let next = i + 1;
    let c = {};//the control point
    c.x = points[i][0];
    c.y = points[i][1];
    let l1 = {};
    l1.x = points[previous][0];
    l1.y = points[previous][1];
    let l2 = {};
    l2.x = points[next][0];
    l2.y = points[next][1];
    let a1 = getAngle(c, l1);
    let a2 = getAngle(c, l2);

    //if great precision is needed remove .toFixed(3)
    //x1 and y1 are defining the start point of the Bézier
    let x1 = (c.x + r * Math.cos(a1)).toFixed(3);
    let y1 = (c.y + r * Math.sin(a1)).toFixed(3);
    //x2 and y2 are defining the end point of the Bézier
    let x2 = (c.x + r * Math.cos(a2)).toFixed(3);
    let y2 = (c.y + r * Math.sin(a2)).toFixed(3);
    //build the d attribute
    d += "L" + x1 + "," + y1 + " Q" + c.x + "," + c.y + " " + x2 + "," + y2;
  }
  //move to the last point and return the d attribute
  return (d += `L${points[points.length - 1][0]},${
    points[points.length - 1][1]
  }`);
}

thePath.setAttributeNS(null, "d", polygonWithRoundedCorners(polypoints, r));
svg{border:solid;width:90vh}
<svg viewBox="-5 30 55 55">
<polyline id="poly" points="10,40 15,45 30,70 35,75" fill="none" stroke="black" ></polyline>  
<path id="thePath" fill="none" stroke="red"  /> 
</svg>

【讨论】:

  • 谢谢,很有帮助!
猜你喜欢
  • 1970-01-01
  • 2018-02-03
  • 2021-12-18
  • 1970-01-01
  • 2018-05-07
相关资源
最近更新 更多