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) @trusted { 68 import std.range : enumerate; 69 import std.format : format; 70 import dextool.plugin.mutate.backend.report.html.constants : DashboardCss; 71 72 auto base = tmplTable(n); 73 DashboardCss.sortableTableDiv(base.div); 74 DashboardCss.sortableTable(base.tbl); 75 76 auto tr = base.div.parentDocument.createElement("tr"); 77 foreach (h; header.enumerate) { 78 auto th = tr.addChild("th", h.value); 79 DashboardCss.sortableTableCol(th).setAttribute("id", 80 format!"col-%s"(h.index)).appendText(" ").addChild("i").addClass("right"); 81 } 82 83 base.tbl.addChild("thead").appendChild(tr); 84 return base.tbl; 85 } 86 87 private struct TableData { 88 Element div; 89 Table tbl; 90 } 91 92 private TableData tmplTable(Element n) @trusted { 93 import dextool.plugin.mutate.backend.report.html.constants : DashboardCss; 94 95 auto div = n.addChild("div"); 96 auto tbl = div.addChild("table").require!Table; 97 DashboardCss.defaultTable(tbl); 98 99 return TableData(div, tbl); 100 } 101 102 Table tmplDefaultMatrixTable(Element n, string[] header) @trusted { 103 import dextool.plugin.mutate.backend.report.html.constants; 104 105 auto tbl = n.addChild("table").require!Table; 106 tbl.addClass(MatrixTable.style); 107 108 auto tr = n.parentDocument.createElement("tr"); 109 foreach (h; header) { 110 auto th = tr.addChild("th", h); 111 th.addClass(MatrixTable.hdrStyle); 112 } 113 114 tbl.addChild("thead").appendChild(tr); 115 116 return tbl; 117 } 118 119 struct PieGraph { 120 alias Width = NamedType!(long, Tag!"PieGraphWidth", long.init, TagStringable); 121 static struct Item { 122 string label; 123 string color; 124 double value; 125 } 126 127 /// Name of the chart 128 string name; 129 130 /// Containted items. 131 Item[] items; 132 133 string data() { 134 import std.algorithm : map; 135 import std.array : array; 136 137 JSONValue j; 138 j["type"] = "pie"; 139 j["data"] = () { 140 JSONValue data_; 141 142 data_["datasets"] = [ 143 () { 144 JSONValue d; 145 d["data"] = items.map!(a => a.value).array; 146 d["backgroundColor"] = items.map!(a => a.color).array; 147 d["label"] = name; 148 return d; 149 }() 150 ]; 151 152 data_["options"] = () { 153 JSONValue d; 154 JSONValue tooltips; 155 tooltips["enable"] = true; 156 d["tooltips"] = tooltips; 157 return d; 158 }(); 159 160 data_["labels"] = items.map!(a => a.label).array; 161 return data_; 162 }(); 163 164 return format!"var %1$sData = %2$s;"(name, j.toString); 165 } 166 167 Element canvas(const Width w) @trusted { 168 auto root = new Element("div", ["style": format!"width:%s%%"(w.get)]); 169 root.addChild(new Element("canvas", ["id": name])); 170 return root; 171 } 172 173 /// Call to initialize the graph with data. 174 string initCall() @trusted { 175 return format!"var ctx%1$s = document.getElementById('%1$s').getContext('2d');\nvar chart%1$s = new Chart(ctx%1$s, %1$sData);\n"( 176 name); 177 } 178 179 void html(Element root, const Width w) @trusted { 180 root.addChild(canvas(w)); 181 root.addChild("script").innerRawSource(data ~ initCall); 182 } 183 } 184 185 struct TimeScalePointGraph { 186 import std.datetime : SysTime; 187 188 alias Width = NamedType!(long, Tag!"PieGraphWidth", long.init, TagStringable); 189 static struct Point { 190 SysTime x; 191 double value; 192 } 193 194 static struct Sample { 195 Point[] values; 196 string bgColor; 197 } 198 199 /// Name of the chart 200 string name; 201 202 /// The key is the sample name. 203 Sample[string] samples; 204 205 this(string name) { 206 this.name = name; 207 } 208 209 void setColor(string sample, string c) { 210 samples.update(sample, { return Sample(null, c); }, (ref Sample s) { 211 s.bgColor = c; 212 }); 213 } 214 215 void put(string sample, Point p) { 216 samples.update(sample, { return Sample([p], "blue"); }, (ref Sample s) { 217 s.values ~= p; 218 }); 219 } 220 221 string data() { 222 import std.algorithm : map; 223 import std.array : array, appender; 224 225 JSONValue j; 226 j["type"] = "line"; 227 j["data"] = () { 228 JSONValue data_; 229 data_["datasets"] = () { 230 auto app = appender!(JSONValue[])(); 231 foreach (sample; samples.byKeyValue) { 232 JSONValue d; 233 d["label"] = sample.key; 234 d["backgroundColor"] = sample.value.bgColor; 235 d["borderColor"] = sample.value.bgColor; 236 d["fill"] = false; 237 auto data = appender!(JSONValue[])(); 238 foreach (v; sample.value.values) { 239 JSONValue tmp; 240 tmp["x"] = v.x.toISOExtString; 241 tmp["y"] = v.value; 242 data.put(tmp); 243 } 244 d["data"] = data.data; 245 app.put(d); 246 } 247 return app.data; 248 }(); 249 250 return data_; 251 }(); 252 253 j["options"] = () { 254 JSONValue d; 255 d["title"] = () { 256 JSONValue tmp; 257 tmp["display"] = true; 258 tmp["text"] = name; 259 return tmp; 260 }(); 261 262 d["tooltips"] = () { JSONValue tmp; tmp["enable"] = true; return tmp; }(); 263 264 d["scales"] = () { 265 JSONValue x; 266 x["type"] = "time"; 267 x["display"] = true; 268 x["scaleLabel"] = ["display": "true", "labelString": "Date"]; 269 x["ticks"] = ["major": ["fontStyle": "bold"]]; 270 271 JSONValue y; 272 y["display"] = true; 273 y["scaleLabel"] = ["display": "true", "labelString": "value"]; 274 275 JSONValue tmp; 276 tmp["xAxes"] = [x]; 277 tmp["yAxes"] = [y]; 278 return tmp; 279 }(); 280 281 return d; 282 }(); 283 284 return format!"var %1$sData = %2$s;"(name, j.toString); 285 } 286 287 Element canvas(const Width w) @trusted { 288 auto root = new Element("div", ["style": format!"width:%s%%"(w.get)]); 289 root.addChild(new Element("canvas", ["id": name])); 290 return root; 291 } 292 293 /// Call to initialize the graph with data. 294 string initCall() @trusted { 295 return format!"var ctx%1$s = document.getElementById('%1$s').getContext('2d');\nvar chart%1$s = new Chart(ctx%1$s, %1$sData);\n"( 296 name); 297 } 298 299 void html(Element root, const Width w) @trusted { 300 root.addChild(canvas(w)); 301 root.addChild("script").innerRawSource(data ~ initCall); 302 } 303 }