You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by ov...@apache.org on 2021/06/10 10:31:47 UTC

[echarts-bar-racing] branch master updated (4298e90 -> b2e7210)

This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/echarts-bar-racing.git.


    from 4298e90  use tailwind css
     new 115f332  chart and table
     new b2e7210  feat: generate bar race chart from table

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 index.html                |   1 +
 package-lock.json         | 100 +++++++++++++++++++
 package.json              |   7 +-
 src/App.vue               |   7 +-
 src/components/BBody.vue  | 103 ++++++++++++++++++++
 src/components/BChart.vue | 130 +++++++++++++++++++++++++
 src/components/BTable.vue | 188 +++++++++++++++++++++++++++++------
 src/components/btable.ts  | 243 ++++++++++++++++++++++++++++++++++++++++++++++
 tsconfig.json             |   4 +-
 9 files changed, 747 insertions(+), 36 deletions(-)
 create mode 100644 src/components/BBody.vue
 create mode 100644 src/components/BChart.vue
 create mode 100644 src/components/btable.ts

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org


[echarts-bar-racing] 01/02: chart and table

Posted by ov...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/echarts-bar-racing.git

commit 115f33214c127ea9c11019751d5ef7a721424f43
Author: Ovilia <zw...@gmail.com>
AuthorDate: Mon Jun 7 17:12:18 2021 +0800

    chart and table
---
 package-lock.json         | 100 +++++++++++++++++++
 package.json              |   5 +
 src/components/BTable.vue |  66 +++++++++----
 src/components/btable.ts  | 243 ++++++++++++++++++++++++++++++++++++++++++++++
 tsconfig.json             |   4 +-
 5 files changed, 398 insertions(+), 20 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index d067e4d..c489f7f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,16 @@
             "resolved": "https://registry.nlark.com/@babel/parser/download/@babel/parser-7.14.3.tgz?cache=0&sync_timestamp=1621284389691&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fparser%2Fdownload%2F%40babel%2Fparser-7.14.3.tgz",
             "integrity": "sha1-m1MO7LBx/QyTUZ3yXF/58UdZ8pg="
         },
+        "@babel/polyfill": {
+            "version": "7.12.1",
+            "resolved": "https://registry.npm.taobao.org/@babel/polyfill/download/@babel/polyfill-7.12.1.tgz",
+            "integrity": "sha1-Hy1jcdEmG72WHzxdWQkVDhLQvZY=",
+            "dev": true,
+            "requires": {
+                "core-js": "^2.6.5",
+                "regenerator-runtime": "^0.13.4"
+            }
+        },
         "@babel/types": {
             "version": "7.14.2",
             "resolved": "https://registry.nlark.com/@babel/types/download/@babel/types-7.14.2.tgz",
@@ -403,6 +413,15 @@
                 "@types/node": "*"
             }
         },
+        "@types/jquery": {
+            "version": "3.5.5",
+            "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.5.tgz",
+            "integrity": "sha512-6RXU9Xzpc6vxNrS6FPPapN1SxSHgQ336WC6Jj/N8q30OiaBZ00l1GBgeP7usjVZPivSkGUfL1z/WW6TX989M+w==",
+            "dev": true,
+            "requires": {
+                "@types/sizzle": "*"
+            }
+        },
         "@types/lodash": {
             "version": "4.14.169",
             "resolved": "https://registry.nlark.com/@types/lodash/download/@types/lodash-4.14.169.tgz",
@@ -420,6 +439,12 @@
             "integrity": "sha1-I6Brh+7bUkAWYW6IaxFrj9yxgK8=",
             "dev": true
         },
+        "@types/sizzle": {
+            "version": "2.3.3",
+            "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
+            "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
+            "dev": true
+        },
         "@types/unist": {
             "version": "2.0.3",
             "resolved": "https://registry.nlark.com/@types/unist/download/@types/unist-2.0.3.tgz?cache=0&sync_timestamp=1621243829208&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Funist%2Fdownload%2F%40types%2Funist-2.0.3.tgz",
@@ -790,6 +815,11 @@
             "integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=",
             "dev": true
         },
