Top 18 Python Pattern Programs You Must Know About [Snippets]

Preparing for technical interviews takes a lot of preparation, and it’s highly probable that you might have to create Python pattern programs there. That’s why we’ve sorted a list of multiple ideas for pattern printing in Python to start your preparations. 

We have multiple kinds of Python pattern programs in this list, so choose your expertise and experience. Make sure that you understand what part of code does what before you move onto the next pattern. Without a proper understanding of how the system works, you would face a lot of difficulty in explaining its working.

Background Patterns, Simplified by Conic Gradients

For those who have missed the big news, Firefox now supports conic gradients!

Starting with Firefox 75, released on the April 7, we can go to about:config, look for the layout.css.conic-gradient.enabled flag and set its value to true (it’s false by default and all it takes to switch is double-clicking it).

Screenshot. Shows the Firefox URL bar at `about:config`, a search for 'conic' giving the `layout.css.conic-gradient.enabled` flag as the sole result and its value set to `true`.
Enabling conic gradients in Firefox 75+

With that enabled, now we can test our CSS including conic gradients in Firefox as well.

While some of the demos in this article work just fine when using a polyfill, some use CSS variables inside the conic gradient and therefore require native support for this feature.

One thing I particularly like about conic gradients is just how much they can simplify background patterns. So let’s take a few linear-gradient() patterns from the gallery created by Lea Verou about a decade ago and see how we can now simplify them with conic-gradient!

Pyramid

Screenshot. Shows the original pyramid pattern with the code that was used to create it.
The pyramid pattern

The pattern above uses four linear gradients:

