├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── build
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.js
├── utils.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── index.html
├── package.json
├── src
├── App.vue
├── assets
│ └── bg.jpg
├── components
│ ├── calendar
│ │ └── calendar.vue
│ ├── checkbox
│ │ └── checkbox.vue
│ ├── column
│ │ └── column.vue
│ ├── dashboard
│ │ └── dashboard.vue
│ ├── filter
│ │ └── filter.vue
│ ├── funnel
│ │ └── funnel.vue
│ ├── header
│ │ └── header.vue
│ ├── heat
│ │ └── heat.vue
│ ├── legend
│ │ └── header.vue
│ ├── line
│ │ └── line.vue
│ ├── multipleColumn
│ │ └── multipleColumn.vue
│ └── point
│ │ └── point.vue
└── main.js
└── static
├── .gitkeep
├── css
└── reset.css
├── data
├── cityData.json
├── heat
│ └── testData.json
└── point
│ └── testData.json
├── img
├── Donate.jpeg
└── demo.jpg
└── js
└── calendar
└── calendar.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015"],
4 | ["stage-2"]
5 | ],
6 | "plugins": [
7 | ["component", [{
8 | "libraryName": "element-ui",
9 | "styleLibraryName": "theme-default"
10 | }]],
11 | ["transform-runtime"]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: 'babel-eslint',
4 | parserOptions: {
5 | sourceType: 'module'
6 | },
7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
8 | extends: 'standard',
9 | // required to lint *.vue files
10 | plugins: [
11 | 'html'
12 | ],
13 | // add your custom rules here
14 | 'rules': {
15 | // allow paren-less arrow functions
16 | 'arrow-parens': 0,
17 | // allow async-await
18 | 'generator-star-spacing': 0,
19 | // allow debugger during development
20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
21 | 'no-multiple-empty-lines': ['error', {
22 | max: 2,
23 | maxEOF: 2,
24 | maxBOF: 2
25 | }],
26 | 'space-before-function-paren': 0,
27 | 'semi': 0,
28 | 'no-new': 0,
29 | 'no-unused-vars': 0,
30 | 'no-undef': 0,
31 | 'quotes': 0,
32 | 'no-dupe-keys': 0,
33 | 'null': 0
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | resource/
5 | npm-debug.log
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 DataVisualization
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 数据可视化
2 |
3 | > 将数据通过图表的形式展现出来将大大的提升可读性和阅读效率
4 |
5 | > 本例包含柱状图、折线图、散点图、热力图、复杂柱状图、预览面板等
6 |
7 | 后续会有新的版本,欢迎大家关注
8 |
9 | # 技术栈
10 |
11 | - vue2.x
12 | - vuex _存储公共变量,如色值等_
13 | - vue-router _路由_
14 | - element-ui _饿了么基于vue2开发组件库,本例使用了其中的datePicker_
15 | - echarts _一款丰富的图表库_
16 | - webpack、ES6、Babel、Stylus...
17 |
18 | # 演示
19 |
20 |
此项目为PC端数据可视化,请在电脑端查看
21 |
22 |
23 | # 项目截图
24 |
25 |
26 |
27 | # 开发
28 |
29 | ## 组件化
30 |
31 | 本项目完全采用组件化的思想进行开发。使用vue-router作为路由,每个页面都是一个组件,每个组件里又包含多个组件。可以看到,多种图表的标题和日期筛选等都是类似的格式,所以这两个都分别做成了组件。
32 |
33 | 除此之外,在筛选产品的时候用到的checkbox也被我写成了组件,有需要的朋友也可以剥离出去单独使用(不过写的比较粗糙~)
34 |
35 | 每个图表都是一个单独的组件,也可以单独的剥离出去使用。
36 |
37 | ## 柱状图
38 |
39 | 本项目包含多种图表,这里挑“柱状图”说一说,其他的图标实现方式类似。
40 |
41 | ```html
42 |
43 |
48 |
49 | ```
50 | 页面HTML可以看到,一个完整的图标是由三个部分组成:
51 |
52 |
53 |
54 | ### v-header
55 | 头组件,存放标题以及不同类型的筛选等
56 |
57 | - name _图表的标题_
58 | - legendArr _图表所包含的多种类型_
59 | - myChart _当前图表对象_
60 |
61 | ### v-filter
62 |
63 | 筛选组件,日期的筛选以及不同产品的筛选
64 |
65 | - myChart _当前图表对象_
66 |
67 | v-if="myChart._dom"表示在当前图表dom元素渲染完成之后再渲染filter组件
68 |
69 | ### main
70 |
71 | _图表的主体文件_
72 |
73 | 需要注意的是:main必须要指定宽高,否则echarts无法渲染dom
74 |
75 | ### 初始化
76 |
77 | 初始化需要在vue的mounted()方法里执行,因为这里能保证当前的页面元素都已经被加载完成。
78 |
79 | ```javascript
80 | mounted() {
81 | // 基于准备好的dom,初始化echarts实例
82 | this.myChart = echarts.init(document.querySelector('.multipleColumn .main'))
83 | this.myChart.setOption(this.options) //this.options为echarts的配置,详情可去我的gitHub查看
84 | }
85 | ```
86 |
87 | 如果要在created()方法里执行,则需要另外加上
88 |
89 | ```javascript
90 | this.$nextTick(() => {
91 | this._init()
92 | })
93 | ```
94 |
95 | ## DashBoard
96 |
97 | dashboard是一个所有图表的预览,并且有一个点击切换的动画效果,这里大概讲解一个实现方式。
98 |
99 | ### html
100 |
101 | ```html
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
117 |
118 |
119 |
120 | ```
121 | 可以看到,这里是设置了四张图表的Wrapper,每个Wrapper里面装一个图表组件。通过动态的改变style样式来切换。
122 |
123 | 整体的思想为:
124 |
125 | - 使用百分比布局,这样才能在不能大小的屏幕做到自适应
126 | - 确定图表显示比例,长宽比
127 | - 只做一个transform变换,这样才能提高性能
128 |
129 | ### 性能
130 |
131 | 关于性能方面,这里多说一句:
132 |
133 | 相信大家都看过在线演示的demo了,不同图表间的切换不仅有位置的变换,还有大小的变换。那么大小的变换大家都知道是用transform的scale变换,但是位置的变换呢,使用left、top?
134 |
135 | 很显然这样是不对的,但是这样确实也能实现效果,但是会非常的消耗性能。一个CSS属性的变化就相当于一个线程,那么如果使用了left、top以及transform的话就是三个线程同时开启,那你的电脑温度将会很快飙升的
136 |
137 | 正确的解决方案还是继续使用transform,使用它的 translate ,如:
138 |
139 | ```css
140 | transform: translate(-22.4%,0) scale(0.33)
141 | ```
142 |
143 | # 结语
144 |
145 | 这个项目还是挺实用的一个项目,较好的运用了vue的组件化思想。
146 |
147 | 大家感兴趣的可以去看看代码,希望对大家有帮助。
148 |
149 |
150 | ## Build Setup
151 |
152 | ``` bash
153 | # install dependencies
154 | npm install
155 |
156 | # serve with hot reload at localhost:8080
157 | npm run dev
158 |
159 | # build for production with minification
160 | npm run build
161 | ```
162 | ## 交流
163 |
164 | 欢迎热爱学习、忠于分享的胖友一起来交流
165 |
166 | - QQ:745913574
167 |
168 | - QQ群:149683643
169 |
170 | - WeChat
171 |
172 |
173 |
174 |
175 | ## Donation
176 |
177 | If you find this project useful, you can buy me a cup of coffee
178 |
179 |
180 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | // https://github.com/shelljs/shelljs
2 | require('./check-versions')()
3 | require('shelljs/global')
4 | env.NODE_ENV = 'production'
5 |
6 | var path = require('path')
7 | var config = require('../config')
8 | var ora = require('ora')
9 | var webpack = require('webpack')
10 | var webpackConfig = require('./webpack.prod.conf')
11 |
12 | console.log(
13 | ' Tip:\n' +
14 | ' Built files are meant to be served over an HTTP server.\n' +
15 | ' Opening index.html over file:// won\'t work.\n'
16 | )
17 |
18 | var spinner = ora('building for production...')
19 | spinner.start()
20 |
21 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
22 | rm('-rf', assetsPath)
23 | mkdir('-p', assetsPath)
24 | cp('-R', 'static/*', assetsPath)
25 |
26 | webpack(webpackConfig, function (err, stats) {
27 | spinner.stop()
28 | if (err) throw err
29 | process.stdout.write(stats.toString({
30 | colors: true,
31 | modules: false,
32 | children: false,
33 | chunks: false,
34 | chunkModules: false
35 | }) + '\n')
36 | })
37 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var semver = require('semver')
2 | var chalk = require('chalk')
3 | var packageConfig = require('../package.json')
4 | var exec = function(cmd) {
5 | return require('child_process')
6 | .execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [{
10 | name: 'node',
11 | currentVersion: semver.clean(process.version),
12 | versionRequirement: packageConfig.engines.node
13 | }, {
14 | name: 'npm',
15 | currentVersion: exec('npm --version'),
16 | versionRequirement: packageConfig.engines.npm
17 | }]
18 |
19 | module.exports = function() {
20 | var warnings = []
21 | for (var i = 0; i < versionRequirements.length; i++) {
22 | var mod = versionRequirements[i]
23 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
24 | warnings.push(mod.name + ': ' +
25 | chalk.red(mod.currentVersion) + ' should be ' +
26 | chalk.green(mod.versionRequirement)
27 | )
28 | }
29 | }
30 |
31 | if (warnings.length) {
32 | console.log('')
33 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
34 | console.log()
35 | for (var i = 0; i < warnings.length; i++) {
36 | var warning = warnings[i]
37 | console.log(' ' + warning)
38 | }
39 | console.log()
40 | process.exit(1)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 | var config = require('../config')
3 | if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
4 | var path = require('path')
5 | var express = require('express')
6 | var webpack = require('webpack')
7 | var opn = require('opn')
8 | var proxyMiddleware = require('http-proxy-middleware')
9 | var webpackConfig = require('./webpack.dev.conf')
10 |
11 | // default port where dev server listens for incoming traffic
12 | var port = process.env.PORT || config.dev.port
13 | // Define HTTP proxies to your custom API backend
14 | // https://github.com/chimurai/http-proxy-middleware
15 | var proxyTable = config.dev.proxyTable
16 |
17 | var app = express()
18 | var compiler = webpack(webpackConfig)
19 |
20 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
21 | publicPath: webpackConfig.output.publicPath,
22 | stats: {
23 | colors: true,
24 | chunks: false
25 | }
26 | })
27 |
28 | var hotMiddleware = require('webpack-hot-middleware')(compiler)
29 | // force page reload when html-webpack-plugin template changes
30 | compiler.plugin('compilation', function (compilation) {
31 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
32 | hotMiddleware.publish({ action: 'reload' })
33 | cb()
34 | })
35 | })
36 |
37 | // proxy api requests
38 | Object.keys(proxyTable).forEach(function (context) {
39 | var options = proxyTable[context]
40 | if (typeof options === 'string') {
41 | options = { target: options }
42 | }
43 | app.use(proxyMiddleware(context, options))
44 | })
45 |
46 | // handle fallback for HTML5 history API
47 | app.use(require('connect-history-api-fallback')())
48 |
49 | // serve webpack bundle output
50 | app.use(devMiddleware)
51 |
52 | // enable hot-reload and state-preserving
53 | // compilation error display
54 | app.use(hotMiddleware)
55 |
56 | // serve pure static assets
57 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
58 | app.use(staticPath, express.static('./static'))
59 |
60 | module.exports = app.listen(port, function (err) {
61 | if (err) {
62 | console.log(err)
63 | return
64 | }
65 | var uri = 'http://localhost:' + port
66 | console.log('Listening at ' + uri + '\n')
67 |
68 | // when env is testing, don't need open it
69 | if (process.env.NODE_ENV !== 'testing') {
70 | opn(uri)
71 | }
72 | })
73 |
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 | // generate loader string to be used with extract text plugin
15 | function generateLoaders (loaders) {
16 | var sourceLoader = loaders.map(function (loader) {
17 | var extraParamChar
18 | if (/\?/.test(loader)) {
19 | loader = loader.replace(/\?/, '-loader?')
20 | extraParamChar = '&'
21 | } else {
22 | loader = loader + '-loader'
23 | extraParamChar = '?'
24 | }
25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
26 | }).join('!')
27 |
28 | // Extract CSS when that option is specified
29 | // (which is the case during production build)
30 | if (options.extract) {
31 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
32 | } else {
33 | return ['vue-style-loader', sourceLoader].join('!')
34 | }
35 | }
36 |
37 | // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
38 | return {
39 | css: generateLoaders(['css']),
40 | postcss: generateLoaders(['css']),
41 | less: generateLoaders(['css', 'less']),
42 | sass: generateLoaders(['css', 'sass?indentedSyntax']),
43 | scss: generateLoaders(['css', 'sass']),
44 | stylus: generateLoaders(['css', 'stylus']),
45 | styl: generateLoaders(['css', 'stylus'])
46 | }
47 | }
48 |
49 | // Generate loaders for standalone style files (outside of .vue)
50 | exports.styleLoaders = function (options) {
51 | var output = []
52 | var loaders = exports.cssLoaders(options)
53 | for (var extension in loaders) {
54 | var loader = loaders[extension]
55 | output.push({
56 | test: new RegExp('\\.' + extension + '$'),
57 | loader: loader
58 | })
59 | }
60 | return output
61 | }
62 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var utils = require('./utils')
4 | var projectRoot = path.resolve(__dirname, '../')
5 |
6 | var env = process.env.NODE_ENV
7 | // check env & config/index.js to decide weither to enable CSS Sourcemaps for the
8 | // various preprocessor loaders added to vue-loader at the end of this file
9 | var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
10 | var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
11 | var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
12 |
13 | module.exports = {
14 | entry: {
15 | app: './src/main.js'
16 | },
17 | output: {
18 | path: config.build.assetsRoot,
19 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
20 | filename: '[name].js'
21 | },
22 | resolve: {
23 | extensions: ['', '.js', '.vue'],
24 | fallback: [path.join(__dirname, '../node_modules')],
25 | alias: {
26 | 'vue$': 'vue/dist/vue.common.js',
27 | 'src': path.resolve(__dirname, '../src'),
28 | 'assets': path.resolve(__dirname, '../src/assets'),
29 | 'components': path.resolve(__dirname, '../src/components'),
30 | 'common': path.resolve(__dirname, '../src/common'),
31 | 'img': path.resolve(__dirname, '../resource/img')
32 | }
33 | },
34 | resolveLoader: {
35 | fallback: [path.join(__dirname, '../node_modules')]
36 | },
37 | module: {
38 | preLoaders: [{
39 | test: /\.vue$/,
40 | loader: 'eslint',
41 | include: projectRoot,
42 | exclude: /node_modules|src\/components\/calendar/
43 | }],
44 | loaders: [{
45 | test: /\.vue$/,
46 | loader: 'vue'
47 | }, {
48 | test: /\.js$/,
49 | loader: 'babel',
50 | include: projectRoot,
51 | exclude: /node_modules/
52 | }, {
53 | test: /\.json$/,
54 | loader: 'json'
55 | }, {
56 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
57 | loader: 'url',
58 | query: {
59 | limit: 10000,
60 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
61 | }
62 | }, {
63 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
64 | loader: 'url',
65 | query: {
66 | limit: 10000,
67 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
68 | }
69 | }]
70 | },
71 | eslint: {
72 | formatter: require('eslint-friendly-formatter')
73 | },
74 | vue: {
75 | loaders: utils.cssLoaders({
76 | sourceMap: useCssSourceMap
77 | }),
78 | postcss: [
79 | require('autoprefixer')({
80 | browsers: ['last 2 versions']
81 | })
82 | ]
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var config = require('../config')
2 | var webpack = require('webpack')
3 | var merge = require('webpack-merge')
4 | var utils = require('./utils')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 |
8 | // add hot-reload related code to entry chunks
9 | Object.keys(baseWebpackConfig.entry).forEach(function(name) {
10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
11 | })
12 |
13 | module.exports = merge(baseWebpackConfig, {
14 | module: {
15 | loaders: utils.styleLoaders({
16 | sourceMap: config.dev.cssSourceMap
17 | })
18 | },
19 | // eval-source-map is faster for development
20 | devtool: '#eval-source-map',
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env': config.dev.env
24 | }),
25 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
26 | new webpack.optimize.OccurenceOrderPlugin(),
27 | new webpack.HotModuleReplacementPlugin(),
28 | new webpack.NoErrorsPlugin(),
29 | // https://github.com/ampedandwired/html-webpack-plugin
30 | new HtmlWebpackPlugin({
31 | filename: 'index.html',
32 | template: 'index.html',
33 | inject: true
34 | })
35 | ]
36 | })
37 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var utils = require('./utils')
4 | var webpack = require('webpack')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var env = config.build.env
10 |
11 | var webpackConfig = merge(baseWebpackConfig, {
12 | module: {
13 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
14 | },
15 | devtool: config.build.productionSourceMap ? '#source-map' : false,
16 | output: {
17 | path: config.build.assetsRoot,
18 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
19 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
20 | },
21 | vue: {
22 | loaders: utils.cssLoaders({
23 | sourceMap: config.build.productionSourceMap,
24 | extract: true
25 | })
26 | },
27 | plugins: [
28 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
29 | new webpack.DefinePlugin({
30 | 'process.env': env
31 | }),
32 | new webpack.optimize.UglifyJsPlugin({
33 | compress: {
34 | warnings: false
35 | }
36 | }),
37 | new webpack.optimize.OccurenceOrderPlugin(),
38 | // extract css into its own file
39 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
40 | // generate dist index.html with correct asset hash for caching.
41 | // you can customize output by editing /index.html
42 | // see https://github.com/ampedandwired/html-webpack-plugin
43 | new HtmlWebpackPlugin({
44 | filename: config.build.index,
45 | template: 'index.html',
46 | inject: true,
47 | minify: {
48 | removeComments: true,
49 | collapseWhitespace: true,
50 | removeAttributeQuotes: true
51 | // more options:
52 | // https://github.com/kangax/html-minifier#options-quick-reference
53 | },
54 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
55 | chunksSortMode: 'dependency'
56 | }),
57 | // split vendor js into its own file
58 | new webpack.optimize.CommonsChunkPlugin({
59 | name: 'vendor',
60 | minChunks: function (module, count) {
61 | // any required modules inside node_modules are extracted to vendor
62 | return (
63 | module.resource &&
64 | /\.js$/.test(module.resource) &&
65 | module.resource.indexOf(
66 | path.join(__dirname, '../node_modules')
67 | ) === 0
68 | )
69 | }
70 | }),
71 | // extract webpack runtime and module manifest to its own file in order to
72 | // prevent vendor hash from being updated whenever app bundle is updated
73 | new webpack.optimize.CommonsChunkPlugin({
74 | name: 'manifest',
75 | chunks: ['vendor']
76 | })
77 | ]
78 | })
79 |
80 | if (config.build.productionGzip) {
81 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
82 |
83 | webpackConfig.plugins.push(
84 | new CompressionWebpackPlugin({
85 | asset: '[path].gz[query]',
86 | algorithm: 'gzip',
87 | test: new RegExp(
88 | '\\.(' +
89 | config.build.productionGzipExtensions.join('|') +
90 | ')$'
91 | ),
92 | threshold: 10240,
93 | minRatio: 0.8
94 | })
95 | )
96 | }
97 |
98 | module.exports = webpackConfig
99 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: '/',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css']
18 | },
19 | dev: {
20 | env: require('./dev.env'),
21 | port: 8081,
22 | assetsSubDirectory: 'static',
23 | assetsPublicPath: '/',
24 | proxyTable: {},
25 | // CSS Sourcemaps off by default because relative paths are "buggy"
26 | // with this option, according to the CSS-Loader README
27 | // (https://github.com/webpack/css-loader#sourcemaps)
28 | // In our experience, they generally work as expected,
29 | // just be aware of this issue when enabling this option.
30 | cssSourceMap: false
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dashboard
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Dashboard",
3 | "version": "1.0.0",
4 | "description": "Charts Dashboard",
5 | "author": "Simon Zhang",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "build": "node build/build.js",
10 | "lint": "eslint --ext .js,.vue src"
11 | },
12 | "dependencies": {
13 | "babel-runtime": "^6.9.0",
14 | "echarts": "^3.3.2",
15 | "element-ui": "^1.1.6",
16 | "modernizr": "^3.3.1",
17 | "stylus": "^0.54.5",
18 | "vue": "^2.1.0",
19 | "vue-router": "^2.1.1",
20 | "vue-tables-2": "^0.2.77"
21 | },
22 | "devDependencies": {
23 | "autoprefixer": "^6.4.0",
24 | "axios": "^0.15.3",
25 | "babel-core": "^6.0.0",
26 | "babel-eslint": "^7.0.0",
27 | "babel-helper-vue-jsx-merge-props": "^2.0.2",
28 | "babel-loader": "^6.0.0",
29 | "babel-plugin-component": "^0.9.0",
30 | "babel-plugin-syntax-jsx": "^6.18.0",
31 | "babel-plugin-transform-runtime": "^6.0.0",
32 | "babel-preset-es2015": "^6.0.0",
33 | "babel-preset-stage-2": "^6.0.0",
34 | "babel-register": "^6.0.0",
35 | "better-scroll": "^0.1.10",
36 | "chalk": "^1.1.3",
37 | "connect-history-api-fallback": "^1.1.0",
38 | "css-loader": "^0.25.0",
39 | "eslint": "^3.7.1",
40 | "eslint-config-standard": "^6.1.0",
41 | "eslint-friendly-formatter": "^2.0.5",
42 | "eslint-loader": "^1.5.0",
43 | "eslint-plugin-html": "^1.3.0",
44 | "eslint-plugin-promise": "^2.0.1",
45 | "eslint-plugin-standard": "^2.0.1",
46 | "eventsource-polyfill": "^0.9.6",
47 | "express": "^4.13.3",
48 | "extract-text-webpack-plugin": "^1.0.1",
49 | "file-loader": "^0.9.0",
50 | "function-bind": "^1.0.2",
51 | "html-webpack-plugin": "^2.8.1",
52 | "http-proxy-middleware": "^0.17.2",
53 | "jquery": "^3.1.1",
54 | "json-loader": "^0.5.4",
55 | "opn": "^4.0.2",
56 | "ora": "^0.3.0",
57 | "semver": "^5.3.0",
58 | "shelljs": "^0.7.4",
59 | "stylus-loader": "^2.1.1",
60 | "url-loader": "^0.5.7",
61 | "vue-loader": "^10.0.0",
62 | "vue-style-loader": "^1.0.0",
63 | "vue-template-compiler": "^2.1.0",
64 | "webpack": "^1.13.2",
65 | "webpack-dev-middleware": "^1.8.3",
66 | "webpack-hot-middleware": "^2.12.2",
67 | "webpack-merge": "^0.14.1"
68 | },
69 | "engines": {
70 | "node": ">= 4.0.0",
71 | "npm": ">= 3.0.0"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
--------------------------------------------------------------------------------
/src/assets/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogjiao/DataVisualization/1912d879b0f01dbf7a0e3c368699f623cf5c0c61/src/assets/bg.jpg
--------------------------------------------------------------------------------
/src/components/calendar/calendar.vue:
--------------------------------------------------------------------------------
1 |
189 |
190 |
191 |
192 |
193 |
204 |
205 |
206 |
207 | {{week}} |
208 |
209 |
210 |
211 |
212 | {{child.day}}
213 | {{child.lunar}}
214 | |
215 |
216 |
217 |
218 |
225 |
226 | 确定
227 | 取消
228 |
229 |
230 |
231 |
232 |
233 |
626 |
--------------------------------------------------------------------------------
/src/components/checkbox/checkbox.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
27 |
38 |
--------------------------------------------------------------------------------
/src/components/column/column.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
160 |
--------------------------------------------------------------------------------
/src/components/dashboard/dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
74 |
75 |
114 |
--------------------------------------------------------------------------------
/src/components/filter/filter.vue:
--------------------------------------------------------------------------------
1 |
76 |
77 |
78 |
79 |
起始时间
80 |
84 |
85 |
86 |
87 |
88 |
截止时间
89 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | 全选
101 |
102 |
103 | 产品
104 |
105 |
106 |
111 |
112 |
113 |
114 |
115 |
223 |
--------------------------------------------------------------------------------
/src/components/funnel/funnel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
14 |
--------------------------------------------------------------------------------
/src/components/header/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{name}}
4 |
5 |
6 | -
7 | {{legend.name}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
77 |
78 |
113 |
--------------------------------------------------------------------------------
/src/components/heat/heat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
110 |
111 |
117 |
--------------------------------------------------------------------------------
/src/components/legend/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{name}}
4 |
5 |
6 | -
7 | {{legend.name}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
65 |
66 |
87 |
--------------------------------------------------------------------------------
/src/components/line/line.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
148 |
--------------------------------------------------------------------------------
/src/components/multipleColumn/multipleColumn.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
167 |
--------------------------------------------------------------------------------
/src/components/point/point.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
18 |
19 |
20 |
211 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App'
3 | import Vuex from 'vuex'
4 | import VueRouer from 'vue-router'
5 | import column from 'components/column/column'
6 | import funnel from 'components/funnel/funnel'
7 | import heat from 'components/heat/heat'
8 | import point from 'components/point/point'
9 | import line from 'components/line/line'
10 | import dashboard from 'components/dashboard/dashboard'
11 | import multipleColumn from 'components/multipleColumn/multipleColumn'
12 | import {DatePicker} from 'element-ui'
13 |
14 | Vue.component(DatePicker.name, DatePicker)
15 |
16 | Vue.use(VueRouer)
17 | Vue.use(Vuex)
18 |
19 | const store = new Vuex.Store({
20 | state: {
21 | count: 0,
22 | color: ['#325B69', '#698570', '#AE5548', '#6D9EA8', '#9CC2B0', '#C98769']
23 | }
24 | });
25 | const router = new VueRouer({
26 | routes: [{
27 | path: '/column',
28 | component: column
29 | }, {
30 | path: '/funnel',
31 | component: funnel
32 | }, {
33 | path: '/heat',
34 | component: heat
35 | }, {
36 | path: '/point',
37 | component: point
38 | }, {
39 | path: '/dashboard',
40 | component: dashboard
41 | }, {
42 | path: '/multipleColumn',
43 | component: multipleColumn
44 | }, {
45 | path: '/line',
46 | component: line
47 | }],
48 | linkActiveClass: 'active'
49 | })
50 | new Vue({
51 | router,
52 | store,
53 | template: '',
54 | components: {
55 | App
56 | },
57 | data: {
58 | eventHub: new Vue(),
59 | charts: []
60 | }
61 | }).$mount('#app')
62 |
63 | router.push('dashboard')
64 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogjiao/DataVisualization/1912d879b0f01dbf7a0e3c368699f623cf5c0c61/static/.gitkeep
--------------------------------------------------------------------------------
/static/css/reset.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
3 | * http://cssreset.com
4 | */
5 |
6 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, article, aside, canvas, details, embed, figure, figcaption, footer, header, menu, nav, output, ruby, section, summary, time, mark, audio, video, input {
7 | margin: 0;
8 | padding: 0;
9 | border: 0;
10 | font-size: 100%;
11 | font-weight: normal;
12 | vertical-align: baseline;
13 | }
14 |
15 | td, th {
16 | display: table-cell;
17 | vertical-align: middle;
18 | }
19 |
20 |
21 | /* HTML5 display-role reset for older browsers */
22 |
23 | article, aside, details, figcaption, figure, footer, header, menu, nav, section {
24 | display: block;
25 | }
26 |
27 | body {
28 | line-height: 1;
29 | }
30 |
31 | blockquote, q {
32 | quotes: none;
33 | }
34 |
35 | blockquote:before, blockquote:after, q:before, q:after {
36 | content: none;
37 | }
38 |
39 | table {
40 | border-collapse: collapse;
41 | border-spacing: 0;
42 | }
43 |
44 |
45 | /* custom */
46 |
47 | a {
48 | color: #7e8c8d;
49 | text-decoration: none;
50 | -webkit-backface-visibility: hidden;
51 | }
52 |
53 | li {
54 | list-style: none;
55 | }
56 |
57 | ::-webkit-scrollbar {
58 | width: 5px;
59 | height: 5px;
60 | }
61 |
62 | ::-webkit-scrollbar-track-piece {
63 | background-color: rgba(0, 0, 0, 0.2);
64 | -webkit-border-radius: 6px;
65 | }
66 |
67 | ::-webkit-scrollbar-thumb:vertical {
68 | height: 5px;
69 | background-color: rgba(125, 125, 125, 0.7);
70 | -webkit-border-radius: 6px;
71 | }
72 |
73 | ::-webkit-scrollbar-thumb:horizontal {
74 | width: 5px;
75 | background-color: rgba(125, 125, 125, 0.7);
76 | -webkit-border-radius: 6px;
77 | }
78 |
79 | html, body {
80 | width: 100%;
81 | }
82 |
83 | body {
84 | -webkit-text-size-adjust: none;
85 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
86 | }
87 |
--------------------------------------------------------------------------------
/static/data/cityData.json:
--------------------------------------------------------------------------------
1 | {
2 | "海门": [121.15, 31.89],
3 | "鄂尔多斯": [109.781327, 39.608266],
4 | "招远": [120.38, 37.35],
5 | "舟山": [122.207216, 29.985295],
6 | "齐齐哈尔": [123.97, 47.33],
7 | "盐城": [120.13, 33.38],
8 | "赤峰": [118.87, 42.28],
9 | "青岛": [120.33, 36.07],
10 | "乳山": [121.52, 36.89],
11 | "金昌": [102.188043, 38.520089],
12 | "泉州": [118.58, 24.93],
13 | "莱西": [120.53, 36.86],
14 | "日照": [119.46, 35.42],
15 | "胶南": [119.97, 35.88],
16 | "南通": [121.05, 32.08],
17 | "拉萨": [91.11, 29.97],
18 | "云浮": [112.02, 22.93],
19 | "梅州": [116.1, 24.55],
20 | "文登": [122.05, 37.2],
21 | "上海": [121.48, 31.22],
22 | "攀枝花": [101.718637, 26.582347],
23 | "威海": [122.1, 37.5],
24 | "承德": [117.93, 40.97],
25 | "厦门": [118.1, 24.46],
26 | "汕尾": [115.375279, 22.786211],
27 | "潮州": [116.63, 23.68],
28 | "丹东": [124.37, 40.13],
29 | "太仓": [121.1, 31.45],
30 | "曲靖": [103.79, 25.51],
31 | "烟台": [121.39, 37.52],
32 | "福州": [119.3, 26.08],
33 | "瓦房店": [121.979603, 39.627114],
34 | "即墨": [120.45, 36.38],
35 | "抚顺": [123.97, 41.97],
36 | "玉溪": [102.52, 24.35],
37 | "张家口": [114.87, 40.82],
38 | "阳泉": [113.57, 37.85],
39 | "莱州": [119.942327, 37.177017],
40 | "湖州": [120.1, 30.86],
41 | "汕头": [116.69, 23.39],
42 | "昆山": [120.95, 31.39],
43 | "宁波": [121.56, 29.86],
44 | "湛江": [110.359377, 21.270708],
45 | "揭阳": [116.35, 23.55],
46 | "荣成": [122.41, 37.16],
47 | "连云港": [119.16, 34.59],
48 | "葫芦岛": [120.836932, 40.711052],
49 | "常熟": [120.74, 31.64],
50 | "东莞": [113.75, 23.04],
51 | "河源": [114.68, 23.73],
52 | "淮安": [119.15, 33.5],
53 | "泰州": [119.9, 32.49],
54 | "南宁": [108.33, 22.84],
55 | "营口": [122.18, 40.65],
56 | "惠州": [114.4, 23.09],
57 | "江阴": [120.26, 31.91],
58 | "蓬莱": [120.75, 37.8],
59 | "韶关": [113.62, 24.84],
60 | "嘉峪关": [98.289152, 39.77313],
61 | "广州": [113.23, 23.16],
62 | "延安": [109.47, 36.6],
63 | "太原": [112.53, 37.87],
64 | "清远": [113.01, 23.7],
65 | "中山": [113.38, 22.52],
66 | "昆明": [102.73, 25.04],
67 | "寿光": [118.73, 36.86],
68 | "盘锦": [122.070714, 41.119997],
69 | "长治": [113.08, 36.18],
70 | "深圳": [114.07, 22.62],
71 | "珠海": [113.52, 22.3],
72 | "宿迁": [118.3, 33.96],
73 | "咸阳": [108.72, 34.36],
74 | "铜川": [109.11, 35.09],
75 | "平度": [119.97, 36.77],
76 | "佛山": [113.11, 23.05],
77 | "海口": [110.35, 20.02],
78 | "江门": [113.06, 22.61],
79 | "章丘": [117.53, 36.72],
80 | "肇庆": [112.44, 23.05],
81 | "大连": [121.62, 38.92],
82 | "临汾": [111.5, 36.08],
83 | "吴江": [120.63, 31.16],
84 | "石嘴山": [106.39, 39.04],
85 | "沈阳": [123.38, 41.8],
86 | "苏州": [120.62, 31.32],
87 | "茂名": [110.88, 21.68],
88 | "嘉兴": [120.76, 30.77],
89 | "长春": [125.35, 43.88],
90 | "胶州": [120.03336, 36.264622],
91 | "银川": [106.27, 38.47],
92 | "张家港": [120.555821, 31.875428],
93 | "三门峡": [111.19, 34.76],
94 | "锦州": [121.15, 41.13],
95 | "南昌": [115.89, 28.68],
96 | "柳州": [109.4, 24.33],
97 | "三亚": [109.511909, 18.252847],
98 | "自贡": [104.778442, 29.33903],
99 | "吉林": [126.57, 43.87],
100 | "阳江": [111.95, 21.85],
101 | "泸州": [105.39, 28.91],
102 | "西宁": [101.74, 36.56],
103 | "宜宾": [104.56, 29.77],
104 | "呼和浩特": [111.65, 40.82],
105 | "成都": [104.06, 30.67],
106 | "大同": [113.3, 40.12],
107 | "镇江": [119.44, 32.2],
108 | "桂林": [110.28, 25.29],
109 | "张家界": [110.479191, 29.117096],
110 | "宜兴": [119.82, 31.36],
111 | "北海": [109.12, 21.49],
112 | "西安": [108.95, 34.27],
113 | "金坛": [119.56, 31.74],
114 | "东营": [118.49, 37.46],
115 | "牡丹江": [129.58, 44.6],
116 | "遵义": [106.9, 27.7],
117 | "绍兴": [120.58, 30.01],
118 | "扬州": [119.42, 32.39],
119 | "常州": [119.95, 31.79],
120 | "潍坊": [119.1, 36.62],
121 | "重庆": [106.54, 29.59],
122 | "台州": [121.420757, 28.656386],
123 | "南京": [118.78, 32.04],
124 | "滨州": [118.03, 37.36],
125 | "贵阳": [106.71, 26.57],
126 | "无锡": [120.29, 31.59],
127 | "本溪": [123.73, 41.3],
128 | "克拉玛依": [84.77, 45.59],
129 | "渭南": [109.5, 34.52],
130 | "马鞍山": [118.48, 31.56],
131 | "宝鸡": [107.15, 34.38],
132 | "焦作": [113.21, 35.24],
133 | "句容": [119.16, 31.95],
134 | "北京": [116.46, 39.92],
135 | "徐州": [117.2, 34.26],
136 | "衡水": [115.72, 37.72],
137 | "包头": [110, 40.58],
138 | "绵阳": [104.73, 31.48],
139 | "乌鲁木齐": [87.68, 43.77],
140 | "枣庄": [117.57, 34.86],
141 | "杭州": [120.19, 30.26],
142 | "淄博": [118.05, 36.78],
143 | "鞍山": [122.85, 41.12],
144 | "溧阳": [119.48, 31.43],
145 | "库尔勒": [86.06, 41.68],
146 | "安阳": [114.35, 36.1],
147 | "开封": [114.35, 34.79],
148 | "济南": [117, 36.65],
149 | "德阳": [104.37, 31.13],
150 | "温州": [120.65, 28.01],
151 | "九江": [115.97, 29.71],
152 | "邯郸": [114.47, 36.6],
153 | "临安": [119.72, 30.23],
154 | "兰州": [103.73, 36.03],
155 | "沧州": [116.83, 38.33],
156 | "临沂": [118.35, 35.05],
157 | "南充": [106.110698, 30.837793],
158 | "天津": [117.2, 39.13],
159 | "富阳": [119.95, 30.07],
160 | "泰安": [117.13, 36.18],
161 | "诸暨": [120.23, 29.71],
162 | "郑州": [113.65, 34.76],
163 | "哈尔滨": [126.63, 45.75],
164 | "聊城": [115.97, 36.45],
165 | "芜湖": [118.38, 31.33],
166 | "唐山": [118.02, 39.63],
167 | "平顶山": [113.29, 33.75],
168 | "邢台": [114.48, 37.05],
169 | "德州": [116.29, 37.45],
170 | "济宁": [116.59, 35.38],
171 | "荆州": [112.239741, 30.335165],
172 | "宜昌": [111.3, 30.7],
173 | "义乌": [120.06, 29.32],
174 | "丽水": [119.92, 28.45],
175 | "洛阳": [112.44, 34.7],
176 | "秦皇岛": [119.57, 39.95],
177 | "株洲": [113.16, 27.83],
178 | "石家庄": [114.48, 38.03],
179 | "莱芜": [117.67, 36.19],
180 | "常德": [111.69, 29.05],
181 | "保定": [115.48, 38.85],
182 | "湘潭": [112.91, 27.87],
183 | "金华": [119.64, 29.12],
184 | "岳阳": [113.09, 29.37],
185 | "长沙": [113, 28.21],
186 | "衢州": [118.88, 28.97],
187 | "廊坊": [116.7, 39.53],
188 | "菏泽": [115.480656, 35.23375],
189 | "合肥": [117.27, 31.86],
190 | "武汉": [114.31, 30.52],
191 | "大庆": [125.03, 46.58],
192 | "林芝地": [94.25, 29.59],
193 | "果洛藏族自治": [97.42, 34.81],
194 | "闵行": [121.23, 31.07],
195 | "那曲地": [92.1, 31.47]
196 | }
197 |
--------------------------------------------------------------------------------
/static/data/heat/testData.json:
--------------------------------------------------------------------------------
1 | [{
2 | "name": "海门",
3 | "value": 9
4 | }, {
5 | "name": "鄂尔多斯",
6 | "value": 12
7 | }, {
8 | "name": "招远",
9 | "value": 12
10 | }, {
11 | "name": "舟山",
12 | "value": 12
13 | }, {
14 | "name": "齐齐哈尔",
15 | "value": 14
16 | }, {
17 | "name": "盐城",
18 | "value": 15
19 | }, {
20 | "name": "赤峰",
21 | "value": 16
22 | }, {
23 | "name": "青岛",
24 | "value": 18
25 | }, {
26 | "name": "乳山",
27 | "value": 18
28 | }, {
29 | "name": "金昌",
30 | "value": 19
31 | }, {
32 | "name": "泉州",
33 | "value": 21
34 | }, {
35 | "name": "莱西",
36 | "value": 21
37 | }, {
38 | "name": "日照",
39 | "value": 21
40 | }, {
41 | "name": "胶南",
42 | "value": 22
43 | }, {
44 | "name": "南通",
45 | "value": 23
46 | }, {
47 | "name": "拉萨",
48 | "value": 24
49 | }, {
50 | "name": "云浮",
51 | "value": 24
52 | }, {
53 | "name": "梅州",
54 | "value": 25
55 | }, {
56 | "name": "文登",
57 | "value": 25
58 | }, {
59 | "name": "上海",
60 | "value": 25
61 | }, {
62 | "name": "攀枝花",
63 | "value": 25
64 | }, {
65 | "name": "威海",
66 | "value": 25
67 | }, {
68 | "name": "承德",
69 | "value": 25
70 | }, {
71 | "name": "厦门",
72 | "value": 26
73 | }, {
74 | "name": "汕尾",
75 | "value": 26
76 | }, {
77 | "name": "潮州",
78 | "value": 26
79 | }, {
80 | "name": "丹东",
81 | "value": 27
82 | }, {
83 | "name": "太仓",
84 | "value": 27
85 | }, {
86 | "name": "曲靖",
87 | "value": 27
88 | }, {
89 | "name": "烟台",
90 | "value": 28
91 | }, {
92 | "name": "福州",
93 | "value": 29
94 | }, {
95 | "name": "瓦房店",
96 | "value": 30
97 | }, {
98 | "name": "即墨",
99 | "value": 30
100 | }, {
101 | "name": "抚顺",
102 | "value": 31
103 | }, {
104 | "name": "玉溪",
105 | "value": 31
106 | }, {
107 | "name": "张家口",
108 | "value": 31
109 | }, {
110 | "name": "阳泉",
111 | "value": 31
112 | }, {
113 | "name": "莱州",
114 | "value": 32
115 | }, {
116 | "name": "湖州",
117 | "value": 32
118 | }, {
119 | "name": "汕头",
120 | "value": 32
121 | }, {
122 | "name": "昆山",
123 | "value": 33
124 | }, {
125 | "name": "宁波",
126 | "value": 33
127 | }, {
128 | "name": "湛江",
129 | "value": 33
130 | }, {
131 | "name": "揭阳",
132 | "value": 34
133 | }, {
134 | "name": "荣成",
135 | "value": 34
136 | }, {
137 | "name": "连云港",
138 | "value": 35
139 | }, {
140 | "name": "葫芦岛",
141 | "value": 35
142 | }, {
143 | "name": "常熟",
144 | "value": 36
145 | }, {
146 | "name": "东莞",
147 | "value": 36
148 | }, {
149 | "name": "河源",
150 | "value": 36
151 | }, {
152 | "name": "淮安",
153 | "value": 36
154 | }, {
155 | "name": "泰州",
156 | "value": 36
157 | }, {
158 | "name": "南宁",
159 | "value": 37
160 | }, {
161 | "name": "营口",
162 | "value": 37
163 | }, {
164 | "name": "惠州",
165 | "value": 37
166 | }, {
167 | "name": "江阴",
168 | "value": 37
169 | }, {
170 | "name": "蓬莱",
171 | "value": 37
172 | }, {
173 | "name": "韶关",
174 | "value": 38
175 | }, {
176 | "name": "嘉峪关",
177 | "value": 38
178 | }, {
179 | "name": "广州",
180 | "value": 38
181 | }, {
182 | "name": "延安",
183 | "value": 38
184 | }, {
185 | "name": "太原",
186 | "value": 39
187 | }, {
188 | "name": "清远",
189 | "value": 39
190 | }, {
191 | "name": "中山",
192 | "value": 39
193 | }, {
194 | "name": "昆明",
195 | "value": 39
196 | }, {
197 | "name": "寿光",
198 | "value": 40
199 | }, {
200 | "name": "盘锦",
201 | "value": 40
202 | }, {
203 | "name": "长治",
204 | "value": 41
205 | }, {
206 | "name": "深圳",
207 | "value": 41
208 | }, {
209 | "name": "珠海",
210 | "value": 42
211 | }, {
212 | "name": "宿迁",
213 | "value": 43
214 | }, {
215 | "name": "咸阳",
216 | "value": 43
217 | }, {
218 | "name": "铜川",
219 | "value": 44
220 | }, {
221 | "name": "平度",
222 | "value": 44
223 | }, {
224 | "name": "佛山",
225 | "value": 44
226 | }, {
227 | "name": "海口",
228 | "value": 44
229 | }, {
230 | "name": "江门",
231 | "value": 45
232 | }, {
233 | "name": "章丘",
234 | "value": 45
235 | }, {
236 | "name": "肇庆",
237 | "value": 46
238 | }, {
239 | "name": "大连",
240 | "value": 47
241 | }, {
242 | "name": "临汾",
243 | "value": 47
244 | }, {
245 | "name": "吴江",
246 | "value": 47
247 | }, {
248 | "name": "石嘴山",
249 | "value": 49
250 | }, {
251 | "name": "沈阳",
252 | "value": 50
253 | }, {
254 | "name": "苏州",
255 | "value": 50
256 | }, {
257 | "name": "茂名",
258 | "value": 50
259 | }, {
260 | "name": "嘉兴",
261 | "value": 51
262 | }, {
263 | "name": "长春",
264 | "value": 51
265 | }, {
266 | "name": "胶州",
267 | "value": 52
268 | }, {
269 | "name": "银川",
270 | "value": 52
271 | }, {
272 | "name": "张家港",
273 | "value": 52
274 | }, {
275 | "name": "三门峡",
276 | "value": 53
277 | }, {
278 | "name": "锦州",
279 | "value": 54
280 | }, {
281 | "name": "南昌",
282 | "value": 54
283 | }, {
284 | "name": "柳州",
285 | "value": 54
286 | }, {
287 | "name": "三亚",
288 | "value": 54
289 | }, {
290 | "name": "自贡",
291 | "value": 56
292 | }, {
293 | "name": "吉林",
294 | "value": 56
295 | }, {
296 | "name": "阳江",
297 | "value": 57
298 | }, {
299 | "name": "泸州",
300 | "value": 57
301 | }, {
302 | "name": "西宁",
303 | "value": 57
304 | }, {
305 | "name": "宜宾",
306 | "value": 58
307 | }, {
308 | "name": "呼和浩特",
309 | "value": 58
310 | }, {
311 | "name": "成都",
312 | "value": 58
313 | }, {
314 | "name": "大同",
315 | "value": 58
316 | }, {
317 | "name": "镇江",
318 | "value": 59
319 | }, {
320 | "name": "桂林",
321 | "value": 59
322 | }, {
323 | "name": "张家界",
324 | "value": 59
325 | }, {
326 | "name": "宜兴",
327 | "value": 59
328 | }, {
329 | "name": "北海",
330 | "value": 60
331 | }, {
332 | "name": "西安",
333 | "value": 61
334 | }, {
335 | "name": "金坛",
336 | "value": 62
337 | }, {
338 | "name": "东营",
339 | "value": 62
340 | }, {
341 | "name": "牡丹江",
342 | "value": 63
343 | }, {
344 | "name": "遵义",
345 | "value": 63
346 | }, {
347 | "name": "绍兴",
348 | "value": 63
349 | }, {
350 | "name": "扬州",
351 | "value": 64
352 | }, {
353 | "name": "常州",
354 | "value": 64
355 | }, {
356 | "name": "潍坊",
357 | "value": 65
358 | }, {
359 | "name": "重庆",
360 | "value": 66
361 | }, {
362 | "name": "台州",
363 | "value": 67
364 | }, {
365 | "name": "南京",
366 | "value": 67
367 | }, {
368 | "name": "滨州",
369 | "value": 70
370 | }, {
371 | "name": "贵阳",
372 | "value": 71
373 | }, {
374 | "name": "无锡",
375 | "value": 71
376 | }, {
377 | "name": "本溪",
378 | "value": 71
379 | }, {
380 | "name": "克拉玛依",
381 | "value": 72
382 | }, {
383 | "name": "渭南",
384 | "value": 72
385 | }, {
386 | "name": "马鞍山",
387 | "value": 72
388 | }, {
389 | "name": "宝鸡",
390 | "value": 72
391 | }, {
392 | "name": "焦作",
393 | "value": 75
394 | }, {
395 | "name": "句容",
396 | "value": 75
397 | }, {
398 | "name": "北京",
399 | "value": 79
400 | }, {
401 | "name": "徐州",
402 | "value": 79
403 | }, {
404 | "name": "衡水",
405 | "value": 80
406 | }, {
407 | "name": "包头",
408 | "value": 80
409 | }, {
410 | "name": "绵阳",
411 | "value": 80
412 | }, {
413 | "name": "乌鲁木齐",
414 | "value": 84
415 | }, {
416 | "name": "枣庄",
417 | "value": 84
418 | }, {
419 | "name": "杭州",
420 | "value": 84
421 | }, {
422 | "name": "淄博",
423 | "value": 85
424 | }, {
425 | "name": "鞍山",
426 | "value": 86
427 | }, {
428 | "name": "溧阳",
429 | "value": 86
430 | }, {
431 | "name": "库尔勒",
432 | "value": 86
433 | }, {
434 | "name": "安阳",
435 | "value": 90
436 | }, {
437 | "name": "开封",
438 | "value": 90
439 | }, {
440 | "name": "济南",
441 | "value": 92
442 | }, {
443 | "name": "德阳",
444 | "value": 93
445 | }, {
446 | "name": "温州",
447 | "value": 95
448 | }, {
449 | "name": "九江",
450 | "value": 96
451 | }, {
452 | "name": "邯郸",
453 | "value": 98
454 | }, {
455 | "name": "临安",
456 | "value": 99
457 | }, {
458 | "name": "兰州",
459 | "value": 99
460 | }, {
461 | "name": "沧州",
462 | "value": 100
463 | }, {
464 | "name": "临沂",
465 | "value": 103
466 | }, {
467 | "name": "南充",
468 | "value": 104
469 | }, {
470 | "name": "天津",
471 | "value": 105
472 | }, {
473 | "name": "富阳",
474 | "value": 106
475 | }, {
476 | "name": "泰安",
477 | "value": 112
478 | }, {
479 | "name": "诸暨",
480 | "value": 112
481 | }, {
482 | "name": "郑州",
483 | "value": 113
484 | }, {
485 | "name": "哈尔滨",
486 | "value": 114
487 | }, {
488 | "name": "聊城",
489 | "value": 116
490 | }, {
491 | "name": "芜湖",
492 | "value": 117
493 | }, {
494 | "name": "唐山",
495 | "value": 119
496 | }, {
497 | "name": "平顶山",
498 | "value": 119
499 | }, {
500 | "name": "邢台",
501 | "value": 119
502 | }, {
503 | "name": "德州",
504 | "value": 120
505 | }, {
506 | "name": "济宁",
507 | "value": 120
508 | }, {
509 | "name": "荆州",
510 | "value": 127
511 | }, {
512 | "name": "宜昌",
513 | "value": 130
514 | }, {
515 | "name": "义乌",
516 | "value": 132
517 | }, {
518 | "name": "丽水",
519 | "value": 133
520 | }, {
521 | "name": "洛阳",
522 | "value": 134
523 | }, {
524 | "name": "秦皇岛",
525 | "value": 136
526 | }, {
527 | "name": "株洲",
528 | "value": 143
529 | }, {
530 | "name": "石家庄",
531 | "value": 147
532 | }, {
533 | "name": "莱芜",
534 | "value": 148
535 | }, {
536 | "name": "常德",
537 | "value": 152
538 | }, {
539 | "name": "保定",
540 | "value": 153
541 | }, {
542 | "name": "湘潭",
543 | "value": 154
544 | }, {
545 | "name": "金华",
546 | "value": 157
547 | }, {
548 | "name": "岳阳",
549 | "value": 169
550 | }, {
551 | "name": "长沙",
552 | "value": 175
553 | }, {
554 | "name": "衢州",
555 | "value": 177
556 | }, {
557 | "name": "廊坊",
558 | "value": 193
559 | }, {
560 | "name": "菏泽",
561 | "value": 194
562 | }, {
563 | "name": "合肥",
564 | "value": 229
565 | }, {
566 | "name": "武汉",
567 | "value": 273
568 | }, {
569 | "name": "大庆",
570 | "value": 279
571 | }]
572 |
--------------------------------------------------------------------------------
/static/data/point/testData.json:
--------------------------------------------------------------------------------
1 | [{
2 | "name": "海门",
3 | "value": 9
4 | }, {
5 | "name": "鄂尔多斯",
6 | "value": 12
7 | }, {
8 | "name": "南京",
9 | "value": 67
10 | }, {
11 | "name": "南充",
12 | "value": 200
13 | }, {
14 | "name": "天津",
15 | "value": 105
16 | }, {
17 | "name": "富阳",
18 | "value": 106
19 | }, {
20 | "name": "泰安",
21 | "value": 112
22 | }, {
23 | "name": "诸暨",
24 | "value": 112
25 | }, {
26 | "name": "郑州",
27 | "value": 113
28 | }, {
29 | "name": "哈尔滨",
30 | "value": 200
31 | }, {
32 | "name": "上海",
33 | "value": 25
34 | }]
35 |
--------------------------------------------------------------------------------
/static/img/Donate.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogjiao/DataVisualization/1912d879b0f01dbf7a0e3c368699f623cf5c0c61/static/img/Donate.jpeg
--------------------------------------------------------------------------------
/static/img/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogjiao/DataVisualization/1912d879b0f01dbf7a0e3c368699f623cf5c0c61/static/img/demo.jpg
--------------------------------------------------------------------------------
/static/js/calendar/calendar.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @authors yusen
4 | * @date 2016-01-08 11:17:59
5 | * https://github.com/yscoder/Calendar
6 | * download by www.sucaijiayuan.com
7 | */
8 | (function(root, factory) {
9 | if (typeof define === 'function' && define.amd) {
10 | define('calendar', ['jquery'], factory);
11 | } else if (typeof exports === 'object') {
12 | module.exports = factory(require('jquery'));
13 | } else {
14 | factory(root.jQuery);
15 | }
16 | }(this, function($) {
17 |
18 | // default config
19 |
20 | var defaults = {
21 |
22 | // 宽度
23 | width: 280,
24 | // 高度, 不包含头部,头部固定高度
25 | height: 280,
26 |
27 | zIndex: 1,
28 |
29 | // selector or element
30 | // 设置触发显示的元素,为null时默认显示
31 | trigger: null,
32 |
33 | // 偏移位置,可设正负值
34 | // trigger 设置时生效
35 | offset: [0, 1],
36 |
37 | // 自定义类,用于重写样式
38 | customClass: '',
39 |
40 | // 显示视图
41 | // 可选:date, month
42 | view: 'date',
43 |
44 | // 默认日期为当前日期
45 | date: new Date(),
46 | format: 'yyyy/mm/dd',
47 |
48 | // 一周的第一天
49 | // 0表示周日,依次类推
50 | startWeek: 0,
51 |
52 | // 星期格式
53 | weekArray: ['日', '一', '二', '三', '四', '五', '六'],
54 |
55 | // 设置选择范围
56 | // 格式:[开始日期, 结束日期]
57 | // 开始日期为空,则无上限;结束日期为空,则无下限
58 | // 如设置2015年11月23日以前不可选:[new Date(), null] or ['2015/11/23']
59 | selectedRang: null,
60 |
61 | // 日期关联数据 [{ date: string, value: object }, ... ]
62 | // 日期格式与 format 一致
63 | // 如 [ {date: '2015/11/23', value: '面试'} ]
64 | data: null,
65 |
66 | // 展示关联数据
67 | // 格式化参数:{m}视图,{d}日期,{v}value
68 | // 设置 false 表示不显示
69 | label: '{d}\n{v}',
70 |
71 | // 切换字符
72 | prev: '<',
73 | next: '>',
74 |
75 | // 切换视图
76 | // 参数:view, y, m
77 | viewChange: $.noop,
78 |
79 | // view: 视图
80 | // date: 不同视图返回不同的值
81 | // value: 日期关联数据
82 | onSelected: function(view, date, value) {
83 | // body...
84 | },
85 |
86 | // 参数同上
87 | onMouseenter: $.noop,
88 |
89 | onClose: $.noop
90 | },
91 |
92 | // static variable
93 |
94 | ACTION_NAMESPACE = 'data-calendar-',
95 |
96 | DISPLAY_VD = '[' + ACTION_NAMESPACE + 'display-date]',
97 | DISPLAY_VM = '[' + ACTION_NAMESPACE + 'display-month]',
98 |
99 | ARROW_DATE = '[' + ACTION_NAMESPACE + 'arrow-date]',
100 | ARROW_MONTH = '[' + ACTION_NAMESPACE + 'arrow-month]',
101 |
102 | ITEM_DAY = ACTION_NAMESPACE + 'day',
103 | ITEM_MONTH = ACTION_NAMESPACE + 'month',
104 |
105 | DISABLED = 'disabled',
106 | MARK_DATA = 'markData',
107 |
108 | VIEW_CLASS = {
109 | date: 'calendar-d',
110 | month: 'calendar-m'
111 | },
112 |
113 | OLD_DAY_CLASS = 'old',
114 | NEW_DAY_CLASS = 'new',
115 | TODAY_CLASS = 'now',
116 | SELECT_CLASS = 'selected',
117 | MARK_DAY_HTML = '',
118 | DATE_DIS_TPL = '{year}/{month}',
119 |
120 | ITEM_STYLE = 'style="width:{w}px;height:{h}px;line-height:{h}px"',
121 | WEEK_ITEM_TPL = '{wk}',
122 | DAY_ITEM_TPL = '{value}',
123 | MONTH_ITEM_TPL = '{m}月',
124 |
125 | TEMPLATE = [
126 | '',
127 | '
',
128 | '
',
129 | '
',
138 | '
',
139 | '
{week}
',
140 | '
',
141 | '
',
142 | '
',
143 | '
',
144 | '
',
145 | '
{yyyy}',
146 | '
',
147 | '{prev}',
148 | '{next}',
149 | '
',
150 | '
',
151 | '
{month}
',
152 | '
',
153 | '
',
154 | '
',
155 | ''
156 | ],
157 | OS = Object.prototype.toString;
158 |
159 | // utils
160 |
161 | function isDate(obj) {
162 | return OS.call(obj) === '[object Date]';
163 | }
164 |
165 | function isString(obj) {
166 | return OS.call(obj) === '[object String]';
167 | }
168 |
169 |
170 | function getClass(el) {
171 | return el.getAttribute('class') || el.getAttribute('className');
172 | }
173 |
174 | // extension methods
175 |
176 | String.prototype.repeat = function(data) {
177 | return this.replace(/\{\w+\}/g, function(str) {
178 | var prop = str.replace(/\{|\}/g, '');
179 | return data[prop] || '';
180 | });
181 | }
182 |
183 | String.prototype.toDate = function() {
184 | var dt = new Date(),
185 | dot = this.replace(/\d/g, '').charAt(0),
186 | arr = this.split(dot);
187 |
188 | dt.setFullYear(arr[0]);
189 | dt.setMonth(arr[1] - 1);
190 | dt.setDate(arr[2]);
191 | return dt;
192 | }
193 |
194 | Date.prototype.format = function(exp) {
195 | var y = this.getFullYear(),
196 | m = this.getMonth() + 1,
197 | d = this.getDate();
198 |
199 | return exp.replace('yyyy', y).replace('mm', m).replace('dd', d);
200 | }
201 |
202 | Date.prototype.isSame = function(y, m, d) {
203 | if (isDate(y)) {
204 | var dt = y;
205 | y = dt.getFullYear();
206 | m = dt.getMonth() + 1;
207 | d = dt.getDate();
208 | }
209 | return this.getFullYear() === y && this.getMonth() + 1 === m && this.getDate() === d;
210 | }
211 |
212 | Date.prototype.add = function(n) {
213 | this.setDate(this.getDate() + n);
214 | }
215 |
216 | Date.prototype.minus = function(n) {
217 | this.setDate(this.getDate() - n);
218 | }
219 |
220 | Date.prototype.clearTime = function(n) {
221 | this.setHours(0);
222 | this.setSeconds(0);
223 | this.setMinutes(0);
224 | this.setMilliseconds(0);
225 | return this;
226 | }
227 |
228 | Date.isLeap = function(y) {
229 | return (y % 100 !== 0 && y % 4 === 0) || (y % 400 === 0);
230 | }
231 |
232 | Date.getDaysNum = function(y, m) {
233 | var num = 31;
234 |
235 | switch (m) {
236 | case 2:
237 | num = this.isLeap(y) ? 29 : 28;
238 | break;
239 | case 4:
240 | case 6:
241 | case 9:
242 | case 11:
243 | num = 30;
244 | break;
245 | }
246 | return num;
247 | }
248 |
249 | Date.getSiblingsMonth = function(y, m, n) {
250 | var d = new Date(y, m - 1);
251 | d.setMonth(m - 1 + n);
252 | return {
253 | y: d.getFullYear(),
254 | m: d.getMonth() + 1
255 | };
256 | }
257 |
258 | Date.getPrevMonth = function(y, m, n) {
259 | return this.getSiblingsMonth(y, m, 0 - (n || 1));
260 | }
261 |
262 | Date.getNextMonth = function(y, m, n) {
263 | return this.getSiblingsMonth(y, m, n || 1);
264 | }
265 |
266 | Date.tryParse = function(obj) {
267 | if (!obj) {
268 | return obj;
269 | }
270 | return isDate(obj) ? obj : obj.toDate();
271 | }
272 |
273 |
274 | // Calendar class
275 |
276 | function Calendar(element, options) {
277 | this.$element = $(element);
278 | this.options = $.extend({}, $.fn.calendar.defaults, options);
279 | this.$element.addClass('calendar ' + this.options.customClass);
280 | this.width = this.options.width;
281 | this.height = this.options.height;
282 | this.date = this.options.date;
283 | this.selectedRang = this.options.selectedRang;
284 | this.data = this.options.data;
285 | this.init();
286 | }
287 |
288 | Calendar.prototype = {
289 | constructor: Calendar,
290 | getDayAction: function(day) {
291 | var action = ITEM_DAY;
292 | if (this.selectedRang) {
293 | var start = Date.tryParse(this.selectedRang[0]),
294 | end = Date.tryParse(this.selectedRang[1]);
295 |
296 | if ((start && day < start.clearTime()) || (end && day > end.clearTime())) {
297 | action = DISABLED;
298 | }
299 | }
300 |
301 | return action;
302 | },
303 | getDayData: function(day) {
304 | var ret, data = this.data;
305 |
306 | if (data) {
307 |
308 | for (var i = 0, len = data.length; i < len; i++) {
309 | var item = data[i];
310 |
311 | if (day.isSame(item.date.toDate())) {
312 | ret = item.value;
313 | }
314 | }
315 | }
316 |
317 | return ret;
318 | },
319 | getDayItem: function(y, m, d, f) {
320 | var dt = this.date,
321 | idt = new Date(y, m - 1, d),
322 | data = {
323 | w: this.width / 7,
324 | h: this.height / 7,
325 | value: d
326 | },
327 | markData,
328 | $item;
329 |
330 | var selected = dt.isSame(y, m, d) ? SELECT_CLASS : '';
331 | if (f === 1) {
332 | data['class'] = OLD_DAY_CLASS;
333 | } else if (f === 3) {
334 | data['class'] = NEW_DAY_CLASS;
335 | } else {
336 | data['class'] = '';
337 | }
338 |
339 | if (dt.isSame(y, m, d)) {
340 | data['class'] += ' ' + TODAY_CLASS;
341 | }
342 |
343 | data.action = this.getDayAction(idt);
344 | markData = this.getDayData(idt);
345 |
346 | $item = $(DAY_ITEM_TPL.repeat(data));
347 |
348 | if (markData) {
349 | $item.data(MARK_DATA, markData);
350 | $item.html(d + MARK_DAY_HTML);
351 | }
352 |
353 | return $item;
354 | },
355 | getDaysHtml: function(y, m) {
356 | var year, month, firstWeek, daysNum, prevM, prevDiff,
357 | dt = this.date,
358 | $days = $('
');
359 |
360 | if (isDate(y)) {
361 | year = y.getFullYear();
362 | month = y.getMonth() + 1;
363 | } else {
364 | year = Number(y);
365 | month = Number(m);
366 | }
367 |
368 | firstWeek = new Date(year, month - 1, 1).getDay() || 7;
369 | prevDiff = firstWeek - this.options.startWeek;
370 | daysNum = Date.getDaysNum(year, month);
371 | prevM = Date.getPrevMonth(year, month);
372 | prevDaysNum = Date.getDaysNum(year, prevM.m);
373 | nextM = Date.getNextMonth(year, month);
374 | // month flag
375 | var PREV_FLAG = 1,
376 | CURR_FLAG = 2,
377 | NEXT_FLAG = 3,
378 | count = 0;
379 |
380 | for (var p = prevDaysNum - prevDiff + 1; p <= prevDaysNum; p++, count++) {
381 |
382 | $days.append(this.getDayItem(prevM.y, prevM.m, p, PREV_FLAG));
383 | }
384 |
385 | for (var c = 1; c <= daysNum; c++, count++) {
386 | $days.append(this.getDayItem(year, month, c, CURR_FLAG));
387 | }
388 |
389 | for (var n = 1, nl = 42 - count; n <= nl; n++) {
390 |
391 | $days.append(this.getDayItem(nextM.y, nextM.m, n, NEXT_FLAG));
392 | }
393 |
394 | return $('').width(this.options.width).append($days);
395 | },
396 | getWeekHtml: function() {
397 | var week = [],
398 | weekArray = this.options.weekArray,
399 | start = this.options.startWeek,
400 | len = weekArray.length,
401 | w = this.width / 7,
402 | h = this.height / 7;
403 |
404 | for (var i = start; i < len; i++) {
405 | week.push(WEEK_ITEM_TPL.repeat({
406 | w: w,
407 | h: h,
408 | wk: weekArray[i]
409 | }));
410 | }
411 |
412 | for (var j = 0; j < start; j++) {
413 | week.push(WEEK_ITEM_TPL.repeat({
414 | w: w,
415 | h: h,
416 | wk: weekArray[j]
417 | }));
418 | }
419 |
420 | return week.join('');
421 | },
422 | getMonthHtml: function() {
423 | var month = [],
424 | w = this.width / 4,
425 | h = this.height / 4,
426 | i = 1;
427 |
428 | for (; i < 13; i++) {
429 | month.push(MONTH_ITEM_TPL.repeat({
430 | w: w,
431 | h: h,
432 | m: i
433 | }));
434 | }
435 |
436 | return month.join('');
437 | },
438 | setMonthAction: function(y) {
439 | var m = this.date.getMonth() + 1;
440 |
441 | this.$monthItems.children().removeClass(TODAY_CLASS);
442 | if (y === this.date.getFullYear()) {
443 | this.$monthItems.children().eq(m - 1).addClass(TODAY_CLASS);
444 | }
445 | },
446 | fillStatic: function() {
447 | var staticData = {
448 | prev: this.options.prev,
449 | next: this.options.next,
450 | week: this.getWeekHtml(),
451 | month: this.getMonthHtml()
452 | };
453 |
454 | this.$element.html(TEMPLATE.join('').repeat(staticData));
455 | },
456 | updateDisDate: function(y, m) {
457 | this.$disDate.html(DATE_DIS_TPL.repeat({
458 | year: y,
459 | month: m
460 | }));
461 | },
462 | updateDisMonth: function(y) {
463 | this.$disMonth.html(y);
464 | },
465 | fillDateItems: function(y, m) {
466 | var ma = [
467 | Date.getPrevMonth(y, m), {
468 | y: y,
469 | m: m
470 | },
471 | Date.getNextMonth(y, m)
472 | ];
473 |
474 | this.$dateItems.html('');
475 | for (var i = 0; i < 3; i++) {
476 | var $item = this.getDaysHtml(ma[i].y, ma[i].m);
477 | this.$dateItems.append($item);
478 | }
479 |
480 | },
481 | hide: function(view, date, data) {
482 | this.$trigger.val(date.format(this.options.format));
483 | this.options.onClose.call(this, view, date, data);
484 | this.$element.hide();
485 | },
486 | trigger: function() {
487 |
488 | this.$trigger = this.options.trigger instanceof $ ? this.options.trigger : $(this.options.trigger);
489 |
490 | var _this = this,
491 | $this = _this.$element,
492 | post = _this.$trigger.offset(),
493 | offs = _this.options.offset;
494 |
495 | $this.addClass('calendar-modal').css({
496 | left: (post.left + offs[0]) + 'px',
497 | top: (post.top + _this.$trigger.outerHeight() + offs[1]) + 'px',
498 | zIndex: _this.options.zIndex
499 | });
500 |
501 | _this.$trigger.click(function() {
502 | $this.show();
503 | });
504 |
505 | $(document).click(function(e) {
506 | if (_this.$trigger[0] != e.target && !$.contains($this[0], e.target)) {
507 | $this.hide();
508 | }
509 | });
510 | },
511 | render: function() {
512 | this.$week = this.$element.find('.week');
513 | this.$dateItems = this.$element.find('.date-items');
514 | this.$monthItems = this.$element.find('.month-items');
515 | this.$label = this.$element.find('.calendar-label');
516 | this.$disDate = this.$element.find(DISPLAY_VD);
517 | this.$disMonth = this.$element.find(DISPLAY_VM);
518 |
519 | var y = this.date.getFullYear(),
520 | m = this.date.getMonth() + 1;
521 |
522 | this.updateDisDate(y, m);
523 | this.updateMonthView(y);
524 |
525 | this.fillDateItems(y, m);
526 |
527 | this.options.trigger && this.trigger();
528 |
529 | },
530 | setView: function(view) {
531 | this.$element.removeClass(VIEW_CLASS.date + ' ' + VIEW_CLASS.month)
532 | .addClass(VIEW_CLASS[view]);
533 | this.view = view;
534 | },
535 | updateDateView: function(y, m, dirc, cb) {
536 | m = m || this.date.getMonth() + 1;
537 |
538 | var _this = this,
539 | $dis = this.$dateItems,
540 | exec = {
541 | prev: function() {
542 | var pm = Date.getPrevMonth(y, m),
543 | ppm = Date.getPrevMonth(y, m, 2),
544 | $prevItem = _this.getDaysHtml(ppm.y, ppm.m);
545 |
546 | m = pm.m;
547 | y = pm.y;
548 |
549 | $dis.animate({
550 | marginLeft: 0
551 | }, 300, 'swing', function() {
552 | $dis.children(':last').remove();
553 | $dis.prepend($prevItem).css('margin-left', '-100%');
554 |
555 | $.isFunction(cb) && cb.call(_this);
556 | });
557 | },
558 | next: function() {
559 | var nm = Date.getNextMonth(y, m),
560 | nnm = Date.getNextMonth(y, m, 2),
561 | $nextItem = _this.getDaysHtml(nnm.y, nnm.m);
562 |
563 | m = nm.m;
564 | y = nm.y;
565 |
566 | $dis.animate({
567 | marginLeft: '-200%'
568 | }, 300, 'swing', function() {
569 | $dis.children(':first').remove();
570 | $dis.append($nextItem).css('margin-left', '-100%');
571 |
572 | $.isFunction(cb) && cb.call(_this);
573 | });
574 |
575 | }
576 | };
577 |
578 |
579 | if (dirc) {
580 | exec[dirc]();
581 | } else {
582 | this.fillDateItems(y, m);
583 | }
584 |
585 | this.updateDisDate(y, m);
586 |
587 | this.setView('date');
588 |
589 | return {
590 | y: y,
591 | m: m
592 | };
593 | },
594 | updateMonthView: function(y) {
595 | this.updateDisMonth(y);
596 | this.setMonthAction(y);
597 | this.setView('month');
598 | },
599 | getDisDateValue: function() {
600 | var arr = this.$disDate.html().split('/'),
601 | y = Number(arr[0]),
602 | m = Number(arr[1].match(/\d{1,2}/)[0]);
603 |
604 | return [y, m];
605 | },
606 | selectedDay: function(d, type) {
607 | var arr = this.getDisDateValue(),
608 | y = arr[0],
609 | m = arr[1],
610 | toggleClass = function() {
611 | this.$dateItems.children(':eq(1)')
612 | .find('[' + ITEM_DAY + ']:not(.' + NEW_DAY_CLASS + ', .' + OLD_DAY_CLASS + ')')
613 | .removeClass(SELECT_CLASS)
614 | .filter(function(index) {
615 | return parseInt(this.innerHTML) === d;
616 | }).addClass(SELECT_CLASS);
617 | };
618 |
619 | if (type) {
620 | var ret = this.updateDateView(y, m, {
621 | 'old': 'prev',
622 | 'new': 'next'
623 | }[type], toggleClass);
624 | y = ret.y;
625 | m = ret.m;
626 | this.options.viewChange('date', y, m);
627 | } else {
628 | toggleClass.call(this);
629 | }
630 |
631 | return new Date(y, m - 1, d);
632 | },
633 | showLabel: function(event, view, date, data) {
634 | var $lbl = this.$label;
635 |
636 | $lbl.find('p').html(this.options.label.repeat({
637 | m: view,
638 | d: date.format(this.options.format),
639 | v: data
640 | }).replace(/\n/g, '
'));
641 |
642 | var w = $lbl.outerWidth(),
643 | h = $lbl.outerHeight();
644 |
645 | $lbl.css({
646 | left: (event.pageX - w / 2) + 'px',
647 | top: (event.pageY - h - 20) + 'px'
648 | }).show();
649 | },
650 | hasLabel: function() {
651 | if (this.options.label) {
652 | $('body').append(this.$label);
653 | return true;
654 | }
655 | return false;
656 | },
657 | event: function() {
658 | var _this = this,
659 | vc = _this.options.viewChange;
660 |
661 | // view change
662 | _this.$element.on('click', DISPLAY_VD, function() {
663 | var arr = _this.getDisDateValue();
664 | _this.updateMonthView(arr[0], arr[1]);
665 |
666 | vc('month', arr[0], arr[1]);
667 |
668 | }).on('click', DISPLAY_VM, function() {
669 | var y = this.innerHTML;
670 |
671 | _this.updateDateView(y);
672 | vc('date', y);
673 | });
674 |
675 | // arrow
676 | _this.$element.on('click', ARROW_DATE, function() {
677 | var arr = _this.getDisDateValue(),
678 | type = getClass(this),
679 | y = arr[0],
680 | m = arr[1];
681 |
682 | var d = _this.updateDateView(y, m, type, function() {
683 | vc('date', d.y, d.m);
684 | });
685 |
686 | }).on('click', ARROW_MONTH, function() {
687 |
688 | var y = Number(_this.$disMonth.html()),
689 | type = getClass(this);
690 |
691 | y = type === 'prev' ? y - 1 : y + 1;
692 | _this.updateMonthView(y);
693 | vc('month', y);
694 | });
695 |
696 | // selected
697 | _this.$element.on('click', '[' + ITEM_DAY + ']', function() {
698 | var d = parseInt(this.innerHTML),
699 | cls = getClass(this),
700 | type = /new|old/.test(cls) ? cls.match(/new|old/)[0] : '';
701 |
702 | var day = _this.selectedDay(d, type);
703 |
704 | _this.options.onSelected.call(this, 'date', day, $(this).data(MARK_DATA));
705 |
706 | _this.$trigger && _this.hide('date', day, $(this).data(MARK_DATA));
707 |
708 | }).on('click', '[' + ITEM_MONTH + ']', function() {
709 | var y = Number(_this.$disMonth.html()),
710 | m = parseInt(this.innerHTML);
711 |
712 | _this.updateDateView(y, m);
713 | vc('date', y, m);
714 | _this.options.onSelected.call(this, 'month', new Date(y, m - 1));
715 | });
716 |
717 | // hover
718 | _this.$element.on('mouseenter', '[' + ITEM_DAY + ']', function(e) {
719 | var arr = _this.getDisDateValue(),
720 | day = new Date(arr[0], arr[1] - 1, parseInt(this.innerHTML));
721 |
722 | if (_this.hasLabel && $(this).data(MARK_DATA)) {
723 | $('body').append(_this.$label);
724 | _this.showLabel(e, 'date', day, $(this).data(MARK_DATA));
725 | }
726 |
727 | _this.options.onMouseenter.call(this, 'date', day, $(this).data(MARK_DATA));
728 | }).on('mouseleave', '[' + ITEM_DAY + ']', function() {
729 | _this.$label.hide();
730 | });
731 | },
732 | resize: function() {
733 | var w = this.width,
734 | h = this.height,
735 | hdH = this.$element.find('.calendar-hd').outerHeight();
736 |
737 | this.$element.width(w).height(h + hdH)
738 | .find('.calendar-inner, .view')
739 | .css('width', w + 'px');
740 |
741 | this.$element.find('.calendar-ct').width(w).height(h);
742 |
743 | },
744 | init: function() {
745 |
746 | this.fillStatic();
747 | this.resize();
748 | this.render();
749 | this.view = this.options.view;
750 | this.setView(this.view);
751 | this.event();
752 | },
753 | setData: function(data) {
754 | this.data = data;
755 |
756 | if (this.view === 'date') {
757 | var d = this.getDisDateValue();
758 | this.fillDateItems(d[0], d[1]);
759 | } else if (this.view === 'month') {
760 | this.updateMonthView(this.$disMonth.html());
761 | }
762 | },
763 | methods: function(name, args) {
764 | if (OS.call(this[name]) === '[object Function]') {
765 | return this[name].apply(this, args);
766 | }
767 | }
768 | };
769 |
770 | $.fn.calendar = function(options) {
771 | var calendar = this.data('calendar'),
772 | fn,
773 | args = [].slice.call(arguments);
774 |
775 | if (!calendar) {
776 | return this.each(function() {
777 | return $(this).data('calendar', new Calendar(this, options));
778 | });
779 | }
780 | if (isString(options)) {
781 | fn = options;
782 | args.shift();
783 | return calendar.methods(fn, args);
784 | }
785 |
786 | return this;
787 | }
788 |
789 | $.fn.calendar.defaults = defaults;
790 |
791 | }));
792 |
--------------------------------------------------------------------------------