+        "bignumber.js": {
+            "version": "8.1.1",
+            "resolved": "https://registry.npm.taobao.org/bignumber.js/download/bignumber.js-8.1.1.tgz",
+            "integrity": "sha1-Swcq5a6pwg9nMOTl1SnfEnHE2IU="
+        },
         "binary-extensions": {
             "version": "2.2.0",
             "resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.2.0.tgz?cache=0&sync_timestamp=1610299293319&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbinary-extensions%2Fdownload%2Fbinary-extensions-2.2.0.tgz",
@@ -1278,6 +1308,12 @@
                 "@babel/types": "^7.6.1"
             }
         },
+        "core-js": {
+            "version": "2.6.12",
+            "resolved": "https://registry.nlark.com/core-js/download/core-js-2.6.12.tgz",
+            "integrity": "sha1-2TM9+nsGXjR8xWgiGdb2kIWcwuw=",
+            "dev": true
+        },
         "core-util-is": {
             "version": "1.0.2",
             "resolved": "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz",
@@ -1500,6 +1536,15 @@
             "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
             "dev": true
         },
+        "echarts": {
+            "version": "5.1.1",
+            "resolved": "https://registry.nlark.com/echarts/download/echarts-5.1.1.tgz",
+            "integrity": "sha1-sYbxYvAXxVXP1nsS7eZ2K9w939o=",
+            "requires": {
+                "tslib": "2.0.3",
+                "zrender": "5.1.0"
+            }
+        },
         "electron-to-chromium": {
             "version": "1.3.747",
             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.747.tgz",
@@ -1861,6 +1906,16 @@
             "integrity": "sha1-/wQLKwhTsjw9MQJ1I3BvGIXXa+4=",
             "dev": true
         },
+        "handsontable": {
+            "version": "6.2.2",
+            "resolved": "https://registry.nlark.com/handsontable/download/handsontable-6.2.2.tgz?cache=0&sync_timestamp=1622544081773&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fhandsontable%2Fdownload%2Fhandsontable-6.2.2.tgz",
+            "integrity": "sha1-8SUPPzdKvffUoAgJUEgtPt7qjwc=",
+            "requires": {
+                "moment": "2.20.1",
+                "numbro": "^2.0.6",
+                "pikaday": "1.5.1"
+            }
+        },
         "has": {
             "version": "1.0.3",
             "resolved": "https://registry.npm.taobao.org/has/download/has-1.0.3.tgz",
@@ -2275,6 +2330,11 @@
             "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
             "dev": true
         },
+        "jquery": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npm.taobao.org/jquery/download/jquery-3.6.0.tgz?cache=0&sync_timestamp=1614705221099&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjquery%2Fdownload%2Fjquery-3.6.0.tgz",
+            "integrity": "sha1-xyoJ8Vwb3OFC9J2/EXC9+K2sJHA="
+        },
         "js-stringify": {
             "version": "1.0.2",
             "resolved": "https://registry.npm.taobao.org/js-stringify/download/js-stringify-1.0.2.tgz",
@@ -2575,6 +2635,11 @@
             "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==",
             "dev": true
         },
+        "moment": {
+            "version": "2.20.1",
+            "resolved": "https://registry.npm.taobao.org/moment/download/moment-2.20.1.tgz?cache=0&sync_timestamp=1601983320283&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmoment%2Fdownload%2Fmoment-2.20.1.tgz",
+            "integrity": "sha1-1usaRsvMFKKy+UNBEsH/iQfzE/0="
+        },
         "ms": {
             "version": "2.1.3",
             "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.3.tgz?cache=0&sync_timestamp=1607433905701&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.3.tgz",
@@ -2680,6 +2745,14 @@
             "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
             "dev": true
         },
+        "numbro": {
+            "version": "2.3.2",
+            "resolved": "https://registry.npm.taobao.org/numbro/download/numbro-2.3.2.tgz",
+            "integrity": "sha1-SWfGMbDqmbF91/iM3R6qSs5NSXE=",
+            "requires": {
+                "bignumber.js": "^8.1.1"
+            }
+        },
         "object-assign": {
             "version": "4.1.1",
             "resolved": "http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz",
@@ -2887,6 +2960,14 @@
             "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
             "dev": true
         },
