OSDN Git Service

出撃統計で単独でドロップしたアイテムが計上されないのを直す
[kancollesniffer/KancolleSniffer.git] / LogViewer / tags.tag
1 <dummy>
2 </dummy>
3
4 <main-tab>
5 <ul class="tab">
6     <li each={name, i in mainTabs} class={select: mainTab === i} onclick={parent.changeTab}>{name}</li>
7 </ul>
8
9 <script>
10 /* global moment, c3, opts */
11
12 this.mainTab = +sessionStorage.getItem('prevTab');
13 opts.observable.trigger("mainTabChanged", this.mainTab);
14
15 this.changeTab = function(e) {
16     this.mainTab = e.item.i;
17     sessionStorage.setItem('prevTab', e.item.i);
18     opts.observable.trigger("mainTabChanged", e.item.i);
19 }.bind(this);
20 </script>
21 </main-tab>
22
23 <log-term>
24 <div id="term" show={enabled}>
25 <ul class="tab tabsub" style="float: left; margin-right: 0.2em">
26     <li each={name, i in rangeTabs} class={select: opts.logRange.val === i} onclick={parent.rangeTabChange}>{name}</li>
27 </ul>
28 <div style="padding: 0.2em 0;">
29 <input type="text" id="term_from" style="width: 7em">~<input type="text" id="term_to" style="width: 7em">
30 </div>
31 </div>
32
33 <script>
34 var self = this;
35
36 this.rangeTabs = [
37 "今日",
38 "今週",
39 "今月",
40 "すべて",
41 "期間指定"
42 ];
43
44 this.enabled = false;
45
46 opts.observable.on("mainTabChanged", function(idx) {
47     self.update({enabled: idx >= 0 && idx < self.logTables});
48 });
49
50 var val = sessionStorage.getItem('logRange');
51 opts.logRange.val = val === null ? 2 : +val;
52
53 this.init = function() {
54     $('#term_from').datepicker({
55         onClose: function() {
56             if (opts.logRange.val === 4)
57                 opts.observable.trigger("logRangeChanged");
58         }
59     });
60     $('#term_to').datepicker({
61         onClose: function() {
62             if (opts.logRange.val === 4)
63                 opts.observable.trigger("logRangeChanged");
64         }
65     });
66 };
67
68 this.on("mount", this.init);
69
70 this.rangeTabChange = function(e) {
71     sessionStorage.setItem("logRange", e.item.i);
72     opts.logRange.val = e.item.i;
73     opts.observable.trigger("logRangeChanged");
74 };
75
76 </script>
77 </log-term>
78
79 <log-tables>
80 <div each={header, i in tables} show={mainTab === i}>
81 <table class="display compact cell-border" id={"log" + i}>
82 <thead>
83 <tr></tr>
84 </thead>
85 </table>
86 </div>
87
88 <script>
89 this.tables = [
90 "<th>日付</th><th>海域</th><th>マス</th><th>ボス</th><th>ランク</th><th>ドロップ艦種</th><th>ドロップ艦娘", // ドロップ
91 "<th>日付</th><th style=\"min-width: 3.2em;\">海域</th><th>マス</th><th>ボス</th><th>ランク</th><th>艦隊行動</th><th>味方陣形</th><th>敵陣形</th><th style=\"min-width: 3.2em;\">敵艦隊</th><th>味方艦1</th><th>味方艦1HP</th><th>味方艦2</th><th>味方艦2HP</th><th>味方艦3</th><th>味方艦3HP</th><th>味方艦4</th><th>味方艦4HP</th><th>味方艦5</th><th>味方艦5HP</th><th>味方艦6</th><th>味方艦6HP</th><th>大破艦</ht><th style=\"min-width: 2.2em;\">敵艦1</th><th>敵艦1HP</th><th style=\"min-width: 2.2em;\">敵艦2</th><th>敵艦2HP</th><th style=\"min-width: 2.2em;\">敵艦3</th><th>敵艦3HP</th><th style=\"min-width: 2.2em;\">敵艦4</th><th>敵艦4HP</th><th style=\"min-width: 2.2em;\">敵艦5</th><th>敵艦5HP</th><th style=\"min-width: 2.2em;\">敵艦6</th><th>敵艦6HP</th><th>味方制空値</th><th>敵制空値</th><th>制空状態</th>", // 海戦
92 "<th>日付</th><th>結果</th><th>遠征</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>開発資材</th><th>高速修復材</th><th>高速建造材</th>", // 遠征
93 "<th>日付</th><th>開発装備</th><th>種別</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>秘書艦</th><th>司令部Lv</th>", // 開発
94 "<th>日付</th><th>種類</th><th>名前</th><th>艦種</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>開発資材</th><th>空きドック</th><th>秘書艦</th><th>司令部Lv</th>", // 建造
95 "<th>日付</th><th>改修装備</th><th>レベル</th><th>成功</th><th>確実化</th><th>消費装備</th><th>消費数</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>開発資材</th><th>改修資材</th><th>秘書艦</th><th>二番艦</th>", // 改修
96 "<th>日付</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>高速建造材</th><th>高速修復材</th><th>開発資材</th><th>改修資材</th>" // 戦果
97 ];
98
99 this.jsons = [
100     "海戦・ドロップ報告書.json",
101     "海戦・ドロップ報告書.json",
102     "遠征報告書.json",
103     "開発報告書.json",
104     "建造報告書.json",
105     "改修報告書.json",
106     "資材ログ.json"
107 ];
108
109 this.on("mount", function() {
110     var records = this.root.querySelectorAll("tr");
111     for (var i = 0; i < records.length; i++)
112         records[i].innerHTML = this.tables[i];
113     this.init();
114 });
115
116 this.mainTab = 0;
117 var self = this;
118
119 opts.observable.on("mainTabChanged", function(idx) {
120     self.update({mainTab: idx});
121     self.show();
122 });
123
124 opts.observable.on("logRangeChanged", function() {
125     self.show();
126 });
127
128 this.init = function() {
129     for (var t = 0; t < this.tables.length; t++) {
130         var opts = {
131             destroy: true,
132             deferRender: true,
133             stateSave: true,
134             order: [[0, "desc"]],
135             pageLength: 50,
136             lengthMenu: [[50, 100, 200, -1], [50, 100, 200, "All"]],
137             drawCallback: function() {
138                 $('#loading').hide();
139             }
140         };
141         if (t === 0) {
142             opts.columns = [{ data: 0 }, { data: 1 }, { data: 2 }, { data: 3 }, { data: 4 }, { data: 9 }, { data: 10 }];
143         } else if (t === 1) {
144             var entries = [];
145             for (var i = 0; i < 39; i++) {
146                 if (i === 9 || i === 10)
147                     continue;
148                 entries.push({ data: i });
149             }
150             opts.columns = entries;
151         }
152         $('#log' + t).dataTable(opts);
153     }
154 };
155
156 this.show = function() {
157     if (this.mainTab >= this.jsons.length)
158         return;
159     var now = moment();
160     var from;
161     var query = "?from=";
162     switch (opts.logRange.val){
163     case 0:
164         from = now.clone().startOf('day').hours(5);
165         if (now.hour() < 5)
166             from.subtract(1, 'days');
167         query += from.valueOf();
168         break;
169     case 1:
170         from = now.clone().startOf('week').hours(5);
171         if (now.hour() < 5 && now.days() === 1)
172             from.subtract(1, 'weeks');
173         query += from.valueOf();
174         break;
175     case 2:
176         if (now.hours() >= 22 &&
177             now.dates() === now.clone().endOf('month').date()) {
178             from = now.clone().hours(22);
179         } else {
180             from = now.clone().startOf('month').subtract(1, 'days').hours(22);
181         }
182         query += from.valueOf();
183         break;
184     case 3:
185         query = "";
186         break;
187     case 4:
188         from = $('#term_from').datepicker("getDate");
189         var to = $('#term_to').datepicker("getDate");
190         if (from === null)
191             return;
192         from = moment(from);
193         if (from.date() === 1)
194             from.subtract(2, 'hours');
195         query += from.valueOf();
196         if (to !== null) {
197             to = moment(to);
198             if (to.date() === to.clone().endOf('month').date()) {
199                 to.hour(22);
200             } else {
201                 to.add(1, 'days').hours(5);
202             }
203             query += "&to=" + to.valueOf();
204         }
205         break;
206     }
207     $('#loading').show();
208     var url = this.jsons[this.mainTab] + query;
209     $('#log' + this.mainTab).DataTable().ajax.url(url).load();
210 };
211 </script>
212 </log-tables>
213
214 <chart-type>
215 <form id="chart_type" show={mainTabs[mainTab] === "資材グラフ"}>
216 <div style="margin: 0 0 0.5em 1em;">
217 <label><input type="radio" name="chart_type" value="0" checked={opts.chartSpec.type === 0} onchange={chartTypeChange}>連続</label>
218 <label><input type="radio" name="chart_type" value="1" checked={opts.chartSpec.type === 1} onchange={chartTypeChange}>差分</label>
219 </div>
220 </form>
221
222 <script>
223 this.mainTab = 0;
224 opts.chartSpec.type = +sessionStorage.getItem('chartType');
225 var self = this;
226
227 this.chartTypeChange = function(e) {
228     opts.chartSpec.type = +e.target.value;
229     sessionStorage.setItem('chartType', opts.chartSpec.type);
230     opts.observable.trigger("chartTypeChanged");
231     opts.observable.trigger("chartSpecChanged");
232 };
233
234 opts.observable.on("mainTabChanged", function(idx) {
235     self.update({mainTab: idx});
236 });
237 </script>
238 </chart-type>
239
240 <chart-range>
241 <div show={mainTabs[mainTab] === "資材グラフ"}>
242 <ul class="tab tabsub" style="float: left; margin-right: 0.2em" show={chartSpec.type === 0}>
243     <li each={name, i in seqChartRanges} class={select: chartSpec.seqRange === i} onclick={parent.rangeTabChange}>{name}</li>
244 </ul>
245
246 <ul class="tab tabsub" style="float: left; margin-right: 0.2em" show={chartSpec.type === 1}>
247     <li each={name, i in diffChartRanges} class={select: chartSpec.diffRange === i} onclick={parent.rangeTabChange}>{name}</li>
248 </ul>
249 <div style="padding: 0.2em 0;">
250 <input type="text" id="chart_from" style="width: 7em">~<input type="text" id="chart_to" style="width: 7em">
251 <label><input type="checkbox" id="tooltip" value="" style="margin-left: 2em;" onchange={tooltipChange} checked={opts.chartSpec.tooltip === 1}>ツールチップ</label>
252 </div>
253 </div>
254
255 <script>
256 this.seqChartRanges = [
257 "一日",
258 "一週間",
259 "一か月",
260 "三か月",
261 "すべて",
262 "期間指定"
263 ];
264
265 this.diffChartRanges = [
266 "一か月(日)",
267 "三か月(日)",
268 "半年(週)",
269 "すべて(月)",
270 "期間指定"
271 ];
272
273 opts.chartSpec.seqRange = +sessionStorage.getItem('seqChartRange');
274 opts.chartSpec.diffRange = +sessionStorage.getItem('diffChartRange');
275 opts.chartSpec.tooltip = +sessionStorage.getItem('chartTooltip');
276 this.chartSpec = opts.chartSpec;
277
278 this.rangeTabChange = function(e) {
279     if (opts.chartSpec.type === 0) {
280         opts.chartSpec.seqRange = e.item.i;
281         sessionStorage.setItem('seqChartRange', e.item.i);
282     } else {
283         opts.chartSpec.diffRange = e.item.i;
284         sessionStorage.setItem('diffChartRange', e.item.i);
285     }
286     opts.observable.trigger("chartSpecChanged");
287 };
288
289 this.tooltipChange = function(e) {
290     opts.chartSpec.tooltip = +e.target.checked;
291     sessionStorage.setItem('chartTooltip', +e.target.checked);
292     opts.observable.trigger("chartSpecChanged");
293 };
294
295 this.useDatePicker = function() {
296     return opts.chartSpec.type === 0 && opts.chartSpec.seqRange === 5 ||
297         opts.chartSpec.type === 1 && opts.chartSpec.diffRange === 4;
298 };
299
300 this.init = function() {
301     $('#chart_from').datepicker({
302         onClose: function() {
303             if (self.useDatePicker())
304                 opts.observable.trigger("chartSpecChanged");
305         }
306     });
307     $('#chart_to').datepicker({
308         onClose: function() {
309             if (self.useDatePicker())
310                 opts.observable.trigger("chartSpecChanged");
311         }
312     });
313 };
314
315 this.mainTab = 0;
316 var self = this;
317
318 this.on("mount", self.init);
319
320 opts.observable.on("mainTabChanged", function(idx) {
321     self.update({mainTab: idx});
322 });
323
324 opts.observable.on("chartTypeChanged", function() {
325     self.update();
326 });
327 </script>
328 </chart-range>
329
330 <sequential-chart>
331 <script>
332 var self = this;
333
334 opts.observable.on("chartSpecChanged", function() {
335     if (opts.chartSpec.type === 0)
336         self.drawChart();
337 });
338
339 opts.observable.on("chartSizeChanged", function() {
340     if (opts.chartSpec.type === 0)
341         self.resize();
342 });
343
344 this.header = ["日付", "燃料", "弾薬", "鋼材", "ボーキ", "高速建造材", "高速修復材", "開発資材", "改修資材"];
345
346 opts.observable.on("offAllLegends", function() {
347     if (opts.chartSpec.type !== 0)
348         return;
349     self.chart.hide();
350     self.header.slice(1).forEach(function(c) {
351         self.unselected[c] = true;
352     });
353 });
354
355 this.resize = function() {
356     if (!self.chart)
357         return;
358     $('#loading').show();
359     setTimeout(function() {
360         self.chart.resize(self.chartSize());
361     });
362 };
363
364 this.drawChart = function(data) {
365     var range = this.calcRange(opts.chartSpec.seqRange);
366     if (range.last === 0)
367         return;
368     if (!data) {
369         $('#loading').show();
370         $.ajax({
371             url: "./資材ログ.json?number=true" +
372                 "&from=" + range.first + "&to=" + range.last,
373             success: function(d) { self.drawChart(d); },
374             dataType: "json", cache: false
375         });
376         return;
377     }
378     var picked;
379     picked = this.pickChartData(data.data, range);
380     picked.data.unshift(self.header);
381     this.drawSeqChart(picked);
382 };
383
384 this.calcRange = function(range) {
385     var first = 0;
386     var last = (new Date()).valueOf();
387     switch (range) {
388         case 0:
389             first = moment(last).subtract(24, 'hours').valueOf();
390             break;
391         case 1:
392             first = moment(last).subtract(7, 'days').valueOf();
393             break;
394         case 2:
395             first = moment(last).subtract(1, 'months').valueOf();
396             break;
397         case 3:
398             first = moment(last).subtract(3, 'months').valueOf();
399             break;
400         case 4:
401             break;
402         case 5:
403             var fromDate = $('#chart_from').datepicker("getDate");
404             var toDate = $('#chart_to').datepicker("getDate");
405             if (fromDate === null || toDate === null)
406                 return {first: 0, last:0};
407             first = fromDate.valueOf() + 3600 * 5000;
408             last = toDate.valueOf() + this.oneDay + 3600 * 5000;
409             break;
410     }
411     return {first: first, last: last};
412 };
413
414 this.unselected = {};
415
416 this.drawSeqChart = function(picked) {
417     var size = this.chartSize();
418     this.chart = c3.generate({
419         bindto: '#chart',
420         size: {
421             height: size.height,
422             width: size.width
423         },
424         data: {
425             x: '日付',
426             xFormat: '%Y-%m-%d %H:%M:%S',
427             rows: picked.data,
428             axes: {
429                 燃料: 'y',
430                 弾薬: 'y',
431                 鋼材: 'y',
432                 ボーキ: 'y',
433                 高速建造材: 'y2',
434                 高速修復材: 'y2',
435                 開発資材: 'y2',
436                 改修資材: 'y2'
437             }
438         },
439         point: {
440             show: false
441         },
442         tooltip: {
443             show: opts.chartSpec.tooltip
444         },
445         grid: {
446             x: {
447                 lines: picked.grid
448             }
449         },
450         axis: {
451             x: {
452                 type: 'timeseries',
453                 tick: {
454                     rotate: 30,
455                     format: "%m-%d %H:%M",
456                     values: picked.tick
457                 }
458             },
459             y2: {
460                 show: true
461             }
462         },
463         legend: {
464             item: {
465                 onclick: function(id) {
466                     self.unselected[id] = !self.unselected[id];
467                     self.chart.toggle(id);
468                 }
469             }
470         },
471         onrendered: function() {
472             $('#loading').hide();
473             opts.observable.trigger("chartRendered");
474         }
475     });
476     self.chart.hide(Object.keys(self.unselected).filter(function(e) {
477         return self.unselected[e];
478     }));
479 };
480
481 this.pickChartData = function(data, range) {
482     var newdata = [];
483     var ticks = [];
484     var grid = [];
485     var first = range.first;
486     var last = range.last;
487     var interval, tickInterval, lastTick;
488     if (last <= first + this.oneDay) {
489         interval = 1000;
490         tickInterval = 3600 * 1000;
491         lastTick = last - last % tickInterval;
492     } else if (last <= first + this.oneDay * 21) {
493         interval = 1000;
494         tickInterval = this.oneDay;
495         lastTick = this.to5am(last);
496     } else if (last <= first + this.oneDay * 63) {
497         interval = 3600 * 1000;
498         tickInterval = this.oneDay * 7;
499         lastTick = this.to5am(moment(last).day(1).valueOf());
500     } else if (last <= first + this.oneDay * 126) {
501         interval = 3600 * 6000;
502         tickInterval = this.oneDay * 14;
503         lastTick = this.to5am(moment(last).day(1).valueOf());
504     } else {
505         var magn = Math.ceil((last - data[0][0]) / (this.oneDay * 365) / 2);
506         interval = this.oneDay * magn;
507         tickInterval = this.oneDay * 28 * magn;
508         lastTick = this.to5am(moment(last).day(1).valueOf());
509     }
510     var lastData;
511     for (var i = data.length - 1; i >= 0; i--) {
512         var row = data[i];
513         var date = row[0];
514         if (date >= first) {
515             if (date <= last) {
516                 var v = date - date % interval;
517                 if (lastData !== v) {
518                     newdata.unshift(row);
519                     lastData = v;
520                 }
521             }
522         } else {
523             break;
524         }
525     }
526     for (var tick = lastTick; tick > lastData; tick -= tickInterval) {
527         var str = self.toDateString(moment(tick));
528         ticks.unshift(str);
529         grid.unshift({ value: str });
530     }
531     return { data: newdata, tick: ticks, grid: grid };
532 };
533 </script>
534 </sequential-chart>
535
536 <differential-chart>
537 <script>
538 var self = this;
539
540 opts.observable.on("chartSpecChanged", function() {
541     if (opts.chartSpec.type === 1)
542         self.drawChart();
543 });
544
545 opts.observable.on("chartSizeChanged", function() {
546     if (opts.chartSpec.type === 1)
547         self.resize();
548 });
549
550 this.header = ["日付", "燃料", "弾薬", "鋼材", "ボーキ"];
551
552 opts.observable.on("offAllLegends", function() {
553     if (opts.chartSpec.type !== 1)
554         return;
555     self.chart.hide();
556     self.header.slice(1).forEach(function(c) {
557         self.unselected[c] = true;
558     });
559 });
560
561 this.resize = function() {
562     if (!self.chart)
563         return;
564     $('#loading').show();
565     setTimeout(function() {
566         self.chart.resize(self.chartSize());
567     });
568 };
569
570 this.drawChart = function(data) {
571     var range = this.calcRange(opts.chartSpec.diffRange);
572     if (range.last === 0)
573         return;
574     if (!data) {
575         $('#loading').show();
576         $.ajax({
577             url: "./資材ログ.json?number=true" +
578                 "&from=" + range.first + "&to=" + range.last,
579             success: function(d) { self.drawChart(d); },
580             dataType: "json", cache: false
581         });
582         return;
583     }
584     var picked;
585     picked = this.pickChartData(data.data, range);
586     picked.data.unshift(self.header);
587     this.drawDiffChart(picked);
588 };
589
590 this.calcRange = function(range) {
591     var first = 0;
592     var last = (new Date()).valueOf();
593     switch (range) {
594         case 0:
595             first = moment(last).subtract(1, 'months').valueOf();
596             break;
597         case 1:
598             first = moment(last).subtract(3, 'months').valueOf();
599             break;
600         case 2:
601             first = moment(last).subtract(6, 'months').subtract(1, 'weeks').valueOf();
602             break;
603         case 3:
604             break;
605         case 4:
606             var fromDate = $('#chart_from').datepicker("getDate");
607             var toDate = $('#chart_to').datepicker("getDate");
608             if (fromDate === null || toDate === null)
609                 return {first: 0, last: 0};
610             var from = fromDate.valueOf() + 3600 * 5000;
611             var to = toDate.valueOf() + this.oneDay + 3600 * 5000;
612             first = Math.max(first, from);
613             last = Math.min(last, to);
614             break;
615     }
616     return {first: first, last: last};
617 };
618
619 this.unselected = {};
620
621 this.drawDiffChart = function(picked) {
622     var size = this.chartSize();
623     this.chart = c3.generate({
624         bindto: '#chart',
625         size: {
626             height: size.height,
627             width: size.width
628         },
629         data: {
630             x: '日付',
631             rows: picked.data,
632             axes: {
633                 燃料: 'y',
634                 弾薬: 'y',
635                 鋼材: 'y',
636                 ボーキ: 'y'
637             },
638             type: 'bar',
639             groups: [["燃料", "弾薬", "鋼材", "ボーキ"]]
640         },
641         bar: {
642             width: {
643                 ratio: picked.width
644             }
645         },
646         tooltip: {
647             show: opts.chartSpec.tooltip
648         },
649         grid: {
650             x: {
651                 lines: picked.grid
652             },
653             y: {
654                 lines: [
655                     { value: 0 }
656                 ]
657             }
658         },
659         axis: {
660             x: {
661                 type: 'timeseries',
662                 tick: {
663                     rotate: 30,
664                     format: picked.monthly ? "%Y-%m" : "%m-%d %H:%M",
665                     values: picked.tick
666                 }
667             }
668         },
669         legend: {
670             item: {
671                 onclick: function(id) {
672                     self.unselected[id] = !self.unselected[id];
673                     self.chart.toggle(id);
674                 }
675             }
676         },
677         onrendered: function() {
678             $('#loading').hide();
679             opts.observable.trigger("chartRendered");
680         }
681     });
682     self.chart.hide(Object.keys(self.unselected).filter(function(e) {
683         return self.unselected[e];
684     }));
685 };
686
687 this.pickChartData = function(data, range) {
688     var newdata = [];
689     var ticks = [];
690     var grid = [];
691     var first = range.first;
692     var last = range.last;
693     var interval, tickInterval, lastTick;
694     var barWidth;
695     if (first === 0)
696         return this.pickMonthlyChartData(data);
697     if (last <= first + this.oneDay * 2 * 31) {
698         interval = this.oneDay;
699         tickInterval = this.oneDay * 2;
700         lastTick = this.to5am(last);
701         barWidth = 0.3;
702     } else if (last <= first + this.oneDay * 3 * 31) {
703         interval = this.oneDay;
704         tickInterval = this.oneDay * 7;
705         lastTick = this.to5am(last);
706         barWidth = 0.1;
707     } else {
708         interval = this.oneDay * 7;
709         tickInterval = this.oneDay * 28;
710         lastTick = this.to5am(moment(last).day(1).valueOf());
711         barWidth = 0.1;
712         if (last <= first + this.oneDay * 6 * 38) {
713             tickInterval = this.oneDay * 14;
714             barWidth = 0.3;
715         }
716     }
717     var lastDate = lastTick;
718     var prevRow;
719     for (var i = data.length - 1; i >= 0; i--) {
720         var row = data[i];
721         var date = row[0];
722         if (date > first) {
723             if (date <= last) {
724                 if (!prevRow) {
725                     prevRow = row;
726                     continue;
727                 }
728                 if (date <= lastDate) {
729                     var newrow = [lastDate];
730                     for (var r = 1; r < 5; r++) {
731                         newrow.push(prevRow[r] - row[r]);
732                     }
733                     newdata.unshift(newrow);
734                     lastDate = lastDate - interval;
735                     prevRow = row;
736                 }
737             }
738         } else {
739             break;
740         }
741     }
742     if (tickInterval >= this.oneDay * 7)
743         lastTick = moment(lastTick).day(1).hour(5).minute(0).valueOf();
744     for (var tick = lastTick; tick > lastDate; tick -= tickInterval) {
745         ticks.unshift(tick);
746         grid.unshift({ value: tick });
747     }
748     return { data: newdata, tick: ticks, grid: grid, width: barWidth };
749 };
750
751 this.pickMonthlyChartData = function(data) {
752     var newdata = [];
753     var ticks = [];
754     var grid = [];
755     var prevRow;
756     var prevMonth;
757     var row;
758     var date;
759     for (var i = data.length - 1; i >= 0; i--) {
760         row = data[i];
761         if (!prevRow) {
762             prevRow = row;
763             var eom = moment(row[0]).endOf('month');
764             prevRow[0] = eom.valueOf();
765             prevMonth = eom.month();
766             continue;
767         }
768         date = new Date(row[0]);
769         if (prevMonth !== date.getMonth()) {
770             var newrow = [prevRow[0]];
771             for (var r = 1; r < 5; r++)
772                 newrow.push(prevRow[r] - row[r]);
773             newdata.unshift(newrow);
774             ticks.unshift(prevRow[0]);
775             grid.unshift({ value: prevRow[0] });
776             prevRow = row;
777             prevMonth = date.getMonth();
778         }
779     }
780     if (prevRow && date !== prevRow[0]) {
781         newrow = [prevRow[0]];
782         for (r = 1; r < 5; r++)
783             newrow.push(prevRow[r] - row[r]);
784         newdata.unshift(newrow);
785         ticks.unshift(prevRow[0]);
786         grid.unshift({ value: prevRow[0] });
787     }
788     return { monthly: true, data: newdata, tick: ticks, grid: grid, width: 0.5 };
789 };
790 </script>
791 </differential-chart>
792
793 <material-chart>
794 <div show={mainTabs[mainTab] === "資材グラフ"}>
795 <span class="c3-legend-item" id="off-all-legends" style="text-decoration: underline; cursor: pointer; z-index: 10; position: absolute; display: none;" onclick={offAllLegends} >全解除</span>
796 <div id="chart" style="clear: both; margin: 1em;"></div>
797 </div>
798
799 <script>
800 this.mainTab = 0;
801 var self = this;
802
803 opts.observable.on("mainTabChanged", function(idx) {
804     self.update({mainTab: idx});
805     if (self.mainTabs[idx] === "資材グラフ")
806         opts.observable.trigger("chartSpecChanged");
807 });
808
809 opts.observable.on("chartRendered", function() {
810     var legend, offset;
811     if (opts.chartSpec.type === 0) {
812         legend = $(".c3-legend-item-改修資材>text").offset();
813         offset = 80;
814     } else {
815         legend = $(".c3-legend-item-ボーキ>text").offset();
816         offset = 60;
817     }
818     if (legend)
819         $("#off-all-legends").offset({top: legend.top, left: legend.left + offset}).show();
820 });
821
822 this.offAllLegends = function() {
823     opts.observable.trigger("offAllLegends");
824 };
825
826 this.timer = null;
827 $(window).resize(function() {
828     if (self.timer)
829         clearTimeout(self.timer);
830     self.timer = setTimeout(function() {
831         if (self.mainTabs[self.mainTab] === "資材グラフ")
832             opts.observable.trigger("chartSizeChanged");
833         else if (self.mainTabs[self.mainTab] === "戦果")
834             opts.observable.trigger("achivementChartSizeChanged");
835     }, 200);
836 });
837 </script>
838 </material-chart>
839
840 <achivement-table>
841 <div show={mainTabs[mainTab] === "戦果"}>
842 <span style="margin-left: 1em;">期間:&nbsp;</span><select style="width: 7em; margin-bottom: 1em;" name="月" onchange={monthChange}>
843 <option each={m, i in months} value={m}>{m}</option>
844 </select>
845 <table id="achivement_table" class="display compact cell-border">
846 <thead>
847 <tr><th>日付</th><th>戦果</th><th>EO</th><th>月毎</th></tr>
848 </thead>
849 </table>
850 <div id="achivementChart" style="margin: 1em;"></div>
851 </div>
852
853 <script>
854 this.on("mount", function() {
855     $("#achivement_table").dataTable({
856         destroy: true,
857         deferRener: true,
858         stateSave: true,
859         order: [[0, "desc"]],
860         paging: false,
861         searching: false,
862         info: false,
863         drawCallback: function() {
864             $('#loading').hide();
865         }
866     });
867 });
868
869 var self = this;
870
871 opts.observable.on("mainTabChanged", function(idx) {
872     self.update({mainTab: idx});
873     if (self.mainTabs[self.mainTab] === "戦果")
874         self.updateData();
875 });
876
877 this.months = [];
878 this.selectedIndex = 0;
879
880 this.monthChange = function(event) {
881     this.selectedIndex = event.target.selectedIndex;
882     if (this.selectedIndex === 0) {
883         this.updateData();
884         return;
885     }
886     this.show();
887 };
888
889 this.calcResult = function(data) {
890     this.result = {};
891     var expPerAch = 10000 / 7.0;
892     var dayEo = 0;
893     var endOfMonth = moment(0);
894     var monthExp = 0;
895     var monthEo = 0;
896     var endOfYear = moment(0);
897     var yearExp = 0;
898     var carryOverAch = 0;
899     var carryOverEo = 0;
900     var prevExp = null;
901     var lastDate = moment(0);
902     var lastExp = -1;
903     var nextDate = moment(0);
904     for (var i = 0; i < data.length; i++) {
905         var row = data[i];
906         var date = this.parseDate(row[0]);
907         var exp = row[1] - 0;
908         var eo = row[2] - 0;
909         var isNewYear = date.isSameOrAfter(endOfYear);
910         var isNewMonth = date.isSameOrAfter(endOfMonth);
911         var isNewDate = date.isSameOrAfter(nextDate);
912         if (isNewDate || isNewMonth || isNewYear) {
913             if (lastDate.add(1, 'hours').isSameOrBefore(date)) {
914                 // 2時を過ぎて最初のexpを戦果の計算に使うと、2時をまたいだ出撃の戦果が前日に加算される。
915                 // そこで2時前のexpを使って戦果を計算するが、2時前のexpが正しく出力されていない場合は
916                 // 戦果を正しく計算できない。記録の間隔が1時間以上空いているときは、2時をまたいだ出撃が
917                 // 行われていない可能性が高いので計算には今のexpを使うことにする。
918                 // これは5時基準で出力された過去のデータで、妥当な戦果を計算するために必要な処理である。
919                 lastExp = exp;
920             }
921             if (nextDate.valueOf() !== 0) {
922                 var d = isNewDate ? nextDate.subtract(1, 'days') : endOfMonth;
923                 var m = d.format("YYYY-MM");
924                 if (!this.result[m])
925                     this.result[m] = [];
926                 this.result[m].push([
927                     d.format("YYYY-MM-DD"),
928                     ((lastExp - prevExp) / expPerAch).toFixed(1), dayEo,
929                     ((lastExp - monthExp) / expPerAch + monthEo + carryOverAch + carryOverEo).toFixed(1)
930                 ]);
931             }
932             prevExp = lastExp === -1 ? exp : lastExp;
933             if (isNewYear) {
934                 endOfYear = date.clone().endOf('year').hour(22).startOf('hour');
935                 if (endOfYear.isSameOrBefore(date))
936                     endOfYear.add(1, 'year');
937                 yearExp = lastExp === -1 ? exp : lastExp;
938                 monthEo = 0;
939             }
940             if (isNewMonth) {
941                 endOfMonth = date.clone().endOf('month');
942                 if (date.date() === endOfMonth.date())
943                     endOfMonth.add(1, 'months').endOf('month');
944                 endOfMonth.hour(22).startOf('hour');
945                 monthExp = lastExp === -1 ? exp : lastExp;
946                 carryOverEo = monthEo * expPerAch / 50000;
947                 carryOverAch = (monthExp - yearExp) / 50000;
948                 monthEo = 0;
949                 m = endOfMonth.format("YYYY-MM");
950                 if (!this.result[m])
951                     this.result[m] = [];
952                 this.result[m].push([endOfMonth.format("YYYY-MM 引継"),
953                     carryOverAch.toFixed(1), carryOverEo.toFixed(1), (carryOverAch + carryOverEo).toFixed(1)]);
954             }
955             dayEo = 0;
956             nextDate = date.clone().hour(2).startOf('hour');
957             if (date.hour() >= 2)
958                 nextDate.add(1, 'days');
959             if (nextDate.date() === 1)
960                 nextDate.add(1, 'days');
961         }
962         if (date.isBefore(date.clone().endOf('month').hour(22).startOf('hour'))) {
963             // 月末22時から翌0時までのEOのボーナス戦果は消える。
964             dayEo += eo;
965             monthEo += eo;
966         }
967         lastDate = date;
968         lastExp = exp;
969     }
970 };
971
972 this.calcChartData = function() {
973     this.chartData = {};
974     for (var month in this.result) {
975         var data = this.chartData[month] = [];
976         var result = this.result[month];
977         var eo = 0;
978         var d = 0;
979         data.push(["日付", "戦果", "EO", "月毎"]);
980         for (var i = 0; i < result.length; i++) {
981             var row = result[i];
982             if (row[0].match(/引継/)) {
983                 eo = row[2] - 0;
984                 data.push([0, row[1], row[2], row[3]]);
985                 continue;
986             }
987             d = moment(row[0], "YYYY-MM-DD").date();
988             eo += row[2];
989             var ach = (row[3] - eo).toFixed(1);
990             data.push([d, ach, eo, row[3]]);
991         }
992         var endOfMonth = moment(month, "YYYY-MM").endOf("month").date();
993         while (d < endOfMonth) {
994             d++;
995             data.push([d, null, null, null]);
996         }
997     }
998 };
999
1000 this.chartSize = function() {
1001     var width = Math.max($(window).width() - 6 * this.pxPerEm, 800);
1002     return {
1003         height: width * 0.4,
1004         width: width
1005     };
1006 };
1007
1008 opts.observable.on("achivementChartSizeChanged", function() {
1009     if (!self.chart)
1010         return;
1011     $('#loading').show();
1012     setTimeout(function() {
1013         self.chart.resize(self.chartSize());
1014     });
1015 });
1016
1017 this.showChart = function(month) {
1018     this.chart = c3.generate({
1019         bindto: "#achivementChart",
1020         size: this.chartSize(),
1021         data: {
1022             x: "日付",
1023             rows: this.chartData[month],
1024             types: {
1025                 戦果: "area",
1026                 EO: "area",
1027                 月毎: "area"
1028             }
1029         },
1030         onrendered: function() { $('#loading').hide(); }
1031     });
1032 };
1033
1034 this.updateData = function(data) {
1035     if (!data) {
1036         $('#loading').show();
1037         $.ajax({
1038             url: "./戦果.json",
1039             success: function(data) {
1040                 self.updateData(data.data);
1041             },
1042             dataType: 'json',
1043             cache: false
1044         });
1045         return;
1046     }
1047     this.calcResult(data);
1048     this.calcChartData();
1049     this.months = Object.keys(this.result).sort(function(a, b) {
1050         if (a === b)
1051             return 0;
1052         if (a < b)
1053             return 1;
1054         return -1;
1055     });
1056     this.update();
1057     this.show();
1058 };
1059
1060 this.show = function() {
1061     if (this.result === undefined){
1062         this.updateData();
1063         return;
1064     }
1065     var dt = $('#achivement_table').DataTable();
1066     dt.clear();
1067     dt.rows.add(this.result[this.months[this.selectedIndex]]).draw();
1068     this.showChart(this.months[this.selectedIndex]);
1069 };
1070 </script>
1071 </achivement-table>
1072
1073 <sortie-stat>
1074 <div show={mainTabs[mainTab] === "出撃統計"}>
1075
1076 <ul class="tab tabsub" style="float: left; margin-right: 0.2em">
1077 <li each={tabs} class={select: parent.type === type} onclick={parent.changeTab}>{label}</li>
1078 </ul>
1079
1080 <div style="padding: 0.2em 0;">
1081 <input type="text" id="sortie_stat_from" style="width: 7em">~<input type="text" id="sortie_stat_to" style="width: 7em">
1082 </div>
1083
1084 <div style="clear: both;" show={type === "recent"}>
1085 <h3>今日</h3>
1086 <table id="sortie_stat_day">
1087 </table>
1088 <h3>今週</h3>
1089 <table id="sortie_stat_week">
1090 </table>
1091 <h3>今月</h3>
1092 <table id="sortie_stat_month">
1093 </table>
1094 </div>
1095
1096 <div show={type === "range"}>
1097 <table id="sortie_stat_all" style="width: 100%;">
1098 </table>
1099 </div>
1100
1101 </div>
1102
1103 <script>
1104 this.tabs = [
1105     {
1106         type: "recent",
1107         label: "直近"
1108     },
1109     {
1110         type: "range",
1111         label: "期間指定"
1112     }
1113 ];
1114 this.type = "recent";
1115 this.changeTab = function(e) {
1116     this.type = e.item.type;
1117     this.show();
1118 }.bind(this);
1119
1120 this.mainTab = 0;
1121 var self = this;
1122
1123 this.on("mount", function() {
1124     $("[id^=sortie]").addClass('display compact cell-border');
1125     this.init();
1126 });
1127
1128 opts.observable.on("mainTabChanged", function(idx) {
1129     self.update({mainTab: idx});
1130     if (self.mainTabs[self.mainTab] === "出撃統計")
1131         self.show();
1132 });
1133
1134 this.init = function() {
1135     this.initDatePicker();
1136 };
1137
1138 this.initDatePicker = function() {
1139     $('#sortie_stat_from').datepicker({
1140         onClose: function() { if (self.type === "range") self.show(); }
1141     });
1142     $('#sortie_stat_to').datepicker({
1143         onClose: function() { if (self.type === "range") self.show(); }
1144     });
1145 };
1146
1147 var self = this;
1148
1149 this.loadData = function() {
1150     var from, to;
1151     if (this.type === "recent") {
1152         from = moment().subtract(1, 'months').subtract(1, 'day').valueOf();
1153         to = new Date().valueOf();
1154     } else {
1155         var fromDate = $('#sortie_stat_from').datepicker("getDate");
1156         var toDate = $('#sortie_stat_to').datepicker("getDate");
1157         if (fromDate === null || toDate === null) {
1158             this.show([]);
1159             return;
1160         }
1161         from = moment(fromDate);
1162         if (from.date() === 1)
1163             from.subtract(2, 'hours');
1164         from = from.valueOf();
1165         to = moment(toDate);
1166         if (to.date() === to.clone().endOf('month').date()) {
1167             to.hour(22);
1168         } else {
1169             to.endOf('day');
1170         }
1171         to = to.valueOf();
1172     }
1173     $.ajax({
1174         url: "./海戦・ドロップ報告書.json?from=" + from + "&to=" + to,
1175         success: function(data) { self.show(data.data); },
1176         dataType: "json", cache: false
1177     });
1178 };
1179
1180 this.initResult = function() {
1181     var now = moment();
1182     var r;
1183     if (this.type === "recent") {
1184         r = {
1185             day: { stat: {} },
1186             week: { stat: {} },
1187             month: { stat: {} }
1188         };
1189         r.day.begin = moment(now).hour(5).minute(0).second(0);
1190         if (now.hour() < 5) {
1191             r.day.begin.subtract(1, 'days');
1192         }
1193         r.week.begin = moment(now).day(1).hour(5).minute(0).second(0);
1194         if (now.day() === 0 || now.day() === 1 && now.hour() < 5) {
1195             r.week.begin.subtract(1, 'weeks');
1196         }
1197         if (moment(now).endOf('month').date() === now.date() &&
1198             now.hour() >= 22) { // 月末22時以降
1199             r.month.begin = moment(now).hour(22).minute(0).second(0);
1200         } else {
1201             r.month.begin =
1202                 moment(now).date(1).subtract(1, 'days').
1203                     hour(22).minute(0).second(0);
1204         }
1205     } else {
1206         r = { all: { stat: {} } };
1207         r.all.begin = moment(0);
1208     }
1209     return r;
1210 };
1211
1212 this.gatherData = function(data) {
1213     var initStat = function() {
1214         return { start: "-", S: 0, A: 0, B: 0, C: 0, D: 0, R: 0 };
1215     };
1216     var r = this.initResult();
1217     for (var i = 0; i < data.length; i++) {
1218         var row = data[i];
1219         var date = moment(row[0]);
1220         var map = row[1];
1221         var isBoss = row[3].indexOf("ボス") !== -1;
1222         var isStart = row[3].indexOf("出撃") !== -1;
1223         var resR = 0;
1224         for (var j = 23; j < row.length; j++) {
1225             if (/^輸送/.test(row[j]) && /^0\x2f/.test(row[j + 1]))
1226                 resR++;
1227         }
1228         var item = /アイテム/.test(row[9]) ? /[^+]+$/.exec(row[10])[0] : null;
1229         var res = row[4];
1230         if (res === "E")
1231             res = "D";
1232         for (var term in r) {
1233             if (!r.hasOwnProperty(term))
1234                 continue;
1235             var to = r[term];
1236             if (to.begin.isAfter(date))
1237                 continue;
1238             for (var b = 0; b < 4; b++) {
1239                 var name = b < 2 ? "合計" : map;
1240                 if (b === 1 || b === 3) {
1241                     if (!isBoss)
1242                         continue;
1243                     name = name + " - ボス";
1244                 }
1245                 var mo = to.stat[name];
1246                 if (!mo) {
1247                     mo = to.stat[name] = initStat();
1248                     if (name === "合計")
1249                         to.stat["合計 - ボス"] = initStat();
1250                 }
1251                 mo["R"] += resR;
1252                 mo[res]++;
1253                 if (item) {
1254                     if (!mo[item])
1255                         mo[item] = 0;
1256                     mo[item]++;
1257                 }
1258                 if ((b === 0 || b === 2) && isStart) {
1259                     if (mo.start === "-")
1260                         mo.start = 0;
1261                     mo.start++;
1262                 }
1263             }
1264         }
1265     }
1266     return r;
1267 };
1268
1269 this.isItemColumn = function(col) {
1270     return !/^(?:map|start|[SABCDR])$/.test(col);
1271 };
1272
1273 this.sortItemOrder = function(items) {
1274     ["お米", "梅干", "海苔", "お茶"].reverse().forEach(function(item) {
1275         var idx = items.indexOf(item);
1276         if (idx !== -1) {
1277             items.splice(idx, 1);
1278             items.unshift(item);
1279         }
1280     });
1281 };
1282
1283 this.setupTable = function(r) {
1284     for (var term in r) {
1285         var header = ["マップ",
1286                   "出撃",
1287                   "S",
1288                   "A",
1289                   "B",
1290                   "C",
1291                   "D以下",
1292                   "輸送船"];
1293         var columns = [{ data: "map" },
1294                    { data: "start" },
1295                    { data: "S" },
1296                    { data: "A" },
1297                    { data: "B" },
1298                    { data: "C" },
1299                    { data: "D" },
1300                    { data: "R" }];
1301         if (term === "month") {
1302             header.pop();
1303             columns.pop();
1304         }
1305         var items = [];
1306         for (var col in r[term].stat["合計"]) {
1307             if (this.isItemColumn(col))
1308                 items.push(col);
1309         }
1310         this.sortItemOrder(items);
1311         items.forEach(function(item) {
1312             header.push(item);
1313             columns.push({data: item});
1314         });
1315         $("#sortie_stat_" + term).html(
1316             "<thead><tr>" +
1317             header.reduce(function(acc, cur) {
1318                 return acc + "<th>" + cur + "</th>";
1319             }, "")
1320             + "</tr></thead>"
1321         );
1322         r[term].columns = columns;
1323     }
1324 };
1325
1326 this.fillupItemRecords = function(r) {
1327     for (var term in r) {
1328         for (var col in r[term].stat["合計"]) {
1329             if (!this.isItemColumn(col))
1330                 continue;
1331             for (var map in r[term].stat) {
1332                 if (map === "合計")
1333                     continue;
1334                 if (!r[term].stat[map][col]){
1335                     r[term].stat[map][col] = 0;
1336                 }
1337             }
1338         }
1339     }
1340 };
1341
1342 this.reorderRows = function(r) {
1343     for (var term in r) {
1344         if (!r.hasOwnProperty(term))
1345             continue;
1346         var table = [];
1347         var pushed = {};
1348         for (var map in r[term].stat) {
1349             if (!r[term].stat.hasOwnProperty(map))
1350                 continue;
1351             if (pushed[map])
1352                 continue;
1353             var e = r[term].stat[map];
1354             e.map = map;
1355             table.push(e);
1356             pushed[map] = 1;
1357             var boss = map + " - ボス";
1358             e = r[term].stat[boss];
1359             if (!e)
1360                 continue;
1361             e.map = boss;
1362             table.push(e);
1363             pushed[boss] = 1;
1364         }
1365         r[term].table = table;
1366     }
1367 };
1368
1369 this.show = function(data) {
1370     if (!data) {
1371         $('#loading').show();
1372         this.loadData();
1373         return;
1374     }
1375     var r = this.gatherData(data);
1376     this.setupTable(r);
1377     this.fillupItemRecords(r);
1378     this.reorderRows(r);
1379     for (var term in r) {
1380         if (!r.hasOwnProperty(term))
1381             continue;
1382         var table = $("#sortie_stat_" + term);
1383         table.DataTable().destroy();
1384         table.DataTable({
1385             paging: false,
1386             searching: false,
1387             ordering: false,
1388             columns: r[term].columns
1389         }).rows.add(r[term].table).draw();
1390     }
1391     $('#loading').hide();
1392 };
1393 </script>
1394 </sortie-stat>