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