Slide 66 de 74

Desenhando em um canvas

Um gráfico, nas unidades do próprio programa
// Doze de largura e cem de altura, então um mês de 68 quilos é desenhado
// com 68 de comprimento e nada precisa virar pixel primeiro.
func chart(harvest: Int[]): ui.View {
	mut ui.Mark[dyn] marks = []
	for i := 0; i < len(harvest); i++ {
		tall := hive.conv.itf(harvest[i])    // a condição do laço prova i
		// Um bar é posicionado pelo seu meio, e é por isso que o chão
		// é 100 e o meio de um em pé sobre ele fica meia barra acima.
		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 por mês"))
	return ui.canvas([
		ui.width(480),
		ui.height(200),
		ui.region(0.0, 0.0, 12.0, 100.0),
	], marks)
}

proc main(): void {
	// A mesma árvore que uma window trocaria é a que uma página escreve.
	echo len(ui.page("Colheita", chart([31, 44, 58, 72, 68, 51])))
}
Uma linha, um ponto e uma palavra
proc main(): void {
	// Uma linha através de cinco lugares — x, y, x, y — e um ponto sobre o último.
	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 fica centralizado no lugar, e sua altura também está nas
	// unidades da região — então esta tem um doze avos do plano em que está.
	label := ui.words([ui.paint(ui.Tone.Muted())], 4.0, 8.4, 0.8, "agora")

	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) é um widget como qualquer outro e um Mark é um valor como qualquer outro, então o que um canvas guarda é o que está nele agora. Não existe contexto de desenho, nem quadro para agendar, nem nada para limpar: um gráfico é reconstruído a partir do estado exatamente como o resto da página, e uma página servida desenha um exatamente como uma janela desenha.

Existem cinco marcas, cada uma aqui porque não dá para fazê-la com as outras:

  • dot(attrs, x, y, radius), um círculo preenchido;
  • bar(attrs, x, y, width, height), um retângulo;
  • path(attrs, points), uma linha por uma sequência de pontos escrita x, y, x, y, e patch(attrs, points), os mesmos números preenchidos;
  • words(attrs, x, y, height, text), texto posicionado no plano.

Um anel é um path em volta de um círculo, uma grade é um vetor de paths e uma seta é um path; um dot é sempre preenchido.

Tudo é dito nas unidades do próprio programa. region(x, y, width, height) diz qual parte do plano a caixa mostra, então um circuito mapeado em metros desenha um carro de nove metros com nove de comprimento; paint(Tone) é a cor de uma marca e thick(Float) a largura de um path, também nas unidades da região — então dar zoom engrossa as estradas, que é o que dar zoom significa. A caixa pode ter qualquer formato, e o que é desenhado mantém o seu, centralizado. Toda marca é posicionada pelo meio — uma barra incluída — e as palavras ficam centralizadas no seu lugar com a altura nas unidades da região. O y cresce para baixo, como numa página, então o chão de um gráfico é o seu maior y.