Compare commits

..

No commits in common. "main" and "feature-human-alt-text" have entirely different histories.

10 changed files with 40 additions and 511 deletions

View File

@ -1,136 +0,0 @@
255 250 250 snow
248 248 255 ghost white
245 245 245 white smoke
220 220 220 gainsboro
255 250 240 floral white
253 245 230 old lace
250 240 230 linen
250 235 215 antique white
255 239 213 papaya whip
255 235 205 blanched almond
255 228 196 bisque
255 218 185 peach puff
255 222 173 navajo white
255 228 181 moccasin
255 248 220 cornsilk
255 255 240 ivory
255 250 205 lemon chiffon
255 245 238 seashell
240 255 240 honeydew
245 255 250 mint cream
240 255 255 azure
240 248 255 alice blue
230 230 250 lavender
255 240 245 lavender blush
255 228 225 misty rose
255 255 255 white
0 0 0 black
47 79 79 dark slate grey
105 105 105 dim grey
112 128 144 slate grey
119 136 153 light slate grey
190 190 190 grey
211 211 211 light grey
25 25 112 midnight blue
0 0 128 navy
0 0 128 navy blue
100 149 237 cornflower blue
72 61 139 dark slate blue
106 90 205 slate blue
123 104 238 medium slate blue
132 112 255 light slate blue
0 0 205 medium blue
65 105 225 royal blue
0 0 255 blue
30 144 255 dodger blue
0 191 255 deep sky blue
135 206 235 sky blue
135 206 250 light sky blue
70 130 180 steel blue
176 196 222 light steel blue
173 216 230 light blue
176 224 230 powder blue
175 238 238 pale turquoise
0 206 209 dark turquoise
72 209 204 medium turquoise
64 224 208 turquoise
0 255 255 cyan
224 255 255 light cyan
95 158 160 cadet blue
102 205 170 medium aquamarine
127 255 212 aquamarine
0 100 0 dark green
85 107 47 dark olive green
143 188 143 dark sea green
46 139 87 sea green
60 179 113 medium sea green
32 178 170 light sea green
152 251 152 pale green
0 255 127 spring green
124 252 0 lawn green
0 255 0 green
127 255 0 chartreuse
0 250 154 medium spring green
173 255 47 green yellow
50 205 50 lime green
154 205 50 yellow green
34 139 34 forest green
107 142 35 olive drab
189 183 107 dark khaki
240 230 140 khaki
238 232 170 pale goldenrod
250 250 210 light goldenrod yellow
255 255 224 light yellow
255 255 0 yellow
255 215 0 gold
238 221 130 light goldenrod
218 165 32 goldenrod
184 134 11 dark goldenrod
188 143 143 rosy brown
205 92 92 indian red
139 69 19 saddle brown
160 82 45 sienna
205 133 63 peru
222 184 135 burlywood
245 245 220 beige
245 222 179 wheat
244 164 96 sandy brown
210 180 140 tan
210 105 30 chocolate
178 34 34 firebrick
165 42 42 brown
233 150 122 dark salmon
250 128 114 salmon
255 160 122 light salmon
255 165 0 orange
255 140 0 dark orange
255 127 80 coral
240 128 128 light coral
255 99 71 tomato
255 69 0 orange red
255 0 0 red
255 105 180 hot pink
255 20 147 deep pink
255 192 203 pink
255 182 193 light pink
219 112 147 pale violet red
176 48 96 maroon
199 21 133 medium violet red
208 32 144 violet red
255 0 255 magenta
238 130 238 violet
221 160 221 plum
218 112 214 orchid
186 85 211 medium orchid
153 50 204 dark orchid
148 0 211 dark violet
138 43 226 blue violet
160 32 240 purple
147 112 219 medium purple
216 191 216 thistle
169 169 169 dark grey
0 0 139 dark blue
0 139 139 dark cyan
139 0 139 dark magenta
139 0 0 dark red
144 238 144 light green

View File