+        "pikaday": {
+            "version": "1.5.1",
+            "resolved": "https://registry.npm.taobao.org/pikaday/download/pikaday-1.5.1.tgz",
+            "integrity": "sha1-CkhUm8GhTqHQjEQHTXYbwvK/z9M=",
+            "requires": {
+                "moment": "2.x"
+            }
+        },
         "pinkie": {
             "version": "2.0.4",
             "resolved": "http://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz",
@@ -3492,6 +3573,12 @@
                 }
             }
         },
+        "regenerator-runtime": {
+            "version": "0.13.7",
+            "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.7.tgz",
+            "integrity": "sha1-ysLazIoepnX+qrrriugziYrkb1U=",
+            "dev": true
+        },
         "registry-auth-token": {
             "version": "4.2.1",
             "resolved": "https://registry.npm.taobao.org/registry-auth-token/download/registry-auth-token-4.2.1.tgz",
@@ -3976,6 +4063,11 @@
             "integrity": "sha1-uLY5zvrX0LsqvTfUM/+Ck++l9AY=",
             "dev": true
         },
+        "tslib": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npm.taobao.org/tslib/download/tslib-2.0.3.tgz?cache=0&sync_timestamp=1617647074515&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-2.0.3.tgz",
+            "integrity": "sha1-jgdBrEX8DCJuWKF7/D5kubxsphw="
+        },
         "type-fest": {
             "version": "0.3.1",
             "resolved": "https://registry.nlark.com/type-fest/download/type-fest-0.3.1.tgz?cache=0&sync_timestamp=1621402383646&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftype-fest%2Fdownload%2Ftype-fest-0.3.1.tgz",
@@ -5031,6 +5123,14 @@
             "requires": {
                 "camelcase": "^4.1.0"
             }
+        },
+        "zrender": {
+            "version": "5.1.0",
+            "resolved": "https://registry.nlark.com/zrender/download/zrender-5.1.0.tgz",
+            "integrity": "sha1-tqhMOqfMxmQu4FGZAcpMCDXE2F4=",
+            "requires": {
+                "tslib": "2.0.3"
+            }
         }
     }
 }
