javascript - D3 条形图和面积图的组合

标签 javascript d3.js

我想知道是否可以按照下面截图的方式实现面积条形图的组合?

同时使中间的区域可单击以执行其他操作。 如果您能指导我一些示例以了解如何实现相同的目标,那将非常有帮助。

Aimed Display

最佳答案

我发布了一个codepen here 。这将创建一个条形图,然后在每个条形图之间分隔面积图。

const BarChart = () => {
  // set data
  const data = [
    {
      value: 48,
      label: 'One Rect'
    },
    {
      value: 32,
      label: 'Two Rect'
    },
    {
      value: 40,
      label: 'Three Rect'
    }
  ];
  // set selector of container div
  const selector = '#bar-chart';
  // set margin
  const margin = {top: 60, right: 0, bottom: 90, left: 30};
  // width and height of chart
  let width;
  let height;
  // skeleton of the chart
  let svg;
  // scales
  let xScale;
  let yScale;
  // axes
  let xAxis;
  let yAxis;
  // bars
  let rect;
  // area
  let areas = [];

  function init() {

    // get size of container
    width = parseInt(d3.select(selector).style('width')) - margin.left - margin.right;
    height = parseInt(d3.select(selector).style('height')) - margin.top - margin.bottom;
    // create the skeleton of the chart
    svg = d3.select(selector)
      .append('svg')
        .attr('width', '100%')
        .attr('height', height + margin.top + margin.bottom)
      .append('g')
        .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');

    xScale = d3.scaleBand().padding(0.15);
    xAxis = d3.axisBottom(xScale);
    yScale = d3.scaleLinear();
    yAxis = d3.axisLeft(yScale);

    svg.append('g')
      .attr('class', 'x axis')
      .attr('transform', `translate(0, ${height})`);

    svg.append('g')
      .attr('class', 'y axis');

    svg.append('g')
      .attr('class', 'x label')
      .attr('transform', `translate(10, 20)`)
      .append('text')
      .text('Value');

    xScale
      .domain(data.map(d => d.label))
      .range([0, width])
      .padding(0.3);

    yScale
      .domain([0, 75])
      .range([height, 0]);

    xAxis
      .scale(xScale);

    yAxis
      .scale(yScale);

    rect = svg.selectAll('rect')
      .data(data);

    rect
      .enter()
      .append('rect')
      .style('fill', d => '#00BCD4')
      .attr('y', d => yScale(d.value))
      .attr('height', d => height - yScale(d.value))
      .attr('x', d => xScale(d.label))
      .attr('width', xScale.bandwidth());

    // call the axes
    svg.select('.x.axis')
      .call(xAxis);

    svg.select('.y.axis')
      .call(yAxis);

    // rotate axis text
    svg.select('.x.axis')
      .selectAll('text')
        .attr('transform', 'rotate(45)')
        .style('text-anchor', 'start');

    if (parseInt(width) >= 600) {
      // level axis text
      svg.select('.x.axis')
        .selectAll('text')
          .attr('transform', 'rotate(0)')
          .style('text-anchor', 'middle');
    }
    
    data.forEach(
      (d, i) => {
        if (data[i + 1]) {
          areas.push([
            {
              x: d.label,
              y: d.value
            },
            {
              x: data[i + 1].label,
              y: data[i + 1].value
            }
          ]);
        }
      }
    );
    
    areas = areas.filter(
      d => Object.keys(d).length !== 0
    );
    
    areas.forEach(
      a => {
        const area = d3.area()
          .x((d, i) => {
            return i === 0 ?
              xScale(d.x) + xScale.bandwidth() :
              xScale(d.x);
          })
          .y0(height)
          .y1(d => yScale(d.y));
        
        svg.append('path')
          .datum(a)
          .attr('class', 'area')
          .style('fill', d => '#B2EBF2')
          .attr('d', area)
          .on('click', d => {
            console.log('hello click!');
        });
      }
    )
  }

  return { init };
};

const myChart = BarChart();
myChart.init();
#bar-chart {
  height: 500px;
  width: 100%;
}
<script src="https://unpkg.com/d3@5.2.0/dist/d3.min.js"></script>
<div id="bar-chart"></div>

创建条形图后,我重新打包数据,使其有利于创建面积图。我创建了一个 Areas 数组,其中每个项目都是一个单独的面积图。我基本上获取第一个条形和下一个条形的值,并将它们打包在一起。

data.forEach(
  (d, i) => {
    if (data[i + 1]) {
      areas.push([
        {
          x: d.label,
          y: d.value
        },
        {
          x: data[i + 1].label,
          y: data[i + 1].value
        }
      ]);
    }
  }
);

areas = areas.filter(
  d => Object.keys(d).length !== 0
);

然后,我迭代区域上的每个元素并创建面积图。

我认为,这里唯一棘手的事情是让面积图从第一个条形的末尾跨越到第二个条形的开头,而不是从第一个条形的末尾到第二个条形的末尾酒吧。为了实现此目的,当处理第一个数据点(但不是第二个数据点)时,我添加了一个从 x 比例到面积图的预期 x 值的矩形宽度。

我认为这是在一条线上制作两个点:一个用于第一个柱,一个用于下一个柱。 D3的区域功能可以对一条线下的所有区域进行阴影处理。因此,我的线上的第一个点应该是第一个栏的右上角。第二个点应该是下一个栏的左上角。

在最后附加一个点击事件非常简单。

areas.forEach(
  a => {
    const area = d3.area()
      .x((d, i) => {
        return i === 0 ?
          xScale(d.x) + xScale.bandwidth() :
          xScale(d.x);
      })
      .y0(height)
      .y1(d => yScale(d.y));

    svg.append('path')
      .datum(a)
      .attr('class', 'area')
      .style('fill', d => '#B2EBF2')
      .attr('d', area)
      .on('click', d => {
        console.log('hello click!');
    });
  }
)

关于javascript - D3 条形图和面积图的组合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50210131/

相关文章:

javascript - 如何在 D3 中链接函数调用?

javascript - 解析数据库中存储的json字符串以渲染D3树布局

javascript - d3.js 更新视觉

javascript - 具有用户输入的数组上的 NaN

javascript - Angular - 使用自定义方法扩展 $resource 子对象

JavaScript/jQuery : Follow path over page

javascript - 如何使用setInterval执行函数?

javascript - D3 中轴文本的背景颜色

javascript - 调用两次时的 D3js 代码会复制图形而不是刷新

javascript - 在 jquery 中将元素添加到二维数组