首页 > 解决方案 > 带有双向图表的 D3 v4 图表

问题描述

在此处输入图像描述

如何用 D3 创建这个图表?任何帮助都将有助于对高图表进行全面尝试,但对完整的点击事件不起作用添加了此向下钻取事件,但对条形图或 y 轴标签的点击不起作用。

const data = [
  { "name": 'IT', "value": 20, "negativeValue": -80 },
  { "name": 'Capital Invest', "value": 30, "negativeValue": -70 },
  { "name": 'Infrastructure', "value": 40, "negativeValue": -60 }
];
Highcharts.setOptions({
    lang: {
        drillUpText: `◁ Back to {series.description}`,
    },
});

Highcharts.chart({
  chart: {
    type: 'bar',
    renderTo: 'alignmentChart',
    height: 530,
    marginRight: 20,
    backgroundColor: 'transparent',
    events: {
      drilldown(e: any) {
        if (e.seriesOptions.fits) {
          linesPositive = e.seriesOptions.line;
        } else {
          lineNegative = e.seriesOptions.line;
        }
        labels = !!e.seriesOptions && e.seriesOptions.data.map(a => a.name);
      },
      drillup(e: any) {
        if (e.seriesOptions.fits) {
          linesPositive = e.seriesOptions.line;
        } else {
          lineNegative = e.seriesOptions.line;
        }
        labels = !!e.seriesOptions && e.seriesOptions.data.map(a => a.name);
      },
    },
  },
  title: {
    text: '',
  },
  colors: ['#f7a704', '#458dde'],
  // tooltip: this.getTooltip(this),
  xAxis: {
    reversed: false,
    tickPositions: Array.from(Array(this.multi.positive.length).keys()),
    labels: {
      useHTML: true,
      formatter() {
        return `<span title="${labels[this.value]}">${labels[this.value]}</span>`;
      },
      style: {
        color: '#000000',
      },
      step: 1,
    },
    lineWidth: 0,
    tickWidth: 0,
  },
  yAxis: {
    title: {
      text: null,
    },
    max: 100,
    min: -100,
    plotLines: [{
      color: '#e5e5e5',
      value: 0,
      width: 1,
      zIndex: 20,
    }],
    lineWidth: 1,
    gridLineWidth: 0,
    tickWidth: 1,
    // offset: 100,
    labels: {
      y: 30,
      align: 'center',
    },
  },

  plotOptions: {
    bar: {
      pointWidth: 12,
    },
    series: {
      stacking: 'normal',
      dataLabels: {
        enabled: true,
        color: '#6b6b6b',
        style: {
          fontSize: '12px',
          fontFamily: 'Proxima Nova'
        },
        formatter() {
          return '';
        },
        inside: false,
      },
    },
  },

  series: [{
    name: 'Fits Role',
    description: 'Subfunctions',
    data: this.multi.positive,
    type: undefined
  }, {
    name: 'Not Fit Role',
    description: 'Subfunctions',
    data: this.multi.negative,
    type: undefined
  }],
  drilldown: {
    allowPointDrilldown: false,
    activeAxisLabelStyle: {
      fontSize: '12px',
      fontWeight: 'bold',
      color: '#007bc7',
      textDecoration: 'none',
    },
    series: this.multi.drilldowns,
  },
  credits: {
    enabled: false,
  },
  legend: {
    enabled: false,
  },
  exporting: {
    enabled: false,
  },
});

标签: javascriptjqueryd3.js

解决方案


与我分享的答案相比,我只需要进行很少的更改。正如我在评论中所说,我为每个项目创建一个g节点,并为每个项目绘制两个rects。

然后我将rects 更新为具有相同的基准形状 ( { name: string, value: number }),无论它是正数还是负数。这让我可以对这两种类型做完全相同的事情。

// Now, the data can also be negative
const data = [{
  "name": 'IT',
  "value": 20,
  "negativeValue": -80
}, {
  "name": 'Capital Invest',
  "value": 30,
  "negativeValue": -70
}, {
  "name": 'Infrastructure',
  "value": 40,
  "negativeValue": -60
}];

const width = 600,
  height = 300,
  margin = {
    top: 20,
    left: 100,
    right: 40,
    bottom: 40
  };

// Now, we don't use 0 as a minimum, but get it from the data using d3.extent
const x = d3.scaleLinear()
  .domain([-100, 100])
  .range([0, width]);