diff --git a/package.json b/package.json
index cd90565..9d4a02a 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,9 @@
         "serve": "vite preview"
     },
     "devDependencies": {
+        "@babel/polyfill": "^7.12.1",
         "@tailwindcss/postcss7-compat": "^2.1.4",
+        "@types/jquery": "^3.5.5",
         "@vitejs/plugin-vue": "^1.2.2",
         "@vue/compiler-sfc": "^3.0.11",
         "autoprefixer": "^9.8.6",
@@ -28,7 +30,10 @@
         "yargs": "^6.6.0"
     },
     "dependencies": {
+        "echarts": "^5.1.1",
         "element-plus": "^1.0.2-beta.44",
+        "handsontable": "^6.2.2",
+        "jquery": "^3.6.0",
         "lodash": "^4.17.19",
         "vue": "^3.0.11",
         "vue-i18n": "^9.1.6"
diff --git a/src/components/BTable.vue b/src/components/BTable.vue
index ea64fd7..9f6fc36 100644
--- a/src/components/BTable.vue
+++ b/src/components/BTable.vue
@@ -1,34 +1,58 @@
 <template>
     <div class="w-full h-full">
-        <div class="grid grid-cols-3 h-full">
-            <div id="el-table" class="col-span-2 h-full" style="border: 1px solid red;"></div>
-            <div id="el-config" class="p-3 align-middle">
-                <div>
-                    <el-button type="button" onclick="run()" size="medium">运行</el-button>
-                    <el-button type="button" size="medium">导出</el-button>
+        <div class="grid grid-cols-12 h-full text-sm">
+            <el-card class="box-card col-span-3">
+                <h1 slot="header" class="clearfix text-xl">
+                    Apache ECharts Bar-Race 生成器
+                </h1>
+                <div id="el-config" class="align-middle">
+                    <!-- <div class="my-3">
+                        <el-button onclick="run()" size="medium">运行</el-button>
+                        <el-button size="medium">导出</el-button>
+                    </div> -->
+                    <el-form ref="form">
+                        <div class="grid grid-cols-3 form-row">
+                            <label class="col-span-1">标题</label>
+                            <el-input id="input-title" value="汽车产量动态排名" size="medium" class="col-span-2"></el-input>
+                        </div>
+                        <div class="grid grid-cols-3 form-row">
+                            <label class="col-span-1">显示排名上限</label>
+                            <el-input id="input-max" type="number" value="10" size="medium" class="col-span-2"></el-input>
+                        </div>
+                    </el-form>
                 </div>
-                <el-form ref="form">
-                    <div class="grid grid-cols-2 form-row">
-                        <label>标题</label>
-                        <el-input id="input-title" value="汽车产量动态排名" size="medium"></el-input>
-                    </div>
-                    <div class="grid grid-cols-2 form-row">
-                        <label>显示排名上限</label>
-                        <el-input id="input-max" type="number" value="10" size="medium"></el-input>
-                    </div>
-                </el-form>
-            </div>
+            </el-card>
+            <el-card class="box-card col-span-4 relative" body-style="height: 100%">
+                <div slot="header" class="clearfix text-base">
+                    数据
+                </div>
+                <div id="table-panel" class="overflow-auto absolute bottom-4 top-14 left-5 right-5 border">
+                </div>
+            </el-card>
+            <el-card class="box-card col-span-5 relative" body-style="height: 100%">
+                <div slot="header" class="clearfix text-base">
+                    预览
+                    <a href="#">
+                        <i class="el-icon-refresh"></i>
+                    </a>
+                </div>
+                <div id="bar-race-preview" class="absolute bottom-4 top-14 left-5 right-5 border">
+                </div>
+            </el-card>
         </div>
     </div>
 </template>
 
 <script lang="ts">
-import {defineComponent} from 'vue'
+import {defineComponent} from 'vue';
+import btable from './btable';
+
 export default defineComponent({
     name: 'BTable',
     props: {
     },
-    setup: () => {
+    mounted: () => {
+        btable.initTable();
     }
 })
 </script>
@@ -42,5 +66,9 @@ export default defineComponent({
             @apply py-1;
         }
     }
+
+    .box-card {
+        @apply m-1;
+    }
 }
 </style>
diff --git a/src/components/btable.ts b/src/components/btable.ts
new file mode 100644
index 0000000..43eafec
--- /dev/null
+++ b/src/components/btable.ts
@@ -0,0 +1,243 @@
+import * as $ from 'jquery';
+import Handsontable from 'handsontable';
+import * as echarts from 'echarts';
+
+const headerLength = 2;
+
+let chart: echarts.ECharts;
+const data = [
+    ["Name", "Ford", "Tesla", "Toyota", "Honda"],
+    ["Color", "", "", "", ""],
+    ["2017", 10, 11, 12, 13],
+    ["2018", 20, 11, 14, 13],
+    ["2019", 30, 15, 12, 13]
+];
+
+function initTable() {
+    for (let i = 0; i < data.length; ++i) {
+        for (let j = data[i].length; j < 50; ++j) {
+            data[i].push('');
+        }
+    }
+    for (let i = data.length; i < 100; ++i) {
+        const row = [];
+        for (let j = 0; j < 50; ++j) {
+            row.push('');
+        }
+        data.push(row);
+    }
+
+    // function colorRenderer(instance, td, row, col) {
+    //     //- console.log(instance);
+
+    // }
+
+    const container = document.getElementById('table-panel') as Element;
+    console.log(container)
+
+    const 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 () {
+            run();
+        }
+    });
+
+    chart = echarts.init($('#bar-race-preview')[0]);
+    run();
+}
+
+const timeoutHandlers: number[] = [];
+
+function clearTimeoutHandlers() {
+    for (let i = 0; i < timeoutHandlers.length; ++i) {
+        clearTimeout(timeoutHandlers[i]);
+        timeoutHandlers.splice(i, 1);
+    }
+}
+function removeTimeoutHandlers(handler: number) {
+    for (let i = 0; i < timeoutHandlers.length; ++i) {
+        if (timeoutHandlers[i] === handler) {
+            timeoutHandlers.splice(i, 1);
+        }
+    }
+}
+
+function initEvents() {
+    $('.form-group').change(function () {
+        run();
+    });
+}
+
+
+function run() {
+    clearTimeoutHandlers();
+
+    const title = $('#input-title').val();
+    const 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);
+
+    const rows = trimRows();
+    for (let i = 1; i < rows.length; ++i) {
+        (function (i) {
+            var dataRow = getChartData(i);
+            let timeout: number;
+            const timeoutCb = function () {
+                chart.setOption({
+                    title: [{
+                        text: getDataName(i)
+                    }],
+                    yAxis: {
+                        animationDuration: 300,
+                        animationDurationUpdate: 300,
+                    },
+                    series: [{
+                        type: 'bar',
+                        data: dataRow
+                    }]
+                });
+                removeTimeoutHandlers(timeout);
+            };
+            timeout = window.setTimeout(timeoutCb, (i - 1) * 5000);
+            timeoutHandlers.push(timeout);
+        })(i);
+    }
+}
+
+const trimColumns = function (rowData: (string | number)[]) {
+    for (let i = rowData.length - 1; i > 0; --i) {
+        if (rowData[i] && rowData[i] !== '') {
+            return rowData.slice(1, i + 1);
+        }
+    }
+    return [];
+};
+
+const trimRows = function () {
+    if (data.length <= headerLength) {
+        return [];
+    }
+    for (let i = data.length - 1; i >= headerLength; --i) {
+        let isEmpty = true;
+        for (let 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 [];
+};
+
+const getYData = function () {
+    if (data.length <= headerLength) {
+        return [];
+    }
+    return trimColumns(data[0]);
+};
+
+const getChartData = function (id: number) {
+    if (data.length <= id + headerLength) {
+        return [];
+    }
+    return trimColumns(data[id + headerLength]);
+};
+
+const getDataName = function (id: number) {
+    if (data.length <= id + headerLength) {
+        return '';
+    }
+    else {
+        return data[id + headerLength][0];
+    }
+}
+
+function download() {
+    // saveFile()
+}
+
+function saveFile(name: string, type: string) {
+    // 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;
+}
+
+export default {initTable};
diff --git a/tsconfig.json b/tsconfig.json
index 5f1b278..6823c9a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,5 +11,7 @@
         "allowJs": true,
     },
     "include": ["src/**/*"],
-    "exclude": ["node_modules"]
+    "exclude": ["node_modules"],
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true
 }
