github.com/serge-v/zero@v1.0.2-0.20220911142406-af4b6a19e68a/examples/chart/main.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"net/http"
     9  	"os"
    10  	"text/template"
    11  	"time"
    12  
    13  	"github.com/serge-v/zero"
    14  	"github.com/wcharczuk/go-chart/v2"
    15  
    16  	_ "embed"
    17  	_ "time/tzdata"
    18  )
    19  
    20  var deploy = flag.Bool("deploy", false, "")
    21  var standalone = flag.Bool("standalone", false, "")
    22  
    23  func main() {
    24  	flag.Parse()
    25  	log.SetFlags(log.LstdFlags | log.Lshortfile)
    26  
    27  	if *standalone {
    28  		if err := saveChart("sensor1", "1.png"); err != nil {
    29  			log.Fatal(err)
    30  		}
    31  		return
    32  	}
    33  
    34  	if *deploy {
    35  		if err := zero.Deploy(8096); err != nil {
    36  			log.Fatal(err)
    37  		}
    38  		return
    39  	}
    40  
    41  	http.HandleFunc("/", handleRoot)
    42  	http.HandleFunc("/chart.png", handleChart)
    43  	http.HandleFunc("/chart.svg", handleSVGChart)
    44  	http.HandleFunc("/data.csv", handleCsv)
    45  	http.HandleFunc("/events.csv", handleEvents)
    46  
    47  	log.Println("starting on http://127.0.0.1:8096")
    48  
    49  	if err := http.ListenAndServe("127.0.0.1:8096", nil); err != nil {
    50  		log.Fatal(err)
    51  	}
    52  }
    53  
    54  //go:embed main.html
    55  var mainPage string
    56  
    57  //go:embed chart.svg
    58  var chartTemplate string
    59  
    60  func handleRoot(w http.ResponseWriter, r *http.Request) {
    61  	fmt.Fprintln(w, mainPage)
    62  }
    63  
    64  func handleChart(w http.ResponseWriter, r *http.Request) {
    65  	fname := r.URL.Query().Get("f")
    66  	w.Header().Set("Content-Type", "image/png")
    67  	generateChart(w, fname)
    68  }
    69  
    70  func handleSVGChart(w http.ResponseWriter, r *http.Request) {
    71  	fname := r.URL.Query().Get("f")
    72  	log.Println("chart svg")
    73  	w.Header().Set("Content-Type", "image/svg+xml")
    74  	if err := generateSVGChart(w, fname); err != nil {
    75  		log.Println(err)
    76  	}
    77  }
    78  
    79  func handleEvents(w http.ResponseWriter, r *http.Request) {
    80  	now := time.Now().Truncate(time.Hour * 24).Add(time.Hour * 4)
    81  	fmt.Fprint(w, "time,event\n")
    82  	for i := -3; i < 1; i++ {
    83  		ts := now.Add(time.Hour * 24 * time.Duration(i))
    84  		fmt.Fprint(w, ts.Format(time.RFC3339), ",midnight", "\n")
    85  		ts = now.Add(time.Hour*24*time.Duration(i) + time.Hour*12)
    86  		fmt.Fprint(w, ts.Format(time.RFC3339), ",noon", "\n")
    87  	}
    88  }
    89  
    90  func handleCsv(w http.ResponseWriter, r *http.Request) {
    91  	fname := r.URL.Query().Get("f")
    92  	lines, err := fetchSensorLog(fname)
    93  	if err != nil {
    94  		http.Error(w, err.Error(), http.StatusInternalServerError)
    95  		return
    96  	}
    97  
    98  	records, err := parseLogLines(lines)
    99  	if err != nil {
   100  		http.Error(w, err.Error(), http.StatusInternalServerError)
   101  		return
   102  	}
   103  
   104  	w.Header().Set("Access-Control-Allow-Origin", "*")
   105  	fmt.Fprint(w, "time,moisture,battery,hall\n")
   106  	for _, r := range records {
   107  		fmt.Fprint(w, r.ts.Add(time.Hour*4).Format(time.RFC3339), ",", r.moisture, ",", fmt.Sprintf("%.2f", r.battery), ",", r.hall, "\n")
   108  	}
   109  }
   110  
   111  func saveChart(fname, outfname string) error {
   112  	f, err := os.Create(outfname)
   113  	if err != nil {
   114  		return fmt.Errorf("save: %w", err)
   115  	}
   116  	defer f.Close()
   117  	if err := generateChart(f, fname); err != nil {
   118  		return fmt.Errorf("generate: %w", err)
   119  	}
   120  	return nil
   121  }
   122  
   123  func generateChart(w io.Writer, fname string) error {
   124  	lines, err := fetchSensorLog(fname)
   125  	if err != nil {
   126  		return fmt.Errorf("fetch: %w", err)
   127  	}
   128  
   129  	records, err := parseLogLines(lines)
   130  	if err != nil {
   131  		return fmt.Errorf("parse: %w", err)
   132  	}
   133  
   134  	var xvalues []time.Time
   135  	var yvalues []float64
   136  
   137  	for _, r := range records {
   138  		xvalues = append(xvalues, r.ts)
   139  		yvalues = append(yvalues, float64(r.moisture))
   140  	}
   141  
   142  	var ygrid []chart.GridLine
   143  
   144  	st := chart.Style{DotWidth: 1, DotColor: chart.ColorBlue, StrokeWidth: chart.Disabled}
   145  	lowHumidity := chart.Style{StrokeWidth: 3, StrokeColor: chart.ColorRed}
   146  
   147  	for i := 300; i < 800; i += 50 {
   148  		ygrid = append(ygrid, chart.GridLine{Value: float64(i)})
   149  		ygrid = append(ygrid, chart.GridLine{Value: float64(i + 25), Style: st})
   150  	}
   151  	ygrid = append(ygrid, chart.GridLine{Value: 525, Style: lowHumidity})
   152  
   153  	var yticks []chart.Tick
   154  	for i := 300; i < 800; i += 50 {
   155  		//		yticks = append(yticks, chart.Tick{Value: float64(i), Label: fmt.Sprintf("%d", i)})
   156  	}
   157  
   158  	yticks = append(yticks, chart.Tick{Value: 625, Label: "high"})
   159  	yticks = append(yticks, chart.Tick{Value: 575, Label: "norm"})
   160  	yticks = append(yticks, chart.Tick{Value: 525, Label: "low"})
   161  
   162  	graph := chart.Chart{
   163  		DPI: 144,
   164  		Series: []chart.Series{
   165  			chart.TimeSeries{
   166  				XValues: xvalues,
   167  				YValues: yvalues,
   168  			},
   169  		},
   170  		YAxis: chart.YAxis{
   171  			GridMajorStyle: chart.Style{
   172  				StrokeColor: chart.ColorAlternateGray,
   173  				StrokeWidth: 0.5,
   174  			},
   175  			GridLines: ygrid,
   176  			Ticks:     yticks,
   177  			Range: &chart.ContinuousRange{
   178  				Min: 200,
   179  				Max: 800,
   180  			},
   181  		},
   182  		XAxis: chart.XAxis{
   183  			ValueFormatter: chart.TimeHourValueFormatter,
   184  			GridMajorStyle: chart.Style{
   185  				StrokeColor: chart.ColorAlternateGray,
   186  				StrokeWidth: 1.0,
   187  			},
   188  			GridLines: dayEventsLines(),
   189  			Ticks:     xTicks(),
   190  		},
   191  	}
   192  	if err := graph.Render(chart.PNG, w); err != nil {
   193  		return fmt.Errorf("render: %w", err)
   194  	}
   195  	return nil
   196  }
   197  
   198  func generateSVGChart(w io.Writer, fname string) error {
   199  	lines, err := fetchSensorLog(fname)
   200  	if err != nil {
   201  		return fmt.Errorf("fetch: %w", err)
   202  	}
   203  
   204  	/*
   205  		buf, err := ioutil.ReadFile("sensor.log")
   206  		if err != nil {
   207  			return fmt.Errorf("read sensor log: %w", err)
   208  		}
   209  	*/
   210  	//	lines = strings.Split(string(buf), "\n")
   211  
   212  	records, err := parseLogLines(lines)
   213  	if err != nil {
   214  		return fmt.Errorf("parse: %w", err)
   215  	}
   216  
   217  	type line struct {
   218  		X1, Y1, X2, Y2 int
   219  		Class          string
   220  	}
   221  
   222  	type data struct {
   223  		Lines     []line
   224  		DayLabels []string
   225  		Polyline  string
   226  	}
   227  
   228  	d := data{}
   229  	year, month, day := time.Now().Add(time.Hour * 24).Date()
   230  	loc, err := time.LoadLocation("America/New_York")
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	start := time.Date(year, month, day, 0, 0, 0, 0, loc)
   236  
   237  	for i := 0; i <= 5; i++ {
   238  		ts := start.Add(time.Duration(-i) * time.Hour * 24).Format("1/2")
   239  		d.DayLabels = append(d.DayLabels, ts)
   240  	}
   241  
   242  	if len(records) > 110 {
   243  		records = records[len(records)-110:]
   244  	}
   245  
   246  	var prev line
   247  
   248  	for i, r := range records {
   249  		var ln line
   250  
   251  		if r.moisture < 410 {
   252  			r.moisture = 410
   253  		}
   254  
   255  		ln.X1 = int(start.Sub(r.ts).Minutes()) - 240
   256  		ln.X2 = ln.X1 + 60
   257  		ln.Y1 = r.moisture
   258  		ln.Y2 = r.moisture
   259  		ln.Class = "mhor"
   260  
   261  		println("=== ", start.String(), r.ts.UTC().String(), ln.X1)
   262  
   263  		if i > 0 {
   264  			ln2 := line{X1: prev.X1, X2: prev.X1, Y1: prev.Y1, Y2: ln.Y1, Class: "mver"}
   265  			d.Lines = append(d.Lines, ln2)
   266  		}
   267  
   268  		d.Lines = append(d.Lines, ln)
   269  		prev = ln
   270  	}
   271  
   272  	var t *template.Template
   273  
   274  	_, err = os.Stat("chart.svg")
   275  	if err != nil {
   276  		t, err = template.New("").Parse(chartTemplate)
   277  	} else {
   278  		t, err = template.ParseFiles("chart.svg")
   279  	}
   280  	if err != nil {
   281  		return fmt.Errorf("parse template: %w", err)
   282  	}
   283  
   284  	if err := t.Execute(w, d); err != nil {
   285  		return fmt.Errorf("execute: %w", err)
   286  	}
   287  
   288  	return nil
   289  }