OSDN Git Service

各種報告書の実装をRiot.jsを使ってモジュール化する
authorKazuhiro Fujieda <fujieda@users.osdn.me>
Thu, 8 Jun 2017 13:00:07 +0000 (22:00 +0900)
committerKazuhiro Fujieda <fujieda@users.osdn.me>
Sat, 10 Jun 2017 12:50:05 +0000 (21:50 +0900)
LogViewer/index.html
LogViewer/tags.html [new file with mode: 0644]

index 7e107ce..956a261 100644 (file)
@@ -1,9 +1,8 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html lang="ja">
 <head>
 <meta charset="utf-8">
 <title>各種報告書 - KancolleSniffer</title>
-
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.7/js/jquery.dataTables.min.js"></script>
 <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.7/css/jquery.dataTables.min.css">
@@ -14,6 +13,7 @@
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
 <link rel="stylesheet" type="text/css" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
 <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/i18n/datepicker-ja.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/riot/3.5.0/riot+compiler.min.js"></script>
 <style>
 body {
     font-family:'Lucida Grande','Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
@@ -22,10 +22,8 @@ body {
 }
 .tab {overflow: hidden; list-style-type: none; margin: 0 0 2em 1em; padding: 0;}
 .tab li {background: #eee; padding: 0.3em 1.5em; float: left; margin-right: 2px;}
-.tab1 li {padding: 0.3em 1em;}
 .tab li.select {background: #ccc;}
-.contents {list-style-type: none; margin: 0; padding: 0;}
-.hide {display: none;}
+.tabsub li {padding: 0.3em 1em;}
 .c3 .tick {font-family:'Lucida Grande','Hiragino Kaku Gothic ProN', Meiryo, sans-serif; font-size: 12px;}
 .c3-legend-item {font-family:'Lucida Grande','Hiragino Kaku Gothic ProN', Meiryo, sans-serif; font-size: 14px;}
 #loading {
@@ -42,1030 +40,83 @@ body {
 </style>
 </head>
 <body>
-<script>
-/* global moment, c3, $ */
-
-var oneDay = 3600 * 24 * 1000;
-
-function showLog() {
-    var query = "?from=" + moment().subtract(1, 'months').valueOf();
-    if ($('input[name=term]:eq(1)').prop('checked')) {
-        var from = $('#term_from').datepicker("getDate");
-        var to = $('#term_to').datepicker("getDate");
-        if (from != null)
-            query = "?from=" + from.valueOf();
-        if (to != null)
-            query += "&to=" + (to.valueOf() + oneDay);
-    }
-    var jsons = [
-        "海戦・ドロップ報告書.json",
-        "海戦・ドロップ報告書.json",
-        "遠征報告書.json",
-        "開発報告書.json",
-        "建造報告書.json",
-        "改修報告書.json",
-        "資材ログ.json"
-    ];
-    $('#loading').show();
-    var url = jsons[selectedTable] + query;
-    $('#log' + selectedTable).DataTable().ajax.url(url).load();
-}
-
-var tables = 7;
+<div id="loading"><img src="https://kancollesniffer.osdn.jp/ajax-loader.gif" alt="読み込み中..."></div>
 
-function initTables() {
-    for (var t = 0; t < tables; t++) {
-        var opts = {
-            destroy: true,
-            deferRender: true,
-            stateSave: true,
-            order: [[0, "desc"]],
-            pageLength: 50,
-            lengthMenu: [[50, 100, 200, -1], [50, 100, 200, "All"]],
-            drawCallback: function () {
-                $('#loading').hide();
-            }
-        };
-        if (t === 0) {
-            opts.columns = [{ data: 0 }, { data: 1 }, { data: 2 }, { data: 3 }, { data: 4 }, { data: 9 }, { data: 10 }];
-        } else if (t === 1) {
-            var entries = [];
-            for (var i = 0; i < 38; i++) {
-                if (i === 9 || i === 10)
-                    continue;
-                entries.push({ data: i });
-            }
-            opts.columns = entries;
-        }
-        $('#log' + t).dataTable(opts);
-    }
-}
+<script>
+/* global moment, riot */
 
 var timeFormat = "YYYY-MM-DD HH:mm:ss";
-function parseDate(d) {
-    return moment(d, timeFormat);
-}
-
-function toString(d) {
-    return d.format(timeFormat);
-}
-
-function to5am(tick) {
-    return tick - tick % (3600 * 24000) - 3600 * 4000;
-}
-
-var selectedTable = 0;
-var seqChartRange = 0;
-var diffChartRange = 0;
-var chartType = 0;
-var showChart = false;
-var currentPickedData;
-
-function drawChart(data) {
-    if (data == null) {
-        $('#loading').show();
-        $.get("./資材ログ.json?time=" + Date.now(), function (d) { drawChart(d); }, "json");
-        return;
-    }
-    var picked, header;
-    if (chartType === 0) {
-        picked = pickChartData(data.data, seqChartRange);
-        header = ["日付", "燃料", "弾薬", "鋼材", "ボーキ", "高速建造材", "高速修復材", "開発資材", "改修資材"];
-        picked.data.unshift(header);
-        drawSeqChart(picked);
-        currentPickedData = picked;
-    } else {
-        picked = pickDiffChartData(data.data, diffChartRange);
-        header = ["日付", "燃料", "弾薬", "鋼材", "ボーキ"];
-        picked.data.unshift(header);
-        drawDiffChart(picked);
-        currentPickedData = picked;
-    }
-}
-
-var chartSeq, chartDiff;
-
-function redrawChart() {
-    if (!currentPickedData)
-        return;
-    $('#loading').show();
-    setTimeout(function () {
-        if (chartType === 0)
-            chartSeq.resize(chartSize());
-        if (chartType === 1)
-            chartDiff.resize(chartSize());
-    });
-}
-
-var timer;
-$(window).resize(function () {
-    if (timer)
-        clearTimeout(timer);
-    timer = setTimeout(function () {
-        if (showChart)
-            redrawChart();
-    }, 200);
-});
-
-function chartSize() {
-    var pxPerEm = Number($('#chart').css('fontSize').match(/(\d*(\.\d*)?)px/)[1]);
-    return {
-        height: Math.max($(document).height() - 15 * pxPerEm, 400),
-        width: Math.max($(document).width() - 6 * pxPerEm, 800)
-    };
-}
-
-var seqChartUnselected = {};
-
-function drawSeqChart(picked) {
-    var size = chartSize();
-    chartSeq = c3.generate({
-        bindto: '#chart',
-        size: {
-            height: size.height,
-            width: size.width
-        },
-        data: {
-            x: '日付',
-            xFormat: '%Y-%m-%d %X',
-            rows: picked.data,
-            axes: {
-                燃料: 'y',
-                弾薬: 'y',
-                鋼材: 'y',
-                ボーキ: 'y',
-                高速建造材: 'y2',
-                高速修復材: 'y2',
-                開発資材: 'y2',
-                改修資材: 'y2'
-            }
-        },
-        point: {
-            show: false
-        },
-        tooltip: {
-            show: $('#tooltip').prop('checked')
-        },
-        grid: {
-            x: {
-                lines: picked.grid
-            }
-        },
-        axis: {
-            x: {
-                type: 'timeseries',
-                tick: {
-                    rotate: 30,
-                    format: function (x) { return moment(x).format("MM-DD HH:mm"); },
-                    values: picked.tick
-                }
-            },
-            y2: {
-                show: true
-            }
-        },
-        legend: {
-            item: {
-                onclick: function(id) {
-                    seqChartUnselected[id] = !seqChartUnselected[id];
-                    chartSeq.toggle(id);
-                }
-            }
-        },
-        onrendered: function () { $('#loading').hide(); }
-    });
-    for (var id in seqChartUnselected) {
-        if (seqChartUnselected.hasOwnProperty(id) && seqChartUnselected[id])
-            chartSeq.toggle(id);
-    }
-}
-
-function pickChartData(data, range) {
-    var newdata = [];
-    var ticks = [];
-    var grid = [];
-    var first = moment(data[0][0]).valueOf();
-    var last = moment(data[data.length - 1][0]).valueOf();
-    var interval, tickInterval, lastTick;
-    switch (range) {
-        case 0:
-            first = moment(last).subtract(24, 'hours').valueOf();
-            break;
-        case 1:
-            first = moment(last).subtract(7, 'days').valueOf();
-            break;
-        case 2:
-            first = moment(last).subtract(1, 'months').valueOf();
-            break;
-        case 3:
-            first = moment(last).subtract(3, 'months').valueOf();
-            break;
-        case 4:
-            first = moment(data[0][0]).valueOf();
-            break;
-        case 5:
-            var fromDate = $('#chart_from').datepicker("getDate");
-            var toDate = $('#chart_to').datepicker("getDate");
-            if (fromDate == null || toDate == null)
-                return { data: [], tick: [], grid: [] };
-            var from = fromDate.valueOf() + 3600 * 5000;
-            var to = toDate.valueOf() + oneDay + 3600 * 5000;
-            first = Math.max(first, from);
-            last = Math.min(last, to);
-            break;
-    }
-    if (last <= first + oneDay) {
-        interval = 1000;
-        tickInterval = 3600 * 1000;
-        lastTick = last - last % tickInterval;
-    } else if (last <= first + oneDay * 21) {
-        interval = 1000;
-        tickInterval = oneDay;
-        lastTick = to5am(last);
-    } else if (last <= first + oneDay * 63) {
-        interval = 3600 * 1000;
-        tickInterval = oneDay * 7;
-        lastTick = to5am(moment(last).day(1).valueOf());
-    } else if (last <= first + oneDay * 126) {
-        interval = 3600 * 6000;
-        tickInterval = oneDay * 14;
-        lastTick = to5am(moment(last).day(1).valueOf());
-    } else {
-        interval = 3600 * 12000;
-        tickInterval = oneDay * 28;
-        lastTick = to5am(moment(last).day(1).valueOf());
-    }
-    var lastData;
-    for (var i = data.length - 1; i >= 0; i--) {
-        var row = data[i];
-        var date = parseDate(row[0]).valueOf();
-        if (date > first) {
-            if (date <= last) {
-                var v = date - date % interval;
-                if (lastData !== v) {
-                    newdata.unshift(row);
-                    lastData = v;
-                }
-            }
-        } else {
-            break;
-        }
-    }
-    for (var tick = lastTick; tick > lastData; tick -= tickInterval) {
-        var str = toString(moment(tick));
-        ticks.unshift(str);
-        grid.unshift({ value: str });
-    }
-    return { data: newdata, tick: ticks, grid: grid };
-}
-
-var diffChartUnselected = {};
-
-function drawDiffChart(picked) {
-    var size = chartSize();
-    chartDiff = c3.generate({
-        bindto: '#chart',
-        size: {
-            height: size.height,
-            width: size.width
-        },
-        data: {
-            x: '日付',
-            xFormat: '%Y-%m-%d %X',
-            rows: picked.data,
-            axes: {
-                燃料: 'y',
-                弾薬: 'y',
-                鋼材: 'y',
-                ボーキ: 'y'
-            },
-            type: 'bar',
-            groups: [["燃料", "弾薬", "鋼材", "ボーキ"]]
-        },
-        bar: {
-            width: {
-                ratio: picked.width
-            }
-        },
-        tooltip: {
-            show: $('#tooltip').prop('checked')
-        },
-        grid: {
-            x: {
-                lines: picked.grid
-            },
-            y: {
-                lines: [
-                    { value: 0 }
-                ]
-            }
-        },
-        axis: {
-            x: {
-                type: 'timeseries',
-                tick: {
-                    rotate: 30,
-                    format: function (x) { return moment(x).format("MM-DD HH:mm"); },
-                    values: picked.tick
-                }
-            }
-        },
-        legend: {
-            item: {
-                onclick: function(id) {
-                    diffChartUnselected[id] = !diffChartUnselected[id];
-                    chartDiff.toggle(id);
-                }
-            }
-        },
-        onrendered: function () { $('#loading').hide(); }
-    });
-    for (var id in diffChartUnselected) {
-        if (diffChartUnselected.hasOwnProperty(id) && diffChartUnselected[id])
-            chartDiff.toggle(id);
-    }
-}
-
-function pickDiffChartData(data, range) {
-    var newdata = [];
-    var ticks = [];
-    var grid = [];
-    var first = moment(data[0][0]).valueOf();
-    var last = moment(data[data.length - 1][0]).valueOf();
-    var interval, tickInterval, lastTick;
-    var oneDay = 3600 * 24 * 1000;
-    switch (range) {
-        case 0:
-            first = moment(last).subtract(1, 'months').valueOf();
-            break;
-        case 1:
-            first = moment(last).subtract(3, 'months').valueOf();
-            break;
-        case 2:
-            first = moment(last).subtract(6, 'months').subtract(1, 'weeks').valueOf();
-            break;
-        case 3:
-            first = moment(data[0][0]).valueOf();
-            break;
-        case 4:
-            var fromDate = $('#chart_from').datepicker("getDate");
-            var toDate = $('#chart_to').datepicker("getDate");
-            if (fromDate == null || toDate == null)
-                return { data: [], tick: [], grid: [] };
-            var from = fromDate.valueOf() + 3600 * 5000;
-            var to = toDate.valueOf() + oneDay + 3600 * 5000;
-            first = Math.max(first, from);
-            last = Math.min(last, to);
-            break;
-    }
-    var barWidth;
-    if (last <= first + oneDay * 2 * 31) {
-        interval = oneDay;
-        tickInterval = oneDay * 2;
-        lastTick = to5am(last);
-        barWidth = 0.3;
-    } else if (last <= first + oneDay * 3 * 31) {
-        interval = oneDay;
-        tickInterval = oneDay * 7;
-        lastTick = to5am(last);
-        barWidth = 0.1;
-    } else {
-        interval = oneDay * 7;
-        tickInterval = oneDay * 28;
-        lastTick = to5am(moment(last).day(1).valueOf());
-        barWidth = 0.1;
-        if (last <= first + oneDay * 6 * 38) {
-            tickInterval = oneDay * 14;
-            barWidth = 0.3;
-        }
-    }
-    var lastDate = lastTick;
-    var prevRow;
-    for (var i = data.length - 1; i >= 0; i--) {
-        var row = data[i];
-        var date = parseDate(row[0]).valueOf();
-        if (date > first && date <= last) {
-            if (!prevRow) {
-                prevRow = row;
-                continue;
-            }
-            if (date <= lastDate) {
-                var newrow = [prevRow[0]];
-                for (var r = 1; r < 5; r++) {
-                    newrow.push(prevRow[r] - row[r]);
-                }
-                newdata.unshift(newrow);
-                lastDate = lastDate - interval;
-                prevRow = row;
-            }
-        } else {
-            break;
-        }
-    }
-    if (tickInterval >= oneDay * 7)
-        lastTick = moment(lastTick).day(1).hour(5).minute(0).valueOf();
-    for (var tick = lastTick; tick > lastDate; tick -= tickInterval) {
-        var str = toString(moment(tick));
-        ticks.unshift(str);
-        grid.unshift({ value: str });
-    }
-    return { data: newdata, tick: ticks, grid: grid, width: barWidth };
-}
-
-var sortieStatRange = 0;
-
-function loadSortieData() {
-    var from, to;
-    if (sortieStatRange === 0) {
-        from = moment().subtract(1, 'months').subtract(1, 'day').valueOf();
-        to = new Date().valueOf();
-    } else {
-        var fromDate = $('#sortie_stat_from').datepicker("getDate");
-        var toDate = $('#sortie_stat_to').datepicker("getDate");
-        if (fromDate == null || toDate == null) {
-            setSortieStat([]);
-            return;
-        }
-        from = fromDate.valueOf();
-        to = toDate.valueOf() + oneDay;
-    }
-    $.ajax({
-        url: "./海戦・ドロップ報告書.json?from=" + from + "&to=" + to,
-        success: function (data) { setSortieStat(data.data); },
-        dataType: "json", cache: false
-    });
-}
-
-function initSortieStatResult() {
-    var now = moment();
-    var r;
-    if (sortieStatRange === 0) {
-        r = {
-            day: { stat: {} },
-            week: { stat: {} },
-            month: { stat: {} }
+var mixin = {
+    mainTabs: [
+        "ドロップ",
+        "海戦",
+        "遠征",
+        "開発",
+        "建造",
+        "改修",
+        "資材",
+        "資材グラフ",
+        "戦果",
+        "出撃統計"
+    ],
+    logTables: 7,
+    oneDay: 3600 * 24 * 1000,
+    parseDate: function(d) {
+        return moment(d, timeFormat);
+    },
+    toDateString: function(d) {
+        return d.format(timeFormat);
+    },
+    to5am: function(tick) {
+        return tick - tick % (3600 * 24000) - 3600 * 4000;
+    },
+    chartSize: function() {
+        var pxPerEm = Number($('#chart').css('fontSize').match(/(\d*(\.\d*)?)px/)[1]);
+        return {
+            height: Math.max($(document).height() - 15 * pxPerEm, 400),
+            width: Math.max($(document).width() - 6 * pxPerEm, 800)
         };
-        r.day.begin = moment(now).hour(5).minute(0).second(0);
-        if (now.hour() < 5) {
-            r.day.begin.subtract(1, 'days');
-        }
-        r.week.begin = moment(now).day(1).hour(5).minute(0).second(0);
-        if (now.day() === 0 || now.day() === 1 && now.hour() < 5) {
-            r.week.begin.subtract(1, 'weeks');
-        }
-        if (moment(now).endOf('month').date() === now.date() &&
-            now.hour() >= 22) { // 月末22時以降
-            r.month.begin = moment(now).hour(22).minute(0).second(0);
-        } else {
-            r.month.begin =
-                moment(now).date(1).subtract(1, 'days').
-                    hour(22).minute(0).second(0);
-        }
-    } else {
-        r = { all: { stat: {} } };
-        r.all.begin = moment(0);
     }
-    return r;
-}
-
-function gatherSortieStat(data) {
-    var initStat = function () {
-        return { start: "-", S: 0, A: 0, B: 0, C: 0, D: 0, R: 0 }
-    };
-    var r = initSortieStatResult();
-    for (var i = 0; i < data.length; i++) {
-        var row = data[i];
-        var date = moment(row[0]);
-        var map = row[1];
-        var isBoss = row[3].indexOf("ボス") !== -1;
-        var isStart = row[3].indexOf("出撃") !== -1;
-        var resR = 0;
-        for (var j = 23; j < row.length; j++) {
-            if (/^輸送/.test(row[j]) && /^0\//.test(row[j + 1]))
-                resR++;
-        }
-        var res = row[4];
-        if (res === "E")
-            res = "D";
-        for (var term in r) {
-            if (!r.hasOwnProperty(term))
-                continue;
-            var to = r[term];
-            if (to.begin.isAfter(date))
-                continue;
-            for (var b = 0; b < 4; b++) {
-                var name = b < 2 ? "合計" : map;
-                if (b === 1 || b === 3) {
-                    if (!isBoss)
-                        continue;
-                    name = name + " - ボス";
-                }
-                var mo = to.stat[name];
-                if (!mo) {
-                    mo = to.stat[name] = initStat();
-                    if (name === "合計")
-                        to.stat["合計 - ボス"] = initStat();
-                }
-                mo["R"] += resR;
-                mo[res]++;
-                if ((b === 0 || b === 2) && isStart) {
-                    if (mo.start === "-")
-                        mo.start = 0;
-                    mo.start++;
-                }
-            }
-        }
-    }
-    return r;
-}
+};
 
-function arrangeSortieStatTable(r) {
-    for (var term in r) {
-        if (!r.hasOwnProperty(term))
-            continue;
-        var table = [];
-        var pushed = {};
-        for (var map in r[term].stat) {
-            if (!r[term].stat.hasOwnProperty(map))
-                continue;
-            if (pushed[map])
-                continue;
-            var e = r[term].stat[map];
-            e.map = map;
-            table.push(e);
-            pushed[map] = 1;
-            var boss = map + " - ボス";
-            e = r[term].stat[boss];
-            if (!e)
-                continue;
-            e.map = boss;
-            table.push(e);
-            pushed[boss] = 1;
-        }
-        r[term].table = table;
-    }
-}
-
-function setSortieStat(data) {
-    if (!data) {
-        $('#loading').show();
-        loadSortieData();
-        return;
-    }
-    var r = gatherSortieStat(data);
-    arrangeSortieStatTable(r);
-    for (var term in r) {
-        if (!r.hasOwnProperty(term))
-            continue;
-        var dt = $("#sortie_stat_" + term).DataTable();
-        dt.clear();
-        dt.rows.add(r[term].table).draw();
-    }
-    $('#loading').hide();
-}
-
-function initSortieStatTables() {
-    var terms = ['day', 'week', 'month', 'all'];
-    for (var i = 0; i < terms.length; i++) {
-        $("#sortie_stat_" + terms[i]).dataTable({
-            paging: false,
-            searching: false,
-            ordering: false,
-            columns: terms[i] !== 'month' ? [
-                { data: "map" },
-                { data: "start" },
-                { data: "S" },
-                { data: "A" },
-                { data: "B" },
-                { data: "C" },
-                { data: "D" },
-                { data: "R" }
-            ] : [
-                    { data: "map" },
-                    { data: "start" },
-                    { data: "S" },
-                    { data: "A" },
-                    { data: "B" },
-                    { data: "C" },
-                    { data: "D" }
-                ]
-        });
-    }
-}
-
-function showAchivementTable(data) {
-    var expPerAch = 10000 / 7.0;
-    if (!data) {
-        $('#loading').show();
-        $.ajax({
-            url: "./戦果.json",
-            success: function (data) {
-                showAchivementTable(data.data);
-            },
-            dataType: 'json',
-            cache: false
-        });
-        return;
-    }
-    var result = [];
-    var dayEo = 0;
-    var endOfMonth = moment(0);
-    var monthExp = 0;
-    var monthEo = 0;
-    var endOfYear = moment(0);
-    var yearExp = 0;
-    var carryOverAch = 0;
-    var carryOverEo = 0;
-    var prevExp = null;
-    var lastDate = moment(0);
-    var lastExp = -1;
-    var nextDate = moment(0);
-    for (var i = 0; i < data.length; i++) {
-        var row = data[i];
-        var date = parseDate(row[0]);
-        var exp = row[1] - 0;
-        var eo = row[2] - 0;
-        var isNewYear = date.isSameOrAfter(endOfYear);
-        var isNewMonth = date.isSameOrAfter(endOfMonth);
-        var isNewDate = date.isSameOrAfter(nextDate);
-        if (isNewDate || isNewMonth || isNewYear) {
-            if (lastDate.add('hour', 1).isSameOrBefore(date)) {
-                // 2時を過ぎて最初のexpを戦果の計算に使うと、2時をまたいだ出撃の戦果が前日に加算される。
-                // そこで2時前のexpを使って戦果を計算するが、2時前のexpが正しく出力されていない場合は
-                // 戦果を正しく計算できない。記録の間隔が1時間以上空いているときは、2時をまたいだ出撃が
-                // 行われていない可能性が高いので計算には今のexpを使うことにする。
-                // これは5時基準で出力された過去のデータで、妥当な戦果を計算するために必要な処理である。
-                lastExp = exp;
-            }
-            if (nextDate.valueOf() != 0) {
-                result.push([
-                    (isNewDate ? nextDate.subtract(1, 'days') : endOfMonth).format("YYYY-MM-DD"),
-                    new Number((lastExp - prevExp) / expPerAch).toFixed(1), dayEo,
-                    new Number((lastExp - monthExp) / expPerAch + monthEo + carryOverAch + carryOverEo).toFixed(1)
-                ]);
-            }
-            prevExp = lastExp === -1 ? exp : lastExp;
-            if (isNewYear) {
-                endOfYear = date.clone().endOf('year').hour(22).startOf('hour');
-                if (endOfYear.isSameOrBefore(date))
-                    endOfYear.add(1, 'year').endOf('year');
-                yearExp = lastExp === -1 ? exp : lastExp;
-            }
-            if (isNewMonth) {
-                endOfMonth = date.clone().endOf('month').hour(22).startOf('hour');
-                if (endOfMonth.isSameOrBefore(date))
-                    endOfMonth.add(1, 'month').endOf('month');
-                monthExp = lastExp === -1 ? exp : lastExp;
-                carryOverEo = monthEo * expPerAch / 50000;
-                carryOverAch = (monthExp - yearExp) / 50000;
-                monthEo = 0;
-                result.push([endOfMonth.format("YYYY-MM 引継"),
-                    carryOverAch.toFixed(1), carryOverEo.toFixed(1), (carryOverAch + carryOverEo).toFixed(1)]);
-            }
-            dayEo = 0;
-            nextDate = date.clone().hour(2).startOf('hour');
-            if (date.hour() >= 2)
-                nextDate.add(1, 'days');
-            if (nextDate.date() === 1)
-                nextDate.add(1, 'days');
-        }
-        if (date.isBefore(date.clone().endOf('month').hour(22).startOf('hour'))) {
-            // 月末22時から翌0時までのEOのボーナス戦果は消える。
-            dayEo += eo;
-            monthEo += eo;
-        }
-        lastDate = date;
-        lastExp = exp;
-    }
-    var dt = $('#achivement_table').DataTable();
-    dt.clear();
-    dt.rows.add(result).draw();
-    $('#loading').hide();
-}
-
-function initAchievementTable() {
-    $("#achivement_table").dataTable({
-        destroy: true,
-        deferRener: true,
-        stateSave: true,
-        order: [[0, "desc"]],
-        pageLength: 50,
-        lengthMenu: [[50, 100, 200, -1], [50, 100, 200, "All"]],
-        drawCallback: function () {
-            $('#loading').hide();
-        }
-    });
-}
-
-function selectTopTab(i) {
-    var chart = tables;
-    showChart = false;
-    if (i < tables) {
-        selectedTable = i;
-        showLog();
-    } else if (i === chart) {
-        showChart = true;
-        drawChart();
-    } else if (i === chart + 1) {
-        showAchivementTable();
-    } else if (i === chart + 2) {
-        setSortieStat();
-    }
-    if (i < tables)
-        $('#term').show();
-    else
-        $('#term').hide();
-    var tab = $('.tab0 li');
-    tab.removeClass('select');
-    tab.eq(i).addClass('select');
-    $('#main_contents .hide').hide();
-    $('#main_contents .hide').eq(i).show();
-}
-
-function initAction() {
-    $('.tab0 li').click(function () {
-        var tab = $('.tab0 li');
-        var i = tab.index(this);
-        selectTopTab(i);
-        sessionStorage.setItem('prevTab', i);
-    });
-    $('#range_seq li').click(function () {
-        var tab = $('#range_seq li');
-        var i = tab.index(this);
-        seqChartRange = i;
-        chartType = 0;
-        drawChart();
-        tab.removeClass('select');
-        tab.eq(i).addClass('select');
-        sessionStorage.setItem('prevSeqRange', i);
-    });
-    $('#range_diff li').click(function () {
-        var tab = $('#range_diff li');
-        var i = tab.index(this);
-        diffChartRange = i;
-        chartType = 1;
-        drawChart();
-        tab.removeClass('select');
-        tab.eq(i).addClass('select');
-        sessionStorage.setItem('prevDiffRange', i);
-    });
-    $('input[name="chart_type"]:radio').change(function () {
-        if ($(this).val() === "0") {
-            $("#range_seq").show();
-            $("#range_diff").hide();
-            chartType = 0;
-        } else {
-            $("#range_seq").hide();
-            $("#range_diff").show();
-            chartType = 1;
-        }
-        drawChart();
-        sessionStorage.setItem('chartType', $(this).val());
-    });
-    $('#tooltip').change(function () {
-        drawChart();
-        sessionStorage.setItem('chartTooltip', $(this).prop("checked"));
-    });
-    $('#range_sortie_stat li').click(function () {
-        var tab = $('#range_sortie_stat li');
-        var i = tab.index(this);
-        sortieStatRange = i;
-        tab.removeClass('select');
-        tab.eq(i).addClass('select');
-        if (sortieStatRange === 0) {
-            $('#sortie_stat_recent_tables').show();
-            $('#sortie_stat_all_table').hide();
-        } else {
-            $('#sortie_stat_recent_tables').hide();
-            $('#sortie_stat_all_table').show();
-        }
-        setSortieStat();
-    });
-}
-
-function initTableDatePicker() {
-    $('#term_from').datepicker({
-        defaultDate: moment().subtract(1, 'months').toDate(),
-        onClose: function () { $('input[name=term]').val(['1']) }
-    });
-    $('#term_to').datepicker({
-        onClose: function () { $('input[name=term]').val(['1']) }
-    });
-    $('#term_apply').click(showLog);
-}
-
-function initChartDatePicker() {
-    $('#chart_from').datepicker({
-        onClose: function () { if (useChartDatePicker()) drawChart(); }
-    });
-    $('#chart_to').datepicker({
-        onClose: function () { if (useChartDatePicker()) drawChart(); }
-    });
-}
-
-function useChartDatePicker() {
-    return (chartType === 0 && seqChartRange === 5) ||
-        (chartType === 1 && diffChartRange === 4);
-}
-
-function initSortieStatDatePicker() {
-    $('#sortie_stat_from').datepicker({
-        onClose: function () { if (sortieStatRange === 1) setSortieStat(); }
-    });
-    $('#sortie_stat_to').datepicker({
-        onClose: function () { if (sortieStatRange === 1) setSortieStat(); }
-    });
-}
-
-function restoreChartSettings() {
-    var type = sessionStorage.getItem('chartType');
-    chartType = type == null ? 0 : +type;
-    var tooltip = sessionStorage.getItem('chartTooltip');
-    $('#tooltip').prop('checked', tooltip === "true");
-    var range = sessionStorage.getItem('prevSeqRange');
-    seqChartRange = range == null ? 0 : +range;
-    range = sessionStorage.getItem('prevDiffRange');
-    diffChartRange = range == null ? 0 : +range;
-    $('input[name="chart_type"]:radio').eq(chartType).prop('checked', true);
-    if (chartType === 0) {
-        $('#range_seq').show();
-        $('#range_diff').hide();
-    } else {
-        $('#range_seq').hide();
-        $('#range_diff').show();
-    }
-    $('#range_diff li').removeClass('select');
-    $('#range_diff li').eq(diffChartRange).addClass('select');
-    $('#range_seq li').removeClass('select');
-    $('#range_seq li').eq(seqChartRange).addClass('select');
-}
-
-$(function () {
-    $.fn.dataTable.ext.errMode = 'throw';
-    initAction();
-    initTableDatePicker();
-    initChartDatePicker();
-    initSortieStatDatePicker();
-    $('table').addClass('display compact cell-border');
-    initTables();
-    initAchievementTable();
-    initSortieStatTables();
-    $('#range_sortie_stat li').removeClass('select');
-    $('#range_sortie_stat li').eq(sortieStatRange).addClass('select');
-    restoreChartSettings();
-    var prev = sessionStorage.getItem('prevTab');
-    selectTopTab(prev == null ? 0 : +prev);
-});
 </script>
 
-<div id="loading"><img src="https://kancollesniffer.osdn.jp/ajax-loader.gif" alt="読み込み中..."></div>
-
-<ul class="tab tab0">
-<li>ドロップ</li>
-<li>海戦</li>
-<li>遠征</li>
-<li>開発</li>
-<li>建造</li>
-<li>改修</li>
-<li>資材</li>
-<li>資材グラフ</li>
-<li>戦果</li>
-<li>出撃統計</li>
-</ul>
-
-<form id="term">
-<p>
-<label><input type="radio" name="term" value="0" checked="checked">直近一か月</label>
-<label><input type="radio" name="term" value="1">期間指定: </label>
-<input type="text" id="term_from" style="width: 7em">~<input type="text" id="term_to" style="width: 7em">
-<input type="button" id="term_apply" value="適用">
-</p>
-</form>
-
-<ul class="contents" id="main_contents">
-<li class="hide">
-<table id="log0">
-<thead>
-<tr><th>日付</th><th>海域</th><th>マス</th><th>ボス</th><th>ランク</th><th>ドロップ艦種</th><th>ドロップ艦娘</th></tr>
-</thead>
-</table>
+<main-tab></main-tab>
 
-<li class="hide">
-<table id="log1">
-<thead>
-<tr><th>日付</th><th>海域</th><th>マス</th><th>ボス</th><th>ランク</th><th>艦隊行動</th><th>味方陣形</th><th>敵陣形</th><th>敵艦隊</th><th>味方艦1</th><th>味方艦1HP</th><th>味方艦2</th><th>味方艦2HP</th><th>味方艦3</th><th>味方艦3HP</th><th>味方艦4</th><th>味方艦4HP</th><th>味方艦5</th><th>味方艦5HP</th><th>味方艦6</th><th>味方艦6HP</th><th>敵艦1</th><th>敵艦1HP</th><th>敵艦2</th><th>敵艦2HP</th><th>敵艦3</th><th>敵艦3HP</th><th>敵艦4</th><th>敵艦4HP</th><th>敵艦5</th><th>敵艦5HP</th><th>敵艦6</th><th>敵艦6HP</th><th>味方制空値</th><th>敵制空値</th><th>制空状態</th></tr>
-</thead>
-</table>
+<log-term></log-term>
 
-<li class="hide">
-<table id="log2">
-<thead>
-<tr><th>日付</th><th>結果</th><th>遠征</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>開発資材</th><th>高速修復材</th><th>高速建造材</th></tr>
-</thead>
-</table>
+<log-tables></log-tables>
 
-<li class="hide">
-<table id="log3">
-<thead>
-<tr><th>日付</th><th>開発装備</th><th>種別</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>秘書艦</th><th>司令部Lv</th></tr>
-</thead>
-</table>
+<chart-type></chart-type>
 
-<li class="hide">
-<table id="log4">
-<thead>
-<tr><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></tr>
-</thead>
-</table>
+<chart-range></chart-range>
 
-<li class="hide">
-<table id="log5">
-<thead>
-<tr><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></tr>
-</thead>
-</table>
+<sequential-chart></sequential-chart>
 
-<li class="hide">
-<table id="log6">
-<thead>
-<tr><th>日付</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>高速建造材</th><th>高速修復材</th><th>開発資材</th><th>改修資材</th></tr>
-</thead>
-</table>
+<differential-chart></differential-chart>
 
-<li class="hide">
-<form id="chart_type">
-<div style="margin: 0 0 0.5em 1em;">
-<label><input type="radio" name="chart_type" value="0" checked="checked">連続</label>
-<label><input type="radio" name="chart_type" value="1">差分</label>
-</div>
-</form>
-<ul class="tab tab1" id="range_seq" style="float: left; margin-right: 0.2em">
-<li>一日</li>
-<li>一週間</li>
-<li>一か月</li>
-<li>三か月</li>
-<li>すべて</li>
-<li>期間指定</li>
-</ul>
-<ul class="tab tab1" id="range_diff" style="float: left; margin-right: 0.2em">
-<li>一か月(日)</li>
-<li>三か月(日)</li>
-<li>半年(週)</li>
-<li>すべて(週)</li>
-<li>期間指定</li>
-</ul>
-<div style="padding: 0.2em 0;">
-<input type="text" id="chart_from" style="width: 7em">~<input type="text" id="chart_to" style="width: 7em">
-<label><input type="checkbox" id="tooltip" value="" style="margin-left: 2em;">ツールチップ</label>
-</div>
-<div id="chart" style="clear: both; margin: 1em;"></div>
+<material-chart></material-chart>
 
-<li class="hide">
-<table id="achivement_table">
-<thead>
-<tr><th>日付</th><th>戦果</th><th>EO</th><th>月毎</th></tr>
-</thead>
-</table>
+<achivement-table></achivement-table>
 
-<li class="hide">
-<ul class="tab tab1" id="range_sortie_stat" style="float: left; margin-right: 0.2em">
-<li>直近</li>
-<li>期間指定</li>
-</ul>
-<div style="padding: 0.2em 0;">
-<input type="text" id="sortie_stat_from" style="width: 7em">~<input type="text" id="sortie_stat_to" style="width: 7em">
-</div>
+<sortie-stat></sortie-stat>
 
-<div id="sortie_stat_recent_tables" style="clear: both;">
+<script src="tags.html" type="riot/tag"></script>
 
-<h3>今日</h3>
-<table id="sortie_stat_day">
-<thead>
-<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th><th>輸送船</th></tr>
-</thead>
-</table>
-
-<h3>今週</h3>
-<table id="sortie_stat_week">
-<thead>
-<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th><th>輸送船</th></tr>
-</table>
-
-<h3>今月</h3>
-<table id="sortie_stat_month">
-<thead>
-<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th></tr>
-</table>
-</div>
-
-<div id="sortie_stat_all_table" style="display: none;">
-<table id="sortie_stat_all">
-<thead>
-<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th><th>輸送船</th></tr>
-</thead>
-</table>
-</div>
-
-</ul>
+<script>
+var obs = riot.observable();
+var spec = new Object();
+riot.mixin(mixin);
+riot.mount("log-term", {observable: obs});
+riot.mount("log-tables", {observable: obs});
+riot.mount("chart-type", {observable: obs, chartSpec: spec});
+riot.mount("chart-range", {observable: obs, chartSpec: spec});
+riot.mount("sequential-chart", {observable: obs, chartSpec: spec});
+riot.mount("differential-chart", {observable: obs, chartSpec: spec});
+riot.mount("material-chart", {observable: obs, chartSpec: spec});
+riot.mount("achivement-table", {observable: obs});
+riot.mount("sortie-stat", {observable: obs});
+riot.mount("main-tab", {observable: obs});
+</script>
 </body>
 </html>
diff --git a/LogViewer/tags.html b/LogViewer/tags.html
new file mode 100644 (file)
index 0000000..6f03c53
--- /dev/null
@@ -0,0 +1,1056 @@
+<main-tab>
+<ul class="tab">
+    <li each={name, i in mainTabs} class={select: mainTab === i} onclick={parent.changeTab}>{name}</li>
+</ul>
+
+<script>
+/* global moment, c3, opts */
+
+this.mainTab = +sessionStorage.getItem('prevTab');
+opts.observable.trigger("mainTabChanged", this.mainTab);
+
+this.changeTab = function(e) {
+    this.mainTab = e.item.i;
+    sessionStorage.setItem('prevTab', e.item.i);
+    opts.observable.trigger("mainTabChanged", e.item.i);
+}.bind(this);
+</script>
+</main-tab>
+
+<log-term>
+<form id="term" show={enabled}>
+<p>
+<label><input type="radio" name="term" value="0" checked="checked">直近一か月</label>
+<label><input type="radio" name="term" value="1">期間指定: </label>
+<input type="text" id="term_from" style="width: 7em">~<input type="text" id="term_to" style="width: 7em">
+<input type="button" id="term_apply" value="適用">
+</p>
+</form>
+
+<script>
+this.on("mount", function() {
+    $('#term_from').datepicker({
+        defaultDate: moment().subtract(1, 'months').toDate(),
+        onClose: function() { $('input[name=term]').val(['1']); }
+    });
+    $('#term_to').datepicker({
+        onClose: function() { $('input[name=term]').val(['1']); }
+    });
+    $('#term_apply').click(function() {
+        opts.observable.trigger("termApplied");
+    });
+});
+
+this.enabled = false;
+var self = this;
+
+opts.observable.on("mainTabChanged", function(idx) {
+    self.update({enabled: idx >= 0 && idx < self.logTables});
+});
+</script>
+</log-term>
+
+<log-tables>
+<div each={header, i in tables} show={mainTab === i}>
+<table class="display compact cell-border" id={"log" + i}>
+<thead>
+<tr></tr>
+</thead>
+</table>
+</div>
+
+<script>
+this.tables = [
+"<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>敵艦隊</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>敵艦1</th><th>敵艦1HP</th><th>敵艦2</th><th>敵艦2HP</th><th>敵艦3</th><th>敵艦3HP</th><th>敵艦4</th><th>敵艦4HP</th><th>敵艦5</th><th>敵艦5HP</th><th>敵艦6</th><th>敵艦6HP</th><th>味方制空値</th><th>敵制空値</th><th>制空状態</th>", // 海戦
+"<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><th>鋼材</th><th>ボーキ</th><th>秘書艦</th><th>司令部Lv</th>", // 開発
+"<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>", // 建造
+"<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>", // 改修
+"<th>日付</th><th>燃料</th><th>弾薬</th><th>鋼材</th><th>ボーキ</th><th>高速建造材</th><th>高速修復材</th><th>開発資材</th><th>改修資材</th>" // 戦果
+];
+
+this.jsons = [
+    "海戦・ドロップ報告書.json",
+    "海戦・ドロップ報告書.json",
+    "遠征報告書.json",
+    "開発報告書.json",
+    "建造報告書.json",
+    "改修報告書.json",
+    "資材ログ.json"
+];
+
+this.on("mount", function() {
+    var records = this.root.querySelectorAll("tr");
+    for (var i = 0; i < records.length; i++)
+        records[i].innerHTML = this.tables[i];
+    this.init();
+});
+
+this.mainTab = 0;
+var self = this;
+
+opts.observable.on("mainTabChanged", function(idx) {
+    self.update({mainTab: idx});
+    self.show();
+});
+
+opts.observable.on("termApplied", function() {
+    self.show();
+});
+
+this.init = function() {
+    for (var t = 0; t < this.tables.length; t++) {
+        var opts = {
+            destroy: true,
+            deferRender: true,
+            stateSave: true,
+            order: [[0, "desc"]],
+            pageLength: 50,
+            lengthMenu: [[50, 100, 200, -1], [50, 100, 200, "All"]],
+            drawCallback: function() {
+                $('#loading').hide();
+            }
+        };
+        if (t === 0) {
+            opts.columns = [{ data: 0 }, { data: 1 }, { data: 2 }, { data: 3 }, { data: 4 }, { data: 9 }, { data: 10 }];
+        } else if (t === 1) {
+            var entries = [];
+            for (var i = 0; i < 38; i++) {
+                if (i === 9 || i === 10)
+                    continue;
+                entries.push({ data: i });
+            }
+            opts.columns = entries;
+        }
+        $('#log' + t).dataTable(opts);
+    }
+};
+
+this.show = function() {
+    if (this.mainTab >= this.jsons.length)
+        return;
+    var query = "?from=" + moment().subtract(1, 'months').valueOf();
+    if ($('input[name=term]:eq(1)').prop('checked')) {
+        var from = $('#term_from').datepicker("getDate");
+        var to = $('#term_to').datepicker("getDate");
+        if (from !== null)
+            query = "?from=" + from.valueOf();
+        if (to !== null)
+            query += "&to=" + (to.valueOf() + this.oneDay);
+    }
+    $('#loading').show();
+    var url = this.jsons[this.mainTab] + query;
+    $('#log' + this.mainTab).DataTable().ajax.url(url).load();
+};
+</script>
+</log-tables>
+
+<chart-type>
+<form id="chart_type" show={mainTabs[mainTab] === "資材グラフ"}>
+<div style="margin: 0 0 0.5em 1em;">
+<label><input type="radio" name="chart_type" value="0" checked={opts.chartSpec.type === 0} onchange={chartTypeChange}>連続</label>
+<label><input type="radio" name="chart_type" value="1" checked={opts.chartSpec.type === 1} onchange={chartTypeChange}>差分</label>
+</div>
+</form>
+
+<script>
+this.mainTab = 0;
+opts.chartSpec.type = +sessionStorage.getItem('chartType');
+var self = this;
+
+this.chartTypeChange = function(e) {
+    opts.chartSpec.type = +e.target.value;
+    sessionStorage.setItem('chartType', opts.chartSpec.type);
+    opts.observable.trigger("chartTypeChanged");
+    opts.observable.trigger("chartSpecChanged");
+};
+
+opts.observable.on("mainTabChanged", function(idx) {
+    self.update({mainTab: idx});
+});
+</script>
+</chart-type>
+
+<chart-range>
+<div show={mainTabs[mainTab] === "資材グラフ"}>
+<ul class="tab tabsub" style="float: left; margin-right: 0.2em" show={chartSpec.type === 0}>
+    <li each={name, i in seqChartRanges} class={select: chartSpec.seqRange === i} onclick={parent.rangeTabChange}>{name}</li>
+</ul>
+
+<ul class="tab tabsub" style="float: left; margin-right: 0.2em" show={chartSpec.type === 1}>
+    <li each={name, i in diffChartRanges} class={select: chartSpec.diffRange === i} onclick={parent.rangeTabChange}>{name}</li>
+</ul>
+<div style="padding: 0.2em 0;">
+<input type="text" id="chart_from" style="width: 7em">~<input type="text" id="chart_to" style="width: 7em">
+<label><input type="checkbox" id="tooltip" value="" style="margin-left: 2em;" onchange={tooltipChange} checked={opts.chartSpec.tooltip === 1}>ツールチップ</label>
+</div>
+</div>
+
+<script>
+this.seqChartRanges = [
+"一日",
+"一週間",
+"一か月",
+"三か月",
+"すべて",
+"期間指定"
+];
+
+this.diffChartRanges = [
+"一か月(日)",
+"三か月(日)",
+"半年(週)",
+"すべて(週)",
+"期間指定"
+];
+
+opts.chartSpec.seqRange = +sessionStorage.getItem('seqChartRange');
+opts.chartSpec.diffRange = +sessionStorage.getItem('diffChartRange');
+opts.chartSpec.tooltip = +sessionStorage.getItem('chartTooltip');
+this.chartSpec = opts.chartSpec;
+
+this.rangeTabChange = function(e) {
+    if (opts.chartSpec.type === 0) {
+        opts.chartSpec.seqRange = e.item.i;
+        sessionStorage.setItem('seqChartRange', e.item.i);
+    } else {
+        opts.chartSpec.diffRange = e.item.i;
+        sessionStorage.setItem('diffChartRange', e.item.i);
+    }
+    opts.observable.trigger("chartSpecChanged");
+};
+
+this.tooltipChange = function(e) {
+    opts.chartSpec.tooltip = +e.target.checked;
+    sessionStorage.setItem('chartTooltip', this.tooltip);
+    opts.observable.trigger("chartSpecChanged");
+};
+
+this.useDatePicker = function() {
+    return opts.chartSpec.type === 0 && opts.chartSpec.seqRange === 5 ||
+        opts.chartSpec.type === 1 && opts.chartSpec.diffRange === 4;
+};
+
+this.init = function() {
+    $('#chart_from').datepicker({
+        onClose: function() { if (self.useDatePicker()) self.drawChart(); }
+    });
+    $('#chart_to').datepicker({
+        onClose: function() { if (self.useDatePicker()) self.drawChart(); }
+    });
+};
+
+this.on("mount", this.init);
+
+this.mainTab = 0;
+var self = this;
+
+opts.observable.on("mainTabChanged", function(idx) {
+    self.update({mainTab: idx});
+});
+
+opts.observable.on("chartTypeChanged", function() {
+    self.update();
+});
+</script>
+</chart-range>
+
+<sequential-chart>
+<script>
+var self = this;
+
+opts.observable.on("chartSpecChanged", function() {
+    if (opts.chartSpec.type === 0)
+        self.drawChart();
+});
+
+opts.observable.on("chartSizeChanged", function() {
+    if (opts.chartSpec.type === 0)
+        self.resize();
+});
+
+this.resize = function() {
+    if (!self.chart)
+        return;
+    $('#loading').show();
+    setTimeout(function() {
+        self.chart.resize(self.chartSize());
+    });
+};
+
+this.drawChart = function(data) {
+    if (!data) {
+        $('#loading').show();
+        $.ajax({
+            url: "./資材ログ.json",
+            success: function(d) { self.drawChart(d); },
+            dataType: "json", cache: false
+        });
+        return;
+    }
+    var picked, header;
+    picked = this.pickChartData(data.data, opts.chartSpec.seqRange);
+    header = ["日付", "燃料", "弾薬", "鋼材", "ボーキ", "高速建造材", "高速修復材", "開発資材", "改修資材"];
+    picked.data.unshift(header);
+    this.drawSeqChart(picked);
+};
+
+this.unselected = {};
+
+this.drawSeqChart = function(picked) {
+    var size = this.chartSize();
+    this.chart = c3.generate({
+        bindto: '#chart',
+        size: {
+            height: size.height,
+            width: size.width
+        },
+        data: {
+            x: '日付',
+            xFormat: '%Y-%m-%d %X',
+            rows: picked.data,
+            axes: {
+                燃料: 'y',
+                弾薬: 'y',
+                鋼材: 'y',
+                ボーキ: 'y',
+                高速建造材: 'y2',
+                高速修復材: 'y2',
+                開発資材: 'y2',
+                改修資材: 'y2'
+            }
+        },
+        point: {
+            show: false
+        },
+        tooltip: {
+            show: opts.chartSpec.tooltip
+        },
+        grid: {
+            x: {
+                lines: picked.grid
+            }
+        },
+        axis: {
+            x: {
+                type: 'timeseries',
+                tick: {
+                    rotate: 30,
+                    format: function(x) { return moment(x).format("MM-DD HH:mm"); },
+                    values: picked.tick
+                }
+            },
+            y2: {
+                show: true
+            }
+        },
+        legend: {
+            item: {
+                onclick: function(id) {
+                    self.unselected[id] = !self.unselected[id];
+                    self.chart.toggle(id);
+                }
+            }
+        },
+        onrendered: function() { $('#loading').hide(); }
+    });
+    for (var id in self.unselected) {
+        if (self.unselected.hasOwnProperty(id) && self.unselected[id])
+            self.chart.toggle(id);
+    }
+};
+
+this.pickChartData = function(data, range) {
+    var newdata = [];
+    var ticks = [];
+    var grid = [];
+    var first = moment(data[0][0]).valueOf();
+    var last = moment(data[data.length - 1][0]).valueOf();
+    var interval, tickInterval, lastTick;
+    switch (range) {
+        case 0:
+            first = moment(last).subtract(24, 'hours').valueOf();
+            break;
+        case 1:
+            first = moment(last).subtract(7, 'days').valueOf();
+            break;
+        case 2:
+            first = moment(last).subtract(1, 'months').valueOf();
+            break;
+        case 3:
+            first = moment(last).subtract(3, 'months').valueOf();
+            break;
+        case 4:
+            first = moment(data[0][0]).valueOf();
+            break;
+        case 5:
+            var fromDate = $('#chart_from').datepicker("getDate");
+            var toDate = $('#chart_to').datepicker("getDate");
+            if (fromDate === null || toDate === null)
+                return { data: [], tick: [], grid: [] };
+            var from = fromDate.valueOf() + 3600 * 5000;
+            var to = toDate.valueOf() + this.oneDay + 3600 * 5000;
+            first = Math.max(first, from);
+            last = Math.min(last, to);
+            break;
+    }
+    if (last <= first + this.oneDay) {
+        interval = 1000;
+        tickInterval = 3600 * 1000;
+        lastTick = last - last % tickInterval;
+    } else if (last <= first + this.oneDay * 21) {
+        interval = 1000;
+        tickInterval = this.oneDay;
+        lastTick = this.to5am(last);
+    } else if (last <= first + this.oneDay * 63) {
+        interval = 3600 * 1000;
+        tickInterval = this.oneDay * 7;
+        lastTick = this.to5am(moment(last).day(1).valueOf());
+    } else if (last <= first + this.oneDay * 126) {
+        interval = 3600 * 6000;
+        tickInterval = this.oneDay * 14;
+        lastTick = this.to5am(moment(last).day(1).valueOf());
+    } else {
+        interval = 3600 * 12000;
+        tickInterval = this.oneDay * 28;
+        lastTick = this.to5am(moment(last).day(1).valueOf());
+    }
+    var lastData;
+    for (var i = data.length - 1; i >= 0; i--) {
+        var row = data[i];
+        var date = this.parseDate(row[0]).valueOf();
+        if (date > first) {
+            if (date <= last) {
+                var v = date - date % interval;
+                if (lastData !== v) {
+                    newdata.unshift(row);
+                    lastData = v;
+                }
+            }
+        } else {
+            break;
+        }
+    }
+    for (var tick = lastTick; tick > lastData; tick -= tickInterval) {
+        var str = self.toDateString(moment(tick));
+        ticks.unshift(str);
+        grid.unshift({ value: str });
+    }
+    return { data: newdata, tick: ticks, grid: grid };
+};
+</script>
+</sequential-chart>
+
+<differential-chart>
+<script>
+var self = this;
+
+opts.observable.on("chartSpecChanged", function() {
+    if (opts.chartSpec.type === 1)
+        self.drawChart();
+});
+
+opts.observable.on("chartSizeChanged", function() {
+    if (opts.chartSpec.type === 1)
+        self.resize();
+});
+
+this.resize = function() {
+    if (!self.chart)
+        return;
+    $('#loading').show();
+    setTimeout(function() {
+        self.chart.resize(self.chartSize());
+    });
+};
+
+this.drawChart = function(data) {
+    if (!data) {
+        $('#loading').show();
+        $.ajax({
+            url: "./資材ログ.json",
+            success: function(d) { self.drawChart(d); },
+            dataType: "json", cache: false
+        });
+        return;
+    }
+    var picked, header;
+    picked = this.pickChartData(data.data, opts.chartSpec.diffRange);
+    header = ["日付", "燃料", "弾薬", "鋼材", "ボーキ"];
+    picked.data.unshift(header);
+    this.drawDiffChart(picked);
+};
+
+this.unselected = {};
+
+this.drawDiffChart = function(picked) {
+    var size = this.chartSize();
+    this.chart = c3.generate({
+        bindto: '#chart',
+        size: {
+            height: size.height,
+            width: size.width
+        },
+        data: {
+            x: '日付',
+            xFormat: '%Y-%m-%d %X',
+            rows: picked.data,
+            axes: {
+                燃料: 'y',
+                弾薬: 'y',
+                鋼材: 'y',
+                ボーキ: 'y'
+            },
+            type: 'bar',
+            groups: [["燃料", "弾薬", "鋼材", "ボーキ"]]
+        },
+        bar: {
+            width: {
+                ratio: picked.width
+            }
+        },
+        tooltip: {
+            show: opts.chartSpec.tooltip
+        },
+        grid: {
+            x: {
+                lines: picked.grid
+            },
+            y: {
+                lines: [
+                    { value: 0 }
+                ]
+            }
+        },
+        axis: {
+            x: {
+                type: 'timeseries',
+                tick: {
+                    rotate: 30,
+                    format: function(x) { return moment(x).format("MM-DD HH:mm"); },
+                    values: picked.tick
+                }
+            }
+        },
+        legend: {
+            item: {
+                onclick: function(id) {
+                    self.unselected[id] = !self.unselected[id];
+                    self.chart.toggle(id);
+                }
+            }
+        },
+        onrendered: function() { $('#loading').hide(); }
+    });
+    for (var id in self.unselected) {
+        if (self.unselected.hasOwnProperty(id) && self.unselected[id])
+            self.chart.toggle(id);
+    }
+};
+
+this.pickChartData = function(data, range) {
+    var newdata = [];
+    var ticks = [];
+    var grid = [];
+    var first = moment(data[0][0]).valueOf();
+    var last = moment(data[data.length - 1][0]).valueOf();
+    var interval, tickInterval, lastTick;
+    switch (range) {
+        case 0:
+            first = moment(last).subtract(1, 'months').valueOf();
+            break;
+        case 1:
+            first = moment(last).subtract(3, 'months').valueOf();
+            break;
+        case 2:
+            first = moment(last).subtract(6, 'months').subtract(1, 'weeks').valueOf();
+            break;
+        case 3:
+            first = moment(data[0][0]).valueOf();
+            break;
+        case 4:
+            var fromDate = $('#chart_from').datepicker("getDate");
+            var toDate = $('#chart_to').datepicker("getDate");
+            if (fromDate === null || toDate === null)
+                return { data: [], tick: [], grid: [] };
+            var from = fromDate.valueOf() + 3600 * 5000;
+            var to = toDate.valueOf() + this.oneDay + 3600 * 5000;
+            first = Math.max(first, from);
+            last = Math.min(last, to);
+            break;
+    }
+    var barWidth;
+    if (last <= first + this.oneDay * 2 * 31) {
+        interval = this.oneDay;
+        tickInterval = this.oneDay * 2;
+        lastTick = this.to5am(last);
+        barWidth = 0.3;
+    } else if (last <= first + this.oneDay * 3 * 31) {
+        interval = this.oneDay;
+        tickInterval = this.oneDay * 7;
+        lastTick = this.to5am(last);
+        barWidth = 0.1;
+    } else {
+        interval = this.oneDay * 7;
+        tickInterval = this.oneDay * 28;
+        lastTick = this.to5am(moment(last).day(1).valueOf());
+        barWidth = 0.1;
+        if (last <= first + this.oneDay * 6 * 38) {
+            tickInterval = this.oneDay * 14;
+            barWidth = 0.3;
+        }
+    }
+    var lastDate = lastTick;
+    var prevRow;
+    for (var i = data.length - 1; i >= 0; i--) {
+        var row = data[i];
+        var date = this.parseDate(row[0]).valueOf();
+        if (date > first && date <= last) {
+            if (!prevRow) {
+                prevRow = row;
+                continue;
+            }
+            if (date <= lastDate) {
+                var newrow = [prevRow[0]];
+                for (var r = 1; r < 5; r++) {
+                    newrow.push(prevRow[r] - row[r]);
+                }
+                newdata.unshift(newrow);
+                lastDate = lastDate - interval;
+                prevRow = row;
+            }
+        } else {
+            break;
+        }
+    }
+    if (tickInterval >= this.oneDay * 7)
+        lastTick = moment(lastTick).day(1).hour(5).minute(0).valueOf();
+    for (var tick = lastTick; tick > lastDate; tick -= tickInterval) {
+        var str = self.toDateString(moment(tick));
+        ticks.unshift(str);
+        grid.unshift({ value: str });
+    }
+    return { data: newdata, tick: ticks, grid: grid, width: barWidth };
+};
+</script>
+</differential-chart>
+
+<material-chart>
+<div show={mainTabs[mainTab] === "資材グラフ"}>
+<div id="chart" style="clear: both; margin: 1em;"></div>
+</div>
+
+<script>
+this.mainTab = 0;
+var self = this;
+
+opts.observable.on("mainTabChanged", function(idx) {
+    self.update({mainTab: idx});
+    if (self.mainTabs[idx] === "資材グラフ")
+        opts.observable.trigger("chartSpecChanged");
+});
+
+this.timer = null;
+$(window).resize(function() {
+    if (self.timer)
+        clearTimeout(self.timer);
+    self.timer = setTimeout(function() {
+        if (self.mainTabs[self.mainTab] === "資材グラフ")
+            opts.observable.trigger("chartSizeChanged");
+    }, 200);
+});
+</script>
+</material-chart>
+
+<achivement-table>
+<div show={mainTabs[mainTab] === "戦果"}>
+<table id="achivement_table" class="display compact cell-border">
+<thead>
+<tr><th>日付</th><th>戦果</th><th>EO</th><th>月毎</th></tr>
+</thead>
+</table>
+</div>
+
+<script>
+this.on("mount", function() {
+    $("#achivement_table").dataTable({
+        destroy: true,
+        deferRener: true,
+        stateSave: true,
+        order: [[0, "desc"]],
+        pageLength: 50,
+        lengthMenu: [[50, 100, 200, -1], [50, 100, 200, "All"]],
+        drawCallback: function() {
+            $('#loading').hide();
+        }
+    });
+});
+
+var self = this;
+
+opts.observable.on("mainTabChanged", function(idx) {
+    self.update({mainTab: idx});
+    if (self.mainTabs[self.mainTab] === "戦果")
+        self.show();
+});
+
+this.show = function(data) {
+    var expPerAch = 10000 / 7.0;
+    if (!data) {
+        $('#loading').show();
+        $.ajax({
+            url: "./戦果.json",
+            success: function(data) {
+                self.show(data.data);
+            },
+            dataType: 'json',
+            cache: false
+        });
+        return;
+    }
+    var result = [];
+    var dayEo = 0;
+    var endOfMonth = moment(0);
+    var monthExp = 0;
+    var monthEo = 0;
+    var endOfYear = moment(0);
+    var yearExp = 0;
+    var carryOverAch = 0;
+    var carryOverEo = 0;
+    var prevExp = null;
+    var lastDate = moment(0);
+    var lastExp = -1;
+    var nextDate = moment(0);
+    for (var i = 0; i < data.length; i++) {
+        var row = data[i];
+        var date = this.parseDate(row[0]);
+        var exp = row[1] - 0;
+        var eo = row[2] - 0;
+        var isNewYear = date.isSameOrAfter(endOfYear);
+        var isNewMonth = date.isSameOrAfter(endOfMonth);
+        var isNewDate = date.isSameOrAfter(nextDate);
+        if (isNewDate || isNewMonth || isNewYear) {
+            if (lastDate.add(1, 'hours').isSameOrBefore(date)) {
+                // 2時を過ぎて最初のexpを戦果の計算に使うと、2時をまたいだ出撃の戦果が前日に加算される。
+                // そこで2時前のexpを使って戦果を計算するが、2時前のexpが正しく出力されていない場合は
+                // 戦果を正しく計算できない。記録の間隔が1時間以上空いているときは、2時をまたいだ出撃が
+                // 行われていない可能性が高いので計算には今のexpを使うことにする。
+                // これは5時基準で出力された過去のデータで、妥当な戦果を計算するために必要な処理である。
+                lastExp = exp;
+            }
+            if (nextDate.valueOf() !== 0) {
+                result.push([
+                    (isNewDate ? nextDate.subtract(1, 'days') : endOfMonth).format("YYYY-MM-DD"),
+                    new Number((lastExp - prevExp) / expPerAch).toFixed(1), dayEo,
+                    new Number((lastExp - monthExp) / expPerAch + monthEo + carryOverAch + carryOverEo).toFixed(1)
+                ]);
+            }
+            prevExp = lastExp === -1 ? exp : lastExp;
+            if (isNewYear) {
+                endOfYear = date.clone().endOf('year').hour(22).startOf('hour');
+                if (endOfYear.isSameOrBefore(date))
+                    endOfYear.add(1, 'year').endOf('year');
+                yearExp = lastExp === -1 ? exp : lastExp;
+            }
+            if (isNewMonth) {
+                endOfMonth = date.clone().endOf('month').hour(22).startOf('hour');
+                if (endOfMonth.isSameOrBefore(date))
+                    endOfMonth.add(1, 'month').endOf('month');
+                monthExp = lastExp === -1 ? exp : lastExp;
+                carryOverEo = monthEo * expPerAch / 50000;
+                carryOverAch = (monthExp - yearExp) / 50000;
+                monthEo = 0;
+                result.push([endOfMonth.format("YYYY-MM 引継"),
+                    carryOverAch.toFixed(1), carryOverEo.toFixed(1), (carryOverAch + carryOverEo).toFixed(1)]);
+            }
+            dayEo = 0;
+            nextDate = date.clone().hour(2).startOf('hour');
+            if (date.hour() >= 2)
+                nextDate.add(1, 'days');
+            if (nextDate.date() === 1)
+                nextDate.add(1, 'days');
+        }
+        if (date.isBefore(date.clone().endOf('month').hour(22).startOf('hour'))) {
+            // 月末22時から翌0時までのEOのボーナス戦果は消える。
+            dayEo += eo;
+            monthEo += eo;
+        }
+        lastDate = date;
+        lastExp = exp;
+    }
+    var dt = $('#achivement_table').DataTable();
+    dt.clear();
+    dt.rows.add(result).draw();
+    $('#loading').hide();
+};
+</script>
+</achivement-table>
+
+<sortie-stat>
+<div show={mainTabs[mainTab] === "出撃統計"}>
+
+<ul class="tab tabsub" style="float: left; margin-right: 0.2em">
+<li each={tabs} class={select: parent.type === type} onclick={parent.changeTab}>{label}</li>
+</ul>
+
+<div style="padding: 0.2em 0;">
+<input type="text" id="sortie_stat_from" style="width: 7em">~<input type="text" id="sortie_stat_to" style="width: 7em">
+</div>
+
+<div style="clear: both;" show={type === "recent"}>
+<h3>今日</h3>
+<table id="sortie_stat_day">
+<thead>
+<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th><th>輸送船</th></tr>
+</thead>
+</table>
+<h3>今週</h3>
+<table id="sortie_stat_week">
+<thead>
+<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th><th>輸送船</th></tr>
+</table>
+<h3>今月</h3>
+<table id="sortie_stat_month">
+<thead>
+<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th></tr>
+</table>
+</div>
+
+<div id="sortie_stat_all_table" show={type === "range"}>
+<table id="sortie_stat_all">
+<thead>
+<tr><th>マップ</th><th>出撃</th><th>S</th><th>A</th><th>B</th><th>C</th><th>D以下</th><th>輸送船</th></tr>
+</thead>
+</table>
+</div>
+
+</div>
+
+<script>
+this.tabs = [
+    {
+        type: "recent",
+        label: "直近"
+    },
+    {
+        type: "range",
+        label: "期間指定"
+    }
+];
+this.type = "recent";
+this.changeTab = function(e) {
+    this.type = e.item.type;
+    this.show();
+}.bind(this);
+
+this.mainTab = 0;
+var self = this;
+
+this.on("mount", function() {
+    $("[id^=sortie]").addClass('display compact cell-border');
+    this.init();
+});
+
+opts.observable.on("mainTabChanged", function(idx) {
+    self.update({mainTab: idx});
+    if (self.mainTabs[self.mainTab] === "出撃統計")
+        self.show();
+});
+
+this.init = function() {
+    this.initTable();
+    this.initDatePicker();
+};
+
+this.initTable = function() {
+    var terms = ['day', 'week', 'month', 'all'];
+    for (var i = 0; i < terms.length; i++) {
+        $("#sortie_stat_" + terms[i]).dataTable({
+            paging: false,
+            searching: false,
+            ordering: false,
+            columns: terms[i] !== 'month' ? [
+                { data: "map" },
+                { data: "start" },
+                { data: "S" },
+                { data: "A" },
+                { data: "B" },
+                { data: "C" },
+                { data: "D" },
+                { data: "R" }
+            ] : [
+                    { data: "map" },
+                    { data: "start" },
+                    { data: "S" },
+                    { data: "A" },
+                    { data: "B" },
+                    { data: "C" },
+                    { data: "D" }
+                ]
+        });
+    }
+};
+
+this.initDatePicker = function() {
+    $('#sortie_stat_from').datepicker({
+        onClose: function() { if (self.type === "range") self.show(); }
+    });
+    $('#sortie_stat_to').datepicker({
+        onClose: function() { if (self.type === "range") self.show(); }
+    });
+};
+
+var self = this;
+
+this.loadData = function() {
+    var from, to;
+    if (this.type === "recent") {
+        from = moment().subtract(1, 'months').subtract(1, 'day').valueOf();
+        to = new Date().valueOf();
+    } else {
+        var fromDate = $('#sortie_stat_from').datepicker("getDate");
+        var toDate = $('#sortie_stat_to').datepicker("getDate");
+        if (fromDate === null || toDate === null) {
+            this.show([]);
+            return;
+        }
+        from = fromDate.valueOf();
+        to = toDate.valueOf() + this.oneDay;
+    }
+    $.ajax({
+        url: "./海戦・ドロップ報告書.json?from=" + from + "&to=" + to,
+        success: function(data) { self.show(data.data); },
+        dataType: "json", cache: false
+    });
+};
+
+this.initResult = function() {
+    var now = moment();
+    var r;
+    if (this.type === "recent") {
+        r = {
+            day: { stat: {} },
+            week: { stat: {} },
+            month: { stat: {} }
+        };
+        r.day.begin = moment(now).hour(5).minute(0).second(0);
+        if (now.hour() < 5) {
+            r.day.begin.subtract(1, 'days');
+        }
+        r.week.begin = moment(now).day(1).hour(5).minute(0).second(0);
+        if (now.day() === 0 || now.day() === 1 && now.hour() < 5) {
+            r.week.begin.subtract(1, 'weeks');
+        }
+        if (moment(now).endOf('month').date() === now.date() &&
+            now.hour() >= 22) { // 月末22時以降
+            r.month.begin = moment(now).hour(22).minute(0).second(0);
+        } else {
+            r.month.begin =
+                moment(now).date(1).subtract(1, 'days').
+                    hour(22).minute(0).second(0);
+        }
+    } else {
+        r = { all: { stat: {} } };
+        r.all.begin = moment(0);
+    }
+    return r;
+};
+
+this.gatherData = function(data) {
+    var initStat = function() {
+        return { start: "-", S: 0, A: 0, B: 0, C: 0, D: 0, R: 0 };
+    };
+    var r = this.initResult();
+    for (var i = 0; i < data.length; i++) {
+        var row = data[i];
+        var date = moment(row[0]);
+        var map = row[1];
+        var isBoss = row[3].indexOf("ボス") !== -1;
+        var isStart = row[3].indexOf("出撃") !== -1;
+        var resR = 0;
+        for (var j = 23; j < row.length; j++) {
+            if (/^輸送/.test(row[j]) && /^0\x2f/.test(row[j + 1]))
+                resR++;
+        }
+        var res = row[4];
+        if (res === "E")
+            res = "D";
+        for (var term in r) {
+            if (!r.hasOwnProperty(term))
+                continue;
+            var to = r[term];
+            if (to.begin.isAfter(date))
+                continue;
+            for (var b = 0; b < 4; b++) {
+                var name = b < 2 ? "合計" : map;
+                if (b === 1 || b === 3) {
+                    if (!isBoss)
+                        continue;
+                    name = name + " - ボス";
+                }
+                var mo = to.stat[name];
+                if (!mo) {
+                    mo = to.stat[name] = initStat();
+                    if (name === "合計")
+                        to.stat["合計 - ボス"] = initStat();
+                }
+                mo["R"] += resR;
+                mo[res]++;
+                if ((b === 0 || b === 2) && isStart) {
+                    if (mo.start === "-")
+                        mo.start = 0;
+                    mo.start++;
+                }
+            }
+        }
+    }
+    return r;
+};
+
+this.arrangeTable = function(r) {
+    for (var term in r) {
+        if (!r.hasOwnProperty(term))
+            continue;
+        var table = [];
+        var pushed = {};
+        for (var map in r[term].stat) {
+            if (!r[term].stat.hasOwnProperty(map))
+                continue;
+            if (pushed[map])
+                continue;
+            var e = r[term].stat[map];
+            e.map = map;
+            table.push(e);
+            pushed[map] = 1;
+            var boss = map + " - ボス";
+            e = r[term].stat[boss];
+            if (!e)
+                continue;
+            e.map = boss;
+            table.push(e);
+            pushed[boss] = 1;
+        }
+        r[term].table = table;
+    }
+};
+
+this.show = function(data) {
+    if (!data) {
+        $('#loading').show();
+        this.loadData();
+        return;
+    }
+    var r = this.gatherData(data);
+    this.arrangeTable(r);
+    for (var term in r) {
+        if (!r.hasOwnProperty(term))
+            continue;
+        var dt = $("#sortie_stat_" + term).DataTable();
+        dt.clear();
+        dt.rows.add(r[term].table).draw();
+    }
+    $('#loading').hide();
+};
+</script>
+</sortie-stat>