OSDN Git Service

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