Skip to content

Instantly share code, notes, and snippets.

@nevernormal1
Last active April 17, 2024 07:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save nevernormal1/f808cffb897c63a8dd4e to your computer and use it in GitHub Desktop.
Save nevernormal1/f808cffb897c63a8dd4e to your computer and use it in GitHub Desktop.
SVG Jigsaw Puzzle Generator
license: MIT
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://d3js.org/d3-path.v0.1.min.js"></script>
<script src="https://d3js.org/d3-shape.v0.5.min.js"></script>
<style>
body {
font: 13px sans-serif;
}
rect,
circle,
path {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
circle {
fill: #fff;
fill-opacity: .2;
}
</style>
<body>
<form>
<label for="rowCount">Rows:</label>
<input type="number" id="rowCount" min="1" max="10" value="10"/>
<label for="columnCount">Columns:</label>
<input type="number" id="columnCount" min="1" max="10" value="10"/>
</form>
<div id="container">
</div>
</body>
<script>
var width = 450, height = 450;
// Returns 6 points representing the shape of one edge of a puzzle piece.
// Point coordinates are expressed as percentage distances across the width
// and height of the piece.
var edgeDistributions = function() {
var randomBetween = function(min, max) {
return Math.random() * (max - min) + min;
};
var baselineOffsets = {
xMin: 51,
xMax: 62,
yMin: -15,
yMax: 5
};
var upperOffsets = {
xMin: 20,
xMax: 30,
yMin: 20,
yMax: 44
};
var point1 = [0, 0];
var point2 = [
randomBetween(baselineOffsets.xMin, baselineOffsets.xMax),
randomBetween(baselineOffsets.yMin, baselineOffsets.yMax)
];
var point3 = [
randomBetween(upperOffsets.xMin, upperOffsets.xMax),
randomBetween(upperOffsets.yMin, upperOffsets.yMax)
];
var point4 = [
randomBetween(100-upperOffsets.xMax, 100-upperOffsets.xMin),
randomBetween(upperOffsets.yMin, upperOffsets.yMax)
];
var point5 = [
randomBetween(100-baselineOffsets.xMax, 100-baselineOffsets.xMin),
randomBetween(baselineOffsets.yMin, baselineOffsets.yMax)
];
var point6 = [100, 0];
var sign = Math.random() < 0.5 ? -1 : 1;
return [point1, point2, point3, point4, point5, point6].map(function(p) {
return [p[0] / 100, p[1] * sign / 100];
});
};
// Builds an m + 1 x n matrix of edge shapes. The first and last rows
// are straight edges.
var buildDistributions = function(m, n) {
var lineGroups = [];
var lines = [];
var points, i, j;
for (j = 0; j < n; j++) {
lines.push([[0, 0], [1,0]]);
}
lineGroups.push(lines);
for (i = 1; i < m; i++) {
lines = [];
for (j = 0; j < n; j++) {
lines.push(edgeDistributions());
}
lineGroups.push(lines);
}
lines = [];
for (j = 0; j < n; j++) {
lines.push([[0, 0], [1,0]]);
}
lineGroups.push(lines);
return lineGroups;
};
var transposePoint = function(point) {
return [point[1], point[0]];
};
var offsetPoint = function(point, columnIndex, rowIndex, columnWidth, rowHeight) {
var offsetColumnPosition = function(percent, columnWidth, columnIndex) {
var columnOffset = columnWidth * columnIndex;
return percent * columnWidth + columnOffset;
};
var offsetRowPosition = function(percent, rowHeight, rowIndex) {
var rowOffset = rowHeight * rowIndex;
return percent * rowHeight + rowOffset;
};
var x = offsetColumnPosition(point[0], columnWidth, columnIndex);
var y = offsetRowPosition(point[1], rowHeight, rowIndex);
return [x, y];
};
var offsetPoints = function(lineGroups, offsetter) {
for (var i=0; i<lineGroups.length; i++) {
var lines = lineGroups[i];
for (var j=0; j<lines.length; j++) {
lines[j] = lines[j].map(function(point) {
return offsetter(point, j, i);
});
}
}
};
var buildPieces = function(rowCount, columnCount) {
var rowHeight = height / rowCount;
var columnWidth = width / columnCount;
var pieces = [];
var rows = buildDistributions(rowCount, columnCount);
offsetPoints(rows, function(point, j, i) {
return offsetPoint(point, j, i, columnWidth, rowHeight);
});
var columns = buildDistributions(columnCount, rowCount);
offsetPoints(columns, function(point, j, i) {
return offsetPoint(transposePoint(point), i, j, columnWidth, rowHeight);
});
for (var rowIndex = 1; rowIndex <= rowCount; rowIndex++) {
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++) {
var edges = [];
edges.push(rows[rowIndex - 1][columnIndex]);
edges.push(columns[columnIndex + 1][rowIndex - 1]);
edges.push(rows[rowIndex][columnIndex].slice().reverse());
edges.push(columns[columnIndex][rowIndex - 1].slice().reverse());
pieces.push(edges);
}
}
return pieces;
};
var d3CurvedLine = d3_shape.line().curve(d3_shape.curveBasis);
var piecePathData = function(piece) {
return piece.map(function(edge) {
return d3CurvedLine(edge);
}).join(" ");
};
var buildPiecePaths = function(pieces) {
return pieces.map(function(piece) {
return svg.path(piecePathData(piece));
});
};
// SVG helpers
var svg = {
openTag: '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="' + width + '" height="' + height + '">',
closeTag: '</svg>',
path: function(pathData) {
return '<path vector-effect="non-scaling-stroke" d="' + pathData + '"/>';
}
};
$(function() {
var generate = function() {
var rowCount = parseInt($("#rowCount").val(), 10);
var columnCount = parseInt($("#columnCount").val(), 10);
var pieces = buildPieces(rowCount, columnCount);
var piecePaths = buildPiecePaths(pieces).join("");
var svgNode = [
svg.openTag,
piecePaths,
svg.closeTag
].join("");
$("#container").empty().append($(svgNode));
}
$('input').change(generate);
generate();
});
</script>
@Scott216
Copy link

