Slide 66 of 74

Drawing on a canvas

A chart, in the program's own units
// Twelve across and a hundred down, so a month of 68 kilos is drawn 68
// long and nothing has to be turned into pixels first.
func chart(harvest: Int[]): ui.View {
	mut ui.Mark[dyn] marks = []
	for i := 0; i < len(harvest); i++ {
		tall := hive.conv.itf(harvest[i])    // the loop's own condition proves i
		// A bar is placed from its middle, which is why the floor is
		// 100 and the middle of one standing on it is half a bar up.
		append(marks, ui.bar([ui.paint(ui.Tone.Good())],
			hive.conv.itf(i) + 0.5, 100.0 - tall / 2.0, 0.7, tall))
	}
	append(marks, ui.words([ui.paint(ui.Tone.Muted())], 6.0, 8.0, 7.0, "kg per month"))
	return ui.canvas([
		ui.width(480),
		ui.height(200),
		ui.region(0.0, 0.0, 12.0, 100.0),
	], marks)
}

proc main(): void {
	// The same tree a window would swap in is what a page writes out.
	echo len(ui.page("Harvest", chart([31, 44, 58, 72, 68, 51])))
}
A line, a dot and a word
proc main(): void {
	// A line through five places — x, y, x, y — and a dot on the last of them.
	trace := ui.path([ui.thick(0.15), ui.paint(ui.Tone.Good())],
		[0.0, 2.0, 1.0, 5.0, 2.0, 4.0, 3.0, 8.0, 4.0, 7.0])
	tip := ui.dot([ui.paint(ui.Tone.Good())], 4.0, 7.0, 0.2)

	// Words are centred on the place, and their height is in region units
	// too — so this one is a twelfth as tall as the plane it sits on.
	label := ui.words([ui.paint(ui.Tone.Muted())], 4.0, 8.4, 0.8, "now")

	view := ui.canvas([
		ui.width(360),
		ui.height(200),
		ui.region(-0.5, 0.0, 5.5, 10.0),
	], [trace, tip, label])

	echo ui.html(view)
}

canvas(attrs, marks) is a widget like any other and a Mark is a value like any other, so what a canvas holds is what is on it right now. There is no drawing context, no frame to schedule and nothing to clear: a chart is rebuilt from the state exactly as the rest of the page is, and a served page draws one exactly as a window does.

There are five marks, each here because it cannot be made out of the others:

  • dot(attrs, x, y, radius), a filled circle;
  • bar(attrs, x, y, width, height), a rectangle;
  • path(attrs, points), a line through a run of points written x, y, x, y, and patch(attrs, points), the same numbers filled;
  • words(attrs, x, y, height, text), text placed on the plane.

A ring is a path round a circle, a grid is a vector of paths and an arrow is a path; a dot is always filled.

Everything is said in the program's own units. region(x, y, width, height) says which part of the plane the box shows, so a circuit mapped in metres draws a car nine metres long nine long; paint(Tone) is a mark's colour and thick(Float) a path's width, in the region's units too — so zooming in thickens the roads, which is what zooming in means. The box may be any shape, and what is drawn keeps its own, centred. Every mark is placed by its middle — a bar included — and words are centred on their place with their height in the region's units. y grows downward, as on a page, so a chart's floor is its largest y.