Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test/output
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@

<a href="https://observablehq.com/d/18b3d6f3affff5bb"><img src="./img/examples.png" alt="examples"></a>

> [!NOTE]
> The current next branch is implementing the new proposal API for production use. Please refer to the [main branch](https://github.com/charming-art/charming/tree/main) for the current release.
Charming is a JavaScript library for data-driven generative art that allows artists, designers, educators, and engineers to create expressive, accessible SVG graphics.

The JavaScript library for generative art based on SVG.
Charming’s API is inspired by data visualization grammar — systems like [AntV G2](https://g2.antv.antgroup.com/), [Observable Plot](https://observablehq.com/plot/) and [Vega-Lite](https://vega.github.io/vega-lite/) — where visuals are built from meaningful, composable units. By combining declarative structure with the power of SVG, Charming encourages a more thoughtful, expressive, inspectable and accessible approach to generative art.

<img src="./img/circles.png" alt="circles" width=492>

```js
import * as cm from "charmingjs";

const svg = cm.svg("svg", {
width: 100,
height: 100,
children: [
cm.svg("rect", {x: 0, y: 0, width: 100, height: 100, fill: "black"}),
cm.svg("circle", {cx: 50, cy: 50, r: 40, fill: "white"}),
function circles(x, y, r, data = []) {
if (r < 16) return;
data.push({x, y, r, depth});
circles(x - r / 2, y, r * 0.5, data);
circles(x + r / 2, y, r * 0.5, data);
circles(x, y - r / 2, r * 0.5, data);
circles(x, y + r / 2, r * 0.5, data);
return data;
}

const svg = cm.render({
width: 480,
height: 480,
marks: [
cm.svg("circle", circles(240, 240, 200), {
cx: (d) => d.x,
cy: (d) => d.y,
r: (d) => d.r,
stroke: "black",
fill: "transparent",
}),
],
});

document.body.appendChild(svg.render());
document.body.appendChild(svg);
```

## Resources 📚
Expand Down
14 changes: 7 additions & 7 deletions docs/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export default defineConfig({
{text: "API Index", link: "/docs/api-index"},
],
},
{
text: "Reference",
items: [
{text: "Charming Mark", link: "/docs/charming-mark"},
{text: "Charming Vector", link: "/docs/charming-vector"},
],
},
// {
// text: "Reference",
// items: [
// {text: "Charming Mark", link: "/docs/charming-mark"},
// {text: "Charming Vector", link: "/docs/charming-vector"},
// ],
// },
],
},
socialLinks: [{icon: "github", link: "https://github.com/charming-art/charming"}],
Expand Down
48 changes: 4 additions & 44 deletions docs/.vitepress/theme/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,17 @@ import "d3-transition";
import * as cm from "../../../src/index.js";
import "./custom.css";

const extended = {
random(min, max) {
return Math.random() * (max - min) + min;
},
constrain(x, min, max) {
return Math.min(Math.max(x, min), max);
},
transition(node, props) {
const {keyframes} = props;
const selection = selectAll([node]);
let transition = selection;

for (const {duration, ease, delay, ...attr} of keyframes) {
transition = transition.transition();
transition
.duration(duration)
.call((t) => ease && t.ease(ease))
.call((t) => delay && t.delay(delay));
for (const key in attr) {
if (key.startsWith("style")) {
const style = key.slice(5).toLowerCase();
transition.style(style, attr[key]);
} else transition.attr(key, attr[key]);
}
}

return node;
},
};

