OSDN Git Service

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