Reputation: 479
I have below piece of code. I am using React and svg for bar charts. I am not using any third party library for charts. With this below piece of code, i am able to get the bar charts. But the issue is that my bar charts show horizontally, I want to show it vertically. I am not able to figure out how to get this same piece of code working for a vertical bar chart.
I saw one video online https://egghead.io/lessons/javascript-build-a-bar-chart-with-svg-from-scratch-with-react This guy is able to achieve the bar chart vertically. I am not sure where i am going wrong in displaying it. Any changes i do or try, it always show horizontal bar chart.
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>
)}
Can someone please help me figure out where i went wrong.
Upvotes: 2
Views: 1260
Reputation: 34158
I am going to take this slightly out of context to show a bar result - more "bars" can be added in the SVG, will leave that to you.
Note the important parts are the <rect width="20" height="75" y="25"></rect>
where the width
and height
show the "size" of the bar.
For fun I added some mouse-over 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" width="200" height="200" aria-labelledby="title desc" role="img">
<title id="title">A single bar chart</title>
<desc id="desc">FunBar day</desc>
<g class="bar">
<rect width="20" height="75" y="25"></rect>
<text x="0" y="115" dy=".25em">Fun</text>
</g>
<g class="bar">
<rect width="20" height="25" y="75" x="30"></rect>
<text x="30" y="115" dy=".25em">Goat</text>
</g>
</svg>
Upvotes: 0
Reputation: 1883
I just figured this out for you. You can do the rest calculation.
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>
);
};
Upvotes: 1