Scott216 commented May 9, 2016

I'm trying out the jigsaw puzzle generator on this page
http://bl.ocks.org/nevernormal1/f808cffb897c63a8dd4e
But I can't figure out how to get an SVG file of the puzzle design. Can you help?

@Bejoscha
Copy link

Bejoscha commented Jun 9, 2016

Same as Scott216.
While I can download by right-click and save-as, the file does not import in CorelDraw.
It also displays differently when opened in Explorer. Can you alter the generator so that the SVG shapes are actually pieces not lines? (Yes, the same edge is part of two pieces then.)
This would make vector-graphics imports so much nicer.

@KeyZer0
Copy link

KeyZer0 commented Nov 23, 2016

If you want to export / save the SVG as of writing this post you have to extract the SVG element from the HTML webpage. You can do so in the Google Chrome web browser by clicking on the OPEN option to get a simpler raw layout . Here's a direct link to the "RAW" webpage layout:

http://bl.ocks.org/nevernormal1/raw/f808cffb897c63a8dd4e/

Right click on the jigsaw puzzle and on the Google Chrome drop down content menu select "Inspect". That should open up a Chrome developer tools window. Right click on the SVG element ( on the line that starts with "<SVG xmlns=" ... ). Select Cut Element from the Developer Tools drop down context menu. That will store the SVG HTML code in your computer's clipboard. Open a plain text editing program ( Notepad on Windows, Textedit on a Mac ) and paste the text into it. You can now save the file "as is" with an SVG file extension ( for example jigsaw.svg ) and it should open and display properly in a vector editing program like Inkscape ( it did for me ) but to ensure there are no compatibility issues with other programs you can add the following line.

OPTIONAL: Add the following as the very first line at the top, right above the "<SVG xmlns=" ... line in the SVG text file.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

Save the file. Once done you should probably clean up the file in Inkscape or some other vector editor. When I open the file I select all under the Edit menu in Inkscape then under Fill and Stroke properties in the Object menu I set Fill to NO PAINT, set Stroke paint to FLAT COLOR, and set Stroke style to 0.001 inches ( or 0.025 mm, same thing ) width to ensure the lines will vector cut on an Epilog laser cutter. Then I select all again and Group all objects under the Object menu so that it's all one shape that can be stretched or resized to fit whatever size of jigsaw I plan to create.

Hope this helps.

@aartek
Copy link

aartek commented Mar 14, 2018

Is it possible to make the jigsaw area clickable? It works for me only for paths. I'd like to do something like $('#container > svg > path:nth-child(2)').on('click',()=>{console.log('jigsaw clicked')})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment