d3

d3 is another popular DOM manipulation library available in JavaScript. This is the domonic-side Python port of that surface.

Ported modules: selection, scale, shape, array, time, hierarchy, interpolate, color, format, dispatch, path, polygon, timer, tile and queue. Not yet ported: axis, brush, chord, contour, delaunay, drag, dsv, ease, force, geo, quadtree, random, scale-chromatic, time-format, transition and zoom.

It has several utilities for working with the DOM.

selection

d3’s selection API is very powerful.

from domonic.dom import *
from domonic.html import *
from domonic.d3.selection import *

import domonic.d3 as d3

Selections support data binding with update, enter, and exit groups:

from domonic.d3.selection import select
from domonic.html import li, ul

items = ul(li(), li())
update = select(items).selectAll("li").data(["one", "two", "three"])
update.enter().append("li").text(lambda data: data)

Build a Small Data List

from domonic.d3.selection import select
from domonic.html import div

root = div(_id="chart")
select(root).selectAll("span").data([10, 20, 30]).enter().append("span").text(
    lambda value: f"{value}%"
)

print(root)
# <div id="chart"><span>10%</span><span>20%</span><span>30%</span></div>

format

d3 includes helpers for formatting numbers and avoiding floating-point display noise.

from domonic.dom import *
from domonic.html import *
from domonic.d3.format import *
from domonic.d3.format import format

dispatch

d3 has an event system.

from domonic.dom import *
from domonic.html import *
from domonic.d3.dispatch import dispatch, Dispatch

scale

Scales map a data domain onto a visual range. Continuous, sequential, diverging, quantise, quantile, threshold, ordinal, band, point, time and UTC scales are ported.

from domonic.d3.scale import scaleLinear, scaleBand, scaleOrdinal

x = scaleLinear().domain([0, 100]).range([0, 960])
x(50)          # 480.0
x.ticks(5)     # [0, 20, 40, 60, 80, 100]

band = scaleBand().domain(["a", "b", "c"]).range([0, 300]).padding(0.1)
band("b")          # 106.45...
band.bandwidth()   # 87.09...

colour = scaleOrdinal().domain(["a", "b"]).range(["red", "blue"])
colour("a")    # "red"

Also available: scaleLog, scalePow / scaleSqrt, scaleSymlog, scaleRadial, scaleQuantize, scaleQuantile, scaleThreshold, scalePoint, scaleSequential, scaleDiverging, scaleTime and scaleUtc.

shape

Generators that turn data into SVG path strings: line, area, arc, pie, stack, link and symbol, plus the curve* interpolators.

from domonic.d3.shape import line, arc, pie

draw = line().x(lambda d, *_: d[0]).y(lambda d, *_: d[1])
draw([(0, 0), (1, 1), (2, 4)])         # "M0,0L1,1L2,4"

wedge = arc().innerRadius(0).outerRadius(100)
wedge({"startAngle": 0, "endAngle": 1.57})

slices = pie()([1, 1, 2])              # three arc-datum dicts

The angle and radius accessors of arc() default to reading startAngle / endAngle / innerRadius / outerRadius off the datum, matching d3.

array

Statistics, search, and data-reshaping helpers over iterables.

from domonic.d3.array import extent, mean, ticks, group, bin

extent([3, 1, 2])        # [1, 3]
mean([1, 2, 3])          # 2.0
ticks(0, 10, 5)          # [0, 2, 4, 6, 8, 10]

group([{"t": "a"}, {"t": "b"}, {"t": "a"}], lambda d: d["t"])

Includes min / max / sum / median / quantile / variance / deviation, bisector and bisect, range / tickStep / nice, group(s) / rollup(s) / index(es), cross / merge / pairs / transpose / zip, the set helpers (union, intersection, difference, …), and bin().

time

TimeInterval objects for flooring, ceiling, offsetting and ranging over datetime values, in local time and UTC.

import datetime
from domonic.d3.time import timeDay, timeWeek, timeMonth

