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 }