Merge branch 'master' into feat-bar-racing
diff --git a/_jade/zh/bar-racing.jade b/_jade/zh/bar-racing.jade
new file mode 100644
index 0000000..e71f891
--- /dev/null
+++ b/_jade/zh/bar-racing.jade
@@ -0,0 +1,298 @@
+extends ../layouts/basic
+
+block variables
+    - var pageTitle = 'ECharts 动态排序柱状图生成工具'
+
+block extra_head
+    title 动态排序柱状图生成器 - Apache ECharts (incubating)
+    link(rel='stylesheet', href='https://cdn.jsdelivr.net/npm/handsontable@6.2.2/dist/handsontable.css')
+    style.
+        #table-row {
+            margin: 0;
+        }
+        #table-panel {
+            border: 1px solid #eee;
+            height: 600px;
+            overflow: hidden;
+        }
+        #bar-race-preview {
+            width: 100%;
+            height: 400px;
+            margin: 30px 0;
+        }
+        #bar-race-preview div, #bar-race-preview canvas {
+            width: 100% !important;
+            height: 100% !important;
+        }
+        .btn {
+            margin-right: 10px;
+        }
+
+block content
+    nav(class='navbar navbar-default navbar-fixed-top', role='navigation')
+        include ../components/nav
+
+    .page-main
+        .page-info
+            .container
+                h1 动态排序柱状图生成工具
+                p.page-info-echarts Apache ECharts (incubating)<sup>TM</sup>
+
+        .page-content
+            .row#table-row
+                .col-md-6
+                    #table-panel
+                .col-md-6
+                    .bar-race-config
+                        form
+                            .form-group
+                                label.col-lg-6 标题
+                                input.form-control#input-title(value='汽车产量动态排名')
+                            .form-group
+                                label.col-lg-6 显示排名上限
+                                input.form-control#input-max(type='number', value='10')
+
+                    div
+                        button.btn.btn-default(type='button', onclick="run()") 运行
+                        button.btn.btn-default(type='button') 导出
+                    #bar-race-preview
+
+        include ../components/footer
+
+block extra_js
+    script(src='https://cdn.jsdelivr.net/npm/handsontable@6.2.2/dist/handsontable.js')
+    script(src='http://localhost/echarts/dist/echarts.js')
+    script(type='text/javascript').
+        document.getElementById('nav-contribute').className = 'active';
+
+        var data = [
+            ["Name", "Ford", "Tesla", "Toyota", "Honda"],
+            ["Color", "", "", "", ""],
+            ["2017", 10, 11, 12, 13],
+            ["2018", 20, 11, 14, 13],
+            ["2019", 30, 15, 12, 13]
+        ];
+        var headerLength = 2;
+        for (var i = 0; i < data.length; ++i) {
+            for (var j = data[i].length; j < 50; ++j) {
+                data[i].push('');
+            }
+        }
+        for (var i = data.length; i < 100; ++i) {
+            var row = [];
+            for (var j = 0; j < 50; ++j) {
+                row.push('');
+            }
+            data.push(row);
+        }
+
+        var trimColumns = function (rowData) {
+            for (var i = rowData.length - 1; i > 0; --i) {
+                if (rowData[i] && rowData[i] !== '') {
+                    return rowData.slice(1, i + 1);
+                }
+            }
+            return [];
+        };
+
+        var trimRows = function () {
+            if (data.length <= headerLength) {
+                return [];
+            }
+            for (var i = data.length - 1; i >= headerLength; --i) {
+                var isEmpty = true;
+                for (var j = 1; j < data[i].length; ++j) {
+                    if (data[i][j] && data[i][j] !== '') {
+                        isEmpty = false;
+                        break;
+                    }
+                }
+                if (!isEmpty) {
+                    return data.slice(headerLength, i + 1);
+                }
+            }
+            return [];
+        };
+
+        var getYData = function () {
+            if (data.length <= headerLength) {
+                return [];
+            }
+            return trimColumns(data[0]);
+        };
+
+        var getChartData = function (id) {
+            if (data.length <= id + headerLength) {
+                return [];
+            }
+            return trimColumns(data[id + headerLength]);
+        };
+
+        var getDataName = function (id) {
+            if (data.length <= id + headerLength) {
+                return '';
+            }
+            else {
+                return data[id + headerLength][0];
+            }
+        }
+
+        var container = document.getElementById('table-panel');
+
+        function colorRenderer(instance, td, row, col) {
+            //- console.log(instance);
+
+        }
+
+        var table = new Handsontable(container, {
+            data: data,
+            rowHeaders: true,
+            colHeaders: true,
+            filters: true,
+            dropdownMenu: true,
+            cell: [{
+                row: 0,
+                col: 0,
+                readOnly: true
+            }, {
+                row: 1,
+                col: 0,
+                readOnly: true
+            }],
+            //- cells: function (row, col) {
+            //-     if (row === 1) {
+            //-         return {
+            //-             renderer: colorRenderer
+            //-         }
+            //-     }
+            //-     else {
+            //-         return {};
+            //-     }
+            //- }
+        });
+        table.updateSettings({
+            afterChange: function (e) {
+                run();
+            }
+        });
+
+        var chart = echarts.init($('#bar-race-preview')[0]);
+
+        var timeoutHandlers = [];
+
+        initEvents();
+        run();
+
+        function clearTimeoutHandlers() {
+            for (var i = 0; i < timeoutHandlers.length; ++i) {
+                clearTimeout(timeoutHandlers[i]);
+                timeoutHandlers.splice(i, 1);
+            }
+        }
+        function removeTimeoutHandlers(handler) {
+            for (var i = 0; i < timeoutHandlers.length; ++i) {
+                if (timeoutHandlers[i] === handler) {
+                    timeoutHandlers.splice(i, 1);
+                }
+            }
+        }
+
+        function initEvents() {
+            $('.form-group').change(function () {
+                run();
+            });
+        }
+
+        function run() {
+            clearTimeoutHandlers();
+
+            var title = $('#input-title').val();
+            var max = $('#input-max').val();
+            chart.setOption({
+                title: [{
+                    text: getDataName(0),
+                    textStyle: {
+                        fontFamily: 'monospace',
+                        fontSize: 80,
+                        color: 'rgba(100, 100, 100, 0.2)'
+                    },
+                    bottom: 60,
+                    right: 20
+                }, {
+                    text: title
+                }],
+                grid: {
+                    right: 20
+                },
+                yAxis: {
+                    type: 'category',
+                    data: getYData(),
+                    inverse: true,
+                    max: max,
+                    animationDuration: 0,
+                    animationDurationUpdate: 0
+                },
+                xAxis: {},
+                series: [{
+                    type: 'bar',
+                    data: getChartData(0),
+                    realtimeSort: true,
+                    colorBy: 'item',
+                    label: {
+                        show: true,
+                        position: 'insideRight'
+                    }
+                }],
+                animationDurationUpdate: 5000,
+                animationEasing: 'linear',
+                animationEasingUpdate: 'linear'
+            }, true);
+
+            var rows = trimRows();
+            for (var i = 1; i < rows.length; ++i) {
+                (function (i) {
+                    var dataRow = getChartData(i);
+                    var timeout = function () {
+                        chart.setOption({
+                            title: [{
+                                text: getDataName(i)
+                            }],
+                            yAxis: {
+                                animationDuration: 300,
+                                animationDurationUpdate: 300,
+                            },
+                            series: [{
+                                type: 'bar',
+                                data: dataRow
+                            }]
+                        });
+                        removeTimeoutHandlers(timeout);
+                    };
+                    timeoutHandlers.push(timeout);
+                    setTimeout(timeout, (i - 1) * 5000);
+                })(i);
+            }
+        }
+
+        function download() {
+            saveFile()
+        }
+
+        function saveFile(data, name, type) {
+            if (isSafari()) {
+                window.open('data:text/plain;charset=utf-8,' + encodeURIComponent(data));
+            } else {
+                try {
+                    var file = new Blob([data], {type: type});
+                    saveAs(file, name);
+                } catch(e) {
+                    console.error(e);
+                    window.open('data:text/plain;charset=utf-8,' + encodeURIComponent(data));
+                }
+            }
+        }
+
+        function isSafari() {
+            return navigator.userAgent.indexOf('Safari') > 0 &&
+                navigator.userAgent.indexOf('Chrome') < 0;
+        }