1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.backend.report.html.tmpl; 11 12 import std.conv : to; 13 import std.format : format; 14 import std.json : JSONValue; 15 16 import arsd.dom : Document, Element, require, Table, RawSource; 17 import my.named_type; 18 19 @safe: 20 21 Document tmplBasicPage() @trusted { 22 auto doc = new Document(`<html lang="en"> 23 <head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></head> 24 <body></body> 25 </html> 26 `); 27 return doc; 28 } 29 30 /// Add the CSS style after the head element. 31 Document dashboardCss(Document doc) @trusted { 32 //import dextool.plugin.mutate.backend.resource : tmplDefaultCss; 33 import arsd.dom : RawSource; 34 import dextool.plugin.mutate.backend.resource : dashboard, jsIndex, tmplDefaultCss; 35 36 auto data = dashboard(); 37 38 auto style = doc.root.childElements("head")[0].addChild("style"); 39 style.addChild(new RawSource(doc, data.bootstrapCss.get)); 40 style.addChild(new RawSource(doc, data.dashboardCss.get)); 41 style.addChild(new RawSource(doc, tmplDefaultCss)); 42 43 return doc; 44 } 45 46 Document filesCss(Document doc) @trusted { 47 import dextool.plugin.mutate.backend.resource : tmplDefaultCss; 48 49 auto style = doc.root.childElements("head")[0].addChild("style"); 50 style.appendText(tmplDefaultCss); 51 52 return doc; 53 } 54 55 Table tmplDefaultTable(Element n, string[] header) @trusted { 56 auto base = tmplTable(n); 57 58 auto tr = base.div.parentDocument.createElement("tr"); 59 foreach (h; header) { 60 tr.addChild("th", h); 61 } 62 63 base.tbl.addChild("thead").appendChild(tr); 64 return base.tbl; 65 } 66 67 Table tmplSortableTable(Element n, string[] header, void delegate(Element e, 68 string header) tdCallback = null) @trusted { 69 import std.range : enumerate; 70 import std.format : format; 71 import dextool.plugin.mutate.backend.report.html.constants : DashboardCss; 72 73 auto base = tmplTable(n); 74 DashboardCss.sortableTableDiv(base.div); 75 DashboardCss.sortableTable(base.tbl); 76 77 auto tr = base.div.parentDocument.createElement("tr"); 78 foreach (h; header.enumerate) { 79 auto th = tr.addChild("th", h.value); 80 if (tdCallback) 81 tdCallback(th, h.value); 82 83 DashboardCss.sortableTableCol(th).setAttribute("id", 84 format!"col-%s"(h.index)).appendText(" ").addChild("i").addClass("right"); 85 } 86 87 base.tbl.addChild("thead").appendChild(tr); 88 return base.tbl; 89 } 90 91 private struct TableData { 92 Element div; 93 Table tbl; 94 } 95 96 private TableData tmplTable(Element n) @trusted { 97 import dextool.plugin.mutate.backend.report.html.constants : DashboardCss; 98 99 auto div = n.addChild("div"); 100 auto tbl = div.addChild("table").require!Table; 101 DashboardCss.defaultTable(tbl); 102 103 return TableData(div, tbl); 104 } 105 106 Table tmplDefaultMatrixTable(Element n, string[] header) @trusted { 107 import dextool.plugin.mutate.backend.report.html.constants; 108 109 auto tbl = n.addChild("table").require!Table; 110 tbl.addClass(MatrixTable.style); 111 112 auto tr = n.parentDocument.createElement("tr"); 113 foreach (h; header) { 114 auto th = tr.addChild("th", h); 115 th.addClass(MatrixTable.hdrStyle); 116 } 117 118 tbl.addChild("thead").appendChild(tr); 119 120 return tbl; 121 } 122 123 struct PieGraph { 124 alias Width = NamedType!(long, Tag!"PieGraphWidth", long.init, TagStringable); 125 static struct Item { 126 string label; 127 string color; 128 double value; 129 } 130 131 /// Name of the chart 132 string name; 133 134 /// Containted items. 135 Item[] items; 136 137 string data() { 138 import std.algorithm : map; 139 import std.array : array; 140 141 JSONValue j; 142 j["type"] = "pie"; 143 j["data"] = () { 144 JSONValue data_; 145 146 data_["datasets"] = [ 147 () { 148 JSONValue d; 149 d["data"] = items.map!(a => a.value).array; 150 d["backgroundColor"] = items.map!(a => a.color).array; 151 d["label"] = name; 152 return d; 153 }() 154 ]; 155 156 data_["options"] = () { 157 JSONValue d; 158 JSONValue tooltips; 159 tooltips["enable"] = true; 160 d["tooltips"] = tooltips; 161 return d; 162 }(); 163 164 data_["labels"] = items.map!(a => a.label).array; 165 return data_; 166 }(); 167 168 return format!"var %1$sData = %2$s;"(name, j.toString); 169 } 170 171 Element canvas(const Width w) @trusted { 172 auto root = new Element("div", ["style": format!"width:%s%%"(w.get)]); 173 root.addChild(new Element("canvas", ["id": name])); 174 return root; 175 } 176 177 /// Call to initialize the graph with data. 178 string initCall() @trusted { 179 return format!"var ctx%1$s = document.getElementById('%1$s').getContext('2d');\nvar chart%1$s = new Chart(ctx%1$s, %1$sData);\n"( 180 name); 181 } 182 183 void html(Element root, const Width w) @trusted { 184 root.addChild(canvas(w)); 185 root.addChild("script").innerRawSource(data ~ initCall); 186 } 187 } 188 189 struct TimeScalePointGraph { 190 import std.datetime : SysTime; 191 192 alias Width = NamedType!(long, Tag!"PieGraphWidth", long.init, TagStringable); 193 static struct Point { 194 SysTime x; 195 double value; 196 } 197 198 static struct Sample { 199 Point[] values; 200 string bgColor; 201 } 202 203 /// Name of the chart 204 string name; 205 206 /// The key is the sample name. 207 Sample[string] samples; 208 209 this(string name) { 210 this.name = name; 211 } 212 213 void setColor(string sample, string c) { 214 samples.update(sample, { return Sample(null, c); }, (ref Sample s) { 215 s.bgColor = c; 216 }); 217 } 218 219 void put(string sample, Point p) { 220 samples.update(sample, { return Sample([p], "blue"); }, (ref Sample s) { 221 s.values ~= p; 222 }); 223 } 224 225 string data() { 226 import std.algorithm : map; 227 import std.array : array, appender; 228 229 JSONValue j; 230 j["type"] = "line"; 231 j["data"] = () { 232 JSONValue data_; 233 data_["datasets"] = () { 234 auto app = appender!(JSONValue[])(); 235 foreach (sample; samples.byKeyValue) { 236 JSONValue d; 237 d["label"] = sample.key; 238 d["backgroundColor"] = sample.value.bgColor; 239 d["borderColor"] = sample.value.bgColor; 240 d["fill"] = false; 241 auto data = appender!(JSONValue[])(); 242 foreach (v; sample.value.values) { 243 JSONValue tmp; 244 tmp["x"] = v.x.toISOExtString; 245 tmp["y"] = v.value; 246 data.put(tmp); 247 } 248 d["data"] = data.data; 249 app.put(d); 250 } 251 return app.data; 252 }(); 253 254 return data_; 255 }(); 256 257 j["options"] = () { 258 JSONValue d; 259 d["title"] = () { 260 JSONValue tmp; 261 tmp["display"] = true; 262 tmp["text"] = name; 263 return tmp; 264 }(); 265 266 d["tooltips"] = () { JSONValue tmp; tmp["enable"] = true; return tmp; }(); 267 268 d["scales"] = () { 269 JSONValue x; 270 x["type"] = "time"; 271 x["display"] = true; 272 x["scaleLabel"] = ["display": "true", "labelString": "Date"]; 273 x["ticks"] = ["major": ["fontStyle": "bold"]]; 274 275 JSONValue y; 276 y["display"] = true; 277 y["scaleLabel"] = ["display": "true", "labelString": "value"]; 278 279 JSONValue tmp; 280 tmp["xAxes"] = [x]; 281 tmp["yAxes"] = [y]; 282 return tmp; 283 }(); 284 285 return d; 286 }(); 287 288 return format!"var %1$sData = %2$s;"(name, j.toString); 289 } 290 291 Element canvas(const Width w) @trusted { 292 auto root = new Element("div", ["style": format!"width:%s%%"(w.get)]); 293 root.addChild(new Element("canvas", ["id": name])); 294 return root; 295 } 296 297 /// Call to initialize the graph with data. 298 string initCall() @trusted { 299 return format!"var ctx%1$s = document.getElementById('%1$s').getContext('2d');\nvar chart%1$s = new Chart(ctx%1$s, %1$sData);\n"( 300 name); 301 } 302 303 void html(Element root, const Width w, const string script_addon = "") @trusted { 304 root.addChild(canvas(w)); 305 root.addChild("script").innerRawSource(data ~ script_addon ~ initCall); 306 } 307 }