\ No newline at end of file

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org


[echarts-bar-racing] 02/02: feat: generate bar race chart from table

Posted by ov...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/echarts-bar-racing.git

commit b2e7210ed122ba0342281a243370ac884bc80d6f
Author: Ovilia <zw...@gmail.com>
AuthorDate: Thu Jun 10 18:31:30 2021 +0800

    feat: generate bar race chart from table
---
 index.html                |   1 +
 package.json              |   2 +-
 src/App.vue               |   7 +-
 src/components/BBody.vue  | 103 ++++++++++++++++++++++
 src/components/BChart.vue | 130 ++++++++++++++++++++++++++++
 src/components/BTable.vue | 212 ++++++++++++++++++++++++++++++++++------------
 6 files changed, 394 insertions(+), 61 deletions(-)

diff --git a/index.html b/index.html
index de4a318..725bacc 100644
--- a/index.html
+++ b/index.html
@@ -5,6 +5,7 @@
         <link rel="icon" href="/favicon.ico" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         <title>ECharts WWW SPA Boilerplate</title>
+        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable@6.2.2/dist/handsontable.css">
         <style>
             body {
                 padding: 0;
diff --git a/package.json b/package.json
index 9d4a02a..bb3f5b7 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
         "element-plus": "^1.0.2-beta.44",
         "handsontable": "^6.2.2",
         "jquery": "^3.6.0",
-        "lodash": "^4.17.19",
+        "lodash": "^4.17.21",
         "vue": "^3.0.11",
         "vue-i18n": "^9.1.6"
     }
diff --git a/src/App.vue b/src/App.vue
index e593a7c..77b2d87 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,17 +1,14 @@
 <template>
 <el-container>
-    <!-- <el-aside>
-        <h3>Side Nav</h3>
-    </el-aside> -->
     <el-main>
-        <BTable/>
+        <BBody/>
     </el-main>
 </el-container>
 </template>
 
 <script lang='ts' setup>
 
