javascript - 如何正确排列圆的标签?

标签 javascript d3.js

我有一个如下所示的圆圈:

enter image description here

您可能会注意到,与其他标签相比,从 0 到 9 的标签与外圆的距离不同。我的期望是:

  1. 如何使标签到外圆的距离相同?
  2. 如何排列标签,使其位于中心线的中间,而不是侧面?

您可以在这里找到我的代码:https://jsfiddle.net/ao4xwpnk/

我尝试更改这部分代码以使标签对称,但没有成功。

  let featureData = features.map((f, i) => {
    let angle = (Math.PI / 2) + (2 * Math.PI * i / features.length);
    let isLeftHalf = angle < Math.PI; // Check if the angle is in the left half of the circle

    let value = isLeftHalf ? 14.5 : 10.5; // Adjust the value for labels in left and right halves

    return {
      "name": f,
      "angle": angle,
      "line_coord": angleToCoordinate(angle, 180),
      "label_coord": angleToCoordinate(angle, value)
    };
  });

最佳答案

使用text-anchordy text 元素上的属性将有所帮助:

// draw axis label
svg.selectAll(".axislabel")
  .data(featureData)
  .join(
    enter => enter.append("text")
      .attr("x", d => d.label_coord.x)
      .attr("y", d => d.label_coord.y)
      .attr("text-anchor", "middle")     // <-- here
      .attr("dy", "0.35em")              // <-- and here
      .text(d => d.name)

text-anchor 适用于水平放置:

The rendered characters are aligned such that the middle of the text string is at the current text position.

这可以解决您尝试使用 isLeftHalf 的问题。

dy 属性适用于垂直放置。 0.35em 的值是一个约定 - 请参阅此 answer有趣的是:

Using dy=0.35em can help vertically centre text regardless of font size

我对此的理解是,该值占字体设计中下降部分的平均值,例如对于 p 和 g,“尾部”大约是高度的 35%,因此您将标签“向下”推,使其与轴对齐。请参阅here例如。

此更新后的代码:

let data = [];
    let features = [0, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; // labels on the circle
    //generate the data
    for (var i = 0; i < 2; i++) {
      var point = {}
      //each feature will be a random number from 1-9
      features.forEach(f => point[f] = 1 + Math.random() * 162);
      data.push(point);
    }

    let width = 600;
    let height = 600;
    let svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);

    //adding radial scale
    let radialScale = d3.scaleLinear()
      .domain([0, 180])
      .range([0, 250]);

    //adding ticks
    let ticks = [20, 40, 60, 80, 100, 120, 140, 160, 180]; // 9 circles

    //adding circles
    svg.selectAll("circle")
      .data(ticks)
      .join(
        enter => enter.append("circle")
          .attr("cx", width / 2)
          .attr("cy", height / 2)
          .attr("fill", "none")
          .attr("stroke", "gray")
          .attr("r", d => radialScale(d))
          .attr("stroke-width", 1) // Set the same stroke-width for all circles
      );

    //adding white background circle to remove filling in the center
    svg.append("circle")
      .attr("cx", width / 2)
      .attr("cy", height / 2)
      .attr("fill", "white")
      .attr("stroke", "gray")
      .attr("r", radialScale(20))
      .attr("stroke-width", 1); // Set the same stroke-width for the center circle

    //adding text labels
    svg.selectAll(".ticklabel")
      .data(ticks)
      .join(
        enter => enter.append("text")
          .attr("class", "ticklabel")
          .attr("x", width / 2 + 5)
          .attr("y", d => height / 2 - radialScale(d))
          .text(d => d.toString())
      );

    //Plotting the Axes
    //map an angle and value into SVG
    function angleToCoordinate(angle, value) {
      let x = Math.cos(angle) * radialScale(value);
      let y = Math.sin(angle) * radialScale(value);
      return { "x": width / 2 + x, "y": height / 2 - y };
    }

    let featureData = features.map((f, i) => {
      let angle = (Math.PI / 2) + (2 * Math.PI * i / features.length);
      let value = i < 9 ? 10.5 : 12; // Adjust the value for labels 13 to 21 to 12
      return {
        "name": f,
        "angle": angle,
        "line_coord": angleToCoordinate(angle, 180),
        "label_coord": angleToCoordinate(angle, 190) // changin 190 allows me to adjust labels proximity to the circle!
      };
    });

    // draw axis line
    svg.selectAll("line")
      .data(featureData)
      .join(
        enter => enter.append("line")
          .attr("x1", width / 2)
          .attr("y1", height / 2)
          .attr("x2", d => d.line_coord.x)
          .attr("y2", d => d.line_coord.y)
          .attr("stroke", "black")
          .attr("stroke-width", 1) // Set the same stroke-width for all axis lines
      );

    // draw axis label
    svg.selectAll(".axislabel")
      .data(featureData)
      .join(
        enter => enter.append("text")
          .attr("x", d => d.label_coord.x)
          .attr("y", d => d.label_coord.y)
          .attr("text-anchor", "middle")
          .attr("dy", "0.35em")
          .text(d => d.name)
      );

    //draw shapes for actual data
    let line = d3.line()
      .x(d => d.x)
      .y(d => d.y);

    let area = d3.area()
      .x(d => d.x)
      .y0(d => d.y)
      .y1(height / 2)
      .curve(d3.curveLinear);

    let colors = ["red", "green", "navy"];

    //helper function to iterate through fields in each data point
    function getPathCoordinates(data_point) {
      let coordinates = [];
      for (var i = 0; i < features.length; i++) {
        let ft_name = features[i];
        let angle = (Math.PI / 2) + (2 * Math.PI * i / features.length);
        coordinates.push({
          x: width / 2 + Math.cos(angle) * radialScale(data_point[ft_name]),
          y: height / 2 - Math.sin(angle) * radialScale(data_point[ft_name])
        });
      }
      return coordinates;
    }

    // Modify the path elements to separate red and green data
    svg.selectAll(".path-red")
      .data([data[0]]) // Data for the red line
      .join(
        enter => enter.append("path")
          .datum(d => getPathCoordinates(d))
          .attr("class", "path-red")
          .attr("d", line)
          .attr("stroke-width", 3)
          .attr("stroke", "red")
          .attr("fill", "none") // No fill for the red line
      );

    svg.selectAll(".path-green")
      .data([data[1]]) // Data for the green line
      .join(
        enter => enter.append("path")
          .datum(d => getPathCoordinates(d))
          .attr("class", "path-green")
          .attr("d", line)
          .attr("stroke-width", 3)
          .attr("stroke", "green")
          .attr("fill", "none") // No fill for the green line
      );

    // Fill the area between the red and green lines
    let areaFillData = [
      ...getPathCoordinates(data[0]),
      ...getPathCoordinates(data[1]).reverse()
    ];

    svg.append("path")
      .datum(areaFillData)
      .attr("d", area)
      .attr("stroke-width", 0)
      .attr("fill", "blue") // Fill the area between the red and green lines with blue color
      .attr("fill-opacity", 0.3); // You can adjust the opacity as you like
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.0/d3.min.js"></script>

关于javascript - 如何正确排列圆的标签?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76603658/

相关文章:

javascript - 如何从 reactjs 和 API 中检索数据

javascript - 如何在 jQuery 的 .on() 方法中动态更新按钮的 ID 选择器?

javascript - 使用 d3.min 的 D3 的域问题

d3.js - D3 V4 Rect 在堆叠条形图中绑定(bind)数据

javascript - 如何使用 d3.js 在选择标签中的选项更改时创建段落?

javascript - 使用 highcharts 调整饼图高度时出错

JavaScript 布局管理器?

javascript - 使用 Canvas HTML 生成 map 网格和图像的问题

javascript - D3访问html属性

javascript - 如何将 D3 数据插值/动画函数应用于动态变化的饼图