OSDN Git Service

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