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