@ -1,101 +0,0 @@
255 255 255 white
0 0 0 black
3 3 3 99% grey
5 5 5 98% grey
8 8 8 97% grey
10 10 10 96% grey
13 13 13 95% grey
15 15 15 94% grey
18 18 18 93% grey
20 20 20 92% grey
23 23 23 91% grey
26 26 26 90% grey
28 28 28 89% grey
31 31 31 88% grey
33 33 33 87% grey
36 36 36 86% grey
38 38 38 85% grey
41 41 41 84% grey
43 43 43 83% grey
46 46 46 82% grey
48 48 48 81% grey
51 51 51 80% grey
54 54 54 79% grey
56 56 56 78% grey
59 59 59 77% grey
61 61 61 76% grey
64 64 64 75% grey
66 66 66 74% grey
69 69 69 73% grey
71 71 71 72% grey
74 74 74 71% grey
77 77 77 70% grey
79 79 79 69% grey
82 82 82 68% grey
84 84 84 67% grey
87 87 87 66% grey
89 89 89 65% grey
92 92 92 64% grey
94 94 94 63% grey
97 97 97 62% grey
99 99 99 61% grey
102 102 102 60% grey
105 105 105 59% grey
107 107 107 58% grey
110 110 110 57% grey
112 112 112 56% grey
115 115 115 55% grey
117 117 117 54% grey
120 120 120 53% grey
122 122 122 52% grey
125 125 125 51% grey
127 127 127 50% grey
130 130 130 49% grey
133 133 133 48% grey
135 135 135 47% grey
138 138 138 46% grey
140 140 140 45% grey
143 143 143 44% grey
145 145 145 43% grey
148 148 148 42% grey
150 150 150 41% grey
153 153 153 40% grey
156 156 156 39% grey
158 158 158 38% grey
161 161 161 37% grey
163 163 163 36% grey
166 166 166 35% grey
168 168 168 34% grey
171 171 171 33% grey
173 173 173 32% grey
176 176 176 31% grey
179 179 179 30% grey
181 181 181 29% grey
184 184 184 28% grey
186 186 186 27% grey
189 189 189 26% grey
191 191 191 25% grey
194 194 194 24% grey
196 196 196 23% grey
199 199 199 22% grey
201 201 201 21% grey
204 204 204 20% grey
207 207 207 19% grey
209 209 209 18% grey
212 212 212 17% grey
214 214 214 16% grey
217 217 217 15% grey
219 219 219 14% grey
222 222 222 13% grey
224 224 224 12% grey
227 227 227 11% grey
229 229 229 10% grey
232 232 232 9% grey
235 235 235 8% grey
237 237 237 7% grey
240 240 240 6% grey
242 242 242 5% grey
245 245 245 4% grey
247 247 247 3% grey
250 250 250 2% grey
252 252 252 1% grey

116
package-lock.json generated
View File

@ -7,12 +7,11 @@
"dependencies": {
"@observablehq/framework": "^1.13.0",
"@resvg/resvg-js": "^2.6.2",
"await-spawn": "^4.0.2",
"d3": "^7.9.0",
"d3-color": "^3.1.0",
"d3-color-difference": "^0.1.3",
"d3-dsv": "^3.0.1",
"d3-time-format": "^4.1.0",
"hex-color-to-color-name": "^1.0.2",
"jsdom": "^26.0.0",
"lodash.shuffle": "^4.2.0",
"random": "^5.1.1",
@ -1444,17 +1443,6 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/await-spawn": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/await-spawn/-/await-spawn-4.0.2.tgz",
"integrity": "sha512-GdADmeLJiMvGKJD3xWBcX40DMn07JNH1sqJYgYJZH7NTGJ3B1qDjKBKzxhhyR1hjIcnUGFUmE/+4D1HcHAJBAA==",
"dependencies": {
"bl": "^4.0.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/b4a": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
@ -1471,25 +1459,6 @@
"integrity": "sha512-Bw2PgKSrZ3uCuSV9WQ998c/GTJTd+9bWj97n7aDQMP8dP/exAZQlJeswPty0ISy+HZD+9Ex+C7CCnc9Q5QJFmQ==",
"optional": true
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bidi-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
@ -1498,29 +1467,6 @@
"require-from-string": "^2.0.2"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/bl/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -1529,29 +1475,6 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/bundle-name": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
@ -1843,19 +1766,6 @@
"node": ">=12"
}
},
"node_modules/d3-color-difference": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/d3-color-difference/-/d3-color-difference-0.1.3.tgz",
"integrity": "sha512-yAGiVdTZR/wpI66n85xvkTvjxFth0IuGrEeX/anl1Q5rzNc2/V7oOjoJdqQnahOuS+SgbAR0zu8T0SYY7hGTtw==",
"dependencies": {
"d3-color": "^1.1.0"
}
},
"node_modules/d3-color-difference/node_modules/d3-color": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
"integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
},
"node_modules/d3-contour": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
@ -2572,6 +2482,11 @@
"he": "bin/he"
}
},
"node_modules/hex-color-to-color-name": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hex-color-to-color-name/-/hex-color-to-color-name-1.0.2.tgz",
"integrity": "sha512-YKPBFTSbYIHH8YKcJB4Q5PV+Tr+kvDXpV60BcPMUu5CSZUcc/qOOx7lkr7luk6MSXKd5A82yfPGZTgedIdQ+aA=="
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
@ -2649,25 +2564,6 @@
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",

