OSDN Git Service

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