-import BTable from './components/BTable.vue';
+import BBody from './components/BBody.vue';
 
 </script>
 
diff --git a/src/components/BBody.vue b/src/components/BBody.vue
new file mode 100644
index 0000000..498a897
--- /dev/null
+++ b/src/components/BBody.vue
@@ -0,0 +1,103 @@
+<template>
+    <div class="w-full h-full">
+        <div class="grid grid-cols-12 h-full text-sm">
+            <el-card class="box-card col-span-3">
+                <h1 slot="header" class="clearfix text-xl">
+                    动态排序柱状图生成器
+                </h1>
+                <div id="el-config" class="align-middle">
+                    <!-- <div class="my-3">
+                        <el-button onclick="run()" size="medium">运行</el-button>
+                        <el-button size="medium">导出</el-button>
+                    </div> -->
+                    <el-form ref="form">
+                        <div class="grid grid-cols-3 form-row">
+                            <label class="col-span-1">标题</label>
+                            <el-input
+                                id="input-title"
+                                value="汽车产量动态排名"
+                                size="medium"
+                                class="col-span-2"
+                                v-model="title"
+                            >
+                            </el-input>
+                        </div>
+                        <div class="grid grid-cols-3 form-row">
+                            <label class="col-span-1">显示排名上限</label>
+                            <el-input
+                                id="input-max"
+                                type="number"
+                                value="10"
+                                size="medium"
+                                class="col-span-2"
+                                v-model="maxDataCnt"
+                            >
+                            </el-input>
+                        </div>
+                    </el-form>
+                </div>
+            </el-card>
+            <el-card
+                class="box-card col-span-4 relative"
+                body-style="height: 100%"
+            >
+                <BTable
+                    @after-change="tableAfterChange"
+                />
+            </el-card>
+            <el-card
+                class="box-card col-span-5 relative"
+                body-style="height: 100%"
+            >
+                <BChart
+                    :title="title"
+                    :chartData="chartData"
+                />
+            </el-card>
+        </div>
+    </div>
+</template>
+
+<script lang="ts">
+import {defineComponent} from 'vue';
+import BTable, {ChartData} from './BTable.vue';
+import BChart from './BChart.vue';
+
+export default defineComponent({
+    name: 'BBody',
+    data() {
+        return {
+            title: '汽车销量',
+            maxDataCnt: 10,
+            chartData: null
+        }
+    },
+    components: {
+        BTable,
+        BChart
+    },
+    mounted: () => {
+    },
+    methods: {
+        tableAfterChange(data: ChartData) {
+            this.chartData = data;
+        }
+    }
+})
+</script>
+
+<style scoped>
+@layer utilities {
+    .form-row {
+        @apply my-3;
+
+        label {
+            @apply py-1;
+        }
+    }
+
+    .box-card {
+        @apply m-1;
+    }
+}
+</style>
diff --git a/src/components/BChart.vue b/src/components/BChart.vue
new file mode 100644
index 0000000..97dc9db
--- /dev/null
+++ b/src/components/BChart.vue
@@ -0,0 +1,130 @@
+<template>
+    <div>
+        <div slot="header" class="clearfix text-base">
+            预览{{title}}
+            <a href="#">
+                <i class="el-icon-refresh"></i>
+            </a>
+        </div>
+        <div
+            id="bar-race-preview"
+            ref="chart"
+            class="absolute bottom-4 top-14 left-5 right-5 border">
+        </div>
+    </div>
+</template>
+
+<script lang="ts">
+import {defineComponent} from 'vue';
+import * as echarts from 'echarts';
+
+const headerLength = 2;
+let chart: echarts.ECharts;
+
+export default defineComponent({
+    name: 'BChart',
+    props: {
+        title: String,
+        chartData: Array
+    },
+    data() {
+        return {
+            timeoutHandlers: []
+        };
+    },
+    watch: {
+        chartData() {
+            this.run();
+        }
+    },
+    mounted() {
+        chart = echarts.init(this.$refs.chart as HTMLElement);
+    },
+    methods: {
+        run() {
+            if (!chart) {
+                return;
+            }
+            const option = {
+                dataset: {
+                    source: this.chartData
+                },
+                xAxis: {
+                    type: 'value'
+                },
+                yAxis: {
+                    type: 'category',
+                    inverse: true,
+                    animationDuration: 300,
+                    animationDurationUpdate: 300
+                },
+                series: [{
+                    id: 'bar',
+                    type: 'bar',
+                    encode: {
+                        x: 2
+                    },
+                    seriesLayoutBy: 'row',
+                    realtimeSort: true,
+                    label: {
+                        show: true,
+                        position: 'right'
+                    }
+                }],
+                grid: {
+                    right: 60
+                },
+                animationDurationUpdate: 5000,
+                animationEasing: 'linear',
+                animationEasingUpdate: 'linear'
+            };
+            chart.setOption(option as echarts.EChartsOption, true);
+
+            const dataCnt = this.chartData.length - headerLength - 1;
+            const that = this;
+            for (let i = 0; i < dataCnt; ++i) {
+                (function (i: number) {
+                    let timeout: number;
+                    const timeoutCb = function () {
+                        chart.setOption({
+                            // title: [{
+                            //     text: getDataName(i)
+                            // }],
+                            series: [{
+                                type: 'bar',
+                                id: 'bar',
+                                encode: {
+                                    x: i + headerLength + 1
+                                }
+                            }]
+                        });
+                        that.removeTimeoutHandlers(timeout);
+                    };
+                    timeout = window.setTimeout(timeoutCb, i * 5000);
+                    that.timeoutHandlers.push(timeout);
+                })(i);
+            }
+        },
+
+        clearTimeoutHandlers() {
+            for (let i = 0; i < this.timeoutHandlers.length; ++i) {
+                clearTimeout(this.timeoutHandlers[i]);
+                this.timeoutHandlers.splice(i, 1);
+            }
+        },
+
+        removeTimeoutHandlers(handler: number) {
+            for (let i = 0; i < this.timeoutHandlers.length; ++i) {
+                if (this.timeoutHandlers[i] === handler) {
+                    this.timeoutHandlers.splice(i, 1);
+                }
+            }
+        }
+    }
+})
+</script>
+
+<style scoped>
+@layer utilities {
+}
+</style>
diff --git a/src/components/BTable.vue b/src/components/BTable.vue
index 9f6fc36..34dc0a3 100644
--- a/src/components/BTable.vue
+++ b/src/components/BTable.vue
@@ -1,74 +1,176 @@
 <template>
