带有 svg 的堆叠条形图(无第 3 方库)

stacked barchart with svg (no 3rd party library)

我正在尝试使用 svg 和 html 创建堆叠条形图,但不使用任何第 3 方库。不幸的是,没有一个在线文档显示如何使用普通 svg 创建堆叠条形图。

我已经创建了一个 codepen,并且我正在实现那个堆叠条形图。谁能告诉我还需要什么才能使它成为堆叠条形图。

https://codepen.io/a166617/pen/qBXvzQd

这是我目前拥有的代码

const ReleaseScopeCharts = () => {
    const data = [
        {
            name: 'Transit',
            passed: 2,
            skipped: 5,
            failed: 22,
        },
        {
            name: 'Access',
            passed: 7,
            skipped: 2,
            failed: 11,
        },
    ];
    const width = 500;
    const colors = ['#30D158', '#005EA7', '#FF453A'];

    const entries = data.map((d) => ({
        name: d.name,
        total: ['passed', 'skipped', 'failed'].reduce((acc, key) => acc + d[key], 0),
        bars: ['passed', 'skipped', 'failed'].map((key, i) => ({
            value: d[key],
            color: colors[i],
        }))
            .filter((bar) => bar.value),
    }));

    const rows = (entry) => entry.bars.map((bar, index) => {
        const height = (bar.value / entry.total) * 100;
        return (
            <g key={index}>
                <rect
                    width={50}
                    height={`${height}%`}
                    fill={bar.color}
                    x={index * 60} // multiply with the width (50) + 10 for space
                />
            </g>
        );
    });

    return (
        <div className="new-card">
            <div />
            {entries.map((entry) => (
                <>
                    {entry.name}
                    <svg viewBox={`0, 0, ${width}, ${500}`}
                        height={500}
                        width={width}
                        style={{ transform: `rotateX(180deg)` }}
                    >
                        {rows(entry)}
                    </svg>
                </>
            ))}
        </div>
    );
};

对于堆叠条形图,我的意思是显示一个在另一个之上。

要堆叠条形图,您需要计算当前列和 space 宽度。将 svg 包装成 div,同时将 text 偏移到 div 并以 display:flex.

为中心

y 键添加到 bars,其中:

  • 起点=通过=0
  • 中点=跳过=通过值
  • 终点=失败=传递值+跳过值
y: key === 'passed' ? 0 : key === 'skipped' ? d['passed'] : d['skipped'] + d['passed'],
// Basic style
const newCardStyle = {
  display: 'flex',
};
const contentStyle = {
  display: 'flex',
  flexFlow: 'column',
  alignItems: 'center',
};
// multiply 50 (width) * 3 (columns) + 10 (space width) * 2 ( space between columns)
const width = 50 * 3 + 10 * 3;

function App() {
  const data = [
    {
      name: 'Transit',
      passed: 2,
      skipped: 5,
      failed: 22,
    },
    {
      name: 'Access',
      passed: 7,
      skipped: 2,
      failed: 11,
    },
  ];

  // Basic style
  const newCardStyle = {
    display: 'flex',
  };
  const contentStyle = {
    display: 'flex',
    flexFlow: 'column',
    alignItems: 'center',
  };
  // multiply 50 (width) * 3 (columns) + 10 (space width) * 2 ( space between columns)
  const width = 50 * 3 + 10 * 3;

  const colors = ['#30D158', '#005EA7', '#FF453A'];
  const entries = data.map(d => ({
    name: d.name,
    total: ['passed', 'skipped', 'failed'].reduce(
      (acc, key) => acc + d[key],
      0
    ),
    bars: ['passed', 'skipped', 'failed']
      .map((key, i) => ({
        value: d[key],
        color: colors[i],
        y:
          key === 'passed'
            ? 0
            : key === 'skipped'
            ? d['passed']
            : d['skipped'] + d['passed'],
      }))
      .filter(bar => bar.value),
  }));

  const rows = entry => {
    return entry.bars.map((bar, index) => {
      const height = (bar.value / entry.total) * 100;
      const y = (bar.y / entry.total) * 100;
      return (
        <g key={Math.random()}>
          <rect
            width={50}
            height={`${height}%`}
            fill={bar.color}
            x={60} // multiply with the width (50) + 10 for space
            y={`${y}%`}
          />
        </g>
      );
    });
  };

  return (
    <div className="new-card" style={newCardStyle}>
      {entries.map(entry => (
        <div style={contentStyle} key={Math.random()}>
          <svg
            viewBox={`0, 0, ${width}, ${500}`}
            height={500}
            width={width}
            style={{ transform: `rotateX(180deg)` }}
          >
            {rows(entry)}
          </svg>
          {entry.name}
        </div>
      ))}
    </div>
  );
}

ReactDOM.render(
    <App />,
  document.getElementById('root')
);
<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
<div id="root"></div>