Compare commits

..

No commits in common. "main" and "feature-bot-script" have entirely different histories.

5 changed files with 62 additions and 55 deletions

View File

@ -1,9 +1,5 @@
# CHANGELOG.md # CHANGELOG.md
## v1.1.1
Made the flashing transitions a bit better, but it still needs more work
## v1.1.0 ## v1.1.0
Added SVG and PNG downloads Added SVG and PNG downloads

View File

@ -3,11 +3,7 @@
An experiment in using Observable to generate colourful SVG patterns An experiment in using Observable to generate colourful SVG patterns
reminiscent of op art or halftone dots. reminiscent of op art or halftone dots.
You can play with it [here.](https://etc.mikelynch.org/poptimal/) Comments, feedback via Mastodon: [Mike Lynch](https://aus.social/@mikelynch)
![Two overlapping grids of purple and lime dots on a teal background](https://git.tilde.town/bombinans/poptimal/raw/branch/main/docs/sample.png)
Comments, feedback via Mastodon: [Mike Lynch](https://old.mermaid.town/@fsvo)
## Observable ## Observable
@ -25,3 +21,46 @@ npm run dev
Then visit <http://localhost:3000> to preview your app. Then visit <http://localhost:3000> to preview your app.
For more, see <https://observablehq.com/framework/getting-started>.
## Project structure
A typical Framework project looks like this:
```ini
.
├─ src
│ ├─ components
│ │ └─ timeline.js # an importable module
│ ├─ data
│ │ ├─ launches.csv.js # a data loader
│ │ └─ events.json # a static data file
│ ├─ example-dashboard.md # a page
│ ├─ example-report.md # another page
│ └─ index.md # the home page
├─ .gitignore
├─ observablehq.config.js # the app config file
├─ package.json
└─ README.md
```
**`src`** - This is the “source root” — where your source files live. Pages go here. Each page is a Markdown file. Observable Framework uses [file-based routing](https://observablehq.com/framework/project-structure#routing), which means that the name of the file controls where the page is served. You can create as many pages as you like. Use folders to organize your pages.
**`src/index.md`** - This is the home page for your app. You can have as many additional pages as youd like, but you should always have a home page, too.
**`src/data`** - You can put [data loaders](https://observablehq.com/framework/data-loaders) or static data files anywhere in your source root, but we recommend putting them here.
**`src/components`** - You can put shared [JavaScript modules](https://observablehq.com/framework/imports) anywhere in your source root, but we recommend putting them here. This helps you pull code out of Markdown files and into JavaScript modules, making it easier to reuse code across pages, write tests and run linters, and even share code with vanilla web applications.
**`observablehq.config.js`** - This is the [app configuration](https://observablehq.com/framework/config) file, such as the pages and sections in the sidebar navigation, and the apps title.
## Command reference
| Command | Description |
| ----------------- | -------------------------------------------------------- |
| `npm install` | Install or reinstall dependencies |
| `npm run dev` | Start local preview server |
| `npm run build` | Build your static site, generating `./dist` |
| `npm run deploy` | Deploy your app to Observable |
| `npm run clean` | Clear the local data loader cache |
| `npm run observable` | Run commands like `observable help` |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 KiB

View File

@ -26,12 +26,6 @@ class DotControls {
this.f.dispatchEvent(new Event("input")); this.f.dispatchEvent(new Event("input"));
} }
null_grid() {
// set to zero to stop flashing when randomising all
this.r.value = 0;
this.r.dispatchEvent(new Event("input"));
}
set_colour(c) { set_colour(c) {
this.fg.value = c; this.fg.value = c;
this.fg.dispatchEvent(new Event("input")); this.fg.dispatchEvent(new Event("input"));

View File

@ -6,9 +6,7 @@ toc: false
<h1>poptimal</h1> <h1>poptimal</h1>
colourful generative patterns using [d3](https://d3js.org/) and [Observable Framework](https://observablehq.com/framework/) <p>v1.1.0 | 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"> <div class="grid grid-cols-2">
@ -60,6 +58,8 @@ const randomise_palette = view(Inputs.button("Random", {label:"palette"}));
const randomise_all = view(Inputs.button("Random", {label:"all"})); const randomise_all = view(Inputs.button("Random", {label:"all"}));
``` ```
```js ```js
@ -82,13 +82,11 @@ ctrl2.random_grid();
```js ```js
randomise_all; randomise_all;
const rpalette = random.choice(Array.from(PALETTES.keys()));
ctrl1.null_grid();
ctrl2.null_grid();
palette_input.value = PALETTES.get(rpalette);
palette_input.dispatchEvent(new Event("input"));
ctrl1.random_grid(); ctrl1.random_grid();
ctrl2.random_grid(); ctrl2.random_grid();
const rpalette = random.choice(Array.from(PALETTES.keys()));
palette_input.value = PALETTES.get(rpalette);
palette_input.dispatchEvent(new Event("input"));
``` ```
@ -125,7 +123,6 @@ const dots2 = dm.dots(1 / m2, n2);
``` ```
```js ```js
// Set up the svg canvas
const svg = d3.create("svg") const svg = d3.create("svg")
@ -133,29 +130,12 @@ const svg = d3.create("svg")
.attr("height", HEIGHT * CELL * MAG) .attr("height", HEIGHT * CELL * MAG)
.attr("viewBox", [ 0, 0, WIDTH, HEIGHT ]); .attr("viewBox", [ 0, 0, WIDTH, HEIGHT ]);
const background = svg.append("rect")
// re transitions: they should only run when updating the palette and
// grid, not via the sliders
// see https://www.d3indepth.com/transitions/ and use enter / exit etc
// note
// do background as a select so that transitions work
const bg_g = svg.append("g")
.attr("id", "background");
bg_g.selectAll("rect")
.data( [ { bg: bg } ] )
.join("rect")
.attr("x", 0) .attr("x", 0)
.attr("y", 0) .attr("y", 0)
.attr("width", WIDTH) .attr("width", WIDTH)
.attr("height", WIDTH) .attr("height", WIDTH)
.attr("fill", (d) => d.bg) .attr("fill", bg);
;
const dots_g1 = svg.append("g") const dots_g1 = svg.append("g")
.attr("id", "dots1"); .attr("id", "dots1");
@ -163,9 +143,10 @@ const dots_g1 = svg.append("g")
dots_g1.selectAll("circle") dots_g1.selectAll("circle")
.data(dots1) .data(dots1)
.join("circle") .join("circle")
.attr("r", (d) => dm.radius(d, f1, r1))
.attr("fill", fg1)
.attr("cx", (d) => d.x) .attr("cx", (d) => d.x)
.attr("cy", (d) => d.y) .attr("cy", (d) => d.y);
.attr("fill", fg1);
const dots_g2 = svg.append("g") const dots_g2 = svg.append("g")
.attr("id", "dots2"); .attr("id", "dots2");
@ -173,22 +154,17 @@ const dots_g2 = svg.append("g")
dots_g2.selectAll("circle") dots_g2.selectAll("circle")
.data(dots2) .data(dots2)
.join("circle") .join("circle")
.attr("r", (d) => dm.radius(d, f2, r2))
.attr("fill", fg2)
.attr("cx", (d) => d.x) .attr("cx", (d) => d.x)
.attr("cy", (d) => d.y) .attr("cy", (d) => d.y);
.attr("fill", fg2);
display(svg.node()); display(svg.node());
``` ```
```js
// separate code block for when I understand transitions better
dots_g1.selectAll("circle").attr("r", (d) => dm.radius(d, f1, r1));
dots_g2.selectAll("circle").attr("r", (d) => dm.radius(d, f2, r2));
```
```js ```js
display(download(() => { display(download(() => {
@ -211,6 +187,8 @@ display(download(() => {
``` ```
(PNGs made with <a href="https://github.com/thx/resvg-js">resvg-wasm</a> in-browser) (PNGs made with <a href="https://github.com/thx/resvg-js">resvg-wasm</a> in-browser)
</div> </div>
<style> <style>