Skip to content

Charming SVG

cm.svg(tag[, options])

Creates an SVG element with the specified tag. If the tag is not specified, returns null.

js
cm.svg();

Applying Attributes

If options is specified, applies the specified attributes to the created SVG element. For kebab-case attributes, specifying them in snake_case is also valid. For styles, specifying them with prefix "style_" or "style-".

js
cm.svg("svg", {
  width: 200,
  height: 100,
  viewBox: "0 0 200 100",
  // kebab-case attributes
  "stroke-width": 10,
  stroke_width: 10,
  // styles
  "style-background": "steelblue",
  style_background: "steelblue",
});

For the text content of SVG elements, specifying it as textContent:

js
(() => {
  return cm.svg("svg", {
    height: 30,
    children: [
      cm.svg("text", {
        dy: "1em",
        textContent: "Hello Charming!",
        fill: "steelblue",
      }),
    ],
  });
})();
js
cm.svg("text", {
  dy: "1em",
  textContent: "Hello Charming!",
  fill: "steelblue",
});

If options.data is specified, maps the data to a list of SVG elements and wraps them with a fragment element. If an attribute value is a constant, all the elements are given the same attribute value; otherwise, if an attribute value is a function, it is evaluated for each created element, in order, being passed the current datum (d), the current index (i), the current data (data), and the current node (node) as positional parameters. The function's return value is then used to set each element's attribute.

js
(() => {
  return cm.svg("svg", {
    width: 200,
    height: 60,
    children: [
      cm.svg("circle", {
        data: [20, 70, 120],
        r: 20,
        cy: 30,
        cx: (d) => d,
        fill: (d, i) => `rgb(${i * 100}, ${i * 100}, ${i * 100})`,
      }),
    ],
  });
})();
js
cm.svg("circle", {
  data: [50, 100, 150],
  r: 20,
  cy: 30,
  cx: (d) => d,
  fill: (d, i) => {
    const b = i * 100;
    return `rgb(${b}, ${b}, ${b})`;
  },
});

Appending Nodes

If options.children is specified as an array of SVG elements, appends the elements to this SVG element. Falsy elements will be filtered out. Nested arrays will be flattened before appending.

js
cm.svg("svg", {
  width: 100,
  height: 60,
  children: [
    cm.svg("circle", {r: 20, cy: 30, cx: 20}),
    [cm.svg("rect", {width: 40, height: 40, y: 10, x: 50})], // Nested array,
    null, // Falsy node
    false, // Falsy node
  ],
});

If a child is a string, a text node will be created and appended to the parent node:

js
(() => {
  return cm.svg("svg", {
    height: 30,
    children: [
      cm.svg("g", {
        transform: `translate(0, 20)`,
        fill: "steelblue",
        children: [cm.svg("text", ["Hello Charming!"])],
      }),
    ],
  });
})();
js
cm.svg("g", {
  fill: "steelblue",
  children: [cm.svg("text", ["Hello Charming!"])],
});

If options is specified as an array, it's a convenient shorthand for {children: options}:

js
cm.svg("svg", [cm.svg("circle"), cm.svg("rect")]);

If options.data is specified, for each child in options.children, if it is a function, it is evaluated for each created element, in order, being passed the current datum (d), the current index (i), the current data (data), and the current node (node) as positional parameters. The function's return value is then appended to the created element.

js
(() => {
  const g = cm.svg("g", {
    data: [0, 1, 2],
    transform: (d) => `translate(${d * 50 + 30}, 0)`,
    children: [
      (d, i) => {
        const a = i * 100;
        return cm.svg("circle", {
          r: 20,
          cy: 30,
          fill: `rgb(${a}, ${a}, ${a})`,
        });
      },
    ],
  });
  return cm.svg("svg", {width: 200, height: 60, children: [g]});
})();
js
cm.svg("g", {
  data: [0, 1, 2],
  transform: (d) => `translate(${(d + 1) * 50}, 0)`,
  children: [
    (d, i) => {
      const a = i * 100;
      return cm.svg("circle", {
        r: 20,
        cy: 30,
        fill: `rgb(${a}, ${a}, ${a})`,
      });
    },
  ],
});

