├── .eslintignore ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── index.js ├── .babelrc ├── .torch.compile.opts.js ├── .gitlab-ci.yml ├── webpack-dev.config.js ├── .travis.yml ├── .eslintrc ├── .editorconfig ├── webpack.config.js ├── LICENSE ├── .gitignore ├── .npmignore ├── package.json ├── README.md ├── test ├── unit │ └── index-spec.js └── fixtures │ └── candleSticks.json └── src ├── index.js └── component └── range.js /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | coverage/ 3 | dist/ 4 | mocks/ 5 | node_modules/ 6 | demos/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | --- 4 | 5 | ## 2.0.0 6 | 7 | 配合 G2@3.0.0 版本升级。 8 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | ### Environment 4 | 5 | ### Expected behavior 6 | 7 | ### Actual behavior 8 | 9 | ### Steps to reproduce the behavior 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Slider = require('./src/index'); 2 | if (window && !window.G2) { 3 | console.err('Please load the G2 script first!'); 4 | } 5 | 6 | module.exports = Slider; 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-remove-strict-mode" 4 | ], 5 | "presets": [ 6 | [ 7 | "@babel/preset-env", 8 | { 9 | "loose": true, 10 | "modules": false 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.torch.compile.opts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | babelrc: { 3 | presets: [ 4 | '@babel/preset-env' 5 | ], 6 | sourceMaps: 'inline' 7 | }, 8 | extensions: ['.js'], 9 | exclude: [ 10 | 'bower_components/**/*.js', 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - Xvfb :99 & 3 | - export DISPLAY=:99.0; 4 | - echo $PATH 5 | test: 6 | image: reg.docker.alibaba-inc.com/dockerlab/macaca-electron:0.2.0 7 | script: 8 | - time tnpm i --no-cache 9 | - time tnpm run ci 10 | tags: 11 | - swarm 12 | -------------------------------------------------------------------------------- /webpack-dev.config.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack.config'); 2 | const _ = require('lodash'); 3 | 4 | module.exports = _.merge({ 5 | devtool: 'cheap-source-map', 6 | watch: true, 7 | watchOptions: { 8 | aggregateTimeout: 300, 9 | poll: 1000 10 | } 11 | }, webpackConfig); 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | 6 | install: 7 | - npm install spm coveralls 8 | 9 | before_script: 10 | - node_modules/spm/bin/spm-install 11 | 12 | script: 13 | - node_modules/spm/bin/spm-test 14 | 15 | after_success: 16 | - node_modules/spm/bin/spm-test --coveralls | node_modules/.bin/coveralls 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "egg" 4 | ], 5 | "parser": "babel-eslint", 6 | "parserOptions": { 7 | "sourceType": "module" 8 | }, 9 | "rules": { 10 | "no-bitwise": [ 11 | 0 12 | ], 13 | "experimentalDecorators": [ 14 | 0 15 | ], 16 | "comma-dangle": [ 17 | "error", 18 | "never" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [Makefile] 16 | indent_style = tab 17 | indent_size = 1 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const { 3 | resolve 4 | } = require('path'); 5 | 6 | module.exports = { 7 | entry: { 8 | 'g2-plugin-slider': './index.js' 9 | }, 10 | output: { 11 | filename: '[name].js', 12 | library: 'Slider', 13 | libraryTarget: 'umd', 14 | path: resolve(__dirname, 'build/') 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | use: { 21 | loader: 'babel-loader', 22 | options: { 23 | babelrc: true 24 | } 25 | } 26 | } 27 | ] 28 | }, 29 | plugins: [ 30 | new webpack.NoEmitOnErrorsPlugin(), 31 | new webpack.optimize.AggressiveMergingPlugin() 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alipay.inc 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # lock 9 | package-lock.json 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | build 64 | dist 65 | temp 66 | .DS_Store 67 | .idea 68 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # lock 9 | package-lock.json 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # build 64 | .DS_Store 65 | 66 | # npmignore - content above this line is automatically generated and modifications may be omitted 67 | # see npmjs.com/npmignore for more details. 68 | test 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@antv/g2-plugin-slider", 3 | "version": "2.1.1", 4 | "description": "A datazoom slider plugin for G2.", 5 | "keywords": [ 6 | "g2", 7 | "slider", 8 | "plugin", 9 | "datazoom" 10 | ], 11 | "main": "build/g2-plugin-slider.js", 12 | "browser": "build/g2-plugin-slider.js", 13 | "module": "index.js", 14 | "homepage": "https://github.com/antvis/g2-plugin-slider", 15 | "author": "sima.zhang1990@gmail.com", 16 | "repository": { 17 | "type": "git", 18 | "url": "git@github.com:antvis/g2-plugin-slider.git" 19 | }, 20 | "bugs": { 21 | "url": "http://gitlab.alibaba-inc.com/datavis/g2-plugin-slider/issues" 22 | }, 23 | "peerDependencies": { 24 | "@antv/g2": ">=3.2.8" 25 | }, 26 | "devDependencies": { 27 | "@antv/data-set": "~0.9.6", 28 | "@antv/g2": "~3.2.8", 29 | "@babel/core": "~7.0.0", 30 | "@babel/preset-env": "~7.0.0", 31 | "torchjs": "~2.1.0", 32 | "babel-eslint": "~7.2.3", 33 | "babel-loader": "~8.0.0", 34 | "babel-plugin-transform-remove-strict-mode": "0.0.2", 35 | "chai": "~4.0.1", 36 | "electron": "~1.8.2-beta5", 37 | "eslint": "~3.19.0", 38 | "eslint-config-airbnb": "~15.0.1", 39 | "eslint-config-egg": "~4.2.0", 40 | "event-simulate": "~1.0.0", 41 | "jquery": "~3.3.1", 42 | "pre-commit": "~1.2.2", 43 | "shelljs": "~0.7.8", 44 | "uglify-js": "~3.0.15", 45 | "webpack": "~3.4.1" 46 | }, 47 | "scripts": { 48 | "build": "webpack", 49 | "ci": "npm run lint && npm run test", 50 | "compress": "uglifyjs --compress --mangle --output dist/g2-plugin-slider.min.js -- build/g2-plugin-slider.js", 51 | "coverage": "npm run coverage-generator && npm run coverage-viewer", 52 | "coverage-viewer": "torch-coverage", 53 | "coverage-generator": "torch --coverage --compile --renderer --recursive test/unit", 54 | "dev": "webpack --config webpack-dev.config.js", 55 | "dist": "rm -rf dist && mkdir dist && npm run build && npm run compress", 56 | "prepublishOnly": "npm run dist", 57 | "lint": "eslint ./", 58 | "lint-fix": "eslint --fix ./", 59 | "test": "torch --compile --renderer --recursive ./test/unit", 60 | "test-live": "torch --compile --renderer --interactive --recursive ./test/unit" 61 | }, 62 | "pre-commit": { 63 | "run": [ 64 | "lint", 65 | "test" 66 | ], 67 | "silent": false 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # g2-plugin-slider 2 | 3 | [![npm package](https://img.shields.io/npm/v/@antv/g2-plugin-slider.svg)](https://www.npmjs.com/package/@antv/g2-plugin-slider) 4 | [![NPM downloads](http://img.shields.io/npm/dm/@antv/g2-plugin-slider.svg)](https://npmjs.org/package/@antv/g2-plugin-slider) 5 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/antvis/g2-plugin-slider.svg)](http://isitmaintained.com/project/antvis/g2-plugin-slider "Percentage of issues still open") 6 | 7 | A datazoom slider plugin for G2. 8 | 9 | ## Install 10 | 11 | **首先**,你先要确保 [G2](https://github.com/antvis/g2) 已经加载。 12 | 13 | 如果你使用 npm,直接 `npm i @antv/g2-plugin-slider`。否则,直接下载[最新脚本](http://unpkg.alipay.com/@antv/g2-plugin-slider)。 14 | 15 | ```javascript 16 | import G2 from '@antv/g2'; 17 | import Slider from '@antv/g2-plugin-slider'; 18 | 19 | const slider = new Slider({ 20 | 21 | }); 22 | ``` 23 | 24 | 25 | ```html 26 | 27 | 28 | 29 | 34 | ``` 35 | 36 | ## API Reference 37 | 38 | ### Create an instance 39 | 40 | ```js 41 | new Slider({ 42 | container: {string} | {HTMLElement}, 43 | width?: {number} | {string}, 44 | height?: {number}, 45 | padding?: {object} | {number} | {array}, 46 | xAxis: {string}, 47 | yAxis: {string}, 48 | start: {string} | {number}, 49 | end: {string} | {number}, 50 | startRadio?: {number}, 51 | endRadio?: {number}, 52 | minSpan: {number}, 53 | maxSpan: {number}, 54 | data: {array} | {dataview}, 55 | fillerStyle?: {object}, 56 | backgroundStyle?: {object}, 57 | textStyle?: {object}, 58 | handleStyle?: {object}, 59 | backgroundChart?: {object} 60 | }); 61 | ``` 62 | 63 | - `container` 64 | 65 | (string | HTMLElement) 66 | 67 | 对应 slider 的 DOM 容器,可以传入该 DOM 的 id 或者直接传入容器的 html 节点对象。 68 | 69 | - `width` 70 | 71 | (string | number) 72 | 73 | 设置 slider 组件的宽度,默认为 `auto`,表示自适应容器的宽度。 74 | 75 | - `height` 76 | 77 | (number) 78 | 79 | 设置 slider 组件的高度,默认为 26,单位为 'px'。 80 | 81 | - `padding` 82 | 83 | 设置 slider 组件所在画布 canvas 的内边距,用于与图表对齐(默认图表的 canvas 容器也是带了内边距),默认值同 G2 默认主题的 padding 相同,[ 20, 20, 95, 80 ]。 84 | 85 | - `xAxis` 86 | 87 | (string) 88 | 89 | **必须声明**,我们的 Slider 是带有背景图表的滑动条组件,该字段用于声明该背景图表的横轴映射字段,同时该字段也是数据过滤字段。 90 | 91 | - `yAxis` 92 | 93 | (string) 94 | 95 | **必须声明**,我们的 Slider 是带有背景图表的滑动条组件,该字段用于声明该背景图表的纵轴轴映射字段。 96 | 97 | - `data` 98 | 99 | (array | dataview) 100 | 101 | **必须声明**,数据源。 102 | 103 | - `startRadio` 104 | 105 | (number) 106 | 107 | 声明滑动条起始滑块的位置对应的范围边界值,值介于 [0, 1]。 108 | 109 | 注意:`startRadio` 和 `start` 同时声明时,以 `startRadio` 为准。 110 | 111 | - `endRadio` 112 | 113 | (number) 114 | 115 | 声明滑动条结束滑块的位置对应的范围边界值,值介于 [0, 1]。 116 | 117 | 注意:`endRadio` 和 `end` 同时声明时,以 `endRadio` 为准。 118 | 119 | 120 | - `start` 121 | 122 | (number | string) 123 | 124 | 声明滑动条起始滑块的位置对应的数据值,默认为最小值。 125 | 126 | - `end` 127 | 128 | (number | string) 129 | 130 | 声明滑动条结束滑块的位置对应的数据值,默认为最大值。 131 | 132 | - `minSpan` 133 | 134 | (number) 135 | 136 | 筛选的最小范围限制,必须对应原始数据的范围,如果是时间,请使用时间戳。 137 | 138 | - `maxSpan` 139 | 140 | (number) 141 | 142 | 筛选的最大范围限制,必须对应原始数据的范围,如果是时间,请使用时间戳。 143 | 144 | - `scales` 145 | 146 | (object) 147 | 148 | 用于对 `xAxis` 和 `yAxis` 字段进行[列定义](/zh-cn/g2/3.x/tutorial/how-to-scale.htm),用于同操作的图表中对应的列定义相同。 149 | 150 | 示例代码: 151 | 152 | ```js 153 | scales: { 154 | [`${xAxis}`]: { 155 | type: 'time', 156 | mask: 'MM-DD' 157 | } 158 | } 159 | ``` 160 | 161 | - `onChange` 162 | 163 | (function) 164 | 165 | 当滑动条滑块发生变化时,触发该回调函数,主要用于更新 ds 的状态量。该回调函数会提供一个参数,该参数是一个对象,包含如下属性: 166 | 167 | ```js 168 | onChange: (obj) => { 169 | const { startValue, endValue, startText, endText, startRadio, endRadio } = obj; 170 | } 171 | ``` 172 | 173 | * `startValue` 起点滑块当前对应的原始数据值,如果是 `time` 或者 `timeCat` 类型是,该值为时间戳,请注意。 174 | * `endValue` 终点滑块当前对应的原始数据值,如果是 `time` 或者 `timeCat` 类型是,该值为时间戳,请注意。 175 | * `startText` 起点滑块当前的显示文本值 176 | * `endText` 终点滑块当前的显示文本值 177 | * `startRadio` 起点滑块当前对应的范围边界值,值介于 [0, 1] 178 | * `endRadio` 终点滑块当前对应的范围边界值,值介于 [0, 1] 179 | 180 | > 说明1:之所以区分 text 和 value,是因为大部分情况下用户会对数值进行格式化,所以在设置状态量和更新状态量时,需要保证前后数值类型的一致。 181 | > 说明2:若数据并非有序排列,则可以通过 `[startRadio, endRadio]` 获取到滑块起点和终点选中的范围 182 | 183 | - `fillerStyle` 184 | 185 | (object) 186 | 187 | 选中区域的样式配置,默认配置如下: 188 | 189 | ```js 190 | { 191 | fill: '#BDCCED', 192 | fillOpacity: 0.3 193 | } 194 | ``` 195 | 196 | 图中红框框选区域: 197 | 198 | - `backgroundStyle` 199 | 200 | (object) 201 | 202 | slider 整体背景样式。 203 | 204 | - `textStyle` 205 | 206 | (object) 207 | 208 | slider 辅助文本字体样式配置。 209 | 210 | - `handleStyle` 211 | 212 | (object) 213 | 214 | slider 滑块的样式配置,可配置的属性如下: 215 | 216 | ```js 217 | { 218 | img: 'https://gw.alipayobjects.com/zos/rmsportal/QXtfhORGlDuRvLXFzpsQ.png', // 可以使图片地址也可以是 data urls 219 | width: 5, 220 | height: 26 221 | } 222 | ``` 223 | 224 | - `backgroundChart` 225 | 226 | (object) 227 | 228 | slider 滑块的背景图表配置,可配置其图表类型以及颜色: 229 | 230 | ```js 231 | { 232 | type: [ 'area' ], // 图表的类型,可以是字符串也可是是数组 233 | color: '#CCD6EC' 234 | } 235 | ``` 236 | 237 | ### Functions 238 | 239 | - `slider.render()` 240 | 241 | `slider.render()` 渲染组件,即将其绘制到页面上。 242 | 243 | - `slider.changeData()` 244 | 245 | `slider.changeData(data)` 更新数据源。 246 | 247 | - `slider.repaint()` 248 | 249 | `slider.repaint()` 重绘。 250 | 251 | - `slider.destroy()` 252 | 253 | `slider.destroy()` 销毁。 254 | -------------------------------------------------------------------------------- /test/unit/index-spec.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const G2 = require('@antv/g2'); 3 | const DataSet = require('@antv/data-set'); 4 | const Slider = require('../../index'); 5 | const PekingAQI = require('../fixtures/peking-aqi.json'); 6 | const kData = require('../fixtures/candleSticks.json'); 7 | const topData = require('../fixtures/top2000.json'); 8 | 9 | const div = document.createElement('div'); 10 | div.id = 'c1'; 11 | document.body.appendChild(div); 12 | 13 | const range = document.createElement('div'); 14 | range.id = 'range'; 15 | document.body.appendChild(range); 16 | 17 | describe('Test cases', function() { 18 | it('changeData && autoWidth', function() { 19 | // 设置状态量 20 | const ds = new DataSet({ 21 | state: { 22 | start: '2004-01-01', 23 | end: '2007-09-24' 24 | } 25 | }); 26 | const dv = ds.createView(); 27 | dv.source(PekingAQI) 28 | .transform({ 29 | type: 'filter', 30 | callback: obj => { 31 | return obj.date <= ds.state.end && obj.date >= ds.state.start; 32 | } 33 | }); 34 | const chart = new G2.Chart({ 35 | id: 'c1', 36 | forceFit: true, 37 | height: 400, 38 | animate: false 39 | }); 40 | 41 | chart.scale({ 42 | date: { 43 | type: 'time', 44 | mask: 'MM-DD', 45 | alias: '日期' 46 | } 47 | }); 48 | 49 | const view1 = chart.view({ 50 | start: { 51 | x: 0, 52 | y: 0 53 | }, 54 | end: { 55 | x: 1, 56 | y: 0.45 57 | } 58 | }); 59 | view1.source(dv); 60 | view1.line().position('date*aqi'); 61 | 62 | const view2 = chart.view({ 63 | start: { 64 | x: 0, 65 | y: 0.55 66 | }, 67 | end: { 68 | x: 1, 69 | y: 1 70 | } 71 | }); 72 | view2.source(dv); 73 | view2.interval().position('date*aqi'); 74 | chart.render(); 75 | 76 | const slider = new Slider({ 77 | container: document.getElementById('range'), // DOM id 78 | width: 'auto', 79 | height: 26, 80 | start: '2004-01-01', // 和状态量对应 81 | end: '2007-09-24', 82 | data: PekingAQI, // 源数据 83 | xAxis: 'date', // 背景图的横轴对应字段,同时为数据筛选的字段 84 | yAxis: 'aqi', // 背景图的纵轴对应字段,同时为数据筛选的字段 85 | backgroundChart: { 86 | type: 'line', 87 | color: 'rgba(0, 0, 0, 0.3)' 88 | }, 89 | // fillerStyle: { 90 | // fill: 'rgba(0, 0, 0, 0.2)' 91 | // }, 92 | // backgroundStyle: { 93 | // stroke: '#f80', 94 | // fill: '#F3F3F3', 95 | // opacity: 0.2, 96 | // lineWidth: 2 97 | // }, 98 | onChange: ({ startText, endText }) => { 99 | ds.setState('start', startText); 100 | ds.setState('end', endText); 101 | } 102 | }); 103 | slider.render(); 104 | 105 | expect(slider.bgChart).not.to.be.empty; 106 | 107 | 108 | setTimeout(function() { 109 | const newData = PekingAQI.slice(10, 90); 110 | ds.setState('start', '2000-06-29'); 111 | ds.setState('end', '2000-08-20'); 112 | dv.source(newData); 113 | slider.start = '2000-06-29'; 114 | slider.end = '2000-08-20'; 115 | slider.changeData(newData); 116 | expect(slider.bgChart).not.to.be.empty; 117 | expect(slider.start).equal('2000-06-29'); 118 | 119 | }, 2000); 120 | }); 121 | 122 | it.only('basic', function() { 123 | // 设置状态量,时间格式建议转换为时间戳 124 | const ds = new DataSet({ 125 | state: { 126 | start: '2015-04-07', 127 | end: '2015-07-28' 128 | } 129 | }); 130 | const dv = ds.createView(); 131 | dv.source(kData) 132 | .transform({ 133 | type: 'filter', 134 | callback: obj => { 135 | const date = obj.time; 136 | return date <= ds.state.end && date >= ds.state.start; 137 | } 138 | }) 139 | .transform({ 140 | type: 'map', 141 | callback: obj => { 142 | obj.trend = (obj.start <= obj.end) ? '上涨' : '下跌'; 143 | obj.range = [ obj.start, obj.end, obj.max, obj.min ]; 144 | return obj; 145 | } 146 | }); 147 | const chart = new G2.Chart({ 148 | container: 'c1', 149 | width: 600, 150 | height: 400, 151 | animate: false, 152 | padding: [ 20, 120, 100 ] 153 | }); 154 | 155 | chart.source(dv, { 156 | trend: { 157 | alias: '趋势' 158 | }, 159 | time: { 160 | type: 'timeCat', 161 | nice: false, 162 | mask: 'MM-DD', 163 | alias: '时间' 164 | // tickCount: 10 165 | }, 166 | volumn: { alias: '成交量' }, 167 | start: { alias: '开盘价' }, 168 | end: { alias: '收盘价' }, 169 | max: { alias: '最高价' }, 170 | min: { alias: '最低价' }, 171 | range: { alias: '股票价格' } 172 | }); 173 | chart.tooltip({ 174 | showTitle: false, 175 | itemTpl: '
  • ' 176 | + '' 177 | + '{name}
    ' 178 | + '开盘价:{start}
    ' 179 | + '收盘价:{end}
    ' 180 | + '最高价:{max}
    ' 181 | + '最低价:{min}
    ' 182 | + '成交量:{volumn}
    ' 183 | + '
  • ' 184 | }); 185 | chart.schema() 186 | .position('time*range') 187 | .color('trend', val => { 188 | if (val === '上涨') { 189 | return '#f04864'; 190 | } 191 | 192 | if (val === '下跌') { 193 | return '#2fc25b'; 194 | } 195 | }) 196 | .shape('candle') 197 | .tooltip('time*start*end*max*min*volumn', (time, start, end, max, min, volumn) => { 198 | return { 199 | name: time, 200 | start, 201 | end, 202 | max, 203 | min, 204 | volumn 205 | }; 206 | }); 207 | chart.render(); 208 | 209 | const slider = new Slider({ 210 | container: 'range', // DOM id 211 | width: 600, 212 | height: 26, 213 | padding: [ 20, 120, 100 ], 214 | minSpan: 30 * 24 * 60 * 60 * 1000, // 设置最小值 215 | maxSpan: 120 * 24 * 60 * 60 * 1000, // 设置最大值 216 | start: '2015-04-07', // 和状态量对应 217 | end: '2015-07-28', 218 | data: kData, // 源数据 219 | xAxis: 'time', // 背景图的横轴对应字段,同时为数据筛选的字段 220 | yAxis: 'volumn', // 背景图的纵轴对应字段,同时为数据筛选的字段 221 | scales: { 222 | time: { 223 | type: 'timeCat' 224 | } 225 | }, 226 | onChange: ({ startText, endText }) => { 227 | ds.setState('start', startText); 228 | ds.setState('end', endText); 229 | } 230 | }); 231 | slider.render(); 232 | }); 233 | 234 | it('linear scale', function() { 235 | const ds = new DataSet({ 236 | state: { 237 | start: 1960, 238 | end: 1984 239 | } 240 | }); 241 | const dv = ds.createView('test') 242 | .source(topData) 243 | .transform({ 244 | as: [ 'count' ], 245 | groupBy: [ 'release' ], 246 | operations: [ 'count' ], 247 | type: 'aggregate' 248 | }) 249 | .transform({ 250 | type: 'map', 251 | callback(obj) { 252 | obj.release *= 1; 253 | return obj; 254 | } 255 | }) 256 | .transform({ 257 | type: 'filter', 258 | callback: obj => { 259 | const date = obj.release; 260 | return date <= ds.state.end && date >= ds.state.start; 261 | } 262 | }); 263 | 264 | const chart = new G2.Chart({ 265 | container: 'c1', 266 | width: 600, 267 | height: 400, 268 | animate: false, 269 | padding: [ 20, 120, 100 ] 270 | }); 271 | chart.source(dv); 272 | chart.scale({ 273 | count: { 274 | alias: 'top2000 唱片总量' 275 | }, 276 | release: { 277 | tickInterval: 5, 278 | alias: '唱片发行年份' 279 | } 280 | }); 281 | chart.interval() 282 | .position('release*count') 283 | .color('#e50000'); 284 | 285 | chart.render(); 286 | 287 | const slider = new Slider({ 288 | container: 'range', // DOM id 289 | width: 600, 290 | height: 26, 291 | padding: [ 20, 120, 100 ], 292 | minSpan: 10, // 设置最小值 293 | maxSpan: 50, // 设置最大值 294 | start: 1960, // 和状态量对应 295 | end: 1984, 296 | data: topData, // 源数据 297 | xAxis: 'release', // 背景图的横轴对应字段,同时为数据筛选的字段 298 | yAxis: 'count', // 背景图的纵轴对应字段,同时为数据筛选的字段 299 | onChange: ({ startText, endText }) => { 300 | ds.setState('start', startText); 301 | ds.setState('end', endText); 302 | } 303 | }); 304 | slider.render(); 305 | 306 | 307 | }); 308 | }); 309 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview G2's plugin for datazoom. 3 | * @author sima.zhang 4 | */ 5 | const Range = require('./component/range'); 6 | 7 | const G2 = window && window.G2; 8 | const { Chart, Util, G, Global } = G2; 9 | const { Canvas } = G; 10 | const { DomUtil } = Util; 11 | 12 | const isNumber = val => typeof val === 'number'; 13 | 14 | class Slider { 15 | _initProps() { 16 | this.height = 26; 17 | this.width = 'auto'; // 默认自适应 18 | this.padding = Global.plotCfg.padding; 19 | this.container = null; 20 | this.xAxis = null; 21 | this.yAxis = null; 22 | // 选中区域的样式 23 | this.fillerStyle = { 24 | fill: '#BDCCED', 25 | fillOpacity: 0.3 26 | }; 27 | // 滑动条背景样式 28 | this.backgroundStyle = { 29 | stroke: '#CCD6EC', 30 | fill: '#CCD6EC', 31 | fillOpacity: 0.3, 32 | lineWidth: 1 33 | }; 34 | this.range = [ 0, 100 ]; 35 | this.layout = 'horizontal'; 36 | // 文本颜色 37 | this.textStyle = { 38 | fill: '#545454' 39 | }; 40 | // 滑块的样式 41 | this.handleStyle = { 42 | img: 'https://gw.alipayobjects.com/zos/rmsportal/QXtfhORGlDuRvLXFzpsQ.png', 43 | width: 5 44 | }; 45 | // 背景图表的配置,如果为 false 则表示不渲染 46 | this.backgroundChart = { 47 | type: [ 'area' ], // 图表的类型,可以是字符串也可是是数组 48 | color: '#CCD6EC' 49 | }; 50 | } 51 | 52 | constructor(cfg) { 53 | this._initProps(); 54 | Util.deepMix(this, cfg); 55 | const container = this.container; 56 | if (!container) { 57 | throw new Error('Please specify the container for the Slider!'); 58 | } 59 | if (Util.isString(container)) { 60 | this.domContainer = document.getElementById(container); 61 | } else { 62 | this.domContainer = container; 63 | } 64 | 65 | this.handleStyle = Util.mix({ 66 | width: this.height, 67 | height: this.height 68 | }, this.handleStyle); 69 | if (this.width === 'auto') { // 宽度自适应 70 | window.addEventListener('resize', Util.wrapBehavior(this, '_initForceFitEvent')); 71 | } 72 | } 73 | 74 | _initForceFitEvent() { 75 | const timer = setTimeout(Util.wrapBehavior(this, 'forceFit'), 200); 76 | clearTimeout(this.resizeTimer); 77 | this.resizeTimer = timer; 78 | } 79 | 80 | forceFit() { 81 | if (!this || this.destroyed) { 82 | return; 83 | } 84 | const width = DomUtil.getWidth(this.domContainer); 85 | const height = this.height; 86 | if (width !== this.domWidth) { 87 | const canvas = this.canvas; 88 | canvas.changeSize(width, height); // 改变画布尺寸 89 | this.bgChart && this.bgChart.changeWidth(width); 90 | canvas.clear(); 91 | this._initWidth(); 92 | this._initSlider(); // 初始化滑动条 93 | this._bindEvent(); 94 | canvas.draw(); 95 | } 96 | } 97 | 98 | _initWidth() { 99 | let width; 100 | if (this.width === 'auto') { 101 | width = DomUtil.getWidth(this.domContainer); 102 | } else { 103 | width = this.width; 104 | } 105 | this.domWidth = width; 106 | const padding = Util.toAllPadding(this.padding); 107 | 108 | if (this.layout === 'horizontal') { 109 | this.plotWidth = width - padding[1] - padding[3]; 110 | this.plotPadding = padding[3]; 111 | this.plotHeight = this.height; 112 | } else if (this.layout === 'vertical') { 113 | this.plotWidth = this.width; 114 | this.plotHeight = this.height - padding[0] - padding[2]; 115 | this.plotPadding = padding[0]; 116 | } 117 | } 118 | 119 | render() { 120 | this._initWidth(); 121 | this._initCanvas(); // 初始化 canvas 122 | this._initBackground(); // 初始化背景图表 123 | this._initSlider(); // 初始化滑动条 124 | this._bindEvent(); 125 | this.canvas.draw(); 126 | } 127 | 128 | changeData(data) { 129 | this.data = data; 130 | this.repaint(); 131 | } 132 | 133 | destroy() { 134 | clearTimeout(this.resizeTimer); 135 | const rangeElement = this.rangeElement; 136 | rangeElement.off('sliderchange'); 137 | this.bgChart && this.bgChart.destroy(); 138 | this.canvas.destroy(); 139 | const container = this.domContainer; 140 | while (container.hasChildNodes()) { 141 | container.removeChild(container.firstChild); 142 | } 143 | window.removeEventListener('resize', Util.getWrapBehavior(this, '_initForceFitEvent')); 144 | this.destroyed = true; 145 | } 146 | 147 | clear() { 148 | this.canvas.clear(); 149 | this.bgChart && this.bgChart.destroy(); 150 | this.bgChart = null; 151 | this.scale = null; 152 | this.canvas.draw(); 153 | } 154 | 155 | repaint() { 156 | this.clear(); 157 | this.render(); 158 | } 159 | 160 | _initCanvas() { 161 | const width = this.domWidth; 162 | const height = this.height; 163 | const canvas = new Canvas({ 164 | width, 165 | height, 166 | containerDOM: this.domContainer, 167 | capture: false 168 | }); 169 | const node = canvas.get('el'); 170 | node.style.position = 'absolute'; 171 | node.style.top = 0; 172 | node.style.left = 0; 173 | node.style.zIndex = 3; 174 | this.canvas = canvas; 175 | } 176 | 177 | _initBackground() { 178 | const data = this.data; 179 | const xAxis = this.xAxis; 180 | const yAxis = this.yAxis; 181 | const scales = Util.deepMix({ 182 | [`${xAxis}`]: { 183 | range: [ 0, 1 ] 184 | } 185 | }, this.scales); // 用户列定义 186 | if (!data) { // 没有数据,则不创建 187 | throw new Error('Please specify the data!'); 188 | } 189 | if (!xAxis) { 190 | throw new Error('Please specify the xAxis!'); 191 | } 192 | if (!yAxis) { 193 | throw new Error('Please specify the yAxis!'); 194 | } 195 | 196 | const backgroundChart = this.backgroundChart; 197 | let type = backgroundChart.type; 198 | const color = backgroundChart.color; 199 | if (!Util.isArray(type)) { 200 | type = [ type ]; 201 | } 202 | 203 | const padding = Util.toAllPadding(this.padding); 204 | const bgChart = new Chart({ 205 | container: this.container, 206 | width: this.domWidth, 207 | height: this.height, 208 | padding: [ 0, padding[1], 0, padding[3] ], 209 | animate: false 210 | }); 211 | bgChart.source(data); 212 | bgChart.scale(scales); 213 | bgChart.axis(false); 214 | bgChart.tooltip(false); 215 | bgChart.legend(false); 216 | Util.each(type, eachType => { 217 | bgChart[eachType]() 218 | .position(xAxis + '*' + yAxis) 219 | .color(color) 220 | .opacity(1); 221 | }); 222 | bgChart.render(); 223 | this.bgChart = bgChart; 224 | this.scale = this.layout === 'horizontal' ? bgChart.getXScale() : bgChart.getYScales()[0]; 225 | if (this.layout === 'vertical') { 226 | bgChart.destroy(); 227 | } 228 | } 229 | 230 | _initRange() { 231 | const startRadio = this.startRadio; 232 | const endRadio = this.endRadio; 233 | const start = this.start; 234 | const end = this.end; 235 | const scale = this.scale; 236 | let min = 0; 237 | let max = 1; 238 | 239 | // startRadio 优先级高于 start 240 | if (isNumber(startRadio)) { 241 | min = startRadio; 242 | } else if (start) { 243 | min = scale.scale(scale.translate(start)); 244 | } 245 | 246 | // endRadio 优先级高于 end 247 | if (isNumber(endRadio)) { 248 | max = endRadio; 249 | } else if (end) { 250 | max = scale.scale(scale.translate(end)); 251 | } 252 | 253 | const { minSpan, maxSpan } = this; 254 | let totalSpan = 0; 255 | if (scale.type === 'time' || scale.type === 'timeCat') { // 时间类型已排序 256 | const values = scale.values; 257 | const firstValue = values[0]; 258 | const lastValue = values[values.length - 1]; 259 | totalSpan = lastValue - firstValue; 260 | } else if (scale.isLinear) { 261 | totalSpan = scale.max - scale.min; 262 | } 263 | 264 | if (totalSpan && minSpan) { 265 | this.minRange = (minSpan / totalSpan) * 100; 266 | } 267 | 268 | if (totalSpan && maxSpan) { 269 | this.maxRange = (maxSpan / totalSpan) * 100; 270 | } 271 | 272 | const range = [ min * 100, max * 100 ]; 273 | this.range = range; 274 | return range; 275 | } 276 | 277 | _getHandleValue(type) { 278 | let value; 279 | const range = this.range; 280 | const min = range[0] / 100; 281 | const max = range[1] / 100; 282 | const scale = this.scale; 283 | if (type === 'min') { 284 | value = this.start ? this.start : scale.invert(min); 285 | } else { 286 | value = this.end ? this.end : scale.invert(max); 287 | } 288 | return value; 289 | } 290 | 291 | _initSlider() { 292 | const canvas = this.canvas; 293 | const range = this._initRange(); 294 | const scale = this.scale; 295 | const rangeElement = canvas.addGroup(Range, { 296 | middleAttr: this.fillerStyle, 297 | range, 298 | minRange: this.minRange, 299 | maxRange: this.maxRange, 300 | layout: this.layout, 301 | width: this.plotWidth, 302 | height: this.plotHeight, 303 | backgroundStyle: this.backgroundStyle, 304 | textStyle: this.textStyle, 305 | handleStyle: this.handleStyle, 306 | minText: scale.getText(this._getHandleValue('min')), 307 | maxText: scale.getText(this._getHandleValue('max')) 308 | }); 309 | if (this.layout === 'horizontal') { 310 | rangeElement.translate(this.plotPadding, 0); 311 | } else if (this.layout === 'vertical') { 312 | rangeElement.translate(0, this.plotPadding); 313 | } 314 | this.rangeElement = rangeElement; 315 | } 316 | 317 | _bindEvent() { 318 | const self = this; 319 | const rangeElement = self.rangeElement; 320 | rangeElement.on('sliderchange', function(ev) { 321 | const range = ev.range; 322 | const minRatio = range[0] / 100; 323 | const maxRatio = range[1] / 100; 324 | self._updateElement(minRatio, maxRatio); 325 | }); 326 | } 327 | 328 | _updateElement(minRatio, maxRatio) { 329 | const scale = this.scale; 330 | const rangeElement = this.rangeElement; 331 | const minTextElement = rangeElement.get('minTextElement'); 332 | const maxTextElement = rangeElement.get('maxTextElement'); 333 | const min = scale.invert(minRatio); 334 | const max = scale.invert(maxRatio); 335 | const minText = scale.getText(min); 336 | const maxText = scale.getText(max); 337 | minTextElement.attr('text', minText); 338 | maxTextElement.attr('text', maxText); 339 | 340 | this.start = min; 341 | this.end = max; 342 | 343 | if (this.onChange) { 344 | this.onChange({ 345 | startText: minText, 346 | endText: maxText, 347 | startValue: min, 348 | endValue: max, 349 | startRadio: minRatio, 350 | endRadio: maxRatio 351 | }); 352 | } 353 | } 354 | } 355 | 356 | module.exports = Slider; 357 | -------------------------------------------------------------------------------- /src/component/range.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview The class of slider 3 | * @author sima.zhang 4 | */ 5 | const G2 = window && window.G2; 6 | const { Util, G } = G2; 7 | const { Group } = G; 8 | const { DomUtil } = Util; 9 | const OFFSET = 5; 10 | 11 | 12 | class Range extends Group { 13 | getDefaultCfg() { 14 | return { 15 | /** 16 | * 范围 17 | * @type {Array} 18 | */ 19 | range: null, 20 | /** 21 | * 中滑块属性 22 | * @type {ATTRS} 23 | */ 24 | middleAttr: null, 25 | /** 26 | * 背景 27 | * @type {G-Element} 28 | */ 29 | backgroundElement: null, 30 | /** 31 | * 下滑块 32 | * @type {G-Element} 33 | */ 34 | minHandleElement: null, 35 | /** 36 | * 上滑块 37 | * @type {G-Element} 38 | */ 39 | maxHandleElement: null, 40 | /** 41 | * 中块 42 | * @type {G-Element} 43 | */ 44 | middleHandleElement: null, 45 | /** 46 | * 当前的激活的元素 47 | * @type {G-Element} 48 | */ 49 | currentTarget: null, 50 | /** 51 | * 布局方式: horizontal,vertical 52 | * @type {String} 53 | */ 54 | layout: 'vertical', 55 | /** 56 | * 宽 57 | * @type {Number} 58 | */ 59 | width: null, 60 | /** 61 | * 高 62 | * @type {Number} 63 | */ 64 | height: null, 65 | /** 66 | * 当前的PageX 67 | * @type {Number} 68 | */ 69 | pageX: null, 70 | /** 71 | * 当前的PageY 72 | * @type {Number} 73 | */ 74 | pageY: null 75 | }; 76 | } 77 | 78 | _initHandle(type) { 79 | const handle = this.addGroup(); 80 | const layout = this.get('layout'); 81 | const handleStyle = this.get('handleStyle'); 82 | const img = handleStyle.img; 83 | const iconWidth = handleStyle.width; 84 | const iconHeight = handleStyle.height; 85 | 86 | let text; 87 | let handleIcon; 88 | let triggerCursor; 89 | 90 | 91 | if (layout === 'horizontal') { 92 | const iconWidth = handleStyle.width; 93 | triggerCursor = 'ew-resize'; 94 | handleIcon = handle.addShape('Image', { 95 | attrs: { 96 | x: -iconWidth / 2, 97 | y: 0, 98 | width: iconWidth, 99 | height: iconHeight, 100 | img, 101 | cursor: triggerCursor 102 | } 103 | }); 104 | text = handle.addShape('Text', { 105 | attrs: Util.mix({ 106 | x: (type === 'min') ? -(iconWidth / 2 + OFFSET) : iconWidth / 2 + OFFSET, 107 | y: iconHeight / 2, 108 | textAlign: (type === 'min') ? 'end' : 'start', 109 | textBaseline: 'middle', 110 | text: type === 'min' ? this.get('minText') : this.get('maxText'), 111 | cursor: triggerCursor 112 | }, this.get('textStyle')) 113 | }); 114 | } else { 115 | triggerCursor = 'ns-resize'; 116 | handleIcon = handle.addShape('Image', { 117 | attrs: { 118 | x: 0, 119 | y: -iconHeight / 2, 120 | width: iconWidth, 121 | height: iconHeight, 122 | img, 123 | cursor: triggerCursor 124 | } 125 | }); 126 | text = handle.addShape('Text', { 127 | attrs: Util.mix({ 128 | x: iconWidth / 2, 129 | y: (type === 'min') ? (iconHeight / 2 + OFFSET) : -(iconHeight / 2 + OFFSET), 130 | textAlign: 'center', 131 | textBaseline: 'middle', 132 | text: type === 'min' ? this.get('minText') : this.get('maxText'), 133 | cursor: triggerCursor 134 | }, this.get('textStyle')) 135 | }); 136 | } 137 | 138 | this.set(type + 'TextElement', text); 139 | this.set(type + 'IconElement', handleIcon); 140 | return handle; 141 | } 142 | 143 | _initSliderBackground() { 144 | const backgroundElement = this.addGroup(); 145 | backgroundElement.initTransform(); 146 | backgroundElement.translate(0, 0); 147 | backgroundElement.addShape('Rect', { 148 | attrs: Util.mix({ 149 | x: 0, 150 | y: 0, 151 | width: this.get('width'), 152 | height: this.get('height') 153 | }, this.get('backgroundStyle')) 154 | }); 155 | return backgroundElement; 156 | } 157 | 158 | _beforeRenderUI() { 159 | const backgroundElement = this._initSliderBackground(); 160 | const minHandleElement = this._initHandle('min'); 161 | const maxHandleElement = this._initHandle('max'); 162 | const middleHandleElement = this.addShape('rect', { 163 | attrs: this.get('middleAttr') 164 | }); 165 | 166 | this.set('middleHandleElement', middleHandleElement); 167 | this.set('minHandleElement', minHandleElement); 168 | this.set('maxHandleElement', maxHandleElement); 169 | this.set('backgroundElement', backgroundElement); 170 | backgroundElement.set('zIndex', 0); 171 | middleHandleElement.set('zIndex', 1); 172 | minHandleElement.set('zIndex', 2); 173 | maxHandleElement.set('zIndex', 2); 174 | middleHandleElement.attr('cursor', 'move'); 175 | this.sort(); 176 | } 177 | 178 | _renderUI() { 179 | if (this.get('layout') === 'horizontal') { 180 | this._renderHorizontal(); 181 | } else { 182 | this._renderVertical(); 183 | } 184 | } 185 | 186 | _transform(layout) { 187 | const range = this.get('range'); 188 | const minRatio = range[0] / 100; 189 | const maxRatio = range[1] / 100; 190 | const width = this.get('width'); 191 | const height = this.get('height'); 192 | const minHandleElement = this.get('minHandleElement'); 193 | const maxHandleElement = this.get('maxHandleElement'); 194 | const middleHandleElement = this.get('middleHandleElement'); 195 | if (minHandleElement.resetMatrix) { 196 | minHandleElement.resetMatrix(); 197 | maxHandleElement.resetMatrix(); 198 | } else { 199 | minHandleElement.initTransform(); 200 | maxHandleElement.initTransform(); 201 | } 202 | if (layout === 'horizontal') { 203 | middleHandleElement.attr({ 204 | x: width * minRatio, 205 | y: 0, 206 | width: (maxRatio - minRatio) * width, 207 | height 208 | }); 209 | 210 | minHandleElement.translate(minRatio * width, 0); 211 | maxHandleElement.translate(maxRatio * width, 0); 212 | } else { 213 | middleHandleElement.attr({ 214 | x: 0, 215 | y: height * (1 - maxRatio), 216 | width, 217 | height: (maxRatio - minRatio) * height 218 | }); 219 | minHandleElement.translate(0, (1 - minRatio) * height); 220 | maxHandleElement.translate(0, (1 - maxRatio) * height); 221 | } 222 | } 223 | 224 | _renderHorizontal() { 225 | this._transform('horizontal'); 226 | } 227 | 228 | _renderVertical() { 229 | this._transform('vertical'); 230 | } 231 | 232 | _bindUI() { 233 | this.on('mousedown', Util.wrapBehavior(this, '_onMouseDown')); 234 | } 235 | 236 | _isElement(target, name) { // 判断是否是该元素 237 | const element = this.get(name); 238 | if (target === element) { 239 | return true; 240 | } 241 | if (element.isGroup) { 242 | const elementChildren = element.get('children'); 243 | return elementChildren.indexOf(target) > -1; 244 | } 245 | return false; 246 | } 247 | 248 | _getRange(diff, range) { 249 | let rst = diff + range; 250 | rst = rst > 100 ? 100 : rst; 251 | rst = rst < 0 ? 0 : rst; 252 | return rst; 253 | } 254 | 255 | _limitRange(diff, limit, range) { 256 | range[0] = this._getRange(diff, range[0]); 257 | range[1] = range[0] + limit; 258 | if (range[1] > 100) { 259 | range[1] = 100; 260 | range[0] = range[1] - limit; 261 | } 262 | } 263 | 264 | _updateStatus(dim, ev) { 265 | const totalLength = dim === 'x' ? this.get('width') : this.get('height'); 266 | dim = Util.upperFirst(dim); 267 | const range = this.get('range'); 268 | const page = this.get('page' + dim); 269 | const currentTarget = this.get('currentTarget'); 270 | const rangeStash = this.get('rangeStash'); 271 | const layout = this.get('layout'); 272 | const sign = layout === 'vertical' ? -1 : 1; 273 | const currentPage = ev[ 'page' + dim ]; 274 | const diffPage = currentPage - page; 275 | const diffRange = (diffPage / totalLength) * 100 * sign; 276 | let diffStashRange; 277 | 278 | const minRange = this.get('minRange'); 279 | const maxRange = this.get('maxRange'); 280 | 281 | if (range[1] <= range[0]) { 282 | if (this._isElement(currentTarget, 'minHandleElement') || this._isElement(currentTarget, 'maxHandleElement')) { 283 | range[0] = this._getRange(diffRange, range[0]); 284 | range[1] = this._getRange(diffRange, range[0]); 285 | } 286 | } else { 287 | if (this._isElement(currentTarget, 'minHandleElement')) { 288 | range[0] = this._getRange(diffRange, range[0]); 289 | if (minRange) { // 设置了最小范围 290 | if ((range[1] - range[0]) <= minRange) { 291 | this._limitRange(diffRange, minRange, range); 292 | } 293 | } 294 | 295 | if (maxRange) { // 设置了最大范围 296 | if (range[1] - range[0] >= maxRange) { 297 | this._limitRange(diffRange, maxRange, range); 298 | } 299 | } 300 | } 301 | if (this._isElement(currentTarget, 'maxHandleElement')) { 302 | range[1] = this._getRange(diffRange, range[1]); 303 | 304 | if (minRange) { // 设置了最小范围 305 | if ((range[1] - range[0]) <= minRange) { 306 | this._limitRange(diffRange, minRange, range); 307 | } 308 | } 309 | 310 | if (maxRange) { // 设置了最大范围 311 | if (range[1] - range[0] >= maxRange) { 312 | this._limitRange(diffRange, maxRange, range); 313 | } 314 | } 315 | } 316 | } 317 | 318 | if (this._isElement(currentTarget, 'middleHandleElement')) { 319 | diffStashRange = (rangeStash[1] - rangeStash[0]); 320 | this._limitRange(diffRange, diffStashRange, range); 321 | } 322 | 323 | this.emit('sliderchange', { 324 | range 325 | }); 326 | 327 | this.set('page' + dim, currentPage); 328 | this._renderUI(); 329 | this.get('canvas').draw(); // need delete 330 | return; 331 | } 332 | 333 | _onMouseDown(ev) { 334 | const currentTarget = ev.currentTarget; 335 | const originEvent = ev.event; 336 | const range = this.get('range'); 337 | originEvent.stopPropagation(); 338 | originEvent.preventDefault(); 339 | this.set('pageX', originEvent.pageX); 340 | this.set('pageY', originEvent.pageY); 341 | this.set('currentTarget', currentTarget); 342 | this.set('rangeStash', [ range[0], range[1] ]); 343 | this._bindCanvasEvents(); 344 | } 345 | 346 | _bindCanvasEvents() { 347 | const containerDOM = this.get('canvas').get('containerDOM'); 348 | this.onMouseMoveListener = DomUtil.addEventListener(containerDOM, 'mousemove', Util.wrapBehavior(this, '_onCanvasMouseMove')); 349 | this.onMouseUpListener = DomUtil.addEventListener(containerDOM, 'mouseup', Util.wrapBehavior(this, '_onCanvasMouseUp')); 350 | // @2018-06-06 by blue.lb 添加mouseleave事件监听,让用户在操作出滑块区域后有一个“正常”的效果,可以正常重新触发滑块的操作流程 351 | this.onMouseLeaveListener = DomUtil.addEventListener(containerDOM, 'mouseleave', Util.wrapBehavior(this, '_onCanvasMouseUp')); 352 | } 353 | 354 | _onCanvasMouseMove(ev) { 355 | const layout = this.get('layout'); 356 | if (layout === 'horizontal') { 357 | this._updateStatus('x', ev); 358 | } else { 359 | this._updateStatus('y', ev); 360 | } 361 | } 362 | 363 | _onCanvasMouseUp() { 364 | this._removeDocumentEvents(); 365 | } 366 | 367 | _removeDocumentEvents() { 368 | this.onMouseMoveListener.remove(); 369 | this.onMouseUpListener.remove(); 370 | this.onMouseLeaveListener.remove(); 371 | } 372 | } 373 | 374 | module.exports = Range; 375 | -------------------------------------------------------------------------------- /test/fixtures/candleSticks.json: -------------------------------------------------------------------------------- 1 | [{"time":"2015-11-19","start":8.18,"max":8.33,"min":7.98,"end":8.32,"volumn":1810,"money":14723.56},{"time":"2015-11-18","start":8.37,"max":8.6,"min":8.03,"end":8.09,"volumn":2790.37,"money":23309.19},{"time":"2015-11-17","start":8.7,"max":8.78,"min":8.32,"end":8.37,"volumn":3729.04,"money":31709.71},{"time":"2015-11-16","start":8.18,"max":8.69,"min":8.05,"end":8.62,"volumn":3095.44,"money":26100.69},{"time":"2015-11-13","start":8.01,"max":8.75,"min":7.97,"end":8.41,"volumn":5815.58,"money":48562.37},{"time":"2015-11-12","start":7.76,"max":8.18,"min":7.61,"end":8.15,"volumn":4742.6,"money":37565.36},{"time":"2015-11-11","start":7.55,"max":7.81,"min":7.49,"end":7.8,"volumn":3133.82,"money":24065.42},{"time":"2015-11-10","start":7.5,"max":7.68,"min":7.44,"end":7.57,"volumn":2670.35,"money":20210.58},{"time":"2015-11-09","start":7.65,"max":7.66,"min":7.3,"end":7.58,"volumn":2841.79,"money":21344.36},{"time":"2015-11-06","start":7.52,"max":7.71,"min":7.48,"end":7.64,"volumn":2725.44,"money":20721.51},{"time":"2015-11-05","start":7.48,"max":7.57,"min":7.29,"end":7.48,"volumn":3520.85,"money":26140.83},{"time":"2015-11-04","start":7.01,"max":7.5,"min":7.01,"end":7.46,"volumn":3591.47,"money":26285.52},{"time":"2015-11-03","start":7.1,"max":7.17,"min":6.82,"end":7,"volumn":2029.21,"money":14202.33},{"time":"2015-11-02","start":7.09,"max":7.44,"min":6.93,"end":7.17,"volumn":3191.31,"money":23205.11},{"time":"2015-10-30","start":6.98,"max":7.27,"min":6.84,"end":7.18,"volumn":3522.61,"money":25083.44},{"time":"2015-10-29","start":6.94,"max":7.2,"min":6.8,"end":7.05,"volumn":2752.27,"money":19328.44},{"time":"2015-10-28","start":7.01,"max":7.14,"min":6.8,"end":6.85,"volumn":2311.11,"money":16137.32},{"time":"2015-10-27","start":6.91,"max":7.31,"min":6.48,"end":7.18,"volumn":3172.9,"money":21827.3},{"time":"2015-10-26","start":6.9,"max":7.08,"min":6.87,"end":6.95,"volumn":2769.31,"money":19337.44},{"time":"2015-10-23","start":6.71,"max":6.85,"min":6.58,"end":6.79,"volumn":2483.18,"money":16714.31},{"time":"2015-10-22","start":6.38,"max":6.67,"min":6.34,"end":6.65,"volumn":2225.88,"money":14465.56},{"time":"2015-10-21","start":7.08,"max":7.1,"min":6.41,"end":6.41,"volumn":2891.47,"money":19585.98},{"time":"2015-10-20","start":6.88,"max":7.19,"min":6.85,"end":7.12,"volumn":2389.62,"money":16813.58},{"time":"2015-10-19","start":7.1,"max":7.14,"min":6.8,"end":6.94,"volumn":2786.61,"money":19474.72},{"time":"2015-10-16","start":6.92,"max":7.38,"min":6.73,"end":7.15,"volumn":3289.27,"money":22963.97},{"time":"2015-10-15","start":6.63,"max":6.9,"min":6.6,"end":6.89,"volumn":2440.37,"money":16575.84},{"time":"2015-10-14","start":6.7,"max":6.99,"min":6.61,"end":6.66,"volumn":2496.38,"money":16858.28},{"time":"2015-10-13","start":6.55,"max":6.81,"min":6.55,"end":6.75,"volumn":2299.83,"money":15338.24},{"time":"2015-10-12","start":6.29,"max":6.89,"min":6.29,"end":6.69,"volumn":3147.58,"money":20738.35},{"time":"2015-10-09","start":6.1,"max":6.44,"min":6.08,"end":6.34,"volumn":2664.04,"money":16811.14},{"time":"2015-10-08","start":6.11,"max":6.28,"min":6,"end":6.12,"volumn":2197.23,"money":13440.92},{"time":"2015-09-30","start":5.93,"max":6.12,"min":5.81,"end":5.83,"volumn":1459.64,"money":8659.52},{"time":"2015-09-29","start":5.96,"max":5.98,"min":5.73,"end":5.83,"volumn":1047.42,"money":6132.72},{"time":"2015-09-28","start":5.86,"max":6.13,"min":5.85,"end":6.07,"volumn":952.45,"money":5717.33},{"time":"2015-09-25","start":6.23,"max":6.28,"min":5.85,"end":5.96,"volumn":1395.27,"money":8465.95},{"time":"2015-09-24","start":6.16,"max":6.32,"min":6.1,"end":6.27,"volumn":1434.38,"money":8920.88},{"time":"2015-09-23","start":6.18,"max":6.32,"min":6.02,"end":6.12,"volumn":1558.54,"money":9631.38},{"time":"2015-09-22","start":6.35,"max":6.4,"min":6.15,"end":6.25,"volumn":1893.83,"money":11901.51},{"time":"2015-09-21","start":5.83,"max":6.32,"min":5.83,"end":6.31,"volumn":1748.35,"money":10807.66},{"time":"2015-09-18","start":6,"max":6.1,"min":5.81,"end":6.02,"volumn":1507.09,"money":8999.44},{"time":"2015-09-17","start":6.1,"max":6.34,"min":5.8,"end":5.83,"volumn":2135.64,"money":13039.56},{"time":"2015-09-16","start":5.58,"max":6.07,"min":5.4,"end":6.07,"volumn":1758.57,"money":10132.25},{"time":"2015-09-15","start":5.81,"max":6.09,"min":5.52,"end":5.52,"volumn":1617.12,"money":9248.69},{"time":"2015-09-14","start":6.98,"max":7.06,"min":6.13,"end":6.13,"volumn":1982.9,"money":12989.01},{"time":"2015-09-11","start":6.87,"max":7.01,"min":6.68,"end":6.81,"volumn":1371.77,"money":9370.49},{"time":"2015-09-10","start":6.7,"max":7.17,"min":6.65,"end":6.86,"volumn":2181.33,"money":15169.4},{"time":"2015-09-09","start":6.76,"max":7.03,"min":6.65,"end":6.93,"volumn":2105.28,"money":14426.76},{"time":"2015-09-08","start":6.26,"max":6.7,"min":6.01,"end":6.64,"volumn":1506.53,"money":9556.54},{"time":"2015-09-07","start":6.19,"max":6.45,"min":6.09,"end":6.2,"volumn":1605.85,"money":10091.26},{"time":"2015-09-02","start":6.2,"max":6.84,"min":5.98,"end":5.99,"volumn":1934.95,"money":12225.68},{"time":"2015-09-01","start":7.22,"max":7.28,"min":6.64,"end":6.64,"volumn":2645.87,"money":18017.91},{"time":"2015-08-31","start":7.83,"max":8,"min":7.35,"end":7.38,"volumn":2984.03,"money":23031.24},{"time":"2015-08-28","start":7.3,"max":7.77,"min":7.1,"end":7.77,"volumn":3092.6,"money":23121.49},{"time":"2015-08-27","start":6.75,"max":7.1,"min":6.43,"end":7.06,"volumn":2903.76,"money":19848.87},{"time":"2015-08-26","start":7.13,"max":7.43,"min":6.42,"end":6.58,"volumn":4212.04,"money":29069.25},{"time":"2015-08-25","start":7.13,"max":7.29,"min":7.13,"end":7.13,"volumn":1838.59,"money":13140.8},{"time":"2015-08-24","start":8.4,"max":8.4,"min":7.92,"end":7.92,"volumn":1766.39,"money":14168.86},{"time":"2015-08-21","start":9,"max":9.61,"min":8.53,"end":8.8,"volumn":3388.27,"money":30952.11},{"time":"2015-08-20","start":10.02,"max":10.24,"min":9.32,"end":9.33,"volumn":3902.17,"money":38391.68},{"time":"2015-08-19","start":9.3,"max":10.59,"min":9.01,"end":10.35,"volumn":5568.96,"money":53928.79},{"time":"2015-08-18","start":11.18,"max":11.5,"min":10,"end":10,"volumn":7332.7,"money":78439.61},{"time":"2015-08-17","start":10.2,"max":11.11,"min":9.9,"end":11.11,"volumn":8121.86,"money":86298.92},{"time":"2015-08-14","start":9.58,"max":10.37,"min":9.37,"end":10.1,"volumn":6001.61,"money":58425.11},{"time":"2015-08-13","start":9.14,"max":9.5,"min":8.91,"end":9.49,"volumn":3854.19,"money":35696.27},{"time":"2015-08-12","start":9.09,"max":9.68,"min":8.98,"end":9.29,"volumn":4238.57,"money":39909.3},{"time":"2015-08-11","start":9.23,"max":9.47,"min":9,"end":9.15,"volumn":4294.85,"money":39674.71},{"time":"2015-08-10","start":8.9,"max":9.38,"min":8.76,"end":9.2,"volumn":4013.11,"money":36287.89},{"time":"2015-08-07","start":8.36,"max":8.8,"min":8.31,"end":8.7,"volumn":3092.66,"money":26336.76},{"time":"2015-08-06","start":8.03,"max":8.42,"min":7.95,"end":8.25,"volumn":2072.21,"money":17060.11},{"time":"2015-08-05","start":8.5,"max":8.69,"min":8.08,"end":8.28,"volumn":3533.94,"money":29796.96},{"time":"2015-08-04","start":7.65,"max":8.39,"min":7.65,"end":8.39,"volumn":3067.22,"money":24773.88},{"time":"2015-08-03","start":8.15,"max":8.4,"min":7.6,"end":7.63,"volumn":3098.33,"money":24382.99},{"time":"2015-07-31","start":8.7,"max":9.01,"min":8.25,"end":8.44,"volumn":3500.61,"money":30289.83},{"time":"2015-07-30","start":8.92,"max":9.65,"min":8.7,"end":8.97,"volumn":6022.8,"money":55624.85},{"time":"2015-07-29","start":8.35,"max":8.91,"min":7.78,"end":8.88,"volumn":4177.18,"money":34893.2},{"time":"2015-07-28","start":8,"max":9,"min":7.92,"end":8.1,"volumn":5114.55,"money":42095.99},{"time":"2015-07-27","start":9.4,"max":9.99,"min":8.8,"end":8.8,"volumn":6001.51,"money":56962.25},{"time":"2015-07-24","start":9.27,"max":10.28,"min":9.11,"end":9.78,"volumn":8446.76,"money":81991.19},{"time":"2015-07-23","start":9,"max":9.78,"min":8.74,"end":9.46,"volumn":8496.1,"money":77551.01},{"time":"2015-07-22","start":8.08,"max":8.97,"min":8.05,"end":8.97,"volumn":8676.75,"money":75751.1},{"time":"2015-07-21","start":7.8,"max":8.33,"min":7.65,"end":8.15,"volumn":4631.15,"money":37450.78},{"time":"2015-07-20","start":7.72,"max":8.27,"min":7.63,"end":8.05,"volumn":6428.2,"money":51127.98},{"time":"2015-07-17","start":6.94,"max":7.73,"min":6.94,"end":7.73,"volumn":4835.44,"money":36666.58},{"time":"2015-07-16","start":6.53,"max":7.48,"min":6.42,"end":7.03,"volumn":3605.77,"money":25031.15},{"time":"2015-07-15","start":7.57,"max":7.83,"min":7.13,"end":7.13,"volumn":2729.59,"money":20041.75},{"time":"2015-07-14","start":8.2,"max":8.7,"min":7.66,"end":7.92,"volumn":7073.81,"money":58131.16},{"time":"2015-07-13","start":7.5,"max":8.1,"min":7.5,"end":8.1,"volumn":4573.92,"money":36083.69},{"time":"2015-07-10","start":6.9,"max":7.36,"min":6.88,"end":7.36,"volumn":4183.37,"money":30411.19},{"time":"2015-07-09","start":5.47,"max":6.69,"min":5.47,"end":6.69,"volumn":6661.78,"money":40398.96},{"time":"2015-07-08","start":6.08,"max":6.08,"min":6.08,"end":6.08,"volumn":158.16,"money":961.61},{"time":"2015-07-07","start":6.77,"max":6.88,"min":6.75,"end":6.75,"volumn":383.45,"money":2590.85},{"time":"2015-07-06","start":9.1,"max":9.1,"min":7.5,"end":7.5,"volumn":2968.98,"money":24002.6},{"time":"2015-07-03","start":8.38,"max":8.87,"min":8.33,"end":8.33,"volumn":2641.73,"money":22223.44},{"time":"2015-07-02","start":10.38,"max":10.38,"min":9.26,"end":9.26,"volumn":2611.06,"money":24620.81},{"time":"2015-07-01","start":11.31,"max":11.61,"min":10.29,"end":10.29,"volumn":3401.45,"money":37212.87},{"time":"2015-06-30","start":10.08,"max":11.52,"min":10.01,"end":11.43,"volumn":4205.99,"money":45381.06},{"time":"2015-06-29","start":12.96,"max":12.96,"min":11.12,"end":11.12,"volumn":3745.68,"money":44252.47},{"time":"2015-06-26","start":13.15,"max":13.41,"min":12.36,"end":12.36,"volumn":3675.91,"money":46759.29},{"time":"2015-06-25","start":13.71,"max":14.46,"min":13.3,"end":13.73,"volumn":4907.6,"money":68290.5},{"time":"2015-06-24","start":13.35,"max":13.85,"min":12.9,"end":13.71,"volumn":3656.8,"money":49219.92},{"time":"2015-06-23","start":13.26,"max":13.64,"min":12.26,"end":13.2,"volumn":3566.35,"money":45904.78},{"time":"2015-06-19","start":14.45,"max":14.97,"min":13.62,"end":13.62,"volumn":3621.43,"money":51108.31},{"time":"2015-06-18","start":14.5,"max":16,"min":14.3,"end":15.13,"volumn":5046.59,"money":77208.53},{"time":"2015-06-17","start":14.46,"max":15,"min":14,"end":14.6,"volumn":3483.7,"money":50495.84},{"time":"2015-06-16","start":14,"max":15.1,"min":13.42,"end":14.8,"volumn":4844.74,"money":68953.77},{"time":"2015-06-15","start":14.5,"max":15.09,"min":14.1,"end":14.39,"volumn":4008.2,"money":58703.24},{"time":"2015-06-12","start":14.07,"max":14.97,"min":14,"end":14.37,"volumn":5399.47,"money":78592.45},{"time":"2015-06-11","start":13.4,"max":14.5,"min":13.19,"end":14.13,"volumn":5472.93,"money":76037.31},{"time":"2015-06-10","start":12.95,"max":13.47,"min":12.71,"end":13.37,"volumn":4087.74,"money":53951.64},{"time":"2015-06-09","start":13.46,"max":13.46,"min":12.85,"end":13.12,"volumn":4371.67,"money":57352.21},{"time":"2015-06-08","start":12.88,"max":13.69,"min":12.59,"end":13.61,"volumn":7406.58,"money":98236.3},{"time":"2015-06-05","start":12.38,"max":12.94,"min":12.24,"end":12.77,"volumn":5386.66,"money":68208.51},{"time":"2015-06-04","start":12.55,"max":12.81,"min":11.29,"end":12.31,"volumn":3905.22,"money":47415.64},{"time":"2015-06-03","start":13,"max":13.15,"min":12.2,"end":12.54,"volumn":4559.61,"money":57928.58},{"time":"2015-06-02","start":11.84,"max":12.77,"min":11.48,"end":12.73,"volumn":4405.17,"money":52747.92},{"time":"2015-06-01","start":11.29,"max":11.8,"min":11,"end":11.74,"volumn":3308.94,"money":38060.2},{"time":"2015-05-29","start":11.3,"max":11.65,"min":10.31,"end":11.11,"volumn":3434.12,"money":38143.88},{"time":"2015-05-28","start":12.79,"max":12.99,"min":11.39,"end":11.4,"volumn":4979.63,"money":61398.36},{"time":"2015-05-27","start":12.89,"max":13.18,"min":12.5,"end":12.66,"volumn":4886.86,"money":62349.63},{"time":"2015-05-26","start":12.4,"max":13.08,"min":11.96,"end":12.92,"volumn":5838.51,"money":73409.96},{"time":"2015-05-25","start":11.7,"max":12.87,"min":11.61,"end":12.3,"volumn":6405.2,"money":78937.32},{"time":"2015-05-22","start":11.39,"max":11.89,"min":11.18,"end":11.71,"volumn":5519.87,"money":63515.93},{"time":"2015-05-21","start":11.27,"max":11.35,"min":11.05,"end":11.33,"volumn":4132.8,"money":46318.65},{"time":"2015-05-20","start":11.23,"max":11.48,"min":10.81,"end":11.32,"volumn":6859.76,"money":76273.65},{"time":"2015-05-19","start":11.5,"max":11.78,"min":11,"end":11.33,"volumn":6864.05,"money":77731.34},{"time":"2015-05-18","start":11.68,"max":12.25,"min":11.45,"end":12.15,"volumn":4236.5,"money":50728.6},{"time":"2015-05-15","start":11.19,"max":12.28,"min":10.8,"end":11.69,"volumn":5515.66,"money":64496.32},{"time":"2015-05-14","start":10.18,"max":11.19,"min":10.11,"end":11.19,"volumn":4181.77,"money":45399.19},{"time":"2015-05-13","start":10.2,"max":10.32,"min":10,"end":10.17,"volumn":2247.39,"money":22781.23},{"time":"2015-05-12","start":10.3,"max":10.36,"min":10.01,"end":10.28,"volumn":2010.65,"money":20480.63},{"time":"2015-05-11","start":9.98,"max":10.36,"min":9.89,"end":10.3,"volumn":2101.26,"money":21367.53},{"time":"2015-05-08","start":9.82,"max":10.08,"min":9.65,"end":9.94,"volumn":1609.43,"money":15845.56},{"time":"2015-05-07","start":9.62,"max":9.84,"min":9.45,"end":9.6,"volumn":1270.86,"money":12241.17},{"time":"2015-05-06","start":10.18,"max":10.25,"min":9.6,"end":9.66,"volumn":1754.7,"money":17347.05},{"time":"2015-05-05","start":10.68,"max":10.68,"min":10,"end":10.02,"volumn":1903.5,"money":19598.64},{"time":"2015-05-04","start":10.61,"max":10.84,"min":10.55,"end":10.72,"volumn":1554.93,"money":16624.43},{"time":"2015-04-30","start":10.4,"max":11.05,"min":10.4,"end":10.63,"volumn":2169.06,"money":23333.06},{"time":"2015-04-29","start":10.31,"max":10.64,"min":10.25,"end":10.4,"volumn":1614.77,"money":16910.96},{"time":"2015-04-28","start":11.07,"max":11.25,"min":10.46,"end":10.49,"volumn":2552.21,"money":27515.88},{"time":"2015-04-27","start":10.6,"max":11.67,"min":10.6,"end":11.06,"volumn":4216.46,"money":47534.53},{"time":"2015-04-24","start":10.5,"max":10.85,"min":10.25,"end":10.61,"volumn":2326.42,"money":24599.63},{"time":"2015-04-23","start":10.26,"max":10.93,"min":10.11,"end":10.7,"volumn":3767.77,"money":39643.72},{"time":"2015-04-22","start":10.22,"max":10.42,"min":10.08,"end":10.23,"volumn":2868.77,"money":29316.49},{"time":"2015-04-21","start":9.56,"max":10.2,"min":9.4,"end":10.19,"volumn":3493.61,"money":34865.01},{"time":"2015-04-20","start":9.71,"max":9.99,"min":9.42,"end":9.6,"volumn":2462.09,"money":23769.5},{"time":"2015-04-17","start":9.79,"max":10.09,"min":9.16,"end":9.82,"volumn":4473.33,"money":43367.29},{"time":"2015-04-16","start":9.36,"max":10.04,"min":8.9,"end":9.66,"volumn":2851.79,"money":27210.03},{"time":"2015-04-15","start":10.03,"max":10.28,"min":9.37,"end":9.43,"volumn":3138.11,"money":30713.13},{"time":"2015-04-14","start":10.33,"max":10.33,"min":9.98,"end":10.03,"volumn":2951.59,"money":29803.4},{"time":"2015-04-13","start":10.3,"max":10.63,"min":10.2,"end":10.33,"volumn":3196.99,"money":33351.76},{"time":"2015-04-10","start":10.25,"max":10.5,"min":10,"end":10.28,"volumn":2565.64,"money":26337.81},{"time":"2015-04-09","start":9.78,"max":10.48,"min":9.58,"end":10.22,"volumn":4316.86,"money":43647.33},{"time":"2015-04-08","start":9.46,"max":9.86,"min":9.02,"end":9.78,"volumn":3683.43,"money":34664.66},{"time":"2015-04-07","start":9.53,"max":9.87,"min":9.38,"end":9.44,"volumn":3874.06,"money":37076.79},{"time":"2015-04-03","start":8.6,"max":9.48,"min":8.4,"end":9.48,"volumn":3760.78,"money":34361.28},{"time":"2015-04-02","start":8.45,"max":8.74,"min":8.18,"end":8.62,"volumn":3076.83,"money":26112.98},{"time":"2015-04-01","start":8.16,"max":8.61,"min":8.06,"end":8.45,"volumn":2396.89,"money":20000.88},{"time":"2015-03-31","start":8.18,"max":8.5,"min":8.13,"end":8.16,"volumn":1938,"money":15989.33},{"time":"2015-03-30","start":8.2,"max":8.53,"min":8.11,"end":8.26,"volumn":2820.79,"money":23532.99},{"time":"2015-03-27","start":8.4,"max":8.4,"min":8.01,"end":8.28,"volumn":4634.57,"money":38032.68},{"time":"2015-03-26","start":7.39,"max":8.12,"min":7.32,"end":8.12,"volumn":4209.29,"money":33643.03},{"time":"2015-03-25","start":7.36,"max":7.6,"min":7.2,"end":7.38,"volumn":1845.49,"money":13550.21},{"time":"2015-03-24","start":7.62,"max":7.62,"min":7.2,"end":7.35,"volumn":2264.5,"money":16699.5},{"time":"2015-03-23","start":7.54,"max":7.68,"min":7.46,"end":7.59,"volumn":1834.28,"money":13855.41},{"time":"2015-03-20","start":7.33,"max":7.65,"min":7.25,"end":7.55,"volumn":2470.71,"money":18588.13},{"time":"2015-03-19","start":7.38,"max":7.66,"min":7.26,"end":7.37,"volumn":2450.54,"money":18247.82},{"time":"2015-03-18","start":7.12,"max":7.46,"min":7.1,"end":7.37,"volumn":2854.4,"money":20828.88},{"time":"2015-03-17","start":6.95,"max":7.13,"min":6.87,"end":7.09,"volumn":2457.13,"money":17162.55},{"time":"2015-03-16","start":6.8,"max":7.06,"min":6.79,"end":6.95,"volumn":1858.78,"money":12924.21},{"time":"2015-03-13","start":6.85,"max":6.93,"min":6.69,"end":6.79,"volumn":1167.06,"money":7909.64},{"time":"2015-03-12","start":6.84,"max":7.06,"min":6.71,"end":6.85,"volumn":2152.85,"money":14835.41},{"time":"2015-03-11","start":6.98,"max":7.04,"min":6.77,"end":6.84,"volumn":1445.77,"money":9886.53},{"time":"2015-03-10","start":6.73,"max":6.99,"min":6.7,"end":6.97,"volumn":1999.93,"money":13770.37},{"time":"2015-03-09","start":6.59,"max":6.88,"min":6.4,"end":6.72,"volumn":2243.1,"money":14951.1},{"time":"2015-03-06","start":6.47,"max":6.6,"min":6.35,"end":6.5,"volumn":1270.49,"money":8229.96},{"time":"2015-03-05","start":6.43,"max":6.54,"min":6.34,"end":6.47,"volumn":1363.09,"money":8789.45},{"time":"2015-03-04","start":6.35,"max":6.45,"min":6.32,"end":6.41,"volumn":1295.42,"money":8265.63},{"time":"2015-03-03","start":6.16,"max":6.47,"min":6.07,"end":6.42,"volumn":2266.82,"money":14214.79},{"time":"2015-03-02","start":6.22,"max":6.25,"min":6.07,"end":6.17,"volumn":1277.88,"money":7850.34},{"time":"2015-02-27","start":6.16,"max":6.33,"min":6.15,"end":6.19,"volumn":908.98,"money":5663.74},{"time":"2015-02-26","start":6.12,"max":6.18,"min":6.1,"end":6.16,"volumn":703.72,"money":4328.56},{"time":"2015-02-25","start":6.09,"max":6.18,"min":6.03,"end":6.12,"volumn":766.56,"money":4678.73},{"time":"2015-02-17","start":6.11,"max":6.15,"min":6.06,"end":6.08,"volumn":766.73,"money":4677.31},{"time":"2015-02-16","start":6.03,"max":6.14,"min":6.01,"end":6.11,"volumn":814.71,"money":4948.33},{"time":"2015-02-13","start":5.98,"max":6.34,"min":5.93,"end":6.08,"volumn":1992.56,"money":12135.01},{"time":"2015-02-12","start":5.72,"max":6.1,"min":5.66,"end":6.01,"volumn":2572.38,"money":15312.73},{"time":"2015-02-11","start":5.69,"max":5.77,"min":5.67,"end":5.72,"volumn":602.66,"money":3443.99},{"time":"2015-02-10","start":5.46,"max":5.75,"min":5.43,"end":5.73,"volumn":1298.63,"money":7307.42},{"time":"2015-02-09","start":5.59,"max":5.59,"min":5.47,"end":5.48,"volumn":435.98,"money":2410.09},{"time":"2015-02-06","start":5.5,"max":5.62,"min":5.48,"end":5.61,"volumn":630.6,"money":3490.13},{"time":"2015-02-05","start":5.58,"max":5.59,"min":5.47,"end":5.48,"volumn":636.7,"money":3521.89},{"time":"2015-02-04","start":5.63,"max":5.67,"min":5.52,"end":5.52,"volumn":635.38,"money":3548.96},{"time":"2015-02-03","start":5.63,"max":5.67,"min":5.56,"end":5.65,"volumn":434.34,"money":2439.08},{"time":"2015-02-02","start":5.55,"max":5.65,"min":5.51,"end":5.61,"volumn":338.71,"money":1896.01},{"time":"2015-01-30","start":5.78,"max":5.85,"min":5.6,"end":5.65,"volumn":574.74,"money":3270.25},{"time":"2015-01-29","start":5.8,"max":5.87,"min":5.74,"end":5.78,"volumn":605.55,"money":3516.14},{"time":"2015-01-28","start":5.89,"max":5.95,"min":5.82,"end":5.85,"volumn":653.47,"money":3846.52},{"time":"2015-01-27","start":5.72,"max":5.94,"min":5.7,"end":5.89,"volumn":1398.84,"money":8194.18},{"time":"2015-01-26","start":5.65,"max":5.73,"min":5.58,"end":5.72,"volumn":930.19,"money":5247.01},{"time":"2015-01-23","start":5.68,"max":5.72,"min":5.6,"end":5.62,"volumn":758.13,"money":4284.8},{"time":"2015-01-22","start":5.49,"max":5.78,"min":5.41,"end":5.71,"volumn":1139.94,"money":6386.11},{"time":"2015-01-21","start":5.36,"max":5.58,"min":5.33,"end":5.55,"volumn":701.11,"money":3840.84},{"time":"2015-01-20","start":5.23,"max":5.35,"min":5.22,"end":5.33,"volumn":817.97,"money":4326.47},{"time":"2015-01-19","start":5.6,"max":5.67,"min":5.12,"end":5.16,"volumn":1248.82,"money":6669.96},{"time":"2015-01-16","start":5.67,"max":5.73,"min":5.66,"end":5.69,"volumn":399.54,"money":2274.94},{"time":"2015-01-15","start":5.6,"max":5.67,"min":5.57,"end":5.67,"volumn":361.28,"money":2031.66},{"time":"2015-01-14","start":5.62,"max":5.69,"min":5.61,"end":5.62,"volumn":321.27,"money":1812.93},{"time":"2015-01-13","start":5.64,"max":5.71,"min":5.58,"end":5.65,"volumn":375.35,"money":2120.87},{"time":"2015-01-12","start":5.79,"max":5.79,"min":5.58,"end":5.6,"volumn":516.19,"money":2921.05},{"time":"2015-01-09","start":5.95,"max":5.97,"min":5.8,"end":5.82,"volumn":701.39,"money":4123.5},{"time":"2015-01-08","start":5.95,"max":6.06,"min":5.91,"end":5.97,"volumn":676.75,"money":4056.12},{"time":"2015-01-07","start":6,"max":6.04,"min":5.92,"end":5.96,"volumn":546.93,"money":3267.16},{"time":"2015-01-06","start":5.89,"max":6.09,"min":5.84,"end":6.07,"volumn":1169.3,"money":6980.48},{"time":"2015-01-05","start":5.89,"max":6,"min":5.75,"end":5.94,"volumn":806.1,"money":4751.15}] 2 | --------------------------------------------------------------------------------