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