与 Svg 垂直堆叠条形图反应(无第三方库)

Posted

技术标签:

【中文标题】与 Svg 垂直堆叠条形图反应(无第三方库)【英文标题】:React with Svg vertical stacked barchart (no third party library) 【发布时间】:2021-07-20 10:09:06 【问题描述】:

我有以下代码。我将 React 和 svg 用于条形图。我没有使用任何第三方库来制作图表。使用下面的这段代码,我可以获得条形图。但问题是我的条形图水平显示,我想垂直显示。我无法弄清楚如何让同一段代码用于垂直条形图。

我在网上看到一个视频https://egghead.io/lessons/javascript-build-a-bar-chart-with-svg-from-scratch-with-react 这家伙能够垂直实现条形图。我不确定我在显示它时哪里出错了。我所做或尝试的任何更改,它总是显示水平条形图。


export const ReleaseScopeCharts = () => 
const data = [
    
        "name": "Transit",
        "passed": 2,
        "skipped": 5,
        "failed": 22,
    ,
    
        "name": "Access",
        "passed": 7,
        "skipped": 2,
        "failed": 11,
    
]
const fontSize=14
const width=1000
const rowHeight=40
const colors = ["#30D158", "#005EA7", "#FF453A"];

const entries = data.map((d) => (
    name: d.name,
    total: d.total,
    bars: ["passed", "skipped", "failed"].map((key, i) => (
        value: d[key],
        portion: d[key] / 29,
        color: colors[i]
    ))
        .filter((bar) => bar.value)
))
const heightPerRow = rowHeight;
const canvasHeight = entries.length * heightPerRow;
const canvasWidth = width;
const labelWidth = canvasWidth / 4;
const plotWidth = canvasWidth - labelWidth;
const verticalPadding = heightPerRow / 2;
const barHeight = heightPerRow - verticalPadding;
const horizontalPadding = 0;
const rows = entries.map((entry, i) => 
    const widths = entry.bars.map((bar) => plotWidth * bar.portion)
    const offsets = entry.bars.map((bar, i, array) => 
        const previous = array.slice(0, i);
        const offset = previous.map((bar) => bar.portion)
            .reduce((a, b) => a + b, 0)

        return offset + bar.portion / 2
    )

    const bars = entry.bars.map((bar, i) => 
        const barWidth = widths[i] - horizontalPadding;
        return (<g key=i transform=`translate($plotWidth * offsets[i], $heightPerRow / 2)`>
            <rect 
            rx=barHeight / 2 ry=barHeight / 2 width=barWidth height=barHeight fill=bar.color x=-barWidth / 2 y=-barHeight / 2 />
            <text fill="#fff" fontSize=fontSize textAnchor="middle" alignmentBaseline="middle">bar.value</text>
        </g>)
    )
    return (
        <g key=i transform=`translate($labelWidth,$heightPerRow * i)`>
            <g transform=`translate($-labelWidth, $heightPerRow / 2)`>
                <text
                    fontSize=fontSize
                    textAnchor="start"
                    alignmentBaseline="middle">entry.name</text>
            </g>
            bars
        </g>
    )
)
return (
    <div className="new-card">
        <div>
        </div>
        <svg viewBox=`0, 0, $canvasWidth, $canvasHeight`
        height=canvasHeight
        width=canvasWidth
        >
            rows
        </svg>
    </div>
)

谁能帮我弄清楚我哪里出错了。

【问题讨论】:

你应该有一个固定的宽度和高度应该是你的计算。你做的完全相反。此外,您还需要基于 % 的高度并为不同的图表呈现不同的 svg。 @Yadab 可能是对的。我试图翻转这些值,但 xP 一无所获。无论如何,我玩了一点,得到了一个垂直的图表——不一样,它非常 WIP,但它是垂直的 -> codesandbox.io/s/… 。也许会有所帮助或提供一些想法,不幸的是我今天不能继续,这是一个有趣的“任务” @Yadab - 如果可以,请分享一个工作示例 【参考方案1】:

我刚刚为您解决了这个问题。您可以进行其余的计算。

export 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>
    );
;

【讨论】:

谢谢@Yadab。上面的代码提供了分组的垂直条形图。你能指出堆积条形图应该做的改变吗? 你想要这样的东西吗? stackblitz.com/edit/… @Yadab- No.. 实际上,在 tat 示例中,2010 年和 2011 年被并排分组,分别用于德国和美国。我希望 2010 年和 2011 年彼此重叠。所以德国是单条,美国是单条。 喜欢这个? stackblitz.com/edit/… 知道了,您想在一个栏中显示通过、跳过和失败?实现它需要一点时间。我今天休息。明天会看到这个。【参考方案2】:

我将稍微断章取意地显示一个条形结果 - 可以在 SVG 中添加更多“条形”,这将留给您。

注意重要部分是&lt;rect width="20" height="75" y="25"&gt;&lt;/rect&gt;,其中widthheight 显示栏的“大小”。

为了好玩,我添加了一些鼠标悬停的 CSS。

.bar 
  fill: lime;
  /* changes the background */
  height: 10rem;
  transition: fill .3s ease;
  cursor: pointer;
  font-family: Helvetica, sans-serif;


.bar text 
  color: black;
  font-size: 0.75rem;


.bar:hover,
.bar:focus 
  fill: blue;


.bar:hover text,
.bar:focus text 
  fill: red;
<svg class="chart"   aria-labelledby="title desc" role="img">
  <title id="title">A single bar chart</title>
  <desc id="desc">FunBar day</desc>
  <g class="bar">
    <rect   y="25"></rect>
    <text x="0" y="115" dy=".25em">Fun</text>
  </g>
   <g class="bar">
    <rect   y="75" x="30"></rect>
    <text x="30" y="115" dy=".25em">Goat</text>
  </g>
 </svg>

【讨论】:

这更多的是静态数据,并没有展示如何使用堆积图。你能帮我处理我上面分享的数据吗?

以上是关于与 Svg 垂直堆叠条形图反应(无第三方库)的主要内容,如果未能解决你的问题,请参考以下文章

Python使用matplotlib绘制柱状图(bar plot)实战:水平条形图垂直条形图分组条形图堆叠条形图

使用 SVG 和 Javascript 创建图表生成器 Web 应用程序(无第三方包或库)

为多个列表反应原生 svg 条形图;我怎样才能分开2条

Tableau 图表大全2.0之堆叠条形图制作

有哪些用 JavaScript 实现的图形库

如何让堆叠的 div 垂直向上增长?