-    <div class="w-full h-full">
-        <div class="grid grid-cols-12 h-full text-sm">
-            <el-card class="box-card col-span-3">
-                <h1 slot="header" class="clearfix text-xl">
-                    Apache ECharts Bar-Race 生成器
-                </h1>
-                <div id="el-config" class="align-middle">
-                    <!-- <div class="my-3">
-                        <el-button onclick="run()" size="medium">运行</el-button>
-                        <el-button size="medium">导出</el-button>
-                    </div> -->
-                    <el-form ref="form">
-                        <div class="grid grid-cols-3 form-row">
-                            <label class="col-span-1">标题</label>
-                            <el-input id="input-title" value="汽车产量动态排名" size="medium" class="col-span-2"></el-input>
-                        </div>
-                        <div class="grid grid-cols-3 form-row">
-                            <label class="col-span-1">显示排名上限</label>
-                            <el-input id="input-max" type="number" value="10" size="medium" class="col-span-2"></el-input>
-                        </div>
-                    </el-form>
-                </div>
-            </el-card>
-            <el-card class="box-card col-span-4 relative" body-style="height: 100%">
-                <div slot="header" class="clearfix text-base">
-                    数据
-                </div>
-                <div id="table-panel" class="overflow-auto absolute bottom-4 top-14 left-5 right-5 border">
-                </div>
-            </el-card>
-            <el-card class="box-card col-span-5 relative" body-style="height: 100%">
-                <div slot="header" class="clearfix text-base">
-                    预览
-                    <a href="#">
-                        <i class="el-icon-refresh"></i>
-                    </a>
-                </div>
-                <div id="bar-race-preview" class="absolute bottom-4 top-14 left-5 right-5 border">
-                </div>
-            </el-card>
+    <div>
+        <div slot='header' class='clearfix text-base'>
+            数据
+        </div>
+        <div ref='table' id='table-panel' class='overflow-auto absolute bottom-4 top-14 left-5 right-5 border'>
         </div>
     </div>
 </template>
 