View File

@ -11,12 +11,11 @@
"dependencies": {
"@observablehq/framework": "^1.13.0",
"@resvg/resvg-js": "^2.6.2",
"await-spawn": "^4.0.2",
"d3": "^7.9.0",
"d3-color": "^3.1.0",
"d3-color-difference": "^0.1.3",
"d3-dsv": "^3.0.1",
"d3-time-format": "^4.1.0",
"hex-color-to-color-name": "^1.0.2",
"jsdom": "^26.0.0",
"lodash.shuffle": "^4.2.0",
"random": "^5.1.1",

View File

@ -4,22 +4,22 @@ import { JSDOM } from "jsdom";
import * as d3 from "d3";
import yargs from "yargs/yargs";
import { hideBin } from "yargs/helpers";
import spawn from "await-spawn";
import random from "random";
const xmlns = "http://www.w3.org/2000/xmlns/";
const xlinkns = "http://www.w3.org/1999/xlink";
const svgns = "http://www.w3.org/2000/svg";
import {RADIUS_OPTS, RADIUS_DESC, DotMaker} from './src/components/dots.js';
import {RADIUS_OPTS, DotMaker} from './src/components/dots.js';
import {PALETTES} from './src/components/palettes.js';
import {ColourNamer} from './src/components/colour_namer.js';
import { GetColorName } from "hex-color-to-color-name";
const CELL = 10;
const MAG = 2;
const WIDTH = 20;
const HEIGHT = WIDTH;
const VISIBLE_DOG = 1000;
function randomise_params() {
const palette_name = random.choice(Array.from(PALETTES.keys()));
@ -40,67 +40,19 @@ function randomise_params() {
}
}
function colour_to_text(d3color) {
const rawname = GetColorName(d3color.formatHex()).toLowerCase();
// some return values are things like "cyan / aqua": take the first
const parts = rawname.split(/\//);
return parts[0].trim();
}
// lol the best way I found to to this was imagemagick!
function image_description(params) {
const bgc = colour_to_text(params.background);
const dotc = params.patterns.map((p) => colour_to_text(p.colour));
const a = bgc.match(/^[aeiou]/) ? 'an' : 'a';
return `A pattern of ${dotc[0]} and ${dotc[1]} dots on ${a} ${bgc} background`;
async function get_histogram(imgfile) {
try {
const bl = await spawn('convert', [ imgfile, '-format', '%c', 'histogram:info:' ]);
return parse_histogram(bl.toString());
} catch (e) {
console.log(e);
}
}
function parse_histogram(convert_out) {
const colour_re = /(\d+): \(\d+,\d+,\d+,\d+\) (#[A-F0-9]+) /;
const colours = new Map();
convert_out.split("\n").forEach((l) => {
const m = l.match(colour_re);
if( m ) {
const colour = m[2].substring(0, 7);
colours.set(colour, Number(m[1]));
}
});
return new Map([...colours].sort((a, b) => b[1] - a[1]));
}
function colour_visible(hist, colour) {
const hexcolour = colour.formatHex().toUpperCase();
return hist.get(hexcolour) > VISIBLE_DOG;
}
function image_description(namer, params, histogram) {
const colours = [
params.background,
params.patterns[0].colour,
params.patterns[1].colour,
];
const i_vis = [0, 1, 2].filter((i) => colour_visible(histogram, colours[i]));
const named_colours = i_vis.map((i) => namer.colour_to_text(colours[i]));
const gradients = i_vis.map((i) => {
if( i > 0 ) {
return RADIUS_DESC[params.patterns[i - 1].f];
} else {
return '';
}
});
if( named_colours.length == 1 ) {
return `A solid field of ${named_colours[0]}`;
}
if( named_colours.length > 1 ) {
const bgc = named_colours[0];
const a = bgc.match(/^[aeiou]/) ? 'an' : 'a';
const dot_desc = named_colours.slice(1);
const gs = gradients.slice(1);
const pattern_desc = dot_desc.map((d, i) => `${d} dots ${gs[i]}`);
const patterns = pattern_desc.join(' and ');
return `A pattern of ${patterns} on ${a} ${bgc} background`;
}
return "";
}
@ -125,7 +77,7 @@ function poptimal_svg(params) {
params.patterns.map((p) => {
const dots = dm.dots(1 / p.m, p.n, false);
const dots = dm.dots(1 / p.m, p.n);
const dots_g = svg.append("g")
.attr("id", `dots${p.i}`);
@ -177,31 +129,25 @@ async function post_image(image, alt_text, cf) {
body: JSON.stringify({ media_ids: [ media_id ] })
});
const bodyjson2 = await resp2.text();
console.log(bodyjson2);
}
async function main() {
const argv = yargs(hideBin(process.argv))
.usage("Usage: -s SIZE -o output.png -c config.json")
.default('s', 1200)
.default('c', 'config.json').argv;
.default('g', 'config.json').argv;
const cfjson = await promises.readFile(argv.c);
const cfjson = await promises.readFile(argv.g);
const cf = JSON.parse(cfjson);
const fn = argv.o || String(Date.now()) + '.png';
const imgfile = cf['working_dir'] + '/' + fn;
const params = randomise_params();
const colourf = params.palette === 'grayscale' ? cf['grayscale'] : cf['colour'];
const namer = new ColourNamer();
await namer.load_colours(colourf);
const alt_text = image_description(params);
const svg = poptimal_svg(params);
const opts = {
@ -216,16 +162,9 @@ async function main() {
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
await promises.writeFile(imgfile, pngBuffer);
// generate the alt_text last to check the image file histogram
// so we don't include obscured colours
const hist = await get_histogram(imgfile);
const alt_text = image_description(namer, params, hist);
console.log(alt_text);
console.log(imgfile);
console.log(alt_text);
if( cf['base_url'] ) {
await post_image(imgfile, alt_text, cf);
}
@ -233,4 +172,4 @@ async function main() {
main();
main();

View File

@ -1,39 +0,0 @@
import { promises } from "fs";
import * as d3 from "d3-color";
import * as d3cd from "d3-color-difference";
class ColourNamer {
constructor() {
this.colmap = new Map();
}
async load_colours(colourfile) {
const colourBuff = await promises.readFile(colourfile);
const colours = colourBuff.toString();
const seen = new Set();
colours.split(/\n/).map((l) => {
const m = l.match(/(\d+)\s+(\d+)\s+(\d+)\s+(.*)/);
if( m ) {
const sig = `${m[1]},${m[2]},${m[3]}`;
if( !seen.has(sig) ) {
this.colmap.set(m[4], d3.rgb(m[1], m[2], m[3]));
seen.add(sig);
}
}
});
}
colour_to_text(d3color) {
const diffs = []
for( const [ name, colour] of this.colmap) {
diffs.push({name: name, dist: d3cd.differenceCie76(colour, d3color)});
}
diffs.sort((a, b) => a.dist - b.dist);
return diffs[0].name;
}
}
export { ColourNamer}

View File

@ -16,21 +16,6 @@ const RADIUS_OPTS = [
"noise",
];
const RADIUS_DESC = {
"const": "of constant size",
"right": "getting larger towards the right",
"left": "getting larger towards the left",
"up": "getting larger towards the top",
"down": "getting larger towards the bottom",
"right-up": "getting larger towards the upper right",
"right-down": "getting larger towards the lower right",
"left-up": "getting larger towards the upper left",
"left-down": "getting larger towards the lower left",
"in": "getting larger in the centre",
"out": "getting larger at the edges",
"noise": "of random sizes",
}
function distance(dx, dy) {
return Math.sqrt(dx ** 2 + dy ** 2);
}
@ -38,8 +23,8 @@ function distance(dx, dy) {
function int_range(v1, v2) {
const vs = [v1, v2];
vs.sort((a, b) => a - b);
const low = Math.floor(vs[0] - 1);
const high = Math.ceil(vs[1] + 1);
const low = Math.floor(vs[0]);
const high = Math.ceil(vs[1]);
return [...Array(high - low + 1).keys()].map((i) => i + low);
}
@ -50,7 +35,7 @@ class DotMaker {
this.cy = 0.5 * width;
}
dots(m, n, clip) {
dots(m, n) {
if( m - n === 0 ) {
return [];
}
@ -61,7 +46,7 @@ class DotMaker {
js.map((j) => {
const x = (j - m * i) / (m - n);
const y = m * (x + i);
if( !clip || (x > 0 && y > 0 && x < this.width && y < this.width) ) {
if( x > 0 && y > 0 && x < this.width && y < this.width ) {
ps.push({i:i, j:j, x:x, y:y});
}
});
@ -107,6 +92,6 @@ class DotMaker {
}
}
export { RADIUS_OPTS, RADIUS_DESC, DotMaker };
export { RADIUS_OPTS, DotMaker };

View File

@ -73,7 +73,7 @@ export async function download_as_png (svg) {
const opts = {
fitTo: {
mode: 'width', // If you need to change the size
value: 1200,
value: 400,
}
};
const resvgJS = new resvg.Resvg(svgstr, opts)

View File

@ -3,7 +3,6 @@ import * as d3 from "d3-color";
import shuffle from "lodash.shuffle";
import random from "random";
const PALETTES = new Map([
[ "random RGB", palette_random ],
[ "grayscale", palette_grayscale ],
@ -76,6 +75,4 @@ function palette_cmy() {
return shuffle(cols);
}
export { PALETTES }

View File

@ -8,7 +8,7 @@ toc: false
colourful generative patterns using [d3](https://d3js.org/) and [Observable Framework](https://observablehq.com/framework/)
<p>v1.1.2 | by <a href="https://mikelynch.org">mike lynch</a> | <a href="https://aus.social/@mikelynch">@mikelynch@aus.social</a> | <a href="https://git.tilde.town/bombinans/poptimal">source</a></p>
<p>v1.1.1 | by <a href="https://mikelynch.org">mike lynch</a> | <a href="https://aus.social/@mikelynch">@mikelynch@aus.social</a> | <a href="https://git.tilde.town/bombinans/poptimal">source</a></p>
<div class="grid grid-cols-2">
@ -118,8 +118,8 @@ if( palette_fn ) {
```js
const dots1 = dm.dots(1 / m1, n1, false);
const dots2 = dm.dots(1 / m2, n2, false);
const dots1 = dm.dots(1 / m1, n1);
const dots2 = dm.dots(1 / m2, n2);
```
@ -134,15 +134,6 @@ const svg = d3.create("svg")
.attr("viewBox", [ 0, 0, WIDTH, HEIGHT ]);
svg.append("clipPath")
.attr("id", "clipRect")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", WIDTH)
.attr("height", HEIGHT);
// re transitions: they should only run when updating the palette and
// grid, not via the sliders
@ -167,8 +158,7 @@ bg_g.selectAll("rect")
const dots_g1 = svg.append("g")
.attr("id", "dots1")
.attr("clip-path", "url(#clipRect)");
.attr("id", "dots1");
dots_g1.selectAll("circle")
.data(dots1)
@ -178,8 +168,7 @@ dots_g1.selectAll("circle")
.attr("fill", fg1);
const dots_g2 = svg.append("g")
.attr("id", "dots2")
.attr("clip-path", "url(#clipRect)");
.attr("id", "dots2");
dots_g2.selectAll("circle")
.data(dots2)