If the child is a constant and the childOptions.data is specified, creates a list of child elements using the parent options.data and appends each child to each parent element.

js
(() => {
  const g = cm.svg("g", {
    data: [0, 1, 2],
    transform: (d) => `translate(${d * 50 + 30}, 0)`,
    children: [
      cm.svg("circle", {
        r: 20,
        cy: 30,
        fill: (d, i) => {
          const a = i * 100;
          return `rgb(${a}, ${a}, ${a})`;
        },
      }),
    ],
  });
  return cm.svg("svg", {width: 200, height: 60, children: [g]});
})();
js
cm.svg("g", {
  data: [0, 1, 2],
  transform: (d) => `translate(${d * 50 + 30}, 0)`,
  children: [
    cm.svg("circle", {
      r: 20,
      cy: 30,
      fill: (d, i) => {
        const a = i * 100;
        return `rgb(${a}, ${a}, ${a})`;
      },
    }),
  ],
});

If the child is a constant and the childOptions.data is specified as a constant, for each parent element, appends a list of child elements using the specified childOptions.data.

js
(() => {
  const g = cm.svg("g", {
    data: [0, 1],
    transform: (d) => `translate(30, ${d * 50})`,
    children: [
      cm.svg("circle", {
        data: [0, 1, 2],
        r: 20,
        cy: 30,
        cx: (d) => d * 50,
        fill: (d, i) => {
          const a = i * 100;
          return `rgb(${a}, ${a}, ${a})`;
        },
      }),
    ],
  });
  return cm.svg("svg", {width: 180, height: 110, children: [g]});
})();
js
cm.svg("g", {
  data: [0, 1],
  transform: (d) => `translate(30, ${d * 50})`,
  children: [
    cm.svg("circle", {
      data: [0, 1, 2],
      r: 20,
      cy: 30,
      cx: (d) => d * 50,
      fill: (d, i) => {
        const a = i * 100;
        return `rgb(${a}, ${a}, ${a})`;
      },
    }),
  ],
});

If the child is a constant and the childOptions.data is specified as a function, it is evaluated for each parent element, in order, being passed the current datum (d), the current index (i), the current data (data), and the current node (node) as positional parameters. The function's return value is then used to create a list of child elements and append to the current parent element.

js
(() => {
  const g = cm.svg("g", {
    data: [
      [0, 1, 2],
      [3, 4, 5],
    ],
    transform: (d, i) => `translate(30, ${i * 50})`,
    children: [
      cm.svg("circle", {
        data: (d) => d,
        r: 20,
        cy: 30,
        cx: (d, i) => i * 50,
        fill: (d, i) => {
          const a = d * 40;
          return `rgb(${a}, ${a}, ${a})`;
        },
      }),
    ],
  });
  return cm.svg("svg", {width: 180, height: 110, children: [g]});
})();
js
cm.svg("g", {
  data: [
    [0, 1, 2],
    [3, 4, 5],
  ],
  transform: (d, i) => `translate(30, ${i * 50})`,
  children: [
    cm.svg("circle", {
      data: (d) => d,
      r: 20,
      cy: 30,
      cx: (d, i) => i * 50,
      fill: (d, i) => {
        const a = d * 40;
        return `rgb(${a}, ${a}, ${a})`;
      },
    }),
  ],
});

Handling Events

If an attribute starts with "on", adds a listener to the created element for the specified event typename. When a specified event is dispatched on the created element, the specified listener will be evaluated for the element, being passed the following positional parameters:

  • event (or e): the current event,
  • node: the current node,
  • d: the current data (if the options.data is specified, otherwise undefined),
  • i: the current index (if the options.data is specified, otherwise undefined),
  • array: the current data array (if the options.data is specified, otherwise undefined).