// More props: https://genji-md.dev/reference/props
const props = {
Theme: DefaultTheme,
library: {cm: {...cm, ...extended}},
library: {cm},
transform: {
module(code) {
let newCode = code
.replace("import {", "const {")
.replace(`from "charmingjs"`, "= cm")
.replace(`.render("#root")`, `.render(_root)`);
return `(() => {
const _root = document.createElement("div");
const newCode = code.replace(`cm.render`, `return cm.render`);
const result = `(() => {
${newCode}
return _root;
})()`;
},
replayable(code) {
return `(() => {
play;
return ${code};
})()`;
return result;
},
},
};
Expand Down
117 changes: 110 additions & 7 deletions docs/docs/api-index.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,115 @@
# API Index
# API Reference

## [Charming Mark](/docs/charming-mark)
- [_cm_.**render**](/docs/api-index#cm-render) - render a SVG element.
- [_cm_.**renderMark**](/docs/api-index#cm-renderMark) - render a mark.
- [_cm_.**svg**](/docs/api-index#cm-svg) - create a SVG mark.
- [_cm_.**html**](/docs/api-index#cm-html) - create a HTML mark.
- [_cm_.**tag**](/docs/api-index#cm-tag) - create a new mark factory.
- [_mark_.**with**](/docs/api-index#mark-with) - append children to mark.

Creating SVG and HTML with pure function calls.
## _cm_.render(_options_) {#cm-render}

- [_cm_.**render**](/docs/charming-mark#render) - render a node.
- [_cm_.**svg**](/docs/charming-mark#svg) - create SVG elements with the specified attributes and child nodes.
```js eval t=module
cm.render({
width: 200,
height: 100,
style_background: "black",
marks: [
cm.svg("circle", {
cx: 100,
cy: 50,
r: 40,
fill: "white",
}),
],
});
```

## [Charming Vector](/docs/charming-vector)
## _cm_.renderMark(_mark_) {#cm-renderMark}

> WIP
```js eval t=module
cm.renderMark(
cm.html("span", {
textContent: "Hello Charming.js",
style_color: "red",
}),
);
```

## _cm_.svg(_tag[, data[, options]]_) {#cm-svg}

```js eval t=module
cm.render({
width: 100,
height: 50,
marks: [
cm.svg("circle", [1, 2, 3], {
cx: (d) => d * 30,
cy: 25,
r: 10,
}),
],
});
```

## _cm_.html(_tag[, data[, options]]_) {#cm-html}

```js eval t=module
const table = [
[11975, 5871, 8916, 2868],
[1951, 10048, 2060, 6171],
[8010, 16145, 8090, 8045],
[1013, 990, 940, 6907],
];

cm.renderMark(
cm.html("table").with([
cm.html("tr", table).with([
cm.html("td", (row) => row, {
textContent: (d) => d,
}),
]),
]),
);
```

## _cm_.tag(_namespace_) {#cm-tag}

```js eval t=module
const math = cm.tag("http://www.w3.org/1998/Math/MathML");

cm.renderMark(
math("math").with([
math("mrow").with([
math("mrow").with([
math("mi", {textContent: "x"}),
math("mo", {textContent: "∗"}),
math("mn", {textContent: "2"}),
]),
math("mo", {textContent: "+"}),
math("mi", {textContent: "y"}),
]),
]),
);
```

## _mark_.with(_children_) {#mark-with}

```js eval t=module
const svg = cm.svg;

cm.render({
width: 100,
height: 50,
marks: [
svg("g", [1, 2, 3], {
transform: (d) => `translate(${d * 30},${25})`,
fill: "steelblue",
}).with([
svg("circle", [1, 2, 3], {
r: 10,
}),
]),
],
});
```
10 changes: 5 additions & 5 deletions docs/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ npm install charmingjs

There are several way to using Charming.

## Try Online
<!-- ## Try Online

Charming uses [Observable Notebook](https://observablehq.com/platform/notebooks) as its playground, try this [starter notebook](https://observablehq.com/d/7b4e552feea11ed3)!
Charming uses [Observable Notebook](https://observablehq.com/platform/notebooks) as its playground, try this [starter notebook](https://observablehq.com/d/7b4e552feea11ed3)! -->

## Installing from Package Manager

Expand Down Expand Up @@ -48,11 +48,11 @@ In vanilla HTML, Charming can be imported as an ES module, say from jsDelivr:
<script type="module">
import * as cm from "https://cdn.jsdelivr.net/npm/charmingjs/+esm";

const app = cm.render({
const svg = cm.render({
// ...
});

document.body.append(app.node());
document.body.append(svg);
</script>
```

Expand All @@ -67,6 +67,6 @@ Charming is also available as a UMD bundle for legacy browsers.
// ...
});

document.body.append(app.node());
document.body.append(svg);
</script>
```
Loading