-<script lang="ts">
+<script lang='ts'>
+import Handsontable from 'handsontable';
 import {defineComponent} from 'vue';
-import btable from './btable';
+import * as _ from 'lodash';
+
+const headerLength = 2;
+export type ChartData = string[][];
 
 export default defineComponent({
     name: 'BTable',
     props: {
     },
-    mounted: () => {
-        btable.initTable();
+    data() {
+        return {
+            tableData: [
+                ['Name', 'Ford', 'Tesla', 'Toyota', 'Honda'],
+                ['Color', '', '', '', ''],
+                ['2017', '10', '11', '12', '13'],
+                ['2018', '20', '11', '14', '13'],
+                ['2019', '30', '15', '12', '13']
+            ],
+            table: null,
+            debouncedTableChange: null
+        }
+    },
+    mounted() {
+        this.insertEmptyCells();
+        this.table = new Handsontable(this.$refs.table as Element, {
+            data: this.tableData,
+            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 {};
+            //-     }
+            //- }
+        });
+        this.table.updateSettings({
+            afterChange: () => {
+                console.log('after')
+                this.debouncedTableChange();
+            }
+        });
+
+        this.debouncedTableChange = _.debounce(() => {
+            this.$emit('afterChange', this.getChartData());
+        }, 500);
+
+        this.$emit('afterChange', this.getChartData());
+    },
+    unmounted() {
+        this.debouncedTableChange.cancel();
+    },
+    methods: {
+        getChartData(): ChartData {
+            let columns = 0;
+            const firstRow = this.tableData[0];
+            for (let i = 0; i < firstRow.length; ++i) {
+                if (!firstRow[i] || !firstRow[i].trim()) {
+                    columns = i;
+                    break;
+                }
+            }
+
+            let rows = 0;
+            for (let i = 0; i < this.tableData.length; ++i) {
+                if (!this.tableData[i] || !this.tableData[i][0] || !this.tableData[i][0]) {
+                    rows = i;
+                    break;
+                }
+            }
+
+            return this.tableData.slice(0, rows)
+                .map(row => row.slice(0, columns));
+        },
+
+        insertEmptyCells() {
+            for (let i = 0; i < this.tableData.length; ++i) {
+                for (let j = this.tableData[i].length; j < 50; ++j) {
+                    this.tableData[i].push('');
+                }
+            }
+            for (let i = this.tableData.length; i < 100; ++i) {
+                const row = [];
+                for (let j = 0; j < 50; ++j) {
+                    row.push('');
+                }
+                this.tableData.push(row);
+            }
+        },
+
+        trimColumns(rowData: (string | number)[]) {
+            for (let i = rowData.length - 1; i > 0; --i) {
+                if (rowData[i] && rowData[i] !== '') {
+                    return rowData.slice(1, i + 1);
+                }
+            }
+            return [];
+        },
+
+        trimRows() {
+            const data = this.tableData;
+            if (data.length <= headerLength) {
+                return [];
+            }
+            for (let i = data.length - 1; i >= headerLength; --i) {
+                let isEmpty = true;
+                for (let 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 [];
+        },
+
+        getYData() {
+            if (this.tableData.length <= headerLength) {
+                return [];
+            }
+            return this.trimColumns(this.tableData[0]);
+        },
+
+        getSeriesData(id: number) {
+            if (this.tableData.length <= id + headerLength) {
+                return [];
+            }
+            return this.trimColumns(this.tableData[id + headerLength]);
+        },
+
+        getDataName(id: number) {
+            if (this.tableData.length <= id + headerLength) {
+                return '';
+            }
+            else {
+                return this.tableData[id + headerLength][0];
+            }
+        }
     }
 })
 </script>
 
 <style scoped>
 @layer utilities {
-    .form-row {
-        @apply my-3;
-
-        label {
-            @apply py-1;
-        }
-    }
-
-    .box-card {
-        @apply m-1;
-    }
 }
 </style>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org