├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .travis.yml
├── docs
└── index.html
├── examples
├── albers.html
├── code
│ ├── css
│ │ └── style.css
│ ├── examples
│ │ ├── albers1.js
│ │ ├── albers2.js
│ │ ├── arrows.js
│ │ ├── astroid.js
│ │ ├── bezier.js
│ │ ├── bugs.js
│ │ ├── clip-path.js
│ │ ├── clock.js
│ │ ├── curves.js
│ │ ├── dragon.js
│ │ ├── draw.js
│ │ ├── hanoi.js
│ │ ├── mask.js
│ │ ├── parabola.js
│ │ ├── polygon.js
│ │ ├── riley1.js
│ │ ├── spiral.js
│ │ ├── star.js
│ │ ├── ten-print.js
│ │ ├── test1.js
│ │ └── text.js
│ ├── images
│ │ ├── dice.svg
│ │ ├── download.svg
│ │ ├── font-size.svg
│ │ ├── moon.svg
│ │ ├── pause.svg
│ │ ├── play.svg
│ │ ├── question.svg
│ │ └── share.svg
│ ├── index.html
│ └── js
│ │ ├── app.js
│ │ ├── live-code.js
│ │ ├── lz-string.min.js
│ │ ├── queryWatcher.js
│ │ └── toolkit.js
├── compare-with.html
├── compare-without.html
├── empty.html
├── japanese-flag.html
├── load-file.html
├── mask.html
├── mystify.html
├── random-walker.html
└── ten-print.html
├── package.json
├── readme.md
├── rollup.config.js
├── src
├── arguments
│ ├── makeCoordinates.js
│ ├── makeViewBox.js
│ └── semiFlattenArrays.js
├── colors
│ ├── convert.js
│ ├── cssColors.js
│ ├── index.js
│ └── parseColor.js
├── constructor
├── environment
│ ├── detect.js
│ ├── lib.js
│ ├── messages.js
│ ├── strings.js
│ └── window.js
├── general
│ ├── algebra.js
│ ├── cdata.js
│ ├── dom.js
│ ├── index.js
│ ├── path.js
│ ├── string.js
│ ├── transforms.js
│ └── viewBox.js
├── index.js
└── spec
│ ├── classes_attributes.js
│ ├── classes_nodes.js
│ ├── namespace.js
│ ├── nodes.js
│ ├── nodes_attributes.js
│ └── nodes_children.js
├── svg.js
├── svg.module.js
├── tests
├── arrow.test.js
├── attributes.test.js
├── circle.test.js
├── class.id.test.js
├── clean.js
├── colors.test.js
├── coordinates.test.js
├── dom.test.js
├── dragon.svg
├── ear.js
├── ellipse.test.js
├── environment.test.js
├── index.html
├── line.test.js
├── lineno.js
├── nodes.test.js
├── object.assign.test.js
├── path.test.js
├── polygon.test.js
├── primitives.test.js
├── rect.test.js
├── stylesheet.test.js
├── svg.animation.test.js
├── svg.args.test.js
├── svg.background.test.js
├── svg.controls.test.js
├── svg.load.test.js
├── svg.save.test.js
├── svg.viewbox.test.js
├── transforms.test.js
├── types.test.js
├── urls.test.js
├── use.test.js
└── window.test.js
├── tsconfig.json
└── types
├── arguments
├── makeCoordinates.d.ts
├── makeViewBox.d.ts
└── semiFlattenArrays.d.ts
├── colors
├── convert.d.ts
├── cssColors.d.ts
├── index.d.ts
└── parseColor.d.ts
├── constructor
├── elements
└── constructors.d.ts
├── environment
├── detect.d.ts
├── lib.d.ts
├── messages.d.ts
├── strings.d.ts
└── window.d.ts
├── general
├── algebra.d.ts
├── cdata.d.ts
├── dom.d.ts
├── index.d.ts
├── path.d.ts
├── string.d.ts
├── transforms.d.ts
└── viewBox.d.ts
├── index.d.ts
└── spec
├── classes_attributes.d.ts
├── classes_nodes.d.ts
├── namespace.d.ts
├── nodes.d.ts
├── nodes_attributes.d.ts
└── nodes_children.d.ts
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "node": true
6 | },
7 | "extends": "airbnb-base",
8 | "parserOptions": {
9 | "ecmaVersion": "latest",
10 | "ecmaFeatures": { "jsx": true },
11 | "sourceType": "module"
12 | },
13 | "ignorePatterns": [
14 | "svg.js",
15 | "svg.module.js",
16 | "svg.module.comments.js"
17 | ],
18 | "rules": {
19 | "arrow-parens": 0,
20 | "camelcase": ["error", { "allow": [".*"] }],
21 | "func-names": ["error", "never"],
22 | "indent": ["error", "tab"],
23 | "no-bitwise": 0,
24 | "no-continue": 0,
25 | "no-sparse-arrays": 0,
26 | "no-tabs": 0,
27 | "import/extensions": ["error", "always", { "ignorePackages": true }],
28 | "import/no-relative-packages": 0,
29 | "object-shorthand": 0,
30 | "object-curly-newline": 0,
31 | "import/prefer-default-export": 0,
32 | "prefer-rest-params": 0,
33 | "prefer-default-export": 0,
34 | "prefer-destructuring": 0,
35 | "quotes": ["error", "double", { "allowTemplateLiterals": true }]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | node_modules/
3 | coverage/
4 | module/
5 | package-lock.json
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .git
3 | .gitignore
4 | .travis.yml
5 | .eslintrc.js
6 | rollup.config.js
7 | coverage/
8 | docs/
9 | examples/
10 | module/
11 | include/
12 | src/
13 | tests/
14 |
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - lts/*
--------------------------------------------------------------------------------
/examples/albers.html:
--------------------------------------------------------------------------------
1 |
2 |
SVG example: introduction
3 |
4 |
5 |
6 |
25 |
--------------------------------------------------------------------------------
/examples/code/css/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | }
5 | body {
6 | margin: 0;
7 | background-image:
8 | linear-gradient(45deg, #f8f8f8 25%, transparent 25%),
9 | linear-gradient(135deg, #f8f8f8 25%, transparent 25%),
10 | linear-gradient(45deg, transparent 75%, #f8f8f8 75%),
11 | linear-gradient(135deg, transparent 75%, #f8f8f8 75%);
12 | background-size: 25px 25px;
13 | background-position: 0 0, 12.5px 0, 12.5px -12.5px, 0px 12.5px;
14 | }
15 | svg {
16 | width: 100%;
17 | height: 100%;
18 | }
19 | #app {
20 | width: 100%;
21 | height: 100%;
22 | display: flex;
23 | flex-direction: row;
24 | }
25 | .code-editor,
26 | .code-canvas {
27 | flex: 1;
28 | }
29 | /* center everything on the canvas */
30 | .code-canvas {
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | }
35 | .code-console {
36 | position: absolute;
37 | z-index: 2;
38 | bottom: 0;
39 | left: 0;
40 | width: 50%;
41 | }
42 | .code-console p {
43 | width: calc(100% - 8rem);
44 | margin: 1rem 4rem;
45 | padding: 0.5rem 1rem;
46 | background-color: #2f312a;
47 | border-radius: 0.5rem;
48 | color: #ddd;
49 | font-family: monospace;
50 | }
51 | /* buttons */
52 | div[class*="-button"] {
53 | position: absolute;
54 | z-index: 8;
55 | width: 2rem;
56 | height: 2rem;
57 | border-radius: 1rem;
58 | cursor: pointer;
59 | background-repeat: no-repeat;
60 | background-size: auto;
61 | background-position: center center;
62 | }
63 | .play-pause-button {
64 | left: 0.5rem;
65 | bottom: 0.5rem;
66 | }
67 | .play-pause-button.pause {
68 | background-color: #5b23;
69 | background-image: url(../images/play.svg);
70 | }
71 | .play-pause-button.play {
72 | background-color: #5b2;
73 | background-image: url(../images/pause.svg);
74 | }
75 | .dark-mode-button {
76 | left: 0.5rem;
77 | bottom: 5.5rem;
78 | background-color: #fff9;
79 | background-image: url(../images/moon.svg);
80 | }
81 | .font-size-button {
82 | left: 0.5rem;
83 | bottom: 8rem;
84 | background-color: #fff9;
85 | background-image: url(../images/font-size.svg);
86 | }
87 | /* additional buttons */
88 | .random-button {
89 | left: 0.5rem;
90 | bottom: 3rem;
91 | background-color: #fb3;
92 | background-image: url(../images/dice.svg);
93 | }
94 | .download-button {
95 | left: 0.5rem;
96 | bottom: 13rem;
97 | background-color: #fff9;
98 | background-image: url(../images/download.svg);
99 | }
100 | .share-button {
101 | left: 0.5rem;
102 | bottom: 10.5rem;
103 | background-color: #fff9;
104 | background-image: url(../images/share.svg);
105 | }
106 | .question-button {
107 | left: calc(50% - 2.5rem);
108 | top: 0.5rem;
109 | opacity: 0.8;
110 | background-color: #fff9;
111 | background-image: url(../images/question.svg);
112 | }
113 | @media not all and (hover), (max-aspect-ratio: 1/1) {
114 | .ace_editor {
115 | font-size: 1rem;
116 | }
117 | #app {
118 | flex-direction: column-reverse;
119 | }
120 | .code-console {
121 | width: 100%;
122 | }
123 | .code-editor,
124 | .code-canvas {
125 | height: 50%;
126 | }
127 | .play-pause-button {
128 | left: initial;
129 | right: 0.5rem;
130 | bottom: 0.5rem;
131 | }
132 | .random-button {
133 | left: initial;
134 | right: 3rem;
135 | bottom: 0.5rem;
136 | }
137 | .dark-mode-button {
138 | left: initial;
139 | right: 5.5rem;
140 | bottom: 0.5rem;
141 | }
142 | .font-size-button {
143 | left: initial;
144 | right: 8rem;
145 | bottom: 0.5rem;
146 | }
147 | .share-button {
148 | left: initial;
149 | right: 10.5rem;
150 | bottom: 0.5rem;
151 | }
152 | .download-button {
153 | left: initial;
154 | right: 13rem;
155 | bottom: 0.5rem;
156 | }
157 | .question-button {
158 | opacity: 1.0;
159 | top: initial;
160 | left: initial;
161 | right: 15.5rem;
162 | bottom: 0.5rem;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/examples/code/examples/albers1.js:
--------------------------------------------------------------------------------
1 | svg.size(90, 90);
2 | svg.background("#9590c0");
3 |
4 | // all shapes will be drawn at the point 0, 0
5 | // shifting the layer will center the shapes
6 | var g = svg.g().translate(35, 15);
7 |
8 | // these attributes will be applied to every shape
9 | // this is useful when you have many similar-looking shapes
10 | var style = { fill: "#2c266d", opacity: 0.4 };
11 |
12 | // transform-origin moves the center of rotation
13 | // right rect
14 | g.rect(30, 50)
15 | .setAttributes(style)
16 | .transformOrigin("0 50")
17 | .rotate(25);
18 |
19 | // no rotation
20 | // center rect
21 | g.rect(30, 50).setAttributes(style);
22 |
23 | // left rect
24 | g.rect(30, 50)
25 | .setAttributes(style)
26 | .transformOrigin("0 50")
27 | .rotate(-25);
28 |
--------------------------------------------------------------------------------
/examples/code/examples/albers2.js:
--------------------------------------------------------------------------------
1 | svg.size(50, 60);
2 | svg.background("white");
3 |
4 | svg.rect(50, 30).fill("steelblue");
5 | svg.rect(50, 30).origin(0, 30).fill("peru");
6 | svg.rect(10, 40).origin(20, 10).fill("#963"); // comment out this line
7 | svg.rect(50, 10).origin(0, 20).fill("midnightblue");
8 | svg.rect(50, 10).origin(0, 30).fill("#fe6");
9 |
--------------------------------------------------------------------------------
/examples/code/examples/arrows.js:
--------------------------------------------------------------------------------
1 | svg.size(1, 1);
2 | svg.background("black");
3 |
4 | let arrowhead = svg.marker()
5 | .setViewBox(0, -1, 2, 2)
6 | .orient("auto-start-reverse");
7 |
8 | arrowhead.polygon(0, 1, 2, 0, 0, -1).fill("white");
9 |
10 | for (var i = 0; i < 10; i += 1) {
11 | svg.curve(Math.random(), Math.random(), Math.random(), Math.random())
12 | .fill("none")
13 | .stroke("white")
14 | .strokeWidth(0.02)
15 | .bend(0.5)
16 | .markerEnd(arrowhead)
17 | .markerStart(arrowhead);
18 | }
19 |
--------------------------------------------------------------------------------
/examples/code/examples/astroid.js:
--------------------------------------------------------------------------------
1 | svg.size(-256, -256, 512, 512);
2 | svg.background("black");
3 |
4 | // all children of this group inherit this style
5 | var layer = svg.g().stroke("white");
6 |
7 | var segments = 30;
8 |
9 | var w = svg.getWidth() / 2 / segments;
10 | var h = svg.getHeight() / 2 / segments;
11 |
12 | // "i" will increment from 0 to (segments - 1)
13 | for (var i = 0; i < segments; i += 1) {
14 | // "j" will decrement from segments to 1
15 | var j = segments - i;
16 | layer.line(-w * i, 0, 0, -h * j); // top left
17 | layer.line(w * j, 0, 0, -h * i); // top right
18 | layer.line(w * i, 0, 0, h * j); // bottom right
19 | layer.line(-w * j, 0, 0, h * i); // bottom left
20 | }
21 |
--------------------------------------------------------------------------------
/examples/code/examples/bezier.js:
--------------------------------------------------------------------------------
1 | svg.size(800, 800);
2 |
3 | // create a layer behind everything
4 | var back = svg.g();
5 |
6 | // shapes on top
7 | var curve = svg.path();
8 | var l1 = svg.line().stroke("black");
9 | var l2 = svg.line().stroke("black");
10 |
11 | // these are control points
12 | // 1.create an svg element to track with each point
13 | // 2.assign a position
14 | // 3.append to a parent
15 | // 4.onChange event handler with "this" bound to the controls
16 | svg.controls(4)
17 | .svg(function () { return SVG.circle(svg.getWidth() * 0.05).fill("#e53"); })
18 | .position(function () { return [random(svg.getWidth()), random(svg.getHeight())]; })
19 | .parent(back)
20 | .onChange(function () {
21 | l1.setPoints(this[0], this[1]);
22 | l2.setPoints(this[3], this[2]);
23 | curve.clear().Move(this[0]).Curve(this[1], this[2], this[3]);
24 | }, true);
25 |
--------------------------------------------------------------------------------
/examples/code/examples/bugs.js:
--------------------------------------------------------------------------------
1 | svg.size(-1, -1, 2, 2);
2 | svg.background("black");
3 |
4 | // dots and lines are separated to their own layers
5 | var lines = svg.g().strokeWidth(0.003);
6 | var dots = svg.g().fill("#ec3");
7 |
8 | // the line endpoint, as vertices 0, 1, 2, 3
9 | var i = [0, 1];
10 | var stroke = "#158";
11 |
12 | svg.play = function (e) {
13 | // every 25th frame or so, change the line color
14 | // and which points the line connects
15 | if (Math.random() < 1/25) {
16 | i[0] = randomInt(4);
17 | i[1] = randomInt(4);
18 | stroke = random(["#158", "#e53"]);
19 | }
20 | dots.circle(noise(e.time + 0), noise(e.time + 100), 0.01);
21 | dots.circle(noise(e.time + 10), noise(e.time + 90), 0.01);
22 | dots.circle(noise(e.time + 20), noise(e.time + 80), 0.01);
23 | dots.circle(noise(e.time + 30), noise(e.time + 70), 0.01);
24 | lines.line(
25 | noise(e.time + 10 * i[0]),
26 | noise(e.time + 100 - 10 * i[0]),
27 | noise(e.time + 10 * i[1]),
28 | noise(e.time + 100 - 10 * i[1])
29 | ).stroke(stroke);
30 |
31 | // limit number of lines and dots
32 | while (dots.childNodes.length > 60) { dots.firstChild.remove(); }
33 | while (lines.childNodes.length > 100) { lines.firstChild.remove(); }
34 | };
35 |
--------------------------------------------------------------------------------
/examples/code/examples/clip-path.js:
--------------------------------------------------------------------------------
1 | svg.size(100, 100);
2 | svg.background("#edb");
3 |
4 | // a clip-path will hide or show parts of shapes.
5 | // presence of a filled-shape indicates visibility
6 | var clip = svg.clipPath();
7 | clip.circle(70.7).origin(50, 0); // the visible portion
8 |
9 | // three circles, untouched
10 | svg.circle(0, 50, 50).fill("#e53");
11 | svg.circle(50, 50, 50).fill("#158").opacity(0.75);
12 | svg.circle(100, 50, 50).fill("#ec3");
13 |
14 | // this circle will be clipped by the clip-path
15 | svg.circle(50, 100, 70.7)
16 | .fill("#e53")
17 | .opacity(0.75)
18 | .clipPath(clip);
19 |
--------------------------------------------------------------------------------
/examples/code/examples/clock.js:
--------------------------------------------------------------------------------
1 | svg.size(-1, -1, 2, 2);
2 | svg.background("#888");
3 | var radius = svg.getWidth() * 0.48;
4 |
5 | for (var i = 0; i < 12; i += 1) {
6 | svg.text(String((i + 11) % 12 + 1))
7 | .fontFamily("Times New Roman")
8 | .fontSize(svg.getWidth() / 8)
9 | .textAnchor("middle")
10 | .setAttribute("style", "transform: rotate("+(i*30)+"deg) translate(0, -"+radius*0.75+"px)");
11 | }
12 |
13 | var pies = [
14 | svg.wedge().fill("#0008"),
15 | svg.wedge().fill("transparent"),
16 | svg.wedge().fill("#fff8")
17 | ];
18 |
19 | svg.play = function (time) {
20 | var d = new Date();
21 | var s = (d.getSeconds() + d.getMilliseconds() / 1000) / 60;
22 | var m = d.getMinutes() / 60;
23 | var h = (d.getHours() % 12) / 12;
24 | [(s), (m + s / 60), (h + m / 12 + s / 720)]
25 | .sort(function(a, b) { return a - b; })
26 | .forEach(function(a, i, arr) {
27 | var a1 = -PI / 2 + 2 * PI * a;
28 | var a2 = -PI / 2 + 2 * PI * arr[(i + 1) % arr.length];
29 | pies[i].setArc(0, 0, radius, a1, a2, true);
30 | });
31 | };
--------------------------------------------------------------------------------
/examples/code/examples/curves.js:
--------------------------------------------------------------------------------
1 | svg.size(100, 100);
2 | svg.background("white");
3 |
4 | var colors = ["#e53", "#158", "#ec3"];
5 |
6 | var pts = [];
7 | for (var i = 0; i < 8; i += 1) {
8 | pts.push([Math.random() * 100, Math.random() * 100]);
9 | }
10 |
11 | for (var i = 0; i < pts.length - 1; i += 1) {
12 | var color = colors[Math.floor(Math.random()*3)];
13 | var width = Math.random() * 12;
14 | var rand1 = Math.random() < 0.5;
15 | var rand2 = Math.random() < 0.8;
16 |
17 | svg.curve(pts[i], pts[i+1])
18 | .fill("none")
19 | .stroke(color)
20 | .strokeWidth(width)
21 | .strokeDasharray(rand2 ? "none" : Math.random() * 8 + 2)
22 | .bend(rand1 ? 0 : 0.5);
23 | }
24 |
--------------------------------------------------------------------------------
/examples/code/examples/dragon.js:
--------------------------------------------------------------------------------
1 | svg.size(600, 600);
2 | svg.background("white", true);
3 |
4 | function dragon(x1, y1, x2, y2, turn, i) {
5 | if (i < 0) { return [[x1, y1], [x2, y2]]; }
6 | var midX = x1 + (x2 - x1) * 0.5 + turn * (y2 - y1) * 0.5;
7 | var midY = y1 + (y2 - y1) * 0.5 + (-1 * turn) * (x2 - x1) * 0.5;
8 | var first = dragon(x1, y1, midX, midY, 1, i - 1);
9 | if (first.length > 1) { first.pop(); }
10 | return first.concat(dragon(midX, midY, x2, y2, -1, i - 1));
11 | }
12 |
13 | var attrs = { strokeLinecap: "square", fill: "none" };
14 |
15 | var x1 = svg.getWidth() * 0.25;
16 | var y1 = svg.getHeight() * 0.6;
17 | var x2 = svg.getWidth() * 0.85;
18 | var y2 = svg.getHeight() * 0.6;
19 |
20 | svg.polyline(dragon(x1, y1, x2, y2, 1, random(4, 8)))
21 | .setAttributes(attrs).stroke("#ec3").strokeWidth(7);
22 | svg.polyline(dragon(x1, y1, x2, y2, 1, random(10, 12)))
23 | .setAttributes(attrs).stroke("#158").strokeWidth(3);
24 |
--------------------------------------------------------------------------------
/examples/code/examples/draw.js:
--------------------------------------------------------------------------------
1 | svg.size(100, 100);
2 |
3 | // keep track of every touch
4 | var points = [];
5 |
6 | // a polygon is a filled shape
7 | // defined by an array of points
8 | var p = svg.polygon()
9 | .fillRule("evenodd")
10 | .fill("#e53")
11 | .stroke("#158");
12 | // "evenodd" is a cool effect for
13 | // when the polygon self-intersects
14 |
15 | // every time a touch moves, add a point to the array
16 | svg.onMove = function (mouse) {
17 | points.push([mouse.x, mouse.y]);
18 | // only 100 points are allowed
19 | if (points.length > 100) { points.shift(); }
20 | // update the polygon with the current set of points
21 | p.setPoints(points);
22 | };
23 |
24 | ///////////////
25 | // //
26 | // draw 🌀🖌 //
27 | // //
28 | ///////////////
29 |
--------------------------------------------------------------------------------
/examples/code/examples/hanoi.js:
--------------------------------------------------------------------------------
1 | svg.size(100, 33);
2 |
3 | const DISKS = 7;
4 | const SPEED = 4; // (int) lower is faster
5 | let steps = [];
6 | let left = Array.from(Array(DISKS))
7 | .map(function(_, i) { return DISKS-i; });
8 | const poles = [left, [], []];
9 |
10 | var resetTimer = undefined;
11 | var resetCount = 0;
12 |
13 | const resetFunc = () => {
14 | resetCount++;
15 | resetTimer = undefined;
16 | frame = 0;
17 | step = 0;
18 | steps = [];
19 | poles[0] = resetCount % 2 === 0 ? Array.from(Array(DISKS)).map((_, i) => DISKS-i) : [];
20 | poles[1] = [];
21 | poles[2] = resetCount % 2 === 0 ? [] : Array.from(Array(DISKS)).map((_, i) => DISKS-i);
22 | if (resetCount % 2 === 0) {
23 | Hanoi(DISKS, 0, 1, 2);
24 | } else {
25 | Hanoi(DISKS, 2, 1, 0);
26 | }
27 | };
28 |
29 | const Hanoi = function (disk, from, buffer, to) {
30 | if (disk <= 0) { return; }
31 | Hanoi(disk - 1, from, to, buffer);
32 | steps.push({ from, to });
33 | Hanoi(disk - 1, buffer, from, to);
34 | };
35 | Hanoi(DISKS, 0, 1, 2);
36 |
37 | const draw = function () {
38 | var thick = 4;
39 | svg.removeChildren();
40 | poles.forEach(function (pole, p) {
41 | var x = 25*(p+1);
42 | svg.line(x, 0, x, svg.getHeight()).stroke("black");
43 | pole.forEach(function (plate, i) {
44 | var w = plate * 25/DISKS;
45 | svg.rect(x-w/2, svg.getHeight()-(i+1)*thick - 0.5, w, thick)
46 | .stroke("black")
47 | .fill("hsl("+(36/DISKS*(plate-1))+",80%,45%)");
48 | });
49 | });
50 | };
51 |
52 | let frame = 0;
53 | let step = 0;
54 | svg.play = function (e) {
55 | // if (step >= steps.length) { svg.stop(); }
56 | if (step >= steps.length) {
57 | if (!resetTimer) { resetTimer = setTimeout(resetFunc, 1000); }
58 | return;
59 | }
60 | if (frame % SPEED === 0) {
61 | poles[steps[step].to].push(poles[steps[step].from].pop());
62 | draw();
63 | step++;
64 | }
65 | frame++;
66 | };
67 |
68 |
--------------------------------------------------------------------------------
/examples/code/examples/mask.js:
--------------------------------------------------------------------------------
1 | svg.size(1, 1);
2 | svg.background("#edb");
3 |
4 | // a mask will hide or show parts of shapes
5 | // depending on the contents. white: visible, black: invisible
6 | var maskA = svg.mask();
7 | var maskB = svg.mask();
8 |
9 | // a polygon from 100 connected random points
10 | var points = Array.from(Array(100))
11 | .map(() => [random(-1, 2), random(-1, 2)]);
12 |
13 | // each mask gets the same polygon
14 | // but the polygon and background color alternate
15 | // white/black and black/white
16 | maskA.polygon(points).fill("white").fillRule("evenodd");
17 | maskB.rect(1, 1).fill("white");
18 | maskB.polygon(points).fill("black").fillRule("evenodd");
19 |
20 | // two circles, assigned to different masks
21 | svg.circle(random(), random(), 0.5)
22 | .fill("black")
23 | .mask(maskA);
24 |
25 | svg.circle(random(), random(), 0.5)
26 | .fill("#e53")
27 | .mask(maskB);
28 |
--------------------------------------------------------------------------------
/examples/code/examples/parabola.js:
--------------------------------------------------------------------------------
1 | svg.size(-50, -50, 100, 100);
2 |
3 | svg.parabola().fill("#000a").rotate(0).translate(0, -30).scale(30, 60);
4 | svg.parabola().fill("#ec3a").rotate(90).translate(0, -30).scale(30, 60);
5 | svg.parabola().fill("#158a").rotate(270).translate(0, -30).scale(30, 60);
6 | svg.parabola().fill("#e53a").rotate(180).translate(0, -30).scale(30, 60);
7 |
8 | svg.rect(-30, -30, 60, 60).fill("none").stroke("black");
9 |
--------------------------------------------------------------------------------
/examples/code/examples/polygon.js:
--------------------------------------------------------------------------------
1 | svg.size(-1, -1, 2, 2);
2 | svg.background("white");
3 |
4 | // all children will inherit this style
5 | svg.stroke("black")
6 | .strokeWidth(0.001)
7 | .fill("none");
8 |
9 | // Math.tan(Math.PI/i) / Math.sin(Math.PI/i);
10 |
11 | for (var i = 3; i < 36; i += 1) {
12 | // all polygons are vertex-aligned along the +X axis
13 | // a -90 degree rotation aligns it with the Y
14 | svg.regularPolygon(i).rotate(-90);
15 | }
16 |
--------------------------------------------------------------------------------
/examples/code/examples/riley1.js:
--------------------------------------------------------------------------------
1 | svg.size(100, 100);
2 |
3 | var space = random(3, 6);
4 | var clip = svg.clipPath();
5 | clip.rect(100, 100);
6 |
7 | for (var i = 0; i < (100 + space) / space; i += 1) {
8 | let p = svg.path()
9 | .fill("none")
10 | .strokeWidth(space)
11 | .strokeLinecap("square")
12 | .stroke(i % 2 === 0 ? "#000" : "#fff")
13 | .clipPath(clip)
14 | .Move(i * space, 0);
15 |
16 | for (var j = 0; j < 10; j += 1) {
17 | var dir = (j % 2) ? 0.5 : -0.5;
18 | var controlA = [(i + dir) * space, (j + 0.333) * 10];
19 | var controlB = [(i + dir) * space, (j + 0.666) * 10];
20 | var end = [i * space, (j + 1) * 10];
21 | p.Curve(controlA, controlB, end);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/code/examples/spiral.js:
--------------------------------------------------------------------------------
1 | svg.size(-10, -10, 20, 20);
2 | svg.background('white');
3 |
4 | var phipi = Math.PI / (Math.sqrt(5) + 1) * 4;
5 |
6 | var radius = 1 / 40;
7 | var size = 1 / 30;
8 |
9 | for(var i = 1; i < 360; i += 1) {
10 | var x = i * radius;
11 | var r = Math.pow(i, 0.5) * size;
12 | svg.regularPolygon(3, x, 0, r)
13 | .rotate(i * phipi * 180 / Math.PI)
14 | .fill("#000");
15 | }
16 |
--------------------------------------------------------------------------------
/examples/code/examples/star.js:
--------------------------------------------------------------------------------
1 | var frametime = 0;
2 | var framerates = [];
3 |
4 | svg.size(-5, -5, 10, 10);
5 |
6 | svg.play = function (t) {
7 | var rate = t.time - frametime;
8 | framerates.push(rate);
9 | while(framerates.length > 60) {
10 | framerates.shift();
11 | }
12 | var avg = framerates
13 | .reduce(function(a, b) { return a+b; },0)
14 | / framerates.length;
15 | frametime = t.time;
16 |
17 | var starpoints = Array.from(Array(1000)).map(function() {
18 | return [Math.random()*0.02, Math.random()*0.02];
19 | });
20 |
21 | svg.removeChildren();
22 | svg.background("black");
23 | svg.polyline(starpoints)
24 | .stroke("white")
25 | .strokeWidth(Math.cos(t.time) + 1.5);
26 |
27 | // framerate
28 | var frameString = String((1/avg).toFixed(1));
29 | svg.text(frameString,-5,-5)
30 | .dominantBaseline("hanging")
31 | .fill("white")
32 | .fontSize(0.4)
33 | .fontWeight(900);
34 | };
--------------------------------------------------------------------------------
/examples/code/examples/ten-print.js:
--------------------------------------------------------------------------------
1 | svg.size(40, 25);
2 | svg.background("#329");
3 |
4 | // apply style to the layer and all children will inherit
5 | var layer = svg.g()
6 | .stroke("#76d")
7 | .strokeWidth("1%")
8 | .strokeLinecap("square");
9 |
10 | // a 2d-grid of either / or \
11 | // the column and row sizes are 1px
12 | for (var y = 0; y < 25; y += 1) {
13 | for (var x = 0; x < 40; x += 1) {
14 | // a random 0 or 1, and "b" will be the opposite
15 | var a = Math.random() - 0.5 > 0 ? 0 : 1;
16 | var b = a ? 0 : 1;
17 | // a diagonal line, upwards or downwards
18 | layer.line(x, y + a, x + 1, y + b);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/code/examples/test1.js:
--------------------------------------------------------------------------------
1 | var frametime = 0;
2 | var framerates = [];
3 |
4 | var testMask = function () {
5 | // svg.size(0, 0, 10, 10);
6 | svg.size(0, 0, 1, 1);
7 | // svg.size(.2, .2, .6, .6);
8 |
9 | svg.play = function (t) {
10 | // console.log(t)
11 | var rate = t.time - frametime;
12 | framerates.push(rate);
13 | while(framerates.length > 50) {
14 | framerates.shift();
15 | }
16 | const avg = framerates
17 | .reduce(function(a, b) { return a+b; },0)
18 | / framerates.length;
19 | frametime = t.time;
20 | svg.removeChildren();
21 | svg.background("white");
22 |
23 | let points = Array.from(Array(1000))
24 | .map(() => [Math.random(), Math.random()]);
25 | svg.polygon(points).fill("black");//.fillRule("evenodd");
26 |
27 | var frameString = String((1/avg).toFixed(3));
28 |
29 | svg.text(frameString,0,0.05)
30 | .fill("white")
31 | .fontSize(0.05)
32 | .fontWeight(900)
33 | .fontFamily("Helvetica");
34 | svg.text(frameString,0,0.05)
35 | .fontSize(0.05)
36 | .fontWeight(100)
37 | .fontFamily("Helvetica");
38 | };
39 | };
40 |
41 | testMask();
--------------------------------------------------------------------------------
/examples/code/examples/text.js:
--------------------------------------------------------------------------------
1 | svg.size(100, 100);
2 | svg.background("black", true);
3 |
4 | var style = {
5 | fontFamily: "avenir next, helvetica neue, arial",
6 | fontWeight: 400,
7 | fontSize: "16px",
8 | textAnchor: "middle"
9 | };
10 |
11 | for (var j = 0; j < 7; j += 1) {
12 | for (var i = 0; i < 9; i += 1) {
13 | var x = 50 + 2 - 0.5 * i;
14 | var y = 0 + j * 18 + 0.5 * i;
15 | svg.text(["los angeles", "new york"][j % 2], x, y)
16 | .setAttributes(style)
17 | .fill(`rgba(255, 0, ${150 + j * 15}, 0.25)`);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/code/images/dice.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/code/images/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/code/images/font-size.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/code/images/moon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/code/images/pause.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/code/images/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/code/images/question.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/code/images/share.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ✨SVG✨
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/code/js/queryWatcher.js:
--------------------------------------------------------------------------------
1 | const QueryWatcher = function (queryKey) {
2 | const { LZString } = window;
3 | const app = {};
4 | const url = new URL(window.location.href);
5 |
6 | const getURLQuery = function () {
7 | // do we need to call url = new URL(window.location.href);
8 | const c = url.searchParams.get(queryKey);
9 | if (c !== null) {
10 | const decoded = LZString.decompressFromEncodedURIComponent(c);
11 | if (decoded == null) {
12 | // bad decoding. user error.
13 | }
14 | return decoded;
15 | }
16 | return "";
17 | };
18 |
19 | const makeURLWithQueryValue = function (string) {
20 | const encoded = LZString.compressToEncodedURIComponent(string);
21 | return (encoded == null || string == null || string === "")
22 | ? `${url.origin}${url.pathname}`
23 | : `${url.origin}${url.pathname}?${queryKey}=${encoded}`;
24 | };
25 |
26 | const setURLQuery = function (rawText) {
27 | const newURL = makeURLWithQueryValue(rawText);
28 | window.history.replaceState(null, null, newURL);
29 | };
30 |
31 | Object.defineProperty(app, "value", {
32 | get: () => getURLQuery(),
33 | set: (newValue) => { setURLQuery(newValue); }
34 | });
35 | app.compress = (...args) => LZString.compressToEncodedURIComponent(...args);
36 | app.uncompress = (...args) => LZString.decompressFromEncodedURIComponent(...args);
37 | app.makeURLWithQueryValue = makeURLWithQueryValue;
38 |
39 | return app;
40 | };
41 |
--------------------------------------------------------------------------------
/examples/compare-with.html:
--------------------------------------------------------------------------------
1 |
6 |
7 | SVG example: introduction
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/compare-without.html:
--------------------------------------------------------------------------------
1 |
6 |
7 | SVG example: introduction
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/empty.html:
--------------------------------------------------------------------------------
1 |
2 | .
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/japanese-flag.html:
--------------------------------------------------------------------------------
1 |
2 | SVG example: Japanese flag
3 |
14 |
15 |
26 |
--------------------------------------------------------------------------------
/examples/load-file.html:
--------------------------------------------------------------------------------
1 |
2 | SVG example: load an SVG file
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/mask.html:
--------------------------------------------------------------------------------
1 |
2 | SVG example: introduction
3 |
4 |
5 |
26 |
--------------------------------------------------------------------------------
/examples/mystify.html:
--------------------------------------------------------------------------------
1 |
2 | SVG example: introduction
3 |
4 |
5 |
6 |
58 |
--------------------------------------------------------------------------------
/examples/random-walker.html:
--------------------------------------------------------------------------------
1 |
2 | SVG example: random walker
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/ten-print.html:
--------------------------------------------------------------------------------
1 |
2 | SVG example: 10PRINT
3 |
4 |
7 |
8 |
9 |
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rabbit-ear-svg",
3 | "version": "0.2.3",
4 | "description": "creative coding with SVG",
5 | "main": "svg.js",
6 | "type": "module",
7 | "sourceType": "module",
8 | "types": "./types/index.d.ts",
9 | "scripts": {
10 | "test": "vitest",
11 | "clean": "node tests/clean.js",
12 | "lineno": "node tests/lineno.js"
13 | },
14 | "author": "Robby Kraft",
15 | "license": "MIT",
16 | "repository": "https://github.com/robbykraft/SVG",
17 | "keywords": [
18 | "svg",
19 | "art",
20 | "vector",
21 | "graphics",
22 | "creative",
23 | "code",
24 | "illustrator",
25 | "animation",
26 | "script",
27 | "library"
28 | ],
29 | "devDependencies": {
30 | "@rollup/plugin-terser": "^0.4.3",
31 | "@xmldom/xmldom": "^0.8.10",
32 | "eslint": "^8.47.0",
33 | "eslint-config-airbnb-base": "^15.0.0",
34 | "eslint-plugin-import": "^2.28.1",
35 | "rollup": "^3.28.1",
36 | "rollup-plugin-cleanup": "^3.2.1",
37 | "typedoc": "^0.25.13",
38 | "typescript": "^5.4.4",
39 | "vitest": "^0.34.6"
40 | },
41 | "jest": {
42 | "collectCoverage": true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # SVG
2 |
3 | [](https://travis-ci.org/robbykraft/SVG)
4 |
5 | creative coding with SVG
6 |
7 | *easy drawing and styling, event handlers, browser or node.js*
8 |
9 | ## Examples
10 |
11 | [Code editor](https://robbykraft.github.io/SVG/examples/code/), a live code editor which includes examples (roll the dice).
12 |
13 | [Download](https://github.com/robbykraft/SVG/releases), and there are more examples in the `examples/` folder.
14 |
15 | ## Install
16 |
17 | The compiled library is one file, and works in the browser or in Node.
18 |
19 | ```
20 | https://robbykraft.github.io/SVG/svg.js
21 | ```
22 |
23 | ```
24 | npm i rabbit-ear-svg
25 | ```
26 |
27 | ## Usage
28 |
29 | Two sources of documentation:
30 |
31 | [SVG docs](https://robbykraft.github.io/SVG/docs/)
32 |
33 | [rabbit ear docs](https://rabbitear.org/book/svg.html)
34 |
35 | ## Credit
36 |
37 | - [vkBeautify](https://github.com/vkiryukhin/vkBeautify) pretty-print for SVG export
38 | - [XML DOM](https://github.com/xmldom/xmldom) for a "window" object in Node
39 |
40 | ## License
41 |
42 | MIT
43 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import cleanup from "rollup-plugin-cleanup";
2 | import terser from "@rollup/plugin-terser";
3 |
4 | const input = "src/index.js";
5 | const name = "SVG";
6 | const banner = "/* SVG (c) Kraft */";
7 |
8 | export default [{
9 | input,
10 | output: {
11 | name,
12 | file: "svg.js",
13 | format: "umd",
14 | banner,
15 | compact: true,
16 | generatedCode: {
17 | constBindings: true,
18 | objectShorthand: true,
19 | },
20 | },
21 | plugins: [cleanup(), terser()],
22 | // plugins: [cleanup()],
23 | }, {
24 | input,
25 | output: {
26 | name,
27 | file: "svg.module.js",
28 | format: "es",
29 | banner,
30 | generatedCode: {
31 | constBindings: true,
32 | objectShorthand: true,
33 | },
34 | },
35 | plugins: [cleanup()],
36 | }, {
37 | input,
38 | output: {
39 | name,
40 | dir: "module/",
41 | format: "es",
42 | banner,
43 | preserveModules: true,
44 | generatedCode: {
45 | constBindings: true,
46 | objectShorthand: true,
47 | },
48 | },
49 | }];
50 |
--------------------------------------------------------------------------------
/src/arguments/makeCoordinates.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import {
5 | str_number,
6 | str_object,
7 | } from "../environment/strings.js";
8 | /**
9 | * this will extract coordinates from a set of inputs
10 | * and present them as a stride-2 flat array. length % 2 === 0
11 | * a 1D array of numbers, alternating x y
12 | *
13 | * use flatten() everytime you call this!
14 | * it's necessary the entries sit at the top level of ...args
15 | * findCoordinates(...flatten(...args));
16 | */
17 | const makeCoordinates = (...args) => args
18 | .filter(a => typeof a === str_number)
19 | .concat(args
20 | .filter(a => typeof a === str_object && a !== null)
21 | .map((el) => {
22 | if (typeof el.x === str_number) { return [el.x, el.y]; }
23 | if (typeof el[0] === str_number) { return [el[0], el[1]]; }
24 | return undefined;
25 | }).filter(a => a !== undefined)
26 | .reduce((a, b) => a.concat(b), []));
27 | // [top-level numbers] concat [{x:,y:} and [0,1]] style
28 |
29 | export default makeCoordinates;
30 |
--------------------------------------------------------------------------------
/src/arguments/makeViewBox.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import makeCoordinates from "./makeCoordinates.js";
5 |
6 | /**
7 | * @param {number} x
8 | * @param {number} y
9 | * @param {number} width
10 | * @param {number} height
11 | * @param {number} [padding=0]
12 | * @returns {string}
13 | */
14 | const viewBoxValuesToString = function (x, y, width, height, padding = 0) {
15 | const scale = 1.0;
16 | const d = (width / scale) - width;
17 | const X = (x - d) - padding;
18 | const Y = (y - d) - padding;
19 | const W = (width + d * 2) + padding * 2;
20 | const H = (height + d * 2) + padding * 2;
21 | return [X, Y, W, H].join(" ");
22 | };
23 |
24 | /**
25 | * @returns {string | undefined}
26 | */
27 | const makeViewBox = (...args) => {
28 | const nums = makeCoordinates(...args.flat());
29 | if (nums.length === 2) { nums.unshift(0, 0); }
30 | return nums.length === 4
31 | ? viewBoxValuesToString(nums[0], nums[1], nums[2], nums[3])
32 | : undefined;
33 | };
34 |
35 | export default makeViewBox;
36 |
--------------------------------------------------------------------------------
/src/arguments/semiFlattenArrays.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import {
5 | str_function,
6 | str_string,
7 | } from "../environment/strings.js";
8 |
9 | const svgIsIterable = (obj) => obj != null
10 | && typeof obj[Symbol.iterator] === str_function;
11 |
12 | /**
13 | * @description flatten only until the point of comma separated entities.
14 | * This will preserve vectors (number[]) in an array of array of vectors.
15 | * @param {any[][]} args any array, intended to contain arrays of arrays.
16 | * @returns {array[]} a flattened copy, flattened up until the point before
17 | * combining arrays of elements.
18 | */
19 | export const svgSemiFlattenArrays = function () {
20 | switch (arguments.length) {
21 | case 0: return Array.from(arguments);
22 | // only if its an array (is iterable) and NOT a string
23 | case 1: return svgIsIterable(arguments[0]) && typeof arguments[0] !== str_string
24 | ? svgSemiFlattenArrays(...arguments[0])
25 | : [arguments[0]];
26 | default:
27 | return Array.from(arguments).map(a => (svgIsIterable(a)
28 | ? [...svgSemiFlattenArrays(a)]
29 | : a));
30 | }
31 | };
32 | export default svgSemiFlattenArrays;
33 |
--------------------------------------------------------------------------------
/src/colors/convert.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 |
5 | /**
6 | * @param {number} n
7 | */
8 | const roundF = n => Math.round(n * 100) / 100;
9 |
10 | /**
11 | * @description Convert hue-saturation-lightness values into
12 | * three RGB values, each between 0 and 1 (not 0-255).
13 | * @param {number} hue value between 0 and 360
14 | * @param {number} saturation value between 0 and 100
15 | * @param {number} lightness value between 0 and 100
16 | * @param {number | undefined} alpha the alpha component from 0 to 1
17 | * @returns {number[]} three values between 0 and 255, or four
18 | * if an alpha value is provided, where the fourth is between 0 and 1.
19 | * @linkcode Origami ./src/convert/svgParsers/colors/hexToRGB.js 10
20 | */
21 | export const hslToRgb = (hue, saturation, lightness, alpha) => {
22 | const s = saturation / 100;
23 | const l = lightness / 100;
24 | /** @param {number} n */
25 | const k = n => (n + hue / 30) % 12;
26 | const a = s * Math.min(l, 1 - l);
27 | /** @param {number} n */
28 | const f = n => (
29 | l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))
30 | );
31 | return alpha === undefined
32 | ? [f(0) * 255, f(8) * 255, f(4) * 255]
33 | : [f(0) * 255, f(8) * 255, f(4) * 255, alpha];
34 | };
35 |
36 | /**
37 | *
38 | */
39 | const mapHexNumbers = (numbers, map) => {
40 | // ensure a minimum number of characters (fill 0 if needed)
41 | const chars = Array.from(Array(map.length))
42 | .map((_, i) => numbers[i] || "0");
43 | // handle abbreviated hex codes: #fb4 or #fb48 (with alpha)
44 | return numbers.length <= 4
45 | ? map.map(i => chars[i]).join("")
46 | : chars.join("");
47 | };
48 |
49 | /**
50 | * @description Convert a hex string into an array of
51 | * three numbers, the rgb values (between 0 and 1).
52 | * This ignores any alpha values.
53 | * @param {string} string a hex color code as a string
54 | * @returns {number[]} three values between 0 and 255
55 | * @linkcode Origami ./src/convert/svgParsers/colors/hexToRGB.js 10
56 | */
57 | export const hexToRgb = (string) => {
58 | const numbers = string.replace(/#(?=\S)/g, "");
59 | const hasAlpha = numbers.length === 4 || numbers.length === 8;
60 | const hexString = hasAlpha
61 | ? mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2, 3, 3])
62 | : mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2]);
63 | const c = parseInt(hexString, 16);
64 | return hasAlpha
65 | ? [(c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, roundF((c & 255) / 256)]
66 | : [(c >> 16) & 255, (c >> 8) & 255, c & 255];
67 | };
68 |
69 | /**
70 | * @param {number} red the red component from 0 to 255
71 | * @param {number} green the green component from 0 to 255
72 | * @param {number} blue the blue component from 0 to 255
73 | * @param {number | undefined} alpha the alpha component from 0 to 1
74 | * @returns {string} hex string, with our without alpha.
75 | */
76 | export const rgbToHex = (red, green, blue, alpha) => {
77 | /** @param {number} n */
78 | const to16 = n => `00${Math.max(0, Math.min(Math.round(n), 255)).toString(16)}`
79 | .slice(-2);
80 | const hex = `#${[red, green, blue].map(to16).join("")}`;
81 | return alpha === undefined
82 | ? hex
83 | : `${hex}${to16(alpha * 255)}`;
84 | };
85 |
--------------------------------------------------------------------------------
/src/colors/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import cssColors from "./cssColors.js";
5 | import * as convert from "./convert.js";
6 | import * as parseColor from "./parseColor.js";
7 |
8 | export default {
9 | cssColors,
10 | ...convert,
11 | ...parseColor,
12 | };
13 |
--------------------------------------------------------------------------------
/src/colors/parseColor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import cssColors from "./cssColors.js";
5 | import {
6 | hexToRgb,
7 | hslToRgb,
8 | rgbToHex,
9 | } from "./convert.js";
10 |
11 | /**
12 | *
13 | */
14 | const getParenNumbers = str => {
15 | const match = str.match(/\(([^\)]+)\)/g);
16 | if (match == null || !match.length) { return []; }
17 | return match[0]
18 | .substring(1, match[0].length - 1)
19 | .split(/[\s,]+/)
20 | .map(parseFloat);
21 | };
22 |
23 | /**
24 | * @description input a color as a string and get back the RGB
25 | * values as three numbers in an array. This supports CSS/SVG
26 | * color strings like named colors, hex colors, rgb(), hsl().
27 | * @param {string} string a CSS/SVG color string in any form
28 | * @returns {number[] | undefined} red green blue values between 0 and 255,
29 | * with possible 4th value between 0 and 1.
30 | */
31 | export const parseColorToRgb = (string) => {
32 | if (cssColors[string]) { return hexToRgb(cssColors[string]); }
33 | if (string[0] === "#") { return hexToRgb(string); }
34 | if (string.substring(0, 4) === "rgba"
35 | || string.substring(0, 3) === "rgb") {
36 | const values = getParenNumbers(string);
37 | [0, 1, 2]
38 | .filter(i => values[i] === undefined)
39 | .forEach(i => { values[i] = 0; });
40 | return values;
41 | }
42 | if (string.substring(0, 4) === "hsla"
43 | || string.substring(0, 3) === "hsl") {
44 | const values = getParenNumbers(string);
45 | [0, 1, 2]
46 | .filter(i => values[i] === undefined)
47 | .forEach(i => { values[i] = 0; });
48 | return hslToRgb(values[0], values[1], values[2], values[3]);
49 | }
50 | return undefined;
51 | };
52 |
53 | /**
54 | * @description input a color as a string and return the
55 | * same color as a hex value string. This supports CSS/SVG
56 | * color strings like named colors, hex colors, rgb(), hsl().
57 | * @param {string} string a CSS/SVG color string in any form
58 | * @returns {string} a hex-color form of the input color string.
59 | */
60 | export const parseColorToHex = (string) => {
61 | if (cssColors[string]) { return cssColors[string].toUpperCase(); }
62 | // convert back and forth, this converts 3 or 4 digit hex to 6 or 8.
63 | if (string[0] === "#") {
64 | const [r, g, b, a] = hexToRgb(string);
65 | return rgbToHex(r, g, b, a);
66 | }
67 | if (string.substring(0, 4) === "rgba"
68 | || string.substring(0, 3) === "rgb") {
69 | const [r, g, b, a] = getParenNumbers(string);
70 | return rgbToHex(r, g, b, a);
71 | }
72 | if (string.substring(0, 4) === "hsla"
73 | || string.substring(0, 3) === "hsl") {
74 | const values = getParenNumbers(string);
75 | [0, 1, 2]
76 | .filter(i => values[i] === undefined)
77 | .forEach(i => { values[i] = 0; });
78 | const [h, s, l, a] = values;
79 | const [r, g, b] = hslToRgb(h, s, l, a);
80 | return rgbToHex(r, g, b, a);
81 | }
82 | return undefined;
83 | };
84 |
--------------------------------------------------------------------------------
/src/constructor/extensions/arc/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import makeArcPath from "../shared/makeArcPath.js";
5 | import { str_path } from "../../../environment/strings.js";
6 | import TransformMethods from "../shared/transforms.js";
7 |
8 | const arcArguments = (a, b, c, d, e) => [makeArcPath(a, b, c, d, e, false)];
9 |
10 | export default {
11 | arc: {
12 | nodeName: str_path,
13 | attributes: ["d"],
14 | args: arcArguments,
15 | methods: {
16 | setArc: (el, ...args) => el.setAttribute("d", arcArguments(...args)),
17 | ...TransformMethods,
18 | },
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/src/constructor/extensions/arrow/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import ArrowMethods from "./methods.js";
5 | import init from "./init.js";
6 |
7 | export default {
8 | arrow: {
9 | nodeName: "g",
10 | attributes: [],
11 | args: () => [], // one function
12 | methods: ArrowMethods, // object of functions
13 | init,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/constructor/extensions/arrow/init.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import {
5 | str_object,
6 | str_arrow,
7 | str_class,
8 | str_path,
9 | str_tail,
10 | str_head,
11 | str_style,
12 | str_stroke,
13 | str_none,
14 | } from "../../../environment/strings.js";
15 | import NS from "../../../spec/namespace.js";
16 | import window from "../../../environment/window.js";
17 | import ArrowMethods from "./methods.js";
18 | import { makeArrowOptions } from "./options.js";
19 | // import Library from "../../../library.js";
20 |
21 | const arrowKeys = Object.keys(makeArrowOptions());
22 |
23 | const matchingOptions = (...args) => {
24 | for (let a = 0; a < args.length; a += 1) {
25 | if (typeof args[a] !== str_object) { continue; }
26 | const keys = Object.keys(args[a]);
27 | for (let i = 0; i < keys.length; i += 1) {
28 | if (arrowKeys.includes(keys[i])) {
29 | return args[a];
30 | }
31 | }
32 | }
33 | return undefined;
34 | };
35 |
36 | const init = function (...args) {
37 | const element = window().document.createElementNS(NS, "g");
38 | element.setAttribute(str_class, str_arrow);
39 | const paths = ["line", str_tail, str_head].map(key => {
40 | const path = window().document.createElementNS(NS, str_path);
41 | path.setAttribute(str_class, `${str_arrow}-${key}`);
42 | element.appendChild(path);
43 | return path;
44 | });
45 | paths[0].setAttribute(str_style, "fill:none;");
46 | paths[1].setAttribute(str_stroke, str_none);
47 | paths[2].setAttribute(str_stroke, str_none);
48 | element.options = makeArrowOptions();
49 | ArrowMethods.setPoints(element, ...args);
50 | const options = matchingOptions(...args);
51 | if (options) {
52 | Object.keys(options)
53 | .filter(key => ArrowMethods[key])
54 | .forEach(key => ArrowMethods[key](element, options[key]));
55 | }
56 | return element;
57 | };
58 |
59 | export default init;
60 |
--------------------------------------------------------------------------------
/src/constructor/extensions/arrow/makeArrowPaths.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import * as S from "../../../environment/strings.js";
5 | import {
6 | svg_add2,
7 | svg_sub2,
8 | svg_scale2,
9 | svg_magnitude2,
10 | } from "../../../general/algebra.js";
11 |
12 | const ends = [S.str_tail, S.str_head];
13 | const stringifyPoint = p => p.join(",");
14 | const pointsToPath = (points) => "M" + points.map(pt => pt.join(",")).join("L") + "Z";
15 |
16 | /**
17 | * @description
18 | * @param {{
19 | * points: [number, number, number, number],
20 | * padding: number,
21 | * bend: number,
22 | * pinch: number,
23 | * }} options
24 | */
25 | const makeArrowPaths = (options) => {
26 | // throughout, tail is 0, head is 1
27 | /** @type {[[number, number], [number, number]]} */
28 | let pts = [
29 | [options.points[0] || 0, options.points[1] || 0],
30 | [options.points[2] || 0, options.points[3] || 0],
31 | ];
32 | let vector = svg_sub2(pts[1], pts[0]);
33 | let midpoint = svg_add2(pts[0], svg_scale2(vector, 0.5));
34 | // make sure arrow isn't too small
35 | const len = svg_magnitude2(vector);
36 | const minLength = ends
37 | .map(s => (options[s].visible
38 | ? (1 + options[s].padding) * options[s].height * 2.5
39 | : 0))
40 | .reduce((a, b) => a + b, 0);
41 | if (len < minLength) {
42 | // check len === exactly 0. don't compare to epsilon here
43 | /** @type {[number, number]} */
44 | const minVec = len === 0 ? [minLength, 0] : svg_scale2(vector, minLength / len);
45 | // pts = [svg_sub2, svg_add2].map(f => f(midpoint, svg_scale2(minVec, 0.5)));
46 | pts = [
47 | svg_sub2(midpoint, svg_scale2(minVec, 0.5)),
48 | svg_add2(midpoint, svg_scale2(minVec, 0.5)),
49 | ];
50 | vector = svg_sub2(pts[1], pts[0]);
51 | } else {
52 | // allow padding to be able to be applied. but still cap it at minLength
53 | // if (options.padding) {}
54 | }
55 | /** @type {[number, number]} */
56 | let perpendicular = [vector[1], -vector[0]];
57 | let bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend));
58 | const bezs = pts.map(pt => svg_sub2(bezPoint, pt));
59 | const bezsLen = bezs.map(v => svg_magnitude2(v));
60 | const bezsNorm = bezs.map((bez, i) => (bezsLen[i] === 0
61 | ? bez
62 | : svg_scale2(bez, 1 / bezsLen[i])));
63 | const vectors = bezsNorm.map(norm => svg_scale2(norm, -1));
64 | /** @type {[[number, number], [number, number]]} */
65 | const normals = [
66 | [vectors[0][1], -vectors[0][0]],
67 | [vectors[1][1], -vectors[1][0]],
68 | ];
69 | // get padding from either head/tail options or root of options
70 | const pad = ends.map((s, i) => (options[s].padding
71 | ? options[s].padding
72 | : (options.padding ? options.padding : 0.0)));
73 | const scales = ends
74 | .map((s, i) => options[s].height * (options[s].visible ? 1 : 0))
75 | .map((n, i) => n + pad[i]);
76 | // .map((s, i) => options[s].height * ((options[s].visible ? 1 : 0) + pad[i]));
77 | const arcs = pts.map((pt, i) => svg_add2(pt, svg_scale2(bezsNorm[i], scales[i])));
78 | // readjust bezier curve now that the arrow heads push inwards
79 | vector = svg_sub2(arcs[1], arcs[0]);
80 | perpendicular = [vector[1], -vector[0]];
81 | midpoint = svg_add2(arcs[0], svg_scale2(vector, 0.5));
82 | bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend));
83 | // done adjust
84 | const controls = arcs
85 | .map((arc, i) => svg_add2(arc, svg_scale2(svg_sub2(bezPoint, arc), options.pinch)))
86 | const polyPoints = ends.map((s, i) => [
87 | svg_add2(arcs[i], svg_scale2(vectors[i], options[s].height)),
88 | svg_add2(arcs[i], svg_scale2(normals[i], options[s].width / 2)),
89 | svg_add2(arcs[i], svg_scale2(normals[i], -options[s].width / 2)),
90 | ]);
91 | return {
92 | line: `M${stringifyPoint(arcs[0])}C${stringifyPoint(controls[0])},${stringifyPoint(controls[1])},${stringifyPoint(arcs[1])}`,
93 | tail: pointsToPath(polyPoints[0]),
94 | head: pointsToPath(polyPoints[1]),
95 | };
96 | };
97 |
98 | export default makeArrowPaths;
99 |
--------------------------------------------------------------------------------
/src/constructor/extensions/arrow/methods.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import * as S from "../../../environment/strings.js";
5 | import { toCamel } from "../../../general/string.js";
6 | import semiFlatten from "../../../arguments/semiFlattenArrays.js";
7 | import makeCoordinates from "../../../arguments/makeCoordinates.js";
8 | import makeArrowPaths from "./makeArrowPaths.js";
9 | import TransformMethods from "../shared/transforms.js";
10 |
11 | // end is "head" or "tail"
12 | const setArrowheadOptions = (element, options, which) => {
13 | if (typeof options === S.str_boolean) {
14 | element.options[which].visible = options;
15 | } else if (typeof options === S.str_object) {
16 | Object.assign(element.options[which], options);
17 | if (options.visible == null) {
18 | element.options[which].visible = true;
19 | }
20 | } else if (options == null) {
21 | element.options[which].visible = true;
22 | }
23 | };
24 |
25 | const setArrowStyle = (element, options = {}, which = S.str_head) => {
26 | const path = element.getElementsByClassName(`${S.str_arrow}-${which}`)[0];
27 | // find options which translate to object methods (el.stroke("red"))
28 | Object.keys(options)
29 | .map(key => ({ key, fn: path[toCamel(key)] }))
30 | .filter(el => typeof el.fn === S.str_function && el.key !== "class")
31 | .forEach(el => el.fn(options[el.key]));
32 | // find options which don't work as methods, set as attributes
33 | // Object.keys(options)
34 | // .map(key => ({ key, fn: path[toCamel(key)] }))
35 | // .filter(el => typeof el.fn !== S.str_function && el.key !== "class")
36 | // .forEach(el => path.setAttribute(el.key, options[el.key]));
37 | //
38 | // apply a class attribute (add, don't overwrite existing classes)
39 | Object.keys(options)
40 | .filter(key => key === "class")
41 | .forEach(key => path.classList.add(options[key]));
42 | };
43 |
44 | const redraw = (element) => {
45 | const paths = makeArrowPaths(element.options);
46 | Object.keys(paths)
47 | .map(path => ({
48 | path,
49 | element: element.getElementsByClassName(`${S.str_arrow}-${path}`)[0],
50 | }))
51 | .filter(el => el.element)
52 | .map(el => { el.element.setAttribute("d", paths[el.path]); return el; })
53 | .filter(el => element.options[el.path])
54 | .forEach(el => el.element.setAttribute(
55 | "visibility",
56 | element.options[el.path].visible
57 | ? "visible"
58 | : "hidden",
59 | ));
60 | return element;
61 | };
62 |
63 | const setPoints = (element, ...args) => {
64 | element.options.points = makeCoordinates(...semiFlatten(...args)).slice(0, 4);
65 | return redraw(element);
66 | };
67 |
68 | const bend = (element, amount) => {
69 | element.options.bend = amount;
70 | return redraw(element);
71 | };
72 |
73 | const pinch = (element, amount) => {
74 | element.options.pinch = amount;
75 | return redraw(element);
76 | };
77 |
78 | const padding = (element, amount) => {
79 | element.options.padding = amount;
80 | return redraw(element);
81 | };
82 |
83 | const head = (element, options) => {
84 | setArrowheadOptions(element, options, S.str_head);
85 | setArrowStyle(element, options, S.str_head);
86 | return redraw(element);
87 | };
88 |
89 | const tail = (element, options) => {
90 | setArrowheadOptions(element, options, S.str_tail);
91 | setArrowStyle(element, options, S.str_tail);
92 | return redraw(element);
93 | };
94 |
95 | const getLine = element => element.getElementsByClassName(`${S.str_arrow}-line`)[0];
96 | const getHead = element => element.getElementsByClassName(`${S.str_arrow}-${S.str_head}`)[0];
97 | const getTail = element => element.getElementsByClassName(`${S.str_arrow}-${S.str_tail}`)[0];
98 |
99 | export default {
100 | setPoints,
101 | points: setPoints,
102 | bend,
103 | pinch,
104 | padding,
105 | head,
106 | tail,
107 | getLine,
108 | getHead,
109 | getTail,
110 | ...TransformMethods,
111 | };
112 |
--------------------------------------------------------------------------------
/src/constructor/extensions/arrow/options.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | const endOptions = () => ({
5 | visible: false,
6 | width: 8,
7 | height: 10,
8 | padding: 0.0,
9 | });
10 |
11 | const makeArrowOptions = () => ({
12 | head: endOptions(),
13 | tail: endOptions(),
14 | bend: 0.0,
15 | padding: 0.0,
16 | pinch: 0.618,
17 | points: [],
18 | });
19 |
20 | export {
21 | makeArrowOptions,
22 | };
23 |
--------------------------------------------------------------------------------
/src/constructor/extensions/arrow/template.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | // const arrow = function (...args) {
5 | // const shape = window.document.createElementNS(svgNS, "g");
6 | // const tailPoly = window.document.createElementNS(svgNS, "polygon");
7 | // const headPoly = window.document.createElementNS(svgNS, "polygon");
8 | // const arrowPath = window.document.createElementNS(svgNS, "path");
9 | // tailPoly.setAttributeNS(null, "class", "svg-arrow-tail");
10 | // headPoly.setAttributeNS(null, "class", "svg-arrow-head");
11 | // arrowPath.setAttributeNS(null, "class", "svg-arrow-path");
12 | // tailPoly.setAttributeNS(null, "style", "stroke: none; pointer-events: none;");
13 | // headPoly.setAttributeNS(null, "style", "stroke: none; pointer-events: none;");
14 | // arrowPath.setAttributeNS(null, "style", "fill: none;");
15 | // shape.appendChild(arrowPath);
16 | // shape.appendChild(tailPoly);
17 | // shape.appendChild(headPoly);
18 | // shape.options = {
19 | // head: { width: 0.5, height: 2, visible: false, padding: 0.0 },
20 | // tail: { width: 0.5, height: 2, visible: false, padding: 0.0 },
21 | // curve: 0.0,
22 | // pinch: 0.618,
23 | // points: [],
24 | // };
25 | // setArrowPoints(shape, ...args);
26 | // prepare("arrow", shape);
27 | // shape.setPoints = (...a) => setArrowPoints(shape, ...a);
28 | // return shape;
29 | // };
30 |
--------------------------------------------------------------------------------
/src/constructor/extensions/circle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import makeCoordinates from "../../arguments/makeCoordinates.js";
5 | import nodes_attributes from "../../spec/nodes_attributes.js";
6 | import { svg_distance2 } from "../../general/algebra.js";
7 | import TransformMethods from "./shared/transforms.js";
8 | import URLMethods from "./shared/urls.js";
9 | import * as DOM from "./shared/dom.js";
10 |
11 | const setRadius = (el, r) => {
12 | el.setAttribute(nodes_attributes.circle[2], r);
13 | return el;
14 | };
15 |
16 | const setOrigin = (el, a, b) => {
17 | [...makeCoordinates(...[a, b].flat()).slice(0, 2)]
18 | .forEach((value, i) => el.setAttribute(nodes_attributes.circle[i], value));
19 | return el;
20 | };
21 |
22 | const fromPoints = (a, b, c, d) => [a, b, svg_distance2([a, b], [c, d])];
23 | /**
24 | * @name circle
25 | * @memberof svg
26 | * @description Draw an SVG Circle element.
27 | * @param {number} radius the radius of the circle
28 | * @param {...number|number[]} center the center of the circle
29 | * @returns {Element} an SVG node element
30 | * @linkcode SVG ./src/nodes/spec/circle.js 28
31 | */
32 | export default {
33 | circle: {
34 | args: (a, b, c, d) => {
35 | const coords = makeCoordinates(...[a, b, c, d].flat());
36 | // console.log("SVG circle coords", coords);
37 | switch (coords.length) {
38 | case 0: case 1: return [, , ...coords];
39 | case 2: case 3: return coords;
40 | // case 4
41 | default: return fromPoints(...coords);
42 | }
43 | // return makeCoordinates(...flatten(a, b, c)).slice(0, 3);
44 | },
45 | methods: {
46 | radius: setRadius,
47 | setRadius,
48 | origin: setOrigin,
49 | setOrigin,
50 | center: setOrigin,
51 | setCenter: setOrigin,
52 | position: setOrigin,
53 | setPosition: setOrigin,
54 | ...TransformMethods,
55 | ...URLMethods,
56 | ...DOM,
57 | },
58 | },
59 | };
60 |
--------------------------------------------------------------------------------
/src/constructor/extensions/curve/arguments.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import makeCoordinates from "../../../arguments/makeCoordinates.js";
5 | import makeCurvePath from "./makeCurvePath.js";
6 |
7 | const curveArguments = (...args) => [
8 | makeCurvePath(makeCoordinates(...args.flat())),
9 | ];
10 |
11 | export default curveArguments;
12 |
--------------------------------------------------------------------------------
/src/constructor/extensions/curve/getCurveEndpoints.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | const getNumbersFromPathCommand = str => str
5 | .slice(1)
6 | .split(/[, ]+/)
7 | .map(s => parseFloat(s));
8 |
9 | // this gets the parameter numbers, in an array
10 | const getCurveTos = d => d
11 | .match(/[Cc][(0-9), .-]+/)
12 | .map(curve => getNumbersFromPathCommand(curve));
13 |
14 | const getMoveTos = d => d
15 | .match(/[Mm][(0-9), .-]+/)
16 | .map(curve => getNumbersFromPathCommand(curve));
17 |
18 | const getCurveEndpoints = (d) => {
19 | // get only the first Move and Curve commands
20 | const move = getMoveTos(d).shift();
21 | const curve = getCurveTos(d).shift();
22 | const start = move
23 | ? [move[move.length - 2], move[move.length - 1]]
24 | : [0, 0];
25 | const end = curve
26 | ? [curve[curve.length - 2], curve[curve.length - 1]]
27 | : [0, 0];
28 | return [...start, ...end];
29 | };
30 |
31 | export default getCurveEndpoints;
32 |
--------------------------------------------------------------------------------
/src/constructor/extensions/curve/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import args from "./arguments.js";
5 | import curve_methods from "./methods.js";
6 | import { str_path } from "../../../environment/strings.js";
7 |
8 | export default {
9 | curve: {
10 | nodeName: str_path,
11 | attributes: ["d"],
12 | args, // one function
13 | methods: curve_methods, // object of functions
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/constructor/extensions/curve/makeCurvePath.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import { svg_add2, svg_sub2, svg_scale2 } from "../../../general/algebra.js";
5 |
6 | // endpoints is an array of 4 numbers
7 | const makeCurvePath = (endpoints = [], bend = 0, pinch = 0.5) => {
8 | /** @type {[number, number]} */
9 | const tailPt = [endpoints[0] || 0, endpoints[1] || 0];
10 | /** @type {[number, number]} */
11 | const headPt = [endpoints[2] || 0, endpoints[3] || 0];
12 | const vector = svg_sub2(headPt, tailPt);
13 | const midpoint = svg_add2(tailPt, svg_scale2(vector, 0.5));
14 | /** @type {[number, number]} */
15 | const perpendicular = [vector[1], -vector[0]];
16 | const bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, bend));
17 | const tailControl = svg_add2(tailPt, svg_scale2(svg_sub2(bezPoint, tailPt), pinch));
18 | const headControl = svg_add2(headPt, svg_scale2(svg_sub2(bezPoint, headPt), pinch));
19 | return `M${tailPt[0]},${tailPt[1]}C${tailControl[0]},${tailControl[1]} ${headControl[0]},${headControl[1]} ${headPt[0]},${headPt[1]}`;
20 | };
21 |
22 | export default makeCurvePath;
23 |
--------------------------------------------------------------------------------
/src/constructor/extensions/curve/methods.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import makeCoordinates from "../../../arguments/makeCoordinates.js";
5 | import makeCurvePath from "./makeCurvePath.js";
6 | import getCurveEndpoints from "./getCurveEndpoints.js";
7 | import TransformMethods from "../shared/transforms.js";
8 |
9 | const setPoints = (element, ...args) => {
10 | const coords = makeCoordinates(...args.flat()).slice(0, 4);
11 | element.setAttribute("d", makeCurvePath(coords, element._bend, element._pinch));
12 | return element;
13 | };
14 |
15 | const bend = (element, amount) => {
16 | element._bend = amount;
17 | return setPoints(element, ...getCurveEndpoints(element.getAttribute("d")));
18 | };
19 |
20 | const pinch = (element, amount) => {
21 | element._pinch = amount;
22 | return setPoints(element, ...getCurveEndpoints(element.getAttribute("d")));
23 | };
24 |
25 | export default {
26 | setPoints,
27 | bend,
28 | pinch,
29 | ...TransformMethods,
30 | };
31 |
--------------------------------------------------------------------------------
/src/constructor/extensions/ellipse.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import makeCoordinates from "../../arguments/makeCoordinates.js";
5 | import nodes_attributes from "../../spec/nodes_attributes.js";
6 | import TransformMethods from "./shared/transforms.js";
7 | import URLMethods from "./shared/urls.js";
8 | import * as DOM from "./shared/dom.js";
9 |
10 | // const setRadii = (el, rx, ry) => [,,rx,ry]
11 | // .forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value));
12 | const setRadii = (el, rx, ry) => {
13 | [, , rx, ry].forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value));
14 | return el;
15 | };
16 |
17 | const setOrigin = (el, a, b) => {
18 | [...makeCoordinates(...[a, b].flat()).slice(0, 2)]
19 | .forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value));
20 | return el;
21 | };
22 |
23 | export default {
24 | ellipse: {
25 | args: (a, b, c, d) => {
26 | const coords = makeCoordinates(...[a, b, c, d].flat()).slice(0, 4);
27 | switch (coords.length) {
28 | case 0: case 1: case 2: return [, , ...coords];
29 | default: return coords;
30 | }
31 | },
32 | methods: {
33 | radius: setRadii,
34 | setRadius: setRadii,
35 | origin: setOrigin,
36 | setOrigin,
37 | center: setOrigin,
38 | setCenter: setOrigin,
39 | position: setOrigin,
40 | setPosition: setOrigin,
41 | ...TransformMethods,
42 | ...URLMethods,
43 | ...DOM,
44 | },
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/src/constructor/extensions/g.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import svgNS from "../../spec/namespace.js";
5 | import window from "../../environment/window.js";
6 | // import * as S from "../../environment/strings.js";
7 | // import { sync } from "../../file/load.js";
8 | // import { moveChildren } from "../../methods/dom.js";
9 | import TransformMethods from "./shared/transforms.js";
10 | import URLMethods from "./shared/urls.js";
11 | import * as DOM from "./shared/dom.js";
12 |
13 | const loadGroup = (group, ...sources) => {
14 | // const elements = sources.map(source => sync(source))
15 | // .filter(a => a !== undefined);
16 | // elements.filter(element => element.tagName === S.str_svg)
17 | // .forEach(element => moveChildren(group, element));
18 | // elements.filter(element => element.tagName !== S.str_svg)
19 | // .forEach(element => group.appendChild(element));
20 | return group;
21 | };
22 |
23 | const init = (...sources) => {
24 | const group = window().document.createElementNS(svgNS, "g");
25 | return loadGroup(group, ...sources);
26 | };
27 |
28 | export default {
29 | g: {
30 | // init,
31 | methods: {
32 | // load: loadGroup,
33 | ...TransformMethods,
34 | ...URLMethods,
35 | ...DOM,
36 | },
37 | },
38 | };
39 |
--------------------------------------------------------------------------------
/src/constructor/extensions/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import svgDef from "./svg/index.js";
5 | import gDef from "./g.js";
6 | import circleDef from "./circle.js";
7 | import ellipseDef from "./ellipse.js";
8 | import lineDef from "./line.js";
9 | import pathDef from "./path.js";
10 | import rectDef from "./rect.js";
11 | import styleDef from "./style.js";
12 | import textDef from "./text.js";
13 | // multiple nodes in one
14 | import maskTypes from "./maskTypes.js";
15 | import polyDefs from "./polys.js";
16 | // extensions
17 | import arcDef from "./arc/index.js";
18 | import arrowDef from "./arrow/index.js";
19 | import curveDef from "./curve/index.js";
20 | import wedgeDef from "./wedge/index.js";
21 | import origamiDef from "./origami/index.js";
22 | /**
23 | * in each of these instances, arguments maps the arguments to attributes
24 | * as the attributes are listed in the "attributes" folder.
25 | *
26 | * arguments: function. this should convert the array of arguments into
27 | * an array of (processed) arguments. 1:1. arguments into arguments.
28 | * make sure it is returning an array.
29 | *
30 | */
31 | export default {
32 | ...svgDef,
33 | ...gDef,
34 | ...circleDef,
35 | ...ellipseDef,
36 | ...lineDef,
37 | ...pathDef,
38 | ...rectDef,
39 | ...styleDef,
40 | ...textDef,
41 | // multiple
42 | ...maskTypes,
43 | ...polyDefs,
44 | // extensions
45 | ...arcDef,
46 | ...arrowDef,
47 | ...curveDef,
48 | ...wedgeDef,
49 | ...origamiDef,
50 | };
51 |
--------------------------------------------------------------------------------
/src/constructor/extensions/line.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import semiFlattenArrays from "../../arguments/semiFlattenArrays.js";
5 | import makeCoordinates from "../../arguments/makeCoordinates.js";
6 | import nodes_attributes from "../../spec/nodes_attributes.js";
7 | import TransformMethods from "./shared/transforms.js";
8 | import URLMethods from "./shared/urls.js";
9 | import * as DOM from "./shared/dom.js";
10 |
11 | const Args = (...args) => makeCoordinates(...semiFlattenArrays(...args)).slice(0, 4);
12 |
13 | const setPoints = (element, ...args) => {
14 | Args(...args).forEach((value, i) => element.setAttribute(nodes_attributes.line[i], value));
15 | return element;
16 | };
17 | /**
18 | * @name line
19 | * @description SVG Line element
20 | * @memberof SVG
21 | * @linkcode SVG ./src/nodes/spec/line.js 18
22 | */
23 | export default {
24 | line: {
25 | args: Args,
26 | methods: {
27 | setPoints,
28 | ...TransformMethods,
29 | ...URLMethods,
30 | ...DOM,
31 | },
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/src/constructor/extensions/maskTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import * as S from "../../environment/strings.js";
5 | import { makeUUID } from "../../general/string.js";
6 | import { setViewBox } from "../../general/viewBox.js";
7 | import TransformMethods from "./shared/transforms.js";
8 | import URLMethods from "./shared/urls.js";
9 | import * as DOM from "./shared/dom.js";
10 |
11 | const makeIDString = function () {
12 | return Array.from(arguments)
13 | .filter(a => typeof a === S.str_string || a instanceof String)
14 | .shift() || makeUUID();
15 | };
16 |
17 | const maskArgs = (...args) => [makeIDString(...args)];
18 |
19 | export default {
20 | mask: {
21 | args: maskArgs,
22 | methods: {
23 | ...TransformMethods,
24 | ...URLMethods,
25 | ...DOM,
26 | },
27 | },
28 | clipPath: {
29 | args: maskArgs,
30 | methods: {
31 | ...TransformMethods,
32 | ...URLMethods,
33 | ...DOM,
34 | },
35 | },
36 | symbol: {
37 | args: maskArgs,
38 | methods: {
39 | ...TransformMethods,
40 | ...URLMethods,
41 | ...DOM,
42 | },
43 | },
44 | marker: {
45 | args: maskArgs,
46 | methods: {
47 | size: setViewBox,
48 | setViewBox: setViewBox,
49 | ...TransformMethods,
50 | ...URLMethods,
51 | ...DOM,
52 | },
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/src/constructor/extensions/origami/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import init from "./init.js";
5 | import methods from "./methods.js";
6 |
7 | export default {
8 | origami: {
9 | nodeName: "g",
10 | init,
11 | args: () => [],
12 | methods,
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/constructor/extensions/origami/init.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | import NS from "../../../spec/namespace.js";
5 | import window from "../../../environment/window.js";
6 | import lib from "../../../environment/lib.js";
7 |
8 | const init = (graph, ...args) => {
9 | const g = window().document.createElementNS(NS, "g");
10 | lib.ear.convert.foldToSvg.render(graph, g, ...args);
11 | return g;
12 | };
13 |
14 | export default init;
15 |
--------------------------------------------------------------------------------
/src/constructor/extensions/origami/methods.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Rabbit Ear (c) Kraft
3 | */
4 | // import window from "../../../environment/window.js";
5 | // import NS from "../../../spec/namespace.js";
6 | // import lib from "../../../environment/lib.js";
7 | import TransformMethods from "../shared/transforms.js";
8 | import URLMethods from "../shared/urls.js";
9 | import * as DOM from "../shared/dom.js";
10 |
11 | // const clearSVG = (element) => {
12 | // Array.from(element.attributes)
13 | // .filter(attr => attr.name !== "xmlns" && attr.name !== "version")
14 | // .forEach(attr => element.removeAttribute(attr.name));
15 | // return DOM.removeChildren(element);
16 | // };
17 |
18 | // const vertices = (...args) => {
19 | // lib.ear.convert.foldToSvg.vertices(...args);
20 | // const g = window().document.createElementNS(NS, "g");
21 | // lib.ear.convert.foldToSvg.drawInto(g, ...args);
22 | // return g;
23 | // };
24 |
25 | // const edges = (...args) => {
26 | // console.log("edges");
27 | // };
28 |
29 | // const faces = (...args) => {
30 | // console.log("faces");
31 | // };
32 |
33 | // these will end up as methods on the