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 }