DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
11 Monitoring and Observability Tools for 2023
Learn more

Build Responsive SVG Layouts With react-svg-flexbox

A web development expert takes a look at this great little library for creating data visualizations with JavaScript and React.

Swizec Teller user avatar by
Swizec Teller
·
Aug. 21, 18 · Tutorial
Like (1)
Save
Tweet
Share
7.21K Views

Join the DZone community and get the full member experience.

Join For Free

SVG is great. Best way to build scalable graphics on the web. SVG can do everything from simple logos to data visualization and even animation.

The best part is, you can manipulate SVG with both CSS and JavaScript. It's an image that's part of your DOM.

No wonder then that SVG is the norm when it comes to data visualization and other programmable graphics.

Just one problem: SVG sucks at layout.

Absolutely terrible. There's no layout support at all. You get absolute positioning and that's it. Sure, you can position absolutely within absolutely positioned elements, which is sort of relative positioning, but ugh...

Absolute Positioning Hell

Image title

Say you're building a small dashboard, like of different scatterplots looking at a dataset about dog breeds.

Because the data is there, and you can, of course.

You create a scatterplot component. It takes an x and a y position and sizing info. Inside, it places two axes, a caption, and the datapoints.

The <Scatterplot> is absolutely positioned via a translate transformation. That moves an SVG element by a vector from (0, 0), thus rendering at the (x, y) coordinate.

You render each scatterplot like this:

<Scatterplot
        data={data}
        x={100}
        y={100}
        width={350}
        height={350}
        filter={d =>
            d.weight &&
            d.height &&
            d.weight[0] &&
            d.height[0]
        }
        xData={d => d.weight[0]}
        yData={d => d.height[0]}
        xLabel="Weight (lbs)"
        yLabel="Height (in)"
        title="Dog Breed Height & Weight"
        entry={props => (
            <Datapoint
                breed={props.d.breed}
                {...props}
            />
        )}
    />

A lot of arguments, I know. Data, positioning, sizing, functions for interpreting data, a few labels, and a render prop for each datapoint.

Once positioned with a grouping element <g>, the scatterplot can use quasi-relative positioning for its elements.

render() {
    return (
        <g transform={`translate(${x}, ${y})`}>
            <Heading y={-25}>{title}</Heading>
            {data.map(d =>
                entry({
                    x: xScale(xData(d)),
                    y: yScale(yData(d)),
                    d: d
                })
            )}
            <Axis
                scale={xScale}
                x={0}
                y={height}
                type="Bottom"
                label={xLabel}
            />
            <Axis scale={yScale} x={0} y={0} type="Left" label={yLabel} />
        </g>
    );
}

Position the main container at x and y. Place Heading at (0, -25), datapoints at wherever they go, one axis at (0, 0), and another at left bottom. All relative to the parent container.

The parent container is technically relative to the whole SVG container...

Absolute or relative, doesn't matter. You're gonna have one hell of a fun time calculating the position of each individual element by hand. D3 scales help, but you still have to think about it.

SVG itself offers zero help.

Want to resize your scatterplots? Here's what happens: 

Image title

Want to resize your browser? Here's what happens: 

Image title

Ugh.

react-svg-flexbox to the Rescue

You can fix the layouting problem with react-svg-flexbox. It's a small library, not a lot of stars, but it's so perfect.

Built on top of Facebook's css-layout, which has recently become a part of yoga, it lets you use CSS flexbox to position SVG elements.

Flexbox might be confusing to grok — I look at tutorials any time I use it for anything — but it's way better than doing it yourself. How many engineers worked on browser layout engines over the past two decades?

Wouldn't wanna retrace all those steps yourself.

Wrap our dashboard in a <Flexbox> element and...

import Flexbox from "react-svg-flexbox";

// ...

// render() etc.
<Svg>
    <Flexbox
        style={{
            flexDirection: "row",
            justifyContent: "flex-start"
        }}
    >
        <Scatterplot
            data={data}
            width={200}
            height={200}
            filter={d =>
                d.weight &&
                d.height &&
                d.weight[0] &&
                d.height[0]
            }
            xData={d => d.weight[0]}
            yData={d => d.height[0]}
            xLabel="Weight (lbs)"
            yLabel="Height (in)"
            title="Dog Breed Height & Weight"
            entry={props => (
                <Datapoint
                    breed={props.d.breed}
                    {...props}
                />
            )}
        />

We take <Flexbox> out of react-svg-flexbox, use flexbox styles to say we want to render in a row that starts at the beginning, and the rest happens on its own.

Note that react-svg-flexbox passes x and y props into our components, so we had to take out manual positioning. Our dashboard now uses up all the space it can: 

Image title

Something's funky with our vertical positioning, but it's an easy fix. Offset y coordinates by a few pixels. It's not happening in the official examples, so it must be something we're doing inside those scatterplots.

The axes are still poking outside our width bounds, but this looks better already.

Image title

Even if we make one of them bigger, everything still works. No changes to positioning required!

Image title

Responsive Layout With react-svg-flexbox

For the biggest win, we add flexWrap: wrap to our <Flexbox> component. Like this: 

 <Flexbox
    style={{
        flexDirection: "row",
        justifyContent: "flex-start",
        flexWrap: "wrap",
        width: 1024,
        height: 1024
    }}
>

You have to specify available width in your styles, otherwise, it doesn't work. That means you should listen to window.onresize and update width accordingly.

Easiest to attach an event listener in componentDidMount. Like this:

class App extends Component {
    state = {
        width: 1024
    };

    svgRef = React.createRef();

    componentDidMount() {
        window.addEventListener("resize", this.updateSize);
    }

    componentWillUnmount() {
        window.removeEventListener("resize", this.updateSize);
    }

    updateSize = () => {
        this.setState({ width: this.svgRef.current.clientWidth });
    };

    render() {
        // ...

        <svg
            style={{ width: "100%", height: 1024 }}
            ref={this.svgRef}
        >
            <Flexbox
                style={{
                    flexDirection: "row",
                    justifyContent: "flex-start",
                    flexWrap: "wrap",
                    width: this.state.width,
                    height: 1024
                }}
            >
    }

And your dashboard becomes responsive! Yay!

See the Code, Play With Examples

You can see a full set of react-svg-flexbox examples on their Storybook.

Code for my dog breed dashboard example is on GitHub here.

You can try it live here.

Fin

Use react-svg-flexbox. Your life will improve. The best thing that's ever happened to me for SVG coding.

Thanks Cody Averett for finding this gem!

SVG Build (game engine)

Published at DZone with permission of Swizec Teller, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Fargate vs. Lambda: The Battle of the Future
  • Little's Law and Lots of Kubernetes
  • 10 Easy Steps To Start Using Git and GitHub
  • Using GPT-3 in Our Applications

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: