Il est parfois utile de créer des visuels afin d’appuyer certaines données statistiques, mathématiques ou scientifiques.
Le programme que vous retrouverez ci-dessous génère un graphique sous la forme d’un histogramme à partir de données.
Le choix du format de fichier svg permet d’éditer l’histogramme généré dans un logiciel d’édition d’image vectorielle du type Inkscape.
Le programme fait appel à la notion d’interface, construction idiomatique du langage Golang. L’utilisation d’interfaces permet de se familiariser avec la programmation-objet par composition. Appliquée à ce programme, la création d’une interface permet une meilleure modularité du programme et son extension pour la construction de widgets futurs.
Le programme est encore largement perfectible surtout autour de la fonction calcorigin() qui est appelée 2 fois et qui pourrait être substituée par l’utilisation d’une structure de stockage. Je laisse le soin au lecteur de faire appel à sa créativité.
Listing du programme :
package main
import (
"os"
"fmt"
"github.com/ajstarks/svgo"
)
var (
canvas = svg.New(cfile("out.svg"))
canvas_w = 1024
canvas_h = 768
)
func cfile(n string) (out *os.File) {
out, err := os.Create(n)
if err != nil {
panic(err)
}
return out
}
type bar struct {
x int
y int
w int
h int
offset int
l string
lx string
ly string
d map[string]int
c *svg.SVG
ga bool
gas int
gw bool
gws int
wb int
s_z string
s_t string
s_x string
s_y string
s_lv string
s_lt string
s_ga string
s_gw string
s_bar string
s_bart string
s_bartt string
}
type widget interface {
//minmax() (min int, max int)
drawgrid()
drawzone()
drawtitle()
drawaxes()
drawbar()
}
func (b bar) minmax() (min int, max int) {
values := []int{}
for _,v := range b.d {
values = append(values, v)
}
max, min = values[0], values[0]
for _, e := range values {
if e > max {
max = e
}
if e < min {
min = e
}
}
return min, max
}
func (b bar) calcorigin() (ax_o_x int,
ax_o_y int,
ax_e_x int,
ax_e_y int,
ay_o_x int,
ay_o_y int,
ay_e_x int,
ay_e_y int,
l_ax int,
l_ay int) {
// axe x, origin and end point
ax_o_x = b.x + b.offset
ax_o_y = b.h + b.y - b.offset
ax_e_x = b.w + b.x - b.offset
ax_e_y = b.h + b.y - b.offset
l_ax = ax_e_x - ax_o_x
// axe y, origin and end point
ay_o_x = b.x + b.offset
ay_o_y = b.y + b.h - b.offset
ay_e_x = b.x + b.offset
ay_e_y = b.y + b.offset
l_ay = ay_o_y - ay_e_y
return ax_o_x, ax_o_y, ax_e_x, ax_e_y, ay_o_x, ay_o_y, ay_e_x, ay_e_y, l_ax, l_ay
}
func (b bar) drawgrid() {
// canvas grid
if b.ga {
canvas.Grid(0, 0, canvas_w, canvas_h, b.gas, b.s_ga)
}
// widget grid
if b.gw {
canvas.Grid(b.x, b.y, b.w, b.h, b.gws, b.s_gw)
}
}
func (b bar) drawzone() {
b.c.Rect(b.x, b.y, b.w, b.h, b.s_z)
}
func (b bar) drawtitle() {
offs := 20
b.c.Text(b.w + b.x - offs, b.y + offs, b.l, b.s_t)
}
func (b bar) drawbar() {
ax_o_x, ax_o_y, _, _, _, _, _, _, l_ax, l_ay := b.calcorigin()
nb_e := len(b.d)
grad := (l_ax - (2 * b.offset)) / nb_e
cut := 0
for k, v := range b.d {
b.c.Rect(ax_o_x + b.offset + cut, ax_o_y - (v * l_ay / 100), b.wb, (v * l_ay / 100), b.s_bar)
b.c.Text(ax_o_x + b.offset + cut + (b.wb/2), ax_o_y - (v * l_ay / 100) - 10, k, b.s_bart)
b.c.Text(ax_o_x + b.offset + cut + (b.wb/2), ax_o_y - (v * l_ay / 100) + 50, fmt.Sprintf("%d", v), b.s_bartt)
cut = cut + grad
}
}
func (b bar) drawaxes() {
// axe x
ax_o_x, ax_o_y, ax_e_x, ax_e_y, ay_o_x, ay_o_y, ay_e_x, ay_e_y, l_ax, l_ay := b.calcorigin()
b.c.Line(ax_o_x, ax_o_y, ax_e_x, ax_e_y, b.s_x)
// axe y
b.c.Line(ay_o_x, ay_o_y, ay_e_x, ay_e_y, b.s_y)
//fmt.Printf("longueur axe x: %d, longueur axe y: %d\n", l_ax, l_ay)
// y graduation and legend
goffs := 20 // graduation offset
voffs := 10 // title value offset
toffs := 20 // title legend y offset
stroke := 5 // graduation width
for y := ay_o_y; y >= ay_e_y; y = y - goffs {
if y == ay_o_y {
b.c.Text(ay_o_x - voffs, y, "0", b.s_lv)
}
if y == ay_e_y {
b.c.Text(ay_o_x - voffs, y, "100", b.s_lv)
}
b.c.Line(ay_o_x, y, ay_o_x - stroke, y, b.s_y)
}
// y axe legend title
b.c.TranslateRotate(ay_o_x - toffs, l_ay / 2 + b.y + b.offset, -90)
b.c.Text(0, 0, b.ly, b.s_lt)
b.c.Gend()
// x axe legend title
b.c.Text(l_ax / 2 + b.x + b.offset, ax_o_y + toffs, b.lx, b.s_lt)
}
func widgetbar(w widget) {
w.drawgrid()
w.drawzone() // widget zone
w.drawtitle() // widget title
w.drawbar() // draw bar
w.drawaxes() // axes widget
//min, max := w.minmax()
//fmt.Printf("min: %d, max: %d\n", min, max)
//fmt.Println(w)
}
func main() {
canvas.Start(canvas_w, canvas_h)
canvas.Title("Widget Bar")
b := bar {
x: 120,
y: 60,
w: 640,
h: 480,
offset: 40,
// legend
l: "Skills languages",
lx: "Languages",
ly: "Percent",
// data
d: map[string]int{ "Python": 95,
"SQL": 85,
"Golang": 70,
"Php": 50,
"Javascript": 70,
"Shell": 60,
"CSS": 70,
"HTML": 95},
c: canvas,
ga: false, // canvas grid
gas: 20, // canvas grid size
gw: false, // widget grid
gws: 20, // widget grid size
wb: 50, // bar width
// Styles
// Widget zone
s_z: "stroke:black;stroke-width:1;fill-opacity:0",
// Widget title
s_t: "font-size:18px;font-family:Ubuntu;text-anchor:end;no-stroke;fill:black",
// Axe style
s_x: "stroke:black",
s_y: "stroke:black",
// Axe title legend style
s_lv: "font-size:14px;font-family:Ubuntu;text-anchor:end;no-stroke;fill:black",
s_lt: "font-size:14px;font-family:Ubuntu;text-anchor:middle;no-stroke;fill:black",
// Grid style
s_ga: "stroke:lightgray",
s_gw: "stroke:red",
// Bar style
s_bar: "fill:lightgrey;no-stroke",
s_bart: "font-size:12px;font-family:Ubuntu;text-anchor:middle;no-stroke;fill:black",
s_bartt:"font-size:16px;font-family:Ubuntu;text-anchor:middle;no-stroke;fill:black",
}
widgetbar(b)
canvas.End()
fmt.Printf("=> Drawing success\n")
}
http://www.svgopen.org/2011/papers/34-SVGo_a_Go_Library_for_SVG_generation/ https://speakerdeck.com/ajstarks/svgo-workshop https://speakerdeck.com/ajstarks/programming-pictures-with-svgo https://www.flickr.com/photos/ajstarks/albums/72157623441699483/page2