![Made to be Plundered](https://img.shields.io/badge/Made%20to%20be%20Plundered-royalblue)
[![Latest version](https://img.shields.io/github/v/release/PaulioRandall/p45)](https://github.com/PaulioRandall/p45/releases)
[![Release date](https://img.shields.io/github/release-date/PaulioRandall/p45)](https://github.com/PaulioRandall/p45/releases)
# P45
Svelte library for programmatically crafting grid based SVGs.
Throughout this README I've used example based axiomatic definitions. My hoped for outcome is to strike a nice balance between concise communication of concepts and the precision needed for effective use of the library. I do hope it does not confuse.
**Requires Svelte version 4.**
## Intentions
> "Craftsmen engage themselves in complex tasks. The complexity of those tasks often gives a simplicity to their lives." - Edward de Bono
I want to make drawing and diagramming quick and easy in scenarios where fine precision is not beneficial. As craftsmen we are inclined to precision; it's in our nature. But unlike painting fine art, meticulousness rarely pays off when drawing small web icons, especially SVGs.
Grid based diagramming aims to improve design speed, consistency, and experience by constraining where users to a grid. **I like to think of it as trading-off freedom of expression for speed of expression.**
A little while back I built a rough prototype [SVG Icon Maker](https://skepticalgoose.com/treasury/prototype-svg-maker) on the theme of grid based diagramming because I find existing tools too fiddly and crafting SVGs by hand too tedious. This library is another, more refined, experiment.
## Trade-offs
This implementation is rather simple and easily replicated. I could have gone a lot further with crafting utility components and functions but it's much more economic to employ an inclusion-by-need rather than inclusion-by-foresight policy.
Those articulate in mental visualisation may be able to effortlessly work out grid coordinates in their head, but most of us will want a visual grid on hand as reference. For non-trivial icons, it helps to do draw them out on paper first. Mapping the coordinates to code will be the quickest and easiest part.
## Quick Start
### Dependency
_package.json_. May need to be within `dependencies` in some scenarios.
```json
{
"devDependencies": {
"p45": "^v1.0.0"
}
}
```
### Svelte Component
```svelte
```
## The P45Grid
The P45Grid is a simple JavaScript class with functions for generating nodes. Nodes are the points that make up a grid (or graph) as opposed to cells which are the square areas within a set of linked nodes.
They require a _size_ for the width and height of the visible area. It must be an odd integer, so we always have a center node, and greater than 2, because anything smaller than _3x3_ is of little use.
```js
import { P45Grid } from 'p45'
const g = new P45Grid(size)
```
```js
// Note that this is an axiomatic representation
// of the algorithm and P45Grid properties, not
// the real thing.
import { P45Grid } from 'p45'
// UNIT is the spacing between nodes.
P45Grid.UNIT === 4
// HALF is half a UNIT.
P45Grid.HALF === 2
// idOf returns a unique ID for every combination of inputs which is designed
// to be easily parsed.
P45Grid.idOf(x, y, offX = 0, offY = 0)
new P45Grid(size) == {
UNIT: P45Grid.UNIT,
HALF: P45Grid.HALF,
// lastIdx is the last index in the grid.
lastIdx: size - 1,
// centerIdx is the center index of both x and y planes.
centerIdx: (size - 1) / 2,
// centerXY holds the coordinates of the center node.
centerXY: {
x: (size - 1) / 2,
y: (size - 1) / 2,
},
// bounds holds the min and max coordinate of the visible grid.
//
// Note that coordinates outside the visible grid are still valid.
bounds: {
xMin: 0,
xMax: size - 1,
yMin: 0,
yMax: size - 1,
},
// boundsPx holds the pixel bounds of the grid.
boundsPx: bounds: {
xMin: 0,
xMax: (size - 1) * P45Grid.UNIT,
yMin: 0,
yMax: (size - 1) * P45Grid.UNIT,
},
// len is the length of the visible grid.
len: size,
// lenPx is the pixel length of the visible grid.
lenPx: (size - 1) * P45Grid.UNIT,
// center is the node at the center of the grid.
//
// Invoking the node function with center coordinates
// will not result in this object being returned but
// the contents will be identical.
center: {
id, // Unique ID of the node that includes offset
coords: { // Grid coordinates of the node on the visible grid.
x,
y,
},
off: { // Offset in pixels.
x,
y,
},
x, // SVG X pixel position.
y, // SVG Y pixel position.
grid, // Reference to the P45Grid that generated the node.
},
// idOf is a proxy for P45Grid.idOf.
idOf(col, row, offX = 0, offY = 0),
// contains returns true if the passed coordinates
// are contained within the bounds.
contains(x = 0, y = 0),
// containsPx returns true if the passed pixel
// positions are contained within the pixel bounds.
containsPx(x = 0, y = 0),
// node returns a Node object containing information
// about the node. Notably, it provides an x and y
// pixel position for plotting the SVG elements.
node(x, y, offX = 0, offY = 0),
// n is short hand alias for the node function.
n(x, y, offX = 0, offY = 0),
}
```
#### `P45Grid.UNIT` & `P45Grid.HALF`
The distance between each node is fixed as _4_ and defined by `P45Grid.UNIT`. All calculations are performed from this such that:
```js
import { P45Grid } from 'p45'
P45Grid.UNIT === g.UNIT === 4
P45Grid.HALF === g.HALF === P45Grid.UNIT / 2
const g = new P45Grid(9)
g.len === 9
g.lenPx === (9 - 1) * P45Grid.UNIT
top__left == g.node(0, 0) == { x: 0, y: 0 }
top_right == g.node(8, 0) == { x: 32, y: 0 }
bot__left == g.node(0, 8) == { x: 0, y: 32 }
bot_right == g.node(8, 8) == { x: 32, y: 32 }
```
#### `P45Grid.idOf`
Returns a unique ID for every combination of inputs which is designed to be easily parsed. Defining the format as an axiomatic example:
```js
// Numbers are always signed and padded with zeros.
const id = P45Grid.idOf(2, -4, -5, 5)
id == 'COL_+002_-005_ROW_-004_+005'
id.split('_') == [
0: 'COL',
1: '+002' == 2 == // column number,
2: '-005' == -5 == // column offset in grid pixels,
3: 'ROW',
4: '-004' == -4 == // row number,
5: '+005' == 5 == // row offset in grid pixels,
]
```
#### `P45Grid.node` & `P45Grid.n`
Visible nodes can be constructed by calling the `node` and `n` functions on a P45Grid instance. `n` being an alias of `node`.
There is no constraint on coordinates when creating nodes. This allows `` control points to be placed off canvas or for partial shapes to be drawn. This allows for greater flexibility but may require `overflow: hidden` on a container as off-grid drawings are visible by default.
A new object is returned in the form:
```js
grid.node(x, y, offX, offY) == {
id: P45Grid.idOf(x, y, offX, offY),
coords: {
x: x,
y: y,
},
off: {
x: offX,
y: offY,
},
x: x * P45Grid.UNIT + offX,
y: y * P45Grid.UNIT + offY,
grid: grid,
}
```
Such that:
```js
import { P45Grid } from 'p45'
const g = new P45Grid(9)
top__left == g.node(0, 0) == {
id: `COL_+000_+000_ROW_+000_+000`,
coords: {
x: 0,
y: 0,
},
off: {
x: 0,
y: 0,
},
x: 0,
y: 0,
grid: g,
}
bot_right == g.node(8, 8) == {
id: `COL_+008_+000_ROW_+008_+000`,
coords: {
x: 8,
y: 8,
},
off: {
x: 0,
y: 0,
},
x: 32, // Grid.UNIT * x
y: 32, // Grid.UNIT * y
grid: g,
}
```
## The Components
To ease the use of SVG commands and drawing common shapes, P45 provides a set Svelte components that accept nodes as props. Only the _SVG_ component is needed, the others are more for convenience.
### `