const y = d3.scaleBand()
  .domain(data.map(d => d.name))
  .range([height, 0])
  .padding(0.1);

const svg = d3.select('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom);

const g = svg
  .append('g')
  .attr('transform', `translate(${margin.left} ${margin.right})`);

// One group per data entry, each holding two bars
const barGroups = g
  .selectAll('.barGroup')
  .data(data);

barGroups.exit().remove();

const newBarGroups = barGroups.enter()
  .append('g')
  .attr('class', 'barGroup');

// Append one bar for the positive value, and one for the negative one
newBarGroups
  .append('rect')
  .attr('class', 'positive')
  .attr('fill', 'darkgreen');

newBarGroups
  .append('rect')
  .attr('class', 'negative')
  .attr('fill', 'darkred');

const positiveBars = newBarGroups
  .merge(barGroups)
  .select('.positive')
  .datum(d => ({
    name: d.name,
    value: d.value
  }));

const negativeBars = newBarGroups
  .merge(barGroups)
  .select('.negative')
  .datum(d => ({
    name: d.name,
    value: d.negativeValue
  }));

newBarGroups.selectAll('rect')
  // If a bar is positive it starts at x = 0, and has positive width
  // If a bar is negative it starts at x < 0 and ends at x = 0
  .attr('x', d => d.value > 0 ? x(0) : x(d.value))
  .attr('y', d => y(d.name))
  // If the bar is positive it ends at x = v, but that means it's x(v) - x(0) wide
  // If the bar is negative it ends at x = 0, but that means it's x(0) - x(v) wide
  .attr('width', d => d.value > 0 ? x(d.value) - x(0) : x(0) - x(d.value))
  .attr('height', y.bandwidth())
// Let's color the bar based on whether the value is positive or negative

g.append('g')
  .classed('x-axis', true)
  .attr('transform', `translate(0, ${height})`)
  .call(d3.axisBottom(x))

g.append('g')
  .classed('y-axis', true)
  .attr('transform', `translate(${x(0)}, 0)`)
  .call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg></svg>


或者,您可以在不合并选择的情况下执行此操作,如下所示:

// Now, the data can also be negative
const data = [{
  "name": 'IT',
  "value": 20,
  "negativeValue": -80
}, {
  "name": 'Capital Invest',
  "value": 30,
  "negativeValue": -70
}, {
  "name": 'Infrastructure',
  "value": 40,
  "negativeValue": -60
}];

const width = 600,
  height = 300,
  margin = {
    top: 20,
    left: 100,
    right: 40,
    bottom: 40
  };

// Now, we don't use 0 as a minimum, but get it from the data using d3.extent
const x = d3.scaleLinear()
  .domain([-100, 100])
  .range([0, width]);

const y = d3.scaleBand()
  .domain(data.map(d => d.name))
  .range([height, 0])
  .padding(0.1);

const svg = d3.select('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom);

const g = svg
  .append('g')
  .attr('transform', `translate(${margin.left} ${margin.right})`);

// One group per data entry, each holding two bars
const positiveBars = g
  .selectAll('.positive')
  .data(data);
positiveBars.exit().remove();
positiveBars.enter()
  .append('rect')
  .attr('class', 'positive')
  .attr('fill', 'darkgreen')
  .merge(positiveBars)
  .attr('x', x(0))
  .attr('y', d => y(d.name))
  // The bar is positive. It ends at x = v, but that means it's x(v) - x(0) wide
  .attr('width', d => x(d.value) - x(0))
  .attr('height', y.bandwidth());

const negativeBars = g
  .selectAll('.negative')
  .data(data);
negativeBars.exit().remove();
negativeBars.enter()
  .append('rect')
  .attr('class', 'negative')
  .attr('fill', 'darkred')
  .merge(negativeBars)
  .attr('x', d => x(d.negativeValue))
  .attr('y', d => y(d.name))
  // The bar is negative. It ends at x = 0, but that means it's x(0) - x(v) wide
  .attr('width', d => x(0) - x(d.negativeValue))
  .attr('height', y.bandwidth());

g.append('g')
  .classed('x-axis', true)
  .attr('transform', `translate(0, ${height})`)
  .call(d3.axisBottom(x))

g.append('g')
  .classed('y-axis', true)
  .attr('transform', `translate(${x(0)}, 0)`)
  .call(d3.axisLeft(y))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg></svg>


推荐阅读