background:
  linear-gradient(315deg, transparent 75%, #d45d55 0) -10px 0,
  linear-gradient(45deg, transparent 75%, #d45d55 0) -10px 0,
  linear-gradient(135deg, #a7332b 50%, transparent 0) 0 0,
  linear-gradient(45deg, #6a201b 50%, #561a16 0) 0 0 #561a16;
background-size: 20px 20px;

That’s quite a bit of CSS and perhaps even a bit intimidating. It’s not easy to just look at this and understand how it all adds up to give us the pyramid pattern. I certainly couldn’t do it. It took me a while to get it, even though gradients are one of the CSS features I’m most comfortable with. So don’t worry if you don’t understand how those gradients manage to create the pyramid pattern because, one, it is complicated and, two, you don’t even need to understand that!

Using conic-gradient(), we can now get the same result in a much simpler manner, with a single background layer instead of four!

What I like to do when coding repeating patterns is draw equidistant vertical and horizontal lines delimiting the rectangular boxes defined by the background-size. In this case, it’s pretty obvious we have square boxes and where their limits are, but it’s a really useful technique for more complex patterns.

Annotated screenshot. Shows the rectangles (squares in this case) defined by the `background-size`.
Highlighting the pattern’s cells

By default, conic gradients start from 12 o’clock and go clockwise. However, in our case, we want to offset the start of the gradient by 45° in the clockwise direction and afterwards make every one of the four shades occupy a quarter (25%) of the available space around the midpoint of our square box.

SVG illustration. Shows how we place a conic gradient into a single pattern cell by rotating the gradient start point 45° in the clockwise (positive) direction.
A pattern cell with a conic gradient’s hard stops at every 25% starting from 45° w.r.t. the vertical axis (live).

This means our pyramid pattern can be reduced to:

$s: 20px;
background:
  conic-gradient(from 45deg, 
    #561a16 25%, 
    #6a201b 0% 50%, 
    #a7332b 0% 75%, 
    #d45d55 0%) 
    50%/ #{$s $s};

Not only does the code look simpler, but we’ve also gone from 260 bytes to 103 bytes, reducing the code needed to get this pattern by more than half.

We’re using the double position syntax as that’s also well supported these days.

We can see it in action in the Pen below:

Checkerboard

Screenshot. Shows the original checkerboard pattern with the code that was used to create it.
The checkerboard pattern

This pattern above is created with two linear gradients:

background-color: #eee;
background-image:
  linear-gradient(45deg, black 25%, transparent 25%, 
    transparent 75%, black 75%, black),
  linear-gradient(45deg, black 25%, transparent 25%, 
    transparent 75%, black 75%, black);
background-size: 60px 60px;
background-position: 0 0, 30px 30px;

Let’s see how we can simplify this CSS when replacing these linear gradients with a conic one!

Just like in the previous case, we draw vertical and horizontal lines in order to better see the rectangles defined by the background-size.

Annotated screenshot. Shows the rectangles (squares in this case) defined by the `background-size`.
Highlighting the pattern’s cells

Looking at the square highlighted in deeppink in the illustration above, we see that, in this case, our conic gradient starts from the default position at 12 o’clock. A quarter of it is black, the next quarter is dirty white and then we have repetition (the same black and then dirty white quarter slices once more).

SVG illustration. Shows how we place a conic gradient into a single pattern cell and then make it repeat after the 50% point.
A pattern cell with a conic gradient’s hard stops at every 25%, starting from the default at 12 o’clock and repeating after 50% (demo).

This repetition in the second half of the [0%, 100%] interval means we can use a repeating-conic-gradient(), which gives us the following code (bringing the compiled CSS from 263 bytes down to only 73 bytes – that’s reducing it by over 70%):

$s: 60px;
background:
  repeating-conic-gradient(#000 0% 25%, #eee 0% 50%) 
    50%/ #{$s $s};

The Pen below shows it in action:

Diagonal checkerboard

Screenshot. Shows the original diagonal checkerboard pattern with the code that was used to create it.
The diagonal checkerboard pattern

Again, we have a pattern created with two linear gradients:

background-color: #eee;
background-image: 
  linear-gradient(45deg, black 25%, transparent 25%, 
    transparent 75%, black 75%, black),
  linear-gradient(-45deg, black 25%, transparent 25%, 
    transparent 75%, black 75%, black);
background-size: 60px 60px;

We draw horizontal and vertical lines to split this pattern into identical rectangles:

Annotated screenshot. Shows the rectangles (squares in this case) defined by the `background-size`.
Highlighting the pattern’s cells

What we now have is pretty much the same checkerbox pattern as before, with the sole difference that we don’t start from the default position at 12 o’clock, but from 45° in the clockwise direction.

If you’re having trouble visualising how simply changing the start angle can make us go from the previous pattern to this one, you can play with it in the interactive demo below:

Note that this demo does not work in browsers that have no native support for conic gradients.

This means our code looks as follows:

$s: 60px;
background:
  repeating-conic-gradient(from 45deg, 
    #000 0% 25%, #eee 0% 50%) 
  50%/ #{$s $s};

We can see it in action below:

Again, not only is the code simpler to understand, but we’ve also gone from 229 bytes to only 83 bytes in the compiled CSS, reducing it by almost two-thirds!

Half-Rombes

Screenshot. Shows the original Half-Rombes pattern with the code that was used to create it.
The half-rombes pattern

This pattern was created with four linear gradients:

background: #36c;
background:
  linear-gradient(115deg, transparent 75%, rgba(255,255,255,.8) 75%) 0 0,
  linear-gradient(245deg, transparent 75%, rgba(255,255,255,.8) 75%) 0 0,
  linear-gradient(115deg, transparent 75%, rgba(255,255,255,.8) 75%) 7px -15px,
  linear-gradient(245deg, transparent 75%, rgba(255,255,255,.8) 75%) 7px -15px,
  #36c;
background-size: 15px 30px;

Just like in the previous cases, we draw equidistant vertical and horizontal lines in order to better see the repeating unit:

Annotated screenshot. Shows the rectangles (squares in this case) defined by the `background-size`.
Highlighting the pattern’s cells.

What we have here is a pattern that’s made up of congruent isosceles triangles (the angled edges are equal and the dark blue triangles are a reflection of the light blue ones) formed by the intersection of equidistant parallel lines that are either horizontal, angled clockwise, or the other way. Each of these three types of parallel lines is highlighted in the illustration below:

Illustration. Shows the equidistant parallel lines which create the pattern of isosceles triangles.
Parallel guides

Every pattern cell contains a full triangle and two adjacent triangle halves in the upper part, then a reflection of this upper part in the lower part. This means we can identify a bunch of congruent right triangles that will help us get the angles we need for our conic-gradient():

SVG illustration. Shows how we place a conic gradient into a single pattern cell by rotating the gradient start point by an angle β in the clockwise (positive) direction such that the 0% line goes through the top right corner and then all the other hard stops are either horizontal or going through the cell corners.
A pattern cell with a conic gradient’s hard stops such that they’re either horizontal or go through the cell corners, all starting from β w.r.t. the vertical axis (demo)

This illustration shows us that the gradient starts from an angle, β, away from the default conic gradient start point at 12 o’clock. The first conic slice (the top right half triangle) goes up to α, the second one (the bottom right dark triangle) up to 2·α, and the third one (the bottom light triangle) goes halfway around the circle from the start (that’s 180°, or 50%). The fourth one (the bottom left dark triangle) goes to 180° + α and the fifth one (the top left light triangle) goes to 180° + 2·α, while the sixth one covers the rest.

SVG illustration. Highlights the right triangle from where we can get α knowing the catheti and shows how we can then compute β.
Getting α and β (demo)

From the highlighted right triangle we get that:

tan(α) = (.5·h)/(.5·w) = h/w

Knowing the width (w) and height (h) of a pattern cell, we can get the angles α and β:

α = atan(h/w)
β = 90° - α

It results in the pattern that’s generated by the following code:

$w: 15px;
$h: 30px;
$a: atan($h/$w)*180deg/pi();
$b: 90deg - $a;
$c0: #36c;
$c1: #d6e0f5;

html {
  background: 
    conic-gradient(from $b, 
      $c1 0% $a, 
      $c0 0% 2*$a, 
      $c1 0% 50%, 
      $c0 0% 180deg + $a, 
      $c1 0% 180deg + 2*$a, 
      $c0 0%) 
    0 0/ #{$w $h};
}

This means going from 343 bytes to only 157 bytes in the compiled CSS. The result can be seen below:

You can tweak the pattern width ($w) and height ($h) in the Sass code in order to see how the pattern gets squished and stretched for different aspect ratios.

In the particular case where the angle between 2*$a and 50% (or 180deg) is also $a, it results that $a is 60deg, our isosceles triangles are equilateral, and our gradient can be reduced to a repeating one (and under 100 bytes in the compiled CSS):

$a: 60deg;
$b: 90deg - $a;
$w: 15px;
$h: $w*tan($a);
$c0: #36c;
$c1: #d6e0f5;

html {
  background: 
    repeating-conic-gradient(from $b, 
      $c1 0% $a, $c0 0% 2*$a) 
    0 0/ #{$w $h}
}

The live result can be seen below:

Bonus: Intersecting line backgrounds!

Screenshot. Shows the original intersecting lines pattern with the code that was used to create it.
Intersecting line background examples

While these are not repeating patterns, they’re examples of a situation where a single conic gradient achieves an effect that would have previously needed a bunch of linear ones.

What we have here is a conic-gradient() created starting from two straight lines intersecting within the rectangular box where we set the background.

SVG illustration. Shows a rectangular box and two random lines intersecting inside it. This intersection point (x,y) is the point the conic gradient goes around, while the gradient's start is from the angle β formed by the line segment closest to the top right corner with the vertical. The hard stops are at α, the angle between the start segment and the next one in clockwise order, at 50% and at 180° + α.
Bonus pattern structure (ldemo)

The gradient goes around the point of coordinates, x,y, where the two straight lines intersect. It starts from an angle, β, which is the angle of the line segment that’s closest to the top-right corner, then has hard stops at α, 50% (or 180°) and 180° + α.

If we want to have multiple elements with similar such patterns created with the help of different intersecting lines and different palettes, we have the perfect use case for CSS variables:

.panel {
  background: 
    conic-gradient(from var(--b) at var(--xy), 
      var(--c0) var(--a), var(--c1) 0% 50%, 
      var(--c2) 0% calc(180deg + var(--a)), var(--c3) 0%);
}

All we have to do is set the position (--xy), the start angle (--b), the first angle (--a) and the palette (--c0 through --c3).

.panel {
  /* same as before */
  
  &:nth-child(1) {
    --xy: 80% 65%; 
    --b: 31deg;
    --a: 121deg; 
    --c0: #be5128;
    --c1: #ce9248;
    --c2: #e4c060;
    --c3: #db9c4e
  }
  
  /* similarly for the other panels */
}

Instead of hardcoding, we could also generate these values randomly or extract them from a data object with the help of a CSS or HTML preprocessor. In this second case, we’d set these custom properties inline, which is precisely what I did in the Pen below:

Since we’re using custom properties inside the conic gradients, this demo does not work in browsers that don’t support them natively.

Well, that’s it! I hope you’ve enjoyed this article and that it gives you some ideas about how conic gradients can make your life easier.

The post Background Patterns, Simplified by Conic Gradients appeared first on CSS-Tricks.

How We Created a Static Site That Generates Tartan Patterns in SVG

Tartan is a patterned cloth that’s typically associated with Scotland, particularly their fashionable kilts. On tartanify.com, we gathered over 5,000 tartan patterns (as SVG and PNG files), taking care to filter out any that have explicit usage restrictions.

The idea was cooked up by Sylvain Guizard during our summer holidays in Scotland. At the very beginning, we were thinking of building the pattern library manually in some graphics software, like Adobe Illustrator or Sketch. But that was before we discovered that the number of tartan patterns comes in thousands. We felt overwhelmed and gave up… until I found out that tartans have a specific anatomy and are referenced by simple strings composed of the numbers of threads and color codes.

Tartan anatomy and SVG

Tartan is made with alternating bands of colored threads woven at right angles that are parallel to each other. The vertical and horizontal bands follow the same pattern of colors and widths. The rectangular areas where the horizontal and vertical bands cross give the appearance of new colors by blending the original ones. Moreover, tartans are woven with a specific technique called twill, which results in visible diagonal lines. I tried to recreate the technique with SVG rectangles as threads here:

Let’s analyze the following SVG structure:


<svg viewBox="0 0 280 280" width="280" height="280" x="0"  y="0" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <mask id="grating" x="0" y="0" width="1" height="1">
      <rect x="0" y="0" width="100%" height="100%" fill="url(#diagonalStripes)"/>
    </mask>
  </defs>
  <g id="horizontalStripes">
    <rect fill="#FF8A00" height="40" width="100%" x="0" y="0"/>    
    <rect fill="#E52E71" height="10" width="100%" x="0" y="40"/>
    <rect fill="#FFFFFF" height="10" width="100%" x="0" y="50"/>
    <rect fill="#E52E71" height="70" width="100%" x="0" y="60"/>   
    <rect fill="#100E17" height="20" width="100%" x="0" y="130"/>    
    <rect fill="#E52E71" height="70" width="100%" x="0" y="150"/>
    <rect fill="#FFFFFF" height="10" width="100%" x="0" y="220"/>
    <rect fill="#E52E71" height="10" width="100%" x="0" y="230"/>   
    <rect fill="#FF8A00" height="40" width="100%" x="0" y="240"/>
  </g>
  <g id="verticalStripes" mask="url(#grating)">
    <rect fill="#FF8A00" width="40" height="100%" x="0" y="0" />  
    <rect fill="#E52E71" width="10" height="100%" x="40" y="0" />
    <rect fill="#FFFFFF" width="10" height="100%" x="50" y="0" />
    <rect fill="#E52E71" width="70" height="100%" x="60" y="0" />
    <rect fill="#100E17" width="20" height="100%" x="130" y="0" />   
    <rect fill="#E52E71" width="70" height="100%" x="150" y="0" />
    <rect fill="#FFFFFF" width="10" height="100%" x="220" y="0" />
    <rect fill="#E52E71" width="10" height="100%" x="230" y="0" />   
    <rect fill="#FF8A00" width="40" height="100%" x="240" y="0" />
  </g>
</svg>

The horizontalStripes group creates a 280x280 square with horizontal stripes. The verticalStripes group creates the same square, but rotated by 90 degrees. Both squares start at (0,0) coordinates. That means the horizontalStripes are completely covered by the verticalStripes; that is, unless we apply a mask on the upper one.

<defs>
  <mask id="grating" x="0" y="0" width="1" height="1">
    <rect x="0" y="0" width="100%" height="100%" fill="url(#diagonalStripes)"/>
  </mask>
</defs>

The mask SVG element defines an alpha mask. By default, the coordinate system used for its x, y, width, and height attributes is the objectBoundingBox. Setting width and height to 1 (or 100%) means that the mask covers the verticalStripes resulting in just the white parts within the mask being full visible.

Can we fill our mask with a pattern? Yes, we can! Let’s reflect the tartan weaving technique using a pattern tile, like this:

In the pattern definition we change the patternUnits from the default  objectBoundingBox to userSpaceOnUse so that now, width and height are defined in pixels.

<svg width="0" height="0">
  <defs>
    <pattern id="diagonalStripes" x="0" y="0" patternUnits="userSpaceOnUse" width="8" height="8">
      <polygon points="0,4 0,8 8,0 4,0" fill="white"/>
      <polygon points="4,8 8,8 8,4" fill="white"/>
    </pattern>    
  </defs> 
</svg>

Using React for tartan weaving

We just saw how we can create a manual “weave” with SVG. Now let’s automatize this process with React. 

The SvgDefs component is straightforward — it returns the defs markup.

const SvgDefs = () => {
  return (
    <defs>
      <pattern
        id="diagonalStripes"
        x="0"
        y="0"
        width="8"
        height="8"
        patternUnits="userSpaceOnUse"
      >
        <polygon points="0,4 0,8 8,0 4,0" fill="#ffffff" />
        <polygon points="4,8 8,8 8,4" fill="#ffffff" />
      </pattern>
      <mask id="grating" x="0" y="0" width="1" height="1">
        <rect
          x="0"
          y="0"
          width="100%"
          height="100%"
          fill="url(#diagonalStripes)"
        />
      </mask>
    </defs>
  )
}

We will represent a tartan as an array of stripes. Each stripe is an object with two properties: fill (a hex color) and size (a number).

const tartan = [
  { fill: "#FF8A00", size: 40 },
  { fill: "#E52E71", size: 10 },
  { fill: "#FFFFFF", size: 10 },
  { fill: "#E52E71", size: 70 },
  { fill: "#100E17", size: 20 },
  { fill: "#E52E71", size: 70 },
  { fill: "#FFFFFF", size: 10 },
  { fill: "#E52E71", size: 10 },
  { fill: "#FF8A00", size: 40 },
]

Tartans data is often available as a pair of strings: Palette and Threadcount that could look like this:

// Palette
O#FF8A00 P#E52E71 W#FFFFFF K#100E17

// Threadcount
O/40 P10 W10 P70 K/10.

I won’t cover how to convert this string representation into the stripes array but, if you are interested, you can find my method in this Gist.

The SvgTile component takes the tartan array as props and returns an SVG structure.

const SvgTile = ({ tartan }) => {

  // We need to calculate the starting position of each stripe and the total size of the tile
  const cumulativeSizes = tartan
    .map(el => el.size)
    .reduce(function(r, a) {
      if (r.length > 0) a += r[r.length - 1]
      r.push(a)
      return r
    }, [])
  
  // The tile size
  const size = cumulativeSizes[cumulativeSizes.length - 1]

  return (
    <svg
      viewBox={`0 0 ${size} ${size}`}
      width={size}
      height={size}
      x="0"
      y="0"
      xmlns="http://www.w3.org/2000/svg"
    >
      <SvgDefs />
      <g id="horizontalStripes">
        {tartan.map((el, index) => {
          return (
            <rect
              fill={el.fill}
              width="100%"
              height={el.size}
              x="0"
              y={cumulativeSizes[index - 1] || 0}
            />
          )
        })}
      </g>
      <g id="verticalStripes" mask="url(#grating)">
        {tartan.map((el, index) => {
          return (
            <rect
              fill={el.fill}
              width={el.size}
              height="100%"
              x={cumulativeSizes[index - 1] || 0}
              y="0"
            />
          )
        })}
      </g>
    </svg>
  )
}

Using a tartan SVG tile as a background image

On tartanify.com, each individual tartan is used as a background image on a full-screen element. This requires some extra manipulation since we don’t have our tartan pattern tile as an SVG image. We're also unable to use an inline SVG directly in the background-image property.

Fortunately, encoding the SVG as a background image does work:

.bg-element {
  background-image: url('data:image/svg+xml;charset=utf-8,<svg>...</svg>');
}

Let’s now create an SvgBg component. It takes the tartan array as props and returns a full-screen div with the tartan pattern as background.

We need to convert the SvgTile React object into a string. The ReactDOMServer object allows us to render components to static markup. Its method renderToStaticMarkup is available both in the browser and on the Node server. The latter is important since later we will server render the tartan pages with Gatsby.

const tartanStr = ReactDOMServer.renderToStaticMarkup(<SvgTile tartan={tartan} />)

Our SVG string contains hex color codes starting with the # symbol. At the same time, # starts a fragment identifier in a URL. It means our code will break unless we escape all of those instances. That’s where the built-in JavaScript encodeURIComponent function comes in handy.

const SvgBg = ({ tartan }) => {
  const tartanStr = ReactDOMServer.renderToStaticMarkup(<SvgTile tartan={tartan} />)
  const tartanData = encodeURIComponent(tartanStr)
  return (
    <div
      style={{
        width: "100%",
        height: "100vh",
        backgroundImage: `url("data:image/svg+xml;utf8,${tartanData}")`,
      }}
    />
  )
}

Making an SVG tartan tile downloadable

Let’s now download our SVG image.

The SvgDownloadLink component takes svgData (the already encoded SVG string) and fileName as props and creates an anchor (<a>) element. The download attribute prompts the user to save the linked URL instead of navigating to it. When used with a value, it suggests the name of the destination file.

const SvgDownloadLink = ({ svgData, fileName = "file" }) => {
  return (
    <a
      download={`${fileName}.svg`}
      href={`data:image/svg+xml;utf8,${svgData}`}
    >
      Download as SVG
    </a>
  )
}

Converting an SVG tartan tile to a high-res PNG image file

What about users that prefer the PNG image format over SVG? Can we provide them with high resolution PNGs?

The PngDownloadLink component, just like SvgDownloadLink, creates an anchor tag and has the tartanData and fileName as props. In this case however, we also need to provide the tartan tile size since we need to set the canvas dimensions.

const Tile = SvgTile({tartan})
// Tartan tiles are always square
const tartanSize = Tile.props.width

In the browser, once the component is ready, we draw the SVG tile on a <canvas> element. We’ll use the canvas toDataUrl() method that returns the image as a data URI. Finally, we set the date URI as the href attribute of our anchor tag.

Notice that we use double dimensions for the canvas and double scale the ctx. This way, we will output a PNG that’s double the size, which is great for high-resolution usage.

const PngDownloadLink = ({ svgData, width, height, fileName = "file" }) => {
  const aEl = React.createRef()
  React.useEffect(() => {
    const canvas = document.createElement("canvas")
    canvas.width = 2 * width
    canvas.height = 2 * height
    const ctx = canvas.getContext("2d")
    ctx.scale(2, 2)
    let img = new Image()
    img.src = `data:image/svg+xml, ${svgData}`
    img.onload = () => {
      ctx.drawImage(img, 0, 0)
      const href = canvas.toDataURL("image/png")
      aEl.current.setAttribute("href", href)
    }
  }, [])
  return (
    <a 
      ref={aEl} 
      download={`${fileName}.png`}
    >
      Download as PNG
    </a>
  )
}

For that demo, I could have skipped React's useEffect hook and the code would worked fine. Nevertheless, our code is executed both on the server and in the browser, thanks to Gatsby. Before we start creating the canvas, we need to be sure that we are in a browser. We should also make sure the anchor element is ”ready” before we modify its attribute. 

Making a static website out of CSV with Gatsby

If you haven’t already heard of Gatsby, it’s a free and open source framework that allows you to pull data from almost anywhere and generate static websites that are powered by React.

Tartanify.com is a Gatsby website coded by myself and designed by Sylvain. At the beginning of the project, all we had was a huge CSV file (seriously, 5,495 rows), a method to convert the palette and threadcount strings into the tartan SVG structure, and an objective to give Gatsby a try.

In order to use a CSV file as the data source, we need two Gatsby plugins: gatsby-transformer-csv and gatsby-source-filesystem. Under the hood, the source plugin reads the files in the /src/data folder (which is where we put the tartans.csv file), then the transformer plugin parses the CSV file into JSON arrays.

// gatsby-config.js
module.exports = {
  /* ... */
  plugins: [
    'gatsby-transformer-csv',
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: `${__dirname}/src/data`,
        name: 'data',
      },
    },
  ],
}

Now, let’s see what happens in the gatsby-node.js file. The file is run during the site-building process. That’s where we can use two Gatsby Node APIs: createPages and onCreateNode. onCreateNode is called when a new node is created. We will add two additional fields to a tartan node: its unique slug and a unique name. It is necessary since the CSV file contains a number of tartan variants that are stored under the same name.

// gatsby-node.js
// We add slugs here and use this array to check if a slug is already in use
let slugs = []
// Then, if needed, we append a number
let i = 1

exports.onCreateNode = ({ node, actions }) => {
  if (node.internal.type === 'TartansCsv') {
    // This transforms any string into slug
    let slug = slugify(node.Name)
    let uniqueName = node.Name
    // If the slug is already in use, we will attach a number to it and the uniqueName
    if (slugs.indexOf(slug) !== -1) {
      slug += `-${i}`
      uniqueName += ` ${i}`
      i++
    } else {
      i = 1
    }
    slugs.push(slug)
  
    // Adding fields to the node happen here
    actions.createNodeField({
      name: 'slug',
      node,
      value: slug,
    })
    actions.createNodeField({
      name: 'Unique_Name',
      node,
      value: uniqueName,
    })
  }
}

Next, we create pages for each individual tartan. We want to have access to its siblings so that we can navigate easily. We will query the previous and next edges and add the result to the tartan page context.

// gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const allTartans = await graphql(`
    query {
      allTartansCsv {
        edges {
          node {
            id
            fields {
              slug
            }
          }
          previous {
            fields {
              slug
              Unique_Name
            }
          }
          next {
            fields {
              slug
              Unique_Name
            }
          }
        }
      }
    }
  `)
  if (allTartans.errors) {
    throw allTartans.errors
  }
  allTartans.data.allTartansCsv.edges.forEach(
    ({ node, next, previous }) => {
      createPage({
        path: `/tartan/${node.fields.slug}`,
        component: path.resolve(`./src/templates/tartan.js`),
        context: {
          id: node.id,
          previous,
          next,
        },
      })
    }
  )
}

We decided to index tartans by letters and create paginated letter pages. These pages list tartans with links to their individual pages. We display a maximum of 60 tartans per page, and the number of pages per letter varies. For example, the letter “a” will have have four pages: tartans/a, tartans/a/2, tartans/a/3 and tartans/a/4. The highest number of pages (15) belongs to “m” due to a high number of traditional names starting with “Mac.”

The tartans/a/4 page should point to tartans/b as its next page and tartans/b should point to tartans/a/4 as its previous page.

We will run a for of loop through the letters array ["a", "b", ... , "z"] and query all tartans that start with a given letter. This can be done with filter and regex operator:

allTartansCsv(filter: { Name: { regex: "/^${letter}/i" } })

The previousLetterLastIndex variable will be updated at the end of each loop and store the number of pages per letter. The /tartans/b page need to know the number of a pages (4) since its previous link should be tartans/a/4.

// gatsby-node.js
const letters = "abcdefghijklmnopqrstuvwxyz".split("")
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  // etc.

  let previousLetterLastIndex = 1
  for (const letter of letters) {
    const allTartansByLetter = await graphql(`
      query {
        allTartansCsv(filter: {Name: {regex: "/^${letter}/i"}}) {
          nodes {
            Palette
            fields {
              slug
              Unique_Name
            }
          }
          totalCount
        }
      }
    `)
    if (allTartansByLetter.errors) {
      throw allTartansByLetter.errors
    }
    const nodes = allTartansByLetter.data.allTartansCsv.nodes
    const totalCountByLetter = allTartansByLetter.data.allTartansCsv.totalCount
    const paginatedNodes = paginateNodes(nodes, pageLength)
    paginatedNodes.forEach((group, index, groups) => {
      createPage({
        path:
          index > 0 ? `/tartans/${letter}/${index + 1}` : `/tartans/${letter}`,
        component: path.resolve(`./src/templates/tartans.js`),
        context: {
          group,
          index,
          last: index === groups.length - 1,
          pageCount: groups.length,
          letter,
          previousLetterLastIndex,
        },
      })
    })
    previousLetterLastIndex = Math.ceil(totalCountByLetter / pageLength)
  }
}

The paginateNode function returns an array where initial elements are grouped by pageLength

const paginateNodes = (array, pageLength) => {
  const result = Array()
  for (let i = 0; i < Math.ceil(array.length / pageLength); i++) {
    result.push(array.slice(i * pageLength, (i + 1) * pageLength))
  }
  return result
}

Now let’s look into the tartan template. Since Gatsby is a React application, we can use the components that we were building in the first part of this article.

// ./src/templates/tartan.js
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
import SvgTile from "../components/svgtile"
import SvgBg from "../components/svgbg"
import svgAsString from "../components/svgasstring"
import SvgDownloadLink from "../components/svgdownloadlink"
import PngDownloadLink from "../components/pngdownloadlink"

export const query = graphql`
  query($id: String!) {
    tartansCsv(id: { eq: $id }) {
      Palette
      Threadcount
      Origin_URL
      fields {
        slug
        Unique_Name
      }
    }
  }
`
const TartanTemplate = props => {
  const { fields, Palette, Threadcount } = props.data.tartansCsv
  const {slug} = fields
  const svg = SvgTile({
    palette: Palette,
    threadcount: Threadcount,
  })
  const svgData = svgAsString(svg)
  const svgSize = svg.props.width
  
  return (
    <Layout>
      <SvgBg svg={svg} />
      {/* title and navigation component comes here */}
      <div className="downloads">
        <SvgDownloadLink svgData={svgData} fileName={slug} />
        <PngDownloadLink svgData={svgData} size={svgSize} fileName={slug} />
      </div>
    </Layout>
  )
}
export default TartanTemplate

Finally let’s focus on the tartans index pages (the letter pages).

// ./src/templates/tartans.js
import React from "react"
import Layout from "../components/layout"
import {Link} from "gatsby"
import TartansNavigation from "../components/tartansnavigation"
const TartansTemplate = ({ pageContext }) => {
  const {
    group,
    index,
    last,
    pageCount,
    letter,
    previousLetterLastIndex,
  } = pageContext

  return (
    <Layout>
      <header>
        <h1>{letter}</h1>
      </header>
      <ul>
        {group.map(node => {
          return (
            <li key={node.fields.slug}>
              <Link to={`/tartan/${node.fields.slug}`}>
                <span>{node.fields.Unique_Name}</span>
              </Link>
            </li>
          )
        })}
      </ul>
      <TartansNavigation
        letter={letter}
        index={index}
        last={last}
        previousLetterLastIndex={previousLetterLastIndex}
      />
    </Layout>
  )
}
export default TartansTemplate

The TartansNavigation component adds next-previous navigation between the index pages.

// ./src/components/tartansnavigation.js
import React from "react"
import {Link} from "gatsby"

const letters = "abcdefghijklmnopqrstuvwxyz".split("")
const TartansNavigation = ({
  className,
  letter,
  index,
  last,
  previousLetterLastIndex,
}) => {
  const first = index === 0
  const letterIndex = letters.indexOf(letter)
  const previousLetter = letterIndex > 0 ? letters[letterIndex - 1] : ""
  const nextLetter =
    letterIndex < letters.length - 1 ? letters[letterIndex + 1] : ""
  
  let previousUrl = null, nextUrl = null

  // Check if previousUrl exists and create it
  if (index === 0 && previousLetter) {
    // First page of each new letter except "a"
    // If the previous letter had more than one page we need to attach the number 
    const linkFragment =
      previousLetterLastIndex === 1 ? "" : `/${previousLetterLastIndex}`
    previousUrl = `/tartans/${previousLetter}${linkFragment}`
  } else if (index === 1) {
    // The second page for a letter
    previousUrl = `/tartans/${letter}`
  } else if (index > 1) {
    // Third and beyond
    previousUrl = `/tartans/${letter}/${index}`
  }
  
  // Check if `nextUrl` exists and create it
  if (last && nextLetter) {
    // Last page of any letter except "z"
    nextUrl = `/tartans/${nextLetter}`
  } else if (!last) {
    nextUrl = `/tartans/${letter}/${(index + 2).toString()}`
  }

  return (
    <nav>
      {previousUrl && (
        <Link to={previousUrl} aria-label="Go to Previous Page" />
      )}
      {nextUrl && (
        <Link to={nextUrl} aria-label="Go to Next Page" />
      )}
    </nav>
  )
}
export default TartansNavigation

Final thoughts

Let’s stop here. I tried to cover all of the key aspects of this project. You can find all the tartanify.com code on GitHub. The structure of this article reflects my personal journey — understanding the specificity of tartans, translating them into SVG, automating the process, generating image versions, and discovering Gatsby to build a user-friendly website. It was maybe not as fun as our Scottish journey itself 😉, but I truly enjoyed it. Once again, a side project proved to be the best way to dig into new technology.

The post How We Created a Static Site That Generates Tartan Patterns in SVG appeared first on CSS-Tricks.