when = datetime.datetime(2026, 3, 15, 12, 30)
timeDay.floor(when)          # 2026-03-15 00:00:00
timeWeek.range(timeMonth.floor(when), when)

timeSecond .. timeYear and the weekday intervals are provided, each with a utc* counterpart, plus timeTicks / timeTickInterval.

hierarchy

Turn nested (or flat, via stratify) data into a tree of Node objects and lay it out.

from domonic.d3.hierarchy import hierarchy, tree

root = hierarchy({
    "name": "root",
    "children": [{"name": "a"}, {"name": "b", "children": [{"name": "c"}]}],
})
root.height                              # 2
[n.data["name"] for n in root.leaves()]  # ["a", "c"]
tree().size([400, 200])(root)            # assigns node.x / node.y

Layouts: tree / cluster, partition, treemap (with treemapSquarify / Dice / Slice / SliceDice / Binary) and pack.

interpolate

Interpolators between two values, returning a function of t in [0, 1].

from domonic.d3.interpolate import interpolate, interpolateRgb

interpolate(0, 100)(0.25)              # 25.0
interpolateRgb("red", "blue")(0.5)     # "rgb(128, 0, 128)"

Covers numbers, rounds, strings (with embedded numbers), dates, arrays, objects, basis splines, the colour-space interpolators (interpolateRgb / Hsl / Lab / Hcl / Cubehelix, with *Long hue variants), plus piecewise, quantize and interpolateZoom.

color

Parse and manipulate colours in RGB, HSL, Lab, HCL and Cubehelix space.

from domonic.d3.color import color, rgb

color("#ff0000")            # rgb(255, 0, 0)
color("steelblue").formatHex()
rgb(255, 0, 0).darker(1)

path

d3’s path API converts SVG paths to a d3.path object compatible with canvas.

from domonic.svg import *
from domonic.d3.path import Path

polygon

d3 has a shape API.

from domonic.d3.polygon import *

D3 expects a polygon to be a 2D array of integers listed counterclockwise, though clockwise works too. This is not the same as SVG’s polygon element. SVG polygons have a points method that you can parse into a 2D array and use here.

polygonArea() finds the area of a given polygon. It returns a float.

from domonic.d3.polygon import polygonArea

irreg_0 = [[5,11],[12,4],[7,7],[6,1]]
square = [[0,4],[4,4],[4,0],[0,0]]
triangle = [[-4,0],[0,4],[2,0]]

print(polygonArea(irreg_0))    # 15.0
print(polygonArea(square))     # 16.0
print(polygonArea(triangle))   # 12.0

polygonCentroid() finds the center of mass of a polygon. It returns two floats in an array, representing the polygon’s center point.

from domonic.d3.polygon import polygonCentroid

irreg = [[-4,0],[8,12],[4,8],[-4,-4],[0,0]]
square = [[0,4],[4,4],[4,0],[0,0]]

print(polygonCentroid(irreg))   # [-0.0, 4.0]
print(polygonCentroid(square))  # [2.0, 2.0]

polygonHull() takes a 2D array of points and determines the hull using Andrew’s monotone chain algorithm. It returns the hull as a 2D array of points.

from domonic.d3.polygon import polygonHull

points = [[0,6],[12,8],[23,-5],[-5,-3],[5,11],[12,4],[7,7],[6,1]]
print(polygonHull(points))
# [[-5, -3], [0, 6], [5, 11], [12, 8], [23, -5]]

polygonContains() takes a polygon and a point. It returns True if the point is inside the polygon and False otherwise.

from domonic.d3.polygon import polygonContains

square = [[0,4],[4,4],[4,0],[0,0]]

print(polygonContains(square, [2, 2]))    # True
print(polygonContains(square, [-2, -2]))  # False

polygonLength() takes a polygon and returns the sum of its side lengths.

from domonic.d3.polygon import polygonLength

irreg_0 = [[5,11],[12,4],[7,7],[6,1]]
square = [[0,4],[4,4],[4,0],[0,0]]

print(round(polygonLength(irreg_0), 3))  # 31.863
print(polygonLength(square))             # 16.0