feat: download
diff --git a/src/components/BBody.vue b/src/components/BBody.vue
index 5972da0..0246a5b 100644
--- a/src/components/BBody.vue
+++ b/src/components/BBody.vue
@@ -6,10 +6,6 @@
动态排序柱状图生成器
</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>
@@ -19,6 +15,7 @@
size="medium"
class="col-span-2"
v-model="title"
+ @change="runChart"
>
</el-input>
</div>
@@ -31,11 +28,12 @@
size="medium"
class="col-span-2"
v-model="maxDataCnt"
+ @change="runChart"
>
</el-input>
</div>
<div class="grid grid-cols-3 form-row">
- <label class="col-span-1">显示排名上限</label>
+ <label class="col-span-1">每行动画时长(毫秒)</label>
<el-input
id="input-animation-duration"
type="number"
@@ -43,9 +41,13 @@
size="medium"
class="col-span-2"
v-model="animationDuration"
+ @change="runChart"
>
</el-input>
</div>
+ <el-form-item>
+ <el-button @click="download">下载</el-button>
+ </el-form-item>
</el-form>
</div>
</el-card>
@@ -54,6 +56,7 @@
body-style="height: 100%"
>
<BTable
+ ref="btable"
@after-change="tableAfterChange"
/>
</el-card>
@@ -62,6 +65,7 @@
body-style="height: 100%"
>
<BChart
+ ref="bchart"
:title="title"
:chartData="chartData"
:maxDataCnt="maxDataCnt"
@@ -76,6 +80,7 @@
import {defineComponent} from 'vue';
import BTable, {ChartData} from './BTable.vue';
import BChart from './BChart.vue';
+import template from '../helper/template';
export default defineComponent({
name: 'BBody',
@@ -84,7 +89,7 @@
title: '汽车销量',
maxDataCnt: null,
chartData: null,
- animationDuration: 5000
+ animationDuration: 3000
}
},
components: {
@@ -96,6 +101,32 @@
methods: {
tableAfterChange(data: ChartData) {
this.chartData = data;
+ },
+
+ runChart() {
+ (this.$refs.bchart as any).run();
+ },
+
+ download() {
+ let html = template;
+ const map = {
+ animationDuration: this.animationDuration,
+ maxDataCnt: this.maxDataCnt,
+ title: this.title,
+ data: (this.$refs.btable as any).getChartData()
+ };
+ for (let attr in map) {
+ const value = (map as any)[attr];
+ html = html.replace(`{{${attr}}}`, JSON.stringify(value));
+ }
+
+ const element = document.createElement('a');
+ element.setAttribute('href', 'data:text/html;charset=utf-8,' + encodeURIComponent(html));
+ element.setAttribute('download', 'echarts-bar-racing.html');
+ element.style.display = 'none';
+ document.body.appendChild(element);
+ element.click();
+ document.body.removeChild(element);
}
}
})
diff --git a/src/components/BChart.vue b/src/components/BChart.vue
index fc55c9f..51f4f01 100644
--- a/src/components/BChart.vue
+++ b/src/components/BChart.vue
@@ -2,7 +2,7 @@
<div>
<div slot="header" class="clearfix text-base">
预览
- <a href="#">
+ <a href="javascript:;" @click="run()">
<i class="el-icon-refresh"></i>
</a>
</div>
@@ -60,18 +60,16 @@
}
chart = echarts.init(this.$refs.chart as HTMLElement);
- const animationDuration = this.animationDuration || 5000;
+ const animationDuration =/* this.animationDuration ||*/ 5000;
const option = {
- dataset: {
- source: this.chartData
- },
xAxis: {
type: 'value',
max: 'dataMax'
},
yAxis: {
type: 'category',
+ data: (this.chartData[0] as string[]).slice(1),
inverse: true,
animationDuration: 300,
animationDurationUpdate: 300,
@@ -80,30 +78,37 @@
series: [{
id: 'bar',
type: 'bar',
- encode: {
- x: 2
- },
+ data: (this.chartData[headerLength] as string[]).slice(1).map(str => parseInt(str, 10)),
seriesLayoutBy: 'row',
realtimeSort: true,
label: {
show: true,
- position: 'right',
- valueAnimation: true
+ position: 'right'
},
itemStyle: {
- color: param => {
- return param.data[1] || colorAll[param.dataIndex % colorAll.length];
+ color: (param: any) => {
+ return (this.chartData[1] as string[])[param.dataIndex + 1] || colorAll[param.dataIndex % colorAll.length];
}
}
}],
grid: {
- right: 60
+ right: 60,
+ bottom: 30
},
- title: {
+ title: [{
+ text: (this.chartData as any)[headerLength][0],
+ right: 20,
+ bottom: 15,
+ textStyle: {
+ color: '#ccc',
+ opacity: 0.3,
+ fontSize: 70
+ }
+ }, {
text: this.title,
left: 10,
top: 10
- },
+ }],
animationDuration: 0,
animationDurationUpdate: animationDuration,
animationEasing: 'linear',
@@ -118,14 +123,12 @@
let timeout: number;
const timeoutCb = function () {
chart.setOption({
- // title: [{
- // text: getDataName(i)
- // }],
series: [{
type: 'bar',
id: 'bar',
- encode: {
- x: i + headerLength + 1
+ data: (that.chartData[headerLength + i + 1] as string[]).slice(1).map(str => parseInt(str, 10)),
+ label: {
+ valueAnimation: true
}
}]
});
diff --git a/src/components/BTable.vue b/src/components/BTable.vue
index 34dc0a3..d972959 100644
--- a/src/components/BTable.vue
+++ b/src/components/BTable.vue
@@ -25,9 +25,9 @@
tableData: [
['Name', 'Ford', 'Tesla', 'Toyota', 'Honda'],
['Color', '', '', '', ''],
- ['2017', '10', '11', '12', '13'],
- ['2018', '20', '11', '14', '13'],
- ['2019', '30', '15', '12', '13']
+ ['2017', '13', '11', '12', '14'],
+ ['2018', '20', '44', '34', '39'],
+ ['2019', '62', '75', '58', '63']
],
table: null,
debouncedTableChange: null
diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue
deleted file mode 100644
index 6583f4b..0000000
--- a/src/components/HelloWorld.vue
+++ /dev/null
@@ -1,34 +0,0 @@
-<template>
-<h1>{{msg}}</h1>
-<el-input-number v-model="count"></el-input-number>
-<p>Input Number: {{count}}</p>
-</template>
-
-<script lang="ts" setup>
-import { ref, defineProps } from 'vue';
-
-defineProps({
- msg: {
- type: String,
- required: true
- }
-});
-
-const count = ref(10);
-</script>
-
-<style scoped lang="scss">
-a {
- color: #42b983;
-}
-label {
- margin: 0 0.5em;
- font-weight: bold;
-}
-code {
- background-color: #eee;
- padding: 2px 4px;
- border-radius: 4px;
- color: #304455;
-}
-</style>
\ No newline at end of file
diff --git a/src/components/btable.ts b/src/components/btable.ts
deleted file mode 100644
index 43eafec..0000000
--- a/src/components/btable.ts
+++ /dev/null
@@ -1,243 +0,0 @@
-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/src/helper/template.ts b/src/helper/template.ts
new file mode 100644
index 0000000..6f89e3c
--- /dev/null
+++ b/src/helper/template.ts
@@ -0,0 +1,154 @@
+export default `
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Apache ECharts Bar Racing</title>
+
+ <style>
+ #chart {
+ width: 100%;
+ height: 500px;
+ border: 1px solid #ddd;
+ }
+ </style>
+
+ <script src="https://cdn.jsdelivr.net/npm/echarts@5.1.2/dist/echarts.js"></script>
+</head>
+<body>
+ <div id="chart"></div>
+
+ <script>
+ var colorAll = [
+ '#5470c6',
+ '#91cc75',
+ '#fac858',
+ '#ee6666',
+ '#73c0de',
+ '#3ba272',
+ '#fc8452',
+ '#9a60b4',
+ '#ea7ccc'
+ ];
+ var headerLength = 2;
+
+ var animationDuration = {{animationDuration}};
+ var maxDataCnt = {{maxDataCnt}};
+ var title = {{title}};
+ var data = {{data}};
+
+ var chart;
+ var timeoutHandlers = [];
+
+ run();
+
+ function run() {
+ clearTimeoutHandlers();
+ if (chart) {
+ chart.dispose();
+ }
+
+ chart = echarts.init(document.getElementById('chart'));
+ var option = {
+ xAxis: {
+ type: 'value',
+ max: 'dataMax'
+ },
+ yAxis: {
+ type: 'category',
+ data: data[0].slice(1),
+ inverse: true,
+ animationDuration: 300,
+ animationDurationUpdate: 300,
+ max: maxDataCnt ? maxDataCnt - 1 : null
+ },
+ series: [{
+ id: 'bar',
+ type: 'bar',
+ data: getDataLine(0),
+ seriesLayoutBy: 'row',
+ realtimeSort: true,
+ label: {
+ show: true,
+ position: 'right'
+ },
+ itemStyle: {
+ color: function (param) {
+ return data[1][param.dataIndex + 1] || colorAll[param.dataIndex % colorAll.length];
+ }
+ }
+ }],
+ grid: {
+ right: 60,
+ bottom: 30
+ },
+ title: [{
+ text: 'aaa',
+ right: 20,
+ bottom: 15,
+ textStyle: {
+ color: '#ccc',
+ opacity: 0.3,
+ fontSize: 70
+ }
+ }, {
+ text: title,
+ left: 10,
+ top: 10
+ }],
+ animationDuration: 0,
+ animationDurationUpdate: animationDuration,
+ animationEasing: 'linear',
+ animationEasingUpdate: 'linear'
+ };
+ chart.setOption(option, true);
+
+ var dataCnt = data.length - headerLength - 1;
+ for (var i = 0; i < dataCnt; ++i) {
+ (function (i) {
+ var timeout;
+ var timeoutCb = function () {
+ chart.setOption({
+ series: [{
+ type: 'bar',
+ id: 'bar',
+ data: getDataLine(i + 1),
+ label: {
+ valueAnimation: true
+ }
+ }]
+ });
+ removeTimeoutHandlers(timeout);
+ };
+ timeout = window.setTimeout(timeoutCb, i * animationDuration);
+ timeoutHandlers.push(timeout);
+ })(i);
+ }
+ }
+
+ function getDataLine(n) {
+ return data[headerLength + n].slice(1).map(function (n) {
+ return parseInt(n, 10);
+ });
+ }
+
+ function clearTimeoutHandlers() {
+ for (let i = 0; i < timeoutHandlers.length; ++i) {
+ clearTimeout(timeoutHandlers[i]);
+ }
+ timeoutHandlers = [];
+ }
+
+ function removeTimeoutHandlers(handler) {
+ for (let i = 0; i < timeoutHandlers.length; ++i) {
+ if (timeoutHandlers[i] === handler) {
+ timeoutHandlers.splice(i, 1);
+ }
+ }
+ }
+ </script>
+</body>
+</html>
+`;