How to build a hexagon using Vue and SVG

Lately I have been more interested in the reactivity of VueJS to generate svg shapes as components. Components offer a fantastic way to re-use code and also share functionality between apps and/or services.

I found a nice little utility made by Brenna that generates a CSS hexagon based on it's width. That seemed like a fantastic idea and I played around with the demo for quite some time because it was that much fun to use.

I wanted to make a monogram of my initials and print it to PDF as part of a side project I currently busy with but printing CSS margins and weird positioning ended up breaking the shape.

I reached for SVG since it's well supported and allows for lots of flexibility when trying to do custom things. The experiment I attempted was that SVG would play well with VueJS. Also crafting shapes as components that automatically builds out the internals of the SVG based on an initial starting point (like width) would be really cool.

TD;DR

The hexagon

To build a hexagon monogram as a SVG you need a couple of things.

  • Initial width
  • 6 points or coordinates
  • Calculated height
  • Letters to generate the monogram with
  • Background color

All of those are sent through as component config via props but to calculate the 6 coordinates is not that arbitrary.

The finished product first:

<svg data-v-5f2854f4="" width="80" height="92.38" class="monogram" style="fill: rgb(0, 168, 232);">
    <polygon points="40,0 80,23.09 80,69.28 40,92.38 0,69.28 0,23.09"></polygon> 
    <text x="50%" y="52%" alignment-baseline="middle" text-anchor="middle" class="text">XB</text>
</svg>

A hexagon is a polygon with point coordinates in a viewbox of a fixed size. The coordinates will be generated starting at (0,0) on the top left and stretches to (width, autoHeight) on the bottom right. To get a visually appealing shape, the height will be a little bit more than the width.

You will see that the <polygon points="..." is where we need to calculate those using the width. Lets work through the 6 points and how I got to them, starting at the top point of the hexagon, they are broken up as indexed (x,y) coordinates. All values will be rounded up to 2 decimal points.

First

var first = [
    (width / 2), 0
];

// 40, 0 

The first point needs to be half (40) that of the width and at the top of the view box (0).

Second

var second = [ 
    width, 
    (width / 2 / Math.sqrt(3))
]

// 80, 23.09

The second point needs to be the full width (80) and the height of the second point is half the width (80) and the third side of a 90/60/30 triangle that is calculated by the sqrt(3).

Image credit: https://www.freemathhelp.com

// ex
80 / 2 = 40
40 / sqrt(3) = 23.09 // Rounded up

Third

var third = [ 
    width, 
    (width / sqrt(3)) + (width / 2 / sqrt(3))
]

// 80, 69.28

The third point is the same as the second and needs to be the full width (80). The height will be the previous point's height (23.09) + the full width divided by the sqrt(3) to get the right hand side bottom coordinate.

Fourth

var fourth = [ 
    width / 2, 
    (width / sqrt(3) * 2)
]

// 40, 92.38

The x coordinate is like the top point, the half of the full width. The height is dynamically calculated by taking twice the width and dividing it by the sqrt(3).

Fifth

var fifth = [ 
    0, 
    (width / sqrt(3)) + (width / 2 / sqrt(3))
]

// 0, 69.28

The width should be 0. The height would be the same as the third point 69.28.

Sixth

var sixth = [ 
    0, 
    (width / 2 / Math.sqrt(3))
]

// 0, 23.09

The width should be 0. The height would be the same as the second point 23.09 to complete the six sided polygon.

The component

The component to render a hexagon shaped monogram is straight forward.

<monogram width="80" letters="EM" :primary-color="primaryColor" />

width: The pixel width that you want and is required. It acts as the starting condition and all the SVG points and sides are calculated from that initially.

letters: A string of two letters that will render in the middle of the shape.

primary-color: Is the string CSS color value that you want the background fill to be.

This will render a nicely scaled hexagon with your properties.

Example Monograms

The internals

The inside of the component Monogram.vue is also pretty straight forward. The SVG element uses VueJS to bind the props correctly.

<template>
    <svg class="monogram" :style="fillColor" :width="width" :height="height">
        <polygon :points="monogramPoints" />
        <text class="text" x="50%" y="52%" alignment-baseline="middle" text-anchor="middle">
            {{ letters }}
        </text>
    </svg>
</template>

<script>
export default {
    name: 'Monogram',
    props: [
        'width',
        'letters',
        'primaryColor'
    ],
    data() {
        return {
            fillColor: {
                fill: this.primaryColor
            }
        }
    },
    methods: {
        calculatePointsForHexagon() {
            var points = [
                [ parseFloat((this.width / 2).toFixed(2)), 0 ],
                [ parseFloat(this.width), parseFloat((this.width / Math.sqrt(3) / 2).toFixed(2)) ],
                [ parseFloat(this.width), parseFloat((this.width / Math.sqrt(3) + (this.width / Math.sqrt(3) / 2)).toFixed(2)) ],
                [ parseFloat((this.width / 2).toFixed(2)), parseFloat((this.width / Math.sqrt(3) * 2).toFixed(2)) ],
                [ 0, parseFloat((this.width / Math.sqrt(3) + (this.width / Math.sqrt(3) / 2)).toFixed(2)) ],
                [ 0, parseFloat((this.width / Math.sqrt(3) / 2).toFixed(2)) ],
            ];

            return points.join(' ');
        }
    },
    computed: {
        monogramPoints: function() {
            return this.calculatePointsForHexagon();
        },
        height: function() {
            return parseFloat((this.width / Math.sqrt(3) * 2).toFixed(2));
        }
    }
}
</script>

<style lang="scss" scoped>
// Feel free to remove the scoped scss. Acts as a starting point
$black: hsl(0, 0%, 4%) !default;
$weight-bold: 700 !default;
$family-serif: Constantia, "Lucida Bright", "Lucidabright", "Lucida Serif", "Lucida", "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif;
$text-2xl: 1.5rem;

.monogram {
    .text {
        fill: $black;
        font-family: $family-serif;
        font-weight: $weight-bold;
        font-size: $text-2xl;
    }
}
</style>

The only property that was weird was the primary color and I used the data() to build the Style object for it.

data() {
    return {
        fillColor: {
            fill: this.primaryColor
        }
    }
}

monogramPoints is a computed property which builds out the coordinates as an array and then joins them as strings separated by spaces. Javascript sorted that out for me so no additional string manipulation was needed.


Show Comments

Get the latest posts delivered right to your inbox.