go4webdev

The Go code for the chart part

The basic of charts in Go is the possibility to add a function into Go HTML template. Similar to Javascript in HTML onclick="myFunction()". The difference is that this custom template function will be reachable from all templates.

1. The chart() function

This is the "func" that is called from the Go template.

func chart(path string) string {
    rest := strings.Split(path, "/")
    module := rest[0]
    mode := rest[1]
    val := rest[2]
    sfx := rest[3] //suffix to address separate charts
                
    json_data := get_data(module, mode, val)
    switch mode {
    case "bar":
        return get_bar(json_data, sfx)
    case "pie":
        return get_pie(json_data, sfx)
    case "line":
        return get_line(json_data, sfx)
    }
    return "bar"
}
2. Add chart() function to all Go templates

I prefer to do this once in an init() function

func init() {
    tpl = template.Must(template.New("").Funcs(template.FuncMap{
        "chart": chart, //add the chart function
    }).ParseGlob("./public/tmpl/*/*.html")) //two levels deep
}
3. The Go chart code

I have separated all chart functions in a separate Go file.

package main

import (
    "strings"
)
        
func chart(path string) string {
    rest := strings.Split(path, "/")
    module := rest[0]
    mode := rest[1]
    val := rest[2]
    sfx := rest[3] //to address separate charts
        
    json_data := get_data(module, mode, val)
    switch mode {
    case "bar":
        return get_bar(json_data, sfx)
    case "pie":
        return get_pie(json_data, sfx)
    case "line":
        return get_line(json_data, sfx)
    }
    return "bar"
}
        
func get_data(module, mode, val string) string {
    return `{
        "Active":"25",
        "On hold":"20",
        "Draft":"30"
        }`
}
        
func get_bar(json_data, sfx string) string {
    return ` 
    var ctx` + sfx + ` = document.getElementById('chrt` + sfx + `').getContext('2d');
    var jsonData` + sfx + ` = ` + json_data + `;
    var labels` + sfx + ` = Object.keys(jsonData` + sfx + `);
    var data` + sfx + ` = Object.values(jsonData` + sfx + `).map(Number);
        
    var myChart` + sfx + ` = new Chart(ctx` + sfx + `, {
        type: 'bar',
        data: {
            labels: labels` + sfx + `,
            datasets: [{
                label: '# of Votes',
                data: data` + sfx + `,
                backgroundColor: [
                    'rgba(255, 99, 132, 10)',
                    'rgba(54, 162, 235, 10)',
                    'rgba(255, 206, 86, 10)',
                    'rgba(75, 192, 192, 10)',
                    'rgba(153, 102, 255, 10)',
                    'rgba(255, 159, 64, 10)'
                ],
                borderWidth: 1
            }]
        },
        options: {
            scales: {
                y: {
                    beginAtZero: true
                }
            },
            plugins: {
                title: {
                    display: true,
                    text: 'Bar Chart Title',
                    font: {
                        size: 20,
                        family: 'Source Sans Pro',
                    } 
                }
            }
        }
    });
    `
}
        
func get_pie(json_data, sfx string) string {
    return `
    var ctx` + sfx + ` = document.getElementById('chrt` + sfx + `').getContext('2d');
    var jsonData` + sfx + ` = ` + json_data + `;
    var labels` + sfx + ` = Object.keys(jsonData` + sfx + `);
    var data` + sfx + ` = Object.values(jsonData` + sfx + `).map(Number);
        
    var myChart` + sfx + ` = new Chart(ctx` + sfx + `, {
        type: 'pie',
        data: {
            labels: labels` + sfx + `,
            datasets: [{
                label: '# of Votes',
                data: data` + sfx + `,
                backgroundColor: [
                    'rgba(255, 99, 132, 10)',
                    'rgba(54, 162, 235, 10)',
                    'rgba(255, 206, 86, 10)',
                    'rgba(75, 192, 192, 10)',
                    'rgba(153, 102, 255, 10)',
                    'rgba(255, 159, 64, 10)'
                ],
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: {
                    position: 'top',
                },
                title: {
                    display: true,
                    text: 'Pie Chart Title',
                    font: {
                        size: 20,
                        family: 'Source Sans Pro',
                    }  
                }
            }
        },
    }); `
}
        
func get_line(json_data, sfx string) string {
    return `
    var ctx` + sfx + ` = document.getElementById('chrt` + sfx + `').getContext('2d');
    var jsonData` + sfx + ` = ` + json_data + `;
    var labels` + sfx + ` = Object.keys(jsonData` + sfx + `);
    var data` + sfx + ` = Object.values(jsonData` + sfx + `).map(Number);
        
    var myChart` + sfx + ` = new Chart(ctx` + sfx + `, {
        type: 'line',
        data: {
            labels: labels` + sfx + `,
            datasets: [{
                label: 'Status',
                data: data` + sfx + `,
                fill: false,
                tension: 0.1
            }]
        },
        options: {
            scales: {
                y: {
                    beginAtZero: true
                },
            },
            plugins: {
                title: {
                    display: true,
                    text: 'Line Chart Title',
                    font: {
                        size: 20,
                        family: 'Source Sans Pro',
                    } 
                }
            }
        }
    });
    `
}        

4. The Go main code
package main

import (
    _ "embed"
    "net/http"
    "strings"
    "text/template"
)
        
//go:embed mbd/chart.min.js
var chartJS []byte
var tpl *template.Template
        
func init() {
    tpl = template.Must(template.New("").Funcs(template.FuncMap{
        "chart": chart,
    }).ParseGlob("./public/tmpl/*/*.html"))
        
    http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./public/img"))))
    http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./public/css"))))
    http.Handle("/icn/", http.StripPrefix("/icn/", http.FileServer(http.Dir("./public/icn"))))
    http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./public/js"))))
    http.Handle("/misc/", http.StripPrefix("/misc/", http.FileServer(http.Dir("./public/misc"))))
    http.HandleFunc("/mbd/chart.min.js", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/javascript")
        w.Write(chartJS)
    })
}
        
func main() {
    http.HandleFunc("/", endpoint)
    http.ListenAndServe(":9098", nil)
}

func endpoint(w http.ResponseWriter, r *http.Request) {
    module, mode, _ := getpath(r.URL.Path)        
    var page string
    switch module {
    case "robots.txt":
        http.ServeFile(w, r, "public/misc/robots.txt")
    case "sitemap.xml":
        http.ServeFile(w, r, "public/misc/sitemap.xml")
    case "favicon.ico", "favicon-32x32.png", "favicon-16x16.png":
        return
    case "":
        module = "home"
    }
        
    switch mode {
    case "edit", "new", "view", "find", "goto", "status", "timer":
    default:
        page = module + ".html"
        set_header(w)
        //	data := json2map(module, mode, val)
        tpl.ExecuteTemplate(w, page, nil)
    }
}
        
// split url
func getpath(path string) (module, mode, val string) {
    parts := strings.Split(path, "/")
    switch len(parts) {
    case 4:
        val = parts[3]
        fallthrough
    case 3:
        mode = parts[2]
        fallthrough
    case 2:
        module = parts[1]
    }
    return // Named return values are used, so just return here
}
        
func set_header(w http.ResponseWriter) {
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "*")
}