// Copyright 2019 Gregory Petrosyan <gregory.petrosyan@gmail.com>//// This Source Code Form is subject to the terms of the Mozilla Public// License, v. 2.0. If a copy of the MPL was not distributed with this// file, You can obtain one at https://mozilla.org/MPL/2.0/. package rapid import ( "bytes" "encoding/base64" "fmt" "html/template" "image" "image/color" "image/png" "io" "math" "time") var ( visBitSetColor = color.Black visBitUnsetColor = color.White visTmpl = template.Must(template.New("rapid-vis").Parse(visHTML))) type visTmplData struct { Title string Images [][]*visTmplImage VisCSS template.CSS RebootCSS template.CSS} type visGroupInfo struct { Classes string Label string} type visTmplImage struct { Base64 string Title string Alt string Width int Height int GroupBegins []visGroupInfo GroupEnds []visGroupInfo} func visWriteHTML(w io.Writer, title string, recData []recordedBits) error { d := &visTmplData{ Title: fmt.Sprintf("%v (%v)", title, time.Now().Format(time.RFC1123)), VisCSS: template.CSS(visCSS), RebootCSS: template.CSS(visRebootCSS), } labelClasses := map[string]string{} lastLabelClass := 0 for _, rd := range recData { var images []*visTmplImage for _, u := range rd.data { tmplImg, err := visNewUint64Image(u).toTmplImage() if err != nil { return err } images = append(images, tmplImg) } for _, group := range rd.groups { if _, ok := labelClasses[group.label]; !ok { labelClasses[group.label] = fmt.Sprintf("label-%v", lastLabelClass) lastLabelClass++ } } for _, group := range rd.groups { images[group.begin].GroupBegins = append(images[group.begin].GroupBegins, visGroupToInfo(labelClasses, group)) if group.end > 0 { images[group.end-1].GroupEnds = append(images[group.end-1].GroupEnds, visGroupToInfo(labelClasses, group)) } } d.Images = append(d.Images, images) } return visTmpl.Execute(w, d)} func visGroupToInfo(labelClasses map[string]string, group groupInfo) visGroupInfo { discardClass := "" if group.discard { discardClass = "discard " } endlessClass := "" if group.end <= 0 { endlessClass = "endless " } return visGroupInfo{ Classes: discardClass + endlessClass + labelClasses[group.label], Label: group.label, }} type visUint64Image struct { u uint64 p color.Palette} func visNewUint64Image(u uint64) *visUint64Image { s := newRandomBitStream(u, false) h1 := genFloat01(s) h2 := genFloat01(s) return &visUint64Image{ u: u, p: color.Palette{visBitSetColor, visBitUnsetColor, visHsv(h1*360, 1, 1), visHsv(h2*360, 1, 1)}, }} func (img *visUint64Image) ColorModel() color.Model { return img.p} func (img *visUint64Image) Bounds() image.Rectangle { return image.Rect(0, 0, 64, 2)} func (img *visUint64Image) ColorIndexAt(x, y int) uint8 { switch y { case 0: if (x/8)%2 == 0 { return 2 } return 3 default: if img.u&(1<<uint(63-x)) != 0 { return 0 } return 1 }} func (img *visUint64Image) At(x, y int) color.Color { return img.p[img.ColorIndexAt(x, y)]} func (img *visUint64Image) toTmplImage() (*visTmplImage, error) { enc := png.Encoder{ CompressionLevel: png.BestCompression, } var buf bytes.Buffer err := enc.Encode(&buf, img) if err != nil { return nil, err } return &visTmplImage{ Base64: base64.StdEncoding.EncodeToString(buf.Bytes()), Title: fmt.Sprintf("0x%x / %d", img.u, img.u), Alt: fmt.Sprintf("0x%x", img.u), Width: 64, Height: 2, }, nil} const visHTML = `<!doctype html><html lang="en"> <head> <meta charset="utf-8"> <meta name="description" content="rapid debug data visualization"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>[rapid] {{.Title}}</title> <style>{{- .RebootCSS }} </style> <style>{{- .VisCSS }} </style> </head> <body> <h1>{{.Title}}</h1> {{range .Images -}} <div class="vis"> {{range . -}} {{range .GroupBegins}}<span title="{{.Label}}" class="group {{.Classes}}"><span class="label">{{.Label}}</span>{{end}} <img title="{{.Title}}" alt="{{.Alt}}" width="{{.Width}}" height="{{.Height}}" src="data:image/png;base64,{{.Base64}}"> {{range .GroupEnds}}</span>{{end}} {{end}} </div> {{end}} </body></html>` const visCSS = `body { margin: 1rem;} .vis { display: flex; margin-bottom: 1rem;} .vis img { height: 1rem; margin: 0.3rem; image-rendering: pixelated; image-rendering: crisp-edges; image-rendering: -moz-crisp-edges; -ms-interpolation-mode: nearest-neighbor;} .vis .group { position: relative; display: inline-flex; padding: 0 0 1rem; background-color: rgba(0, 0, 0, 0.05); border-radius: 0 0 0.5rem 0.5rem; border-bottom: 2px solid black;} .vis .group.discard { background-color: rgba(255, 0, 0, 0.2);} .vis .group.endless { background-color: white;} .vis .group .label { font-size: 80%; white-space: nowrap; position: absolute; bottom: 0; left: 0.3rem; max-width: 100%; overflow: hidden;} .vis .group.discard > .label { color: red;} .vis .group.label-0 { border-bottom-color: red;}.vis .group.label-1 { border-bottom-color: maroon;}.vis .group.label-2 { border-bottom-color: yellow;}.vis .group.label-3 { border-bottom-color: olive;}.vis .group.label-4 { border-bottom-color: lime;}.vis .group.label-5 { border-bottom-color: green;}.vis .group.label-6 { border-bottom-color: aqua;}.vis .group.label-7 { border-bottom-color: teal;}.vis .group.label-8 { border-bottom-color: blue;}.vis .group.label-9 { border-bottom-color: navy;}.vis .group.label-10 { border-bottom-color: fuchsia;}.vis .group.label-11 { border-bottom-color: purple;}.vis .group.label-12 { border-bottom-color: red;}.vis .group.label-13 { border-bottom-color: maroon;}.vis .group.label-14 { border-bottom-color: yellow;}.vis .group.label-15 { border-bottom-color: olive;}.vis .group.label-16 { border-bottom-color: lime;}.vis .group.label-17 { border-bottom-color: green;}.vis .group.label-18 { border-bottom-color: aqua;}.vis .group.label-19 { border-bottom-color: teal;}.vis .group.label-20 { border-bottom-color: blue;}.vis .group.label-21 { border-bottom-color: navy;}.vis .group.label-22 { border-bottom-color: fuchsia;}.vis .group.label-23 { border-bottom-color: purple;}` const visRebootCSS = `/*! * Bootstrap Reboot v4.1.3 (https://getbootstrap.com/) * Copyright 2011-2018 The Bootstrap Authors * Copyright 2011-2018 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) */*,*::before,*::after { box-sizing: border-box;} html { font-family: sans-serif; line-height: 1.15; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -ms-overflow-style: scrollbar; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);} @-ms-viewport { width: device-width;} article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { display: block;} body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; text-align: left; background-color: #fff;} [tabindex="-1"]:focus { outline: 0 !important;} hr { box-sizing: content-box; height: 0; overflow: visible;} h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5rem;} p { margin-top: 0; margin-bottom: 1rem;} abbr[title],abbr[data-original-title] { text-decoration: underline; -webkit-text-decoration: underline dotted; text-decoration: underline dotted; cursor: help; border-bottom: 0;} address { margin-bottom: 1rem; font-style: normal; line-height: inherit;} ol,ul,dl { margin-top: 0; margin-bottom: 1rem;} ol ol,ul ul,ol ul,ul ol { margin-bottom: 0;} dt { font-weight: 700;} dd { margin-bottom: .5rem; margin-left: 0;} blockquote { margin: 0 0 1rem;} dfn { font-style: italic;} b,strong { font-weight: bolder;} small { font-size: 80%;} sub,sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline;} sub { bottom: -.25em;} sup { top: -.5em;} a { color: #007bff; text-decoration: none; background-color: transparent;} a:hover { color: #0056b3; text-decoration: underline;} a:not([href]):not([tabindex]) { color: inherit; text-decoration: none;} a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { color: inherit; text-decoration: none;} a:not([href]):not([tabindex]):focus { outline: 0;} pre,code,kbd,samp { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 1em;} pre { margin-top: 0; margin-bottom: 1rem; overflow: auto; -ms-overflow-style: scrollbar;} figure {Lines 1–500 of 693. Load more
// Copyright 2019 Gregory Petrosyan <gregory.petrosyan@gmail.com> // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. package rapid import ( "bytes" "encoding/base64" "fmt" "html/template" "image" "image/color" "image/png" "io" "math" "time" ) var ( visBitSetColor = color.Black visBitUnsetColor = color.White visTmpl = template.Must(template.New("rapid-vis").Parse(visHTML)) ) type visTmplData struct { Title string Images [][]*visTmplImage VisCSS template.CSS RebootCSS template.CSS } type visGroupInfo struct { Classes string Label string } type visTmplImage struct { Base64 string Title string Alt string Width int Height int GroupBegins []visGroupInfo GroupEnds []visGroupInfo } func visWriteHTML(w io.Writer, title string, recData []recordedBits) error { d := &visTmplData{ Title: fmt.Sprintf("%v (%v)", title, time.Now().Format(time.RFC1123)), VisCSS: template.CSS(visCSS), RebootCSS: template.CSS(visRebootCSS), } labelClasses := map[string]string{} lastLabelClass := 0 for _, rd := range recData { var images []*visTmplImage for _, u := range rd.data { tmplImg, err := visNewUint64Image(u).toTmplImage() if err != nil { return err } images = append(images, tmplImg) } for _, group := range rd.groups { if _, ok := labelClasses[group.label]; !ok { labelClasses[group.label] = fmt.Sprintf("label-%v", lastLabelClass) lastLabelClass++ } } for _, group := range rd.groups { images[group.begin].GroupBegins = append(images[group.begin].GroupBegins, visGroupToInfo(labelClasses, group)) if group.end > 0 { images[group.end-1].GroupEnds = append(images[group.end-1].GroupEnds, visGroupToInfo(labelClasses, group)) } } d.Images = append(d.Images, images) } return visTmpl.Execute(w, d) } func visGroupToInfo(labelClasses map[string]string, group groupInfo) visGroupInfo { discardClass := "" if group.discard { discardClass = "discard " } endlessClass := "" if group.end <= 0 { endlessClass = "endless " } return visGroupInfo{ Classes: discardClass + endlessClass + labelClasses[group.label], Label: group.label, } } type visUint64Image struct { u uint64 p color.Palette } func visNewUint64Image(u uint64) *visUint64Image { s := newRandomBitStream(u, false) h1 := genFloat01(s) h2 := genFloat01(s) return &visUint64Image{ u: u, p: color.Palette{visBitSetColor, visBitUnsetColor, visHsv(h1*360, 1, 1), visHsv(h2*360, 1, 1)}, } } func (img *visUint64Image) ColorModel() color.Model { return img.p } func (img *visUint64Image) Bounds() image.Rectangle { return image.Rect(0, 0, 64, 2) } func (img *visUint64Image) ColorIndexAt(x, y int) uint8 { switch y { case 0: if (x/8)%2 == 0 { return 2 } return 3 default: if img.u&(1<<uint(63-x)) != 0 { return 0 } return 1 } } func (img *visUint64Image) At(x, y int) color.Color { return img.p[img.ColorIndexAt(x, y)] } func (img *visUint64Image) toTmplImage() (*visTmplImage, error) { enc := png.Encoder{ CompressionLevel: png.BestCompression, } var buf bytes.Buffer err := enc.Encode(&buf, img) if err != nil { return nil, err } return &visTmplImage{ Base64: base64.StdEncoding.EncodeToString(buf.Bytes()), Title: fmt.Sprintf("0x%x / %d", img.u, img.u), Alt: fmt.Sprintf("0x%x", img.u), Width: 64, Height: 2, }, nil } const visHTML = `<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="description" content="rapid debug data visualization"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>[rapid] {{.Title}}</title> <style> {{- .RebootCSS }} </style> <style> {{- .VisCSS }} </style> </head> <body> <h1>{{.Title}}</h1> {{range .Images -}} <div class="vis"> {{range . -}} {{range .GroupBegins}}<span title="{{.Label}}" class="group {{.Classes}}"><span class="label">{{.Label}}</span>{{end}} <img title="{{.Title}}" alt="{{.Alt}}" width="{{.Width}}" height="{{.Height}}" src="data:image/png;base64,{{.Base64}}"> {{range .GroupEnds}}</span>{{end}} {{end}} </div> {{end}} </body> </html>` const visCSS = ` body { margin: 1rem; } .vis { display: flex; margin-bottom: 1rem; } .vis img { height: 1rem; margin: 0.3rem; image-rendering: pixelated; image-rendering: crisp-edges; image-rendering: -moz-crisp-edges; -ms-interpolation-mode: nearest-neighbor; } .vis .group { position: relative; display: inline-flex; padding: 0 0 1rem; background-color: rgba(0, 0, 0, 0.05); border-radius: 0 0 0.5rem 0.5rem; border-bottom: 2px solid black; } .vis .group.discard { background-color: rgba(255, 0, 0, 0.2); } .vis .group.endless { background-color: white; } .vis .group .label { font-size: 80%; white-space: nowrap; position: absolute; bottom: 0; left: 0.3rem; max-width: 100%; overflow: hidden; } .vis .group.discard > .label { color: red; } .vis .group.label-0 { border-bottom-color: red; } .vis .group.label-1 { border-bottom-color: maroon; } .vis .group.label-2 { border-bottom-color: yellow; } .vis .group.label-3 { border-bottom-color: olive; } .vis .group.label-4 { border-bottom-color: lime; } .vis .group.label-5 { border-bottom-color: green; } .vis .group.label-6 { border-bottom-color: aqua; } .vis .group.label-7 { border-bottom-color: teal; } .vis .group.label-8 { border-bottom-color: blue; } .vis .group.label-9 { border-bottom-color: navy; } .vis .group.label-10 { border-bottom-color: fuchsia; } .vis .group.label-11 { border-bottom-color: purple; } .vis .group.label-12 { border-bottom-color: red; } .vis .group.label-13 { border-bottom-color: maroon; } .vis .group.label-14 { border-bottom-color: yellow; } .vis .group.label-15 { border-bottom-color: olive; } .vis .group.label-16 { border-bottom-color: lime; } .vis .group.label-17 { border-bottom-color: green; } .vis .group.label-18 { border-bottom-color: aqua; } .vis .group.label-19 { border-bottom-color: teal; } .vis .group.label-20 { border-bottom-color: blue; } .vis .group.label-21 { border-bottom-color: navy; } .vis .group.label-22 { border-bottom-color: fuchsia; } .vis .group.label-23 { border-bottom-color: purple; } ` const visRebootCSS = ` /*! * Bootstrap Reboot v4.1.3 (https://getbootstrap.com/) * Copyright 2011-2018 The Bootstrap Authors * Copyright 2011-2018 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) */ *, *::before, *::after { box-sizing: border-box; } html { font-family: sans-serif; line-height: 1.15; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -ms-overflow-style: scrollbar; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } @-ms-viewport { width: device-width; } article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { display: block; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; text-align: left; background-color: #fff; } [tabindex="-1"]:focus { outline: 0 !important; } hr { box-sizing: content-box; height: 0; overflow: visible; } h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5rem; } p { margin-top: 0; margin-bottom: 1rem; } abbr[title], abbr[data-original-title] { text-decoration: underline; -webkit-text-decoration: underline dotted; text-decoration: underline dotted; cursor: help; border-bottom: 0; } address { margin-bottom: 1rem; font-style: normal; line-height: inherit; } ol, ul, dl { margin-top: 0; margin-bottom: 1rem; } ol ol, ul ul, ol ul, ul ol { margin-bottom: 0; } dt { font-weight: 700; } dd { margin-bottom: .5rem; margin-left: 0; } blockquote { margin: 0 0 1rem; } dfn { font-style: italic; } b, strong { font-weight: bolder; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sub { bottom: -.25em; } sup { top: -.5em; } a { color: #007bff; text-decoration: none; background-color: transparent; } a:hover { color: #0056b3; text-decoration: underline; } a:not([href]):not([tabindex]) { color: inherit; text-decoration: none; } a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { color: inherit; text-decoration: none; } a:not([href]):not([tabindex]):focus { outline: 0; } pre, code, kbd, samp { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 1em; } pre { margin-top: 0; margin-bottom: 1rem; overflow: auto; -ms-overflow-style: scrollbar; } figure {