For all events, the listener receives (event, node, d, i, array). For non-data-driven elements, d, i, and array will be undefined.

js
(() => {
  const onclick = (event, node) => {
    const current = cm.attr(node, "style-background");
    const next = current === "steelblue" ? "orange" : "steelblue";
    cm.attr(node, "style-background", next);
  };

  return cm.svg("svg", {
    onclick,
    width: 100,
    height: 100,
    style_background: "steelblue",
    style_cursor: "pointer",
  });
})();
js
const onclick = (event, node) => {
  const current = cm.attr(node, "style-background");
  const next = current === "steelblue" ? "orange" : "steelblue";
  cm.attr(node, "style-background", next);
};

cm.svg("svg", {
  onclick,
  width: 100,
  height: 100,
  style_background: "steelblue",
  style_cursor: "pointer",
});

If the attribute value is specified as an array, the first element of it will be specified as the listener, while the second element will specify the characteristics about the event listener, such as whether it is capturing or passive; see element.addEventListener.

js
cm.svg("svg", {onclick: [onclick, {capture: true}]});

cm.html(tag[, options])

Similar to cm.svg, but creates HTML elements instead.

js
cm.html("div", {
  style_background: "steelblue",
  style_width: "100px",
  style_height: "100px",
});

cm.tag(namespace)

Creates an element factory with the specified namespace. For example, creates a math factory for MathML:

js
(() => {
  const math = cm.tag("http://www.w3.org/1998/Math/MathML");

  return math("math", [
    math("mrow", [
      math("mrow", [math("mi", {textContent: "x"}), math("mo", {textContent: "∗"}), math("mn", {textContent: "2"})]),
      math("mo", {textContent: "+"}),
      math("mi", {textContent: "y"}),
    ]),
  ]);
})();
js
const math = cm.tag("http://www.w3.org/1998/Math/MathML");

const node = math("math", [
  math("mrow", [
    math("mrow", [
      // equivalent to math("mi", ["x"])
      math("mi", {textContent: "x"}),
      math("mo", {textContent: "∗"}),
      math("mn", {textContent: "2"}),
    ]),
    math("mo", {textContent: "+"}),
    math("mi", {textContent: "y"}),
  ]),
]);

cm.attr(node, key[, value])

If value is not specified, gets the current value of the specified key of the specified node attributes.

js
const svg = cm.svg("svg", {
  height: 100,
  style_background: "red",
});
js
svg.getAttribute("height");
js
cm.attr(svg, "style_background");

If value is specified, sets the specified attribute with the specified value to the specified node.

js
(() => {
  const svg = cm.svg("svg");
  cm.attr(svg, "width", 100);
  cm.attr(svg, "height", 100);
  cm.attr(svg, "style-background", "steelblue");
  cm.attr(svg, "style-cursor", "pointer");
  cm.attr(svg, "onclick", (event, node) => {
    const current = cm.attr(node, "style-background");
    const next = current === "steelblue" ? "orange" : "steelblue";
    cm.attr(node, "style-background", next);
  });
  return svg;
})();
js
const svg = cm.svg("svg");
cm.attr(svg, "width", 100);
cm.attr(svg, "height", 100);
cm.attr(svg, "style-background", "steelblue");
cm.attr(svg, "style-cursor", "pointer");
cm.attr(svg, "onclick", (event, node) => {
  const current = cm.attr(node, "style-background");
  const next = current === "steelblue" ? "orange" : "steelblue";
  cm.attr(node, "style-background", next);
});

If value is specified as null, remove that attribute.

js
cm.attr(input, "checked", null);
cm.attr(div, "class", null);
cm.attr(div, "style-color", null);
cm.attr(span, "textContent", null);

If an event listener is specified as null, removes that event listener. Otherwise, removes the older one if exists and adds the new one.

js
const svg = cm.svg("svg");
cm.attr(svg, "onclick", () => alert("hello charming"));
cm.attr(svg, "onclick", null);