barchart rendered in SVG without the use of any external libraries

Creating a stacked barchart with SVG and HTML without relying on any third-party library has been quite a challenge. Despite searching extensively online, I have not come across a single resource that demonstrates how to build a stacked bar chart using plain SVG.

I have made significant progress towards achieving this goal on Codepen, but I am currently stuck at a certain point. If anyone could provide insights on what additional steps are required to complete the stacked barchart, it would be greatly appreciated.

Check out my Codepen here

Below is the code that I have managed to create:

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

By stacked barchart, I mean visualizing one category of data overlaid on another, as shown in the following image: https://i.sstatic.net/barKP.png

Answer №1

To create a stacked bar chart, you must first calculate the widths of the current columns and spaces. Enclose the svg element in a div, position the text inside a div and center it using display:flex.

Include the y key for the bars, where:

  • start point = passed = 0
  • middle point = skipped = passed value
  • end point = failed = passed value + skipped value
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',
};
// calculate total width: 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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What are the steps for sorting objects in an array by their data type attribute?

I am currently working with a JavaScript array of people objects that is dynamically rendered from JS. My goal is to implement a filter on the objects based on the selection made in the dropdown menu and matching it with the department attribute specified ...

Angular Redirect Function: An Overview

In the Angular project I'm working on, there is a function that should navigate to the home when executed. Within this function, there is a condition where if true, it should redirect somewhere. if (condition) { location.url('/home') ...

Utilizing MaterialUI and CSS to craft this stunning box

Looking to create a new MaterialUI component similar to this one: original box Struggling with implementing it using the card component, but so far it looks like: poorly designed box Here's my custom styling using makeStyles: const useStyles = ma ...

What is the best way to align the logo image in the center of the header vertically?

I am trying to vertically center my logo, but I have not been successful with using margin: auto or margin: center. Here is what I have tried so far: ・text-align: center; ・margin: auto; ・margin: center: ・navbar-brand-center The logo currently app ...

How can I make sure to consider the scrollbar when using viewport width units?

I've been working on developing a carousel similar to Netflix, but I'm facing an issue with responsiveness. I've been using a codepen example as a reference: Check out the example here The problem lies in the hardcoded width and height use ...

Avoid production build warnings in Vue.js without the need to build the code

Experimenting with vuejs, I decided to use it for a simple page even though I could have achieved the same without any framework. Now, my project is nearly production ready. The only issue is that it's just a single js and html file, but it shows thi ...

Differences in behavior across operating systems when pasting content copied from Excel into Next.js

Exploring the Issue I am currently working on a project using Next.js 14 where users can paste data copied from an Excel file into a spreadsheet-like component called react-data-grid. However, I have encountered some inconsistencies when copy-pasting on M ...

How can I implement Material UI icons in my project?

While using the Material UI table, I encountered an issue with the font color being white, making it difficult to read the text. However, upon selecting the text, the data is visible but there seems to be a problem with the icons as well. Even though I hav ...

What are the steps to create a dynamic background image that adjusts to various

When viewing the website on a computer screen, the entire cat is visible in the background image. However, when switching to a mobile screen size, only a portion of the cat is displayed, appearing as though it has been cut off. The image is not smaller on ...

Prevent the hiding of a select element with jQuery Chosen Select

I am facing difficulty hiding a select element with the chosen-select class if it does not have any elements present. I attempted: $("#Category2").hide(); and also tried removing the chosen-select class before hiding the element: $("#Category2").re ...

Having difficulty retrieving an angular file from a location outside of the asset folder

I'm encountering issues with a small project that is meant to read a log and present it in table format. Here is the outline of the project structure: project structure Within the LOG directory, I should be able to access motore.log from my DataServi ...

Styling ul items with sx in React MUI5

I recently started using Material UI 5 in a fresh project and I'm facing an issue with styling a list (ul). I attempted to style the ul element using the sx prop, but unfortunately, it's not working as expected. The code I used is as follows: &l ...

Learn how to retrieve the HTTP headers of a request using AngularJS

When working with AngularJS, I know that accessing an HTTP request's GET parameters is easy using: $location.search().parameterOfInterest But how can I access the HTTP headers of the request? It's worth noting that I'm not utilizing $http ...

Fixed background & cover design for Internet Explorer 11

Visit this link for more information: http://apolytos.com/new/img/test.html <!doctype html> <html> <head> <meta charset="utf-8"> <style> body { background:url("background.jpg"); background-repeat:no-repeat; backgr ...

Vanished were the empty voids within our

It seems that the spaces between words have mysteriously vanished in a font I am currently using. Take a look at this website: I am utilizing a slightly modified Twitter Bootstrap with Google Web fonts, and the font causing the issue is Oswald from Googl ...

Exploring the possibilities of working with deeply nested components in React recursively

I have a basic to-do list with functionality for adding todos, toggling completion status, and deleting todos. My goal is to have the ability to nest todos infinitely. I've been able to display nested todos through recursion, but I'm struggling t ...

Steps to include a fresh string into an array within a json document

I'm currently working on a Discord bot that uses a profile.json file. My goal is to have a specific command that allows users to add input arguments to an array within the file, like this: {"Profile_name":"id":"idhere", "array":["item_1"]} The ultim ...

Interactive image rotator featuring navigation buttons for next and previous slides

I recently followed a tutorial from W3Schools Now, I am looking to enhance it by adding previous / next buttons for the indicators, rather than for the slider itself Here is what I aim to accomplish: https://i.sstatic.net/qH1PQ.png Below is the code sn ...

I'm looking to streamline my code by creating shared functionality across multiple reducers with the help of create

Previously, in the older way of using Redux, we could create a reducer like this - handling different action types but with the same action: export default function authReducer(state = initialState, action) { switch (action.type) { case AUTH_ERROR: ...

Media queries in CSS appear to be dysfunctional when used on Microsoft Edge

@media (min-width: 992px) and (max-width: 1140px) { .mr-1024-none { margin-right: 0px !important; } .mt-1024 { margin-top: 1rem !important; } .d-1024-none { display: none !important; } } Utilizing the ...