json 如何在D3.js条形图上添加线标记?

mzsu5hc0  于 2023-01-10  发布在  其他
关注(0)|答案(1)|浏览(127)

我是D3.js的新手(版本5),但我已经能够使用JSON数据创建基本的条形图。Y轴标记成绩百分比,X轴标记日期。目前它显示每月的成绩。如何添加标记每月目标成绩的线标记?另外,如何添加从每个Y轴刻度开始在图表中移动的水平线和图例?

<div style="text-align: center;">
    <h1>Grade %</h1>
    <div id="GradeChart"></div>
</div>
<script>
    // Data Example
    data = { 
    'month': date, 'grade': 90, 'target': 85,
    'month': date, 'grade': 89, 'target': 85,
    'month': date, 'grade': 78, 'target': 85,
    'month': date, 'grade': 99, 'target': 100,
    'month': date, 'grade': 50, 'target': 85,
    'month': date, 'grade': 60, 'target': 90,
    'month': date, 'grade': 65, 'target': 85,
    'month': date, 'grade': 75, 'target': 85,
    'month': date, 'grade': 86, 'target': 80,
    'month': date, 'grade': 100, 'target': 85,
    'month': date, 'grade': 1, 'target': 85,
    'month': date, 'grade': 27, 'target': 85,
    };

    const width = 1200;
    const height = 600;
    const margin = { top: 50, bottom: 150, left: 50, right: 50 };

    const svg = d3.select('#GradeChart')
        .append('svg')
        .attr('width', width - margin.left - margin.right)
        .attr('height', height - margin.top - margin.bottom)
        .attr("viewBox", [0, 0, width, height]);

    const x = d3.scaleBand()
        .domain(d3.range(data.length))
        .range([margin.left, width - margin.right])
        .padding(0.1)

    const y= d3.scaleLinear()
        .domain([0, 100])
        .range([height - margin.bottom, margin.top])

    svg
        .append("g")
        .attr("fill", 'royalblue')
        .selectAll("rect")
        .data(data)
        .join("rect")
        .attr("x", (d, i) => x(i))
        .attr("y", d => y(d.percent))
        .attr('title', (d) => d.percent)
        .attr("class", "rect")
        .attr("height", d => y(0) - y(d.percent))
        .attr("width", x.bandwidth());

    function yAxis(g) {
        g.attr("transform", `translate(${margin.left}, 0)`)
        .call(d3.axisLeft(y).ticks(null, data.format))
        .attr("font-size", '20px')
    }

    function xAxis(g) {
        g.attr("transform", `translate(0,${height - margin.bottom})`)
        .call(d3.axisBottom(x).tickFormat(i => data[i].month))
        .attr("font-size", '20px')
        .selectAll("text")  
        .style("text-anchor", "end")
        .attr("dx", "-.8em")
        .attr("dy", ".15em")
        .attr("transform", "rotate(-65)")
    }

    svg.append("g").call(xAxis);
    svg.append("g").call(yAxis);
    svg.node();
</script>

我尝试将目标数据追加为另一个条形以创建分组条形图,但条形重叠而不是并排。如果下面的条形的值小于上面的条形,则下面的条形不可见。

svg
        .append("g")
        .attr("fill", 'green')
        .selectAll("rect")
        .data(data)
        .join("rect")
        .attr("x", (d, i) => x(i))
        .attr("y", d => y(d.target))
        .attr('title', (d) => d.target)
        .attr("class", "rect")
        .attr("height", d => y(0) - y(d.target))
        .attr("width", x.bandwidth());
7tofc5zh

7tofc5zh1#

不要把d3.js看作是一个绘制图表的库,而是一个用SVG和HTML可视化数据的工具箱(因此得名 * Data-Driven Documents *)。这更加抽象和乏味。但它让您完全控制如何可视化数据。

    • 条形图上的线条**

您可以通过向svg添加另一组line元素来绘制目标成绩的图形。

svg
    .append('g')
    .attr('class', 'target-grades')
    .attr('stroke', 'green')
    .attr('stroke-width', 5)
    .selectAll('line')
    .data(data)
    .join('line')
    .attr('x1', (dataItemForMonth, index) => {
      // catch the very beginning of the array
      if (index > 0) {
        // get the x coordinate fo the previous sample
        return x(index - 1) + x.bandwidth() / 2;
      } else {
        return x(index) + x.bandwidth() / 2; // create a zero line (same start and end point)
      }
    })
    .attr('y1', (dataItemForMonth, index) => {
      // catch the very beginning of the array
      if (index > 0) {
        // get the data item of the previous month
        let fromLineDataPoint = data[index - 1];
        // use the same scale for vertical positioning
        return y(fromLineDataPoint.target);
      } else {
        return y(dataItemForMonth.target); // zero line
      }
    })
    .attr('x2', (dataItemForMonth, index) => {
      // same as for the bar charts
      return x(index) + x.bandwidth() / 2;
    })
    .attr('y2', (dataItemForMonth) => {
      // use the same scale for vertical positioning
      return y(dataItemForMonth.target);
    });

对于data数组中的每个月,此代码绘制一条从上个月(线的起点-x1/y1)到当前月(线的终点-x2/y2)的绿线。请注意,我们考虑了没有上个月的第一个月。在本例中,代码创建一条起点和终点相同的线。

    • Y轴水平线**

使用tickSize()创建覆盖图表整个宽度的刻度线。

d3.axisLeft(y)
    .ticks(null, data.format)
    // the minus points the ticks to the right - full width minus margins
    .tickSize(-(width - margin.left - margin.right))

要在刻度线上方绘制图表内容(条形图和目标等级),请将y轴添加到在所有其他内容之前创建的组中。

const svg = d3.select('#GradeChart')
    .append('svg')
    .attr('width', width - margin.left - margin.right)
    .attr('height', height - margin.top - margin.bottom)
    .attr("viewBox", [0, 0, width, height]);

var groupForYAxis = svg.append('g')
    .attr('class', 'y-axis');

// all content. rects and lines
// ...

groupForYAxis.call(yAxis);
    • 图例**

如上所述,你必须自己构造图例的可视化,没有神奇的d3 legend函数。

var legendGroup = svg.append('g')
  .attr('class', 'legend')
  // where ever you want to place your legend
  .attr('transform', 'translate(' + width + ', 10)');

// the rect
legendGroup.append('rect')
  .attr('x', 0)
  .attr('y', 0)
  .attr('width', 50)
  .attr('height', 25)
  .attr('fill', 'royalblue');
legendGroup.append('text')
  .attr('x', 55)
  .attr('y', 18)
  .text('Grades');

// the targets
legendGroup.append('line')
  .attr('x1', 0)
  .attr('y1', 45)
  .attr('x2', 50)
  .attr('y2', 45)
  .attr('stroke', 'green')
  .attr('stroke-width', 5);
legendGroup.append('text')
  .attr('x', 55)
  .attr('y', 50)
  .text('Targets');

所有这些都产生了以下结果:

相关问题