From 3e757c1688c757fff7d7c7df8cf20e93a255d11e Mon Sep 17 00:00:00 2001 From: royye62 Date: Sun, 12 Apr 2020 03:24:18 +0800 Subject: [PATCH] mini program: add qcloud_device_linkage_demo --- .../qcloud_device_linkage_demo/README.md | 24 + .../cloudfunctions/iothub-publish/index.js | 38 + .../iothub-publish/package.json | 14 + .../iothub-shadow-query/index.js | 34 + .../iothub-shadow-query/package.json | 15 + .../cloudfunctions/package-lock.json | 549 +++++ .../cloudfunctions/query/index.js | 37 + .../cloudfunctions/query/package.json | 15 + .../miniprogram/app.js | 22 + .../miniprogram/app.json | 13 + .../miniprogram/app.wxss | 156 ++ .../miniprogram/config.js | 24 + .../miniprogram/images/TencentOS_tiny_log.png | Bin 0 -> 48034 bytes .../miniprogram/pages/index/index.js | 266 +++ .../miniprogram/pages/index/index.json | 1 + .../miniprogram/pages/index/index.wxml | 66 + .../miniprogram/pages/index/index.wxss | 32 + .../miniprogram/sitemap.json | 7 + .../miniprogram/utils/timeutil.wxs | 48 + .../miniprogram/utils/util.js | 33 + .../miniprogram/utils/wxcharts.js | 2048 +++++++++++++++++ .../project.config.json | 52 + 22 files changed, 3494 insertions(+) create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/README.md create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/index.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/package.json create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/index.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/package.json create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/package-lock.json create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/index.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/package.json create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.json create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.wxss create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/config.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/images/TencentOS_tiny_log.png create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.json create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxml create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxss create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/sitemap.json create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/timeutil.wxs create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/util.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/wxcharts.js create mode 100644 tools/Mini_Program/qcloud_device_linkage_demo/project.config.json diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/README.md b/tools/Mini_Program/qcloud_device_linkage_demo/README.md new file mode 100644 index 00000000..6814ac45 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/README.md @@ -0,0 +1,24 @@ +# 设备联动Demo + +## 配置文件 miniprogram/config.js +``` +// 传感设备配置 +const SensorDevice = { + // 腾讯云物联网通信平台中获取 产品ID和设备名称 + ProductId: "ZLN6Q20QHU", // 产品ID + DeviceName: "dev001", // 设备名称 + // 腾讯云控制台-访问管理-访问密钥-API密钥管理中获取 SecretId, SecretKey + SecretId: "XXXX", + SecretKey: "XXXX", +} + +// 执行设备配置 +const ExecuteDevice = { + ProductId: "U1BZWHF7F9", // 产品ID + DeviceName: "dev_01", // 设备名称 + // 腾讯云控制台-访问管理-访问密钥-API密钥管理中获取 SecretId, SecretKey + SecretId: "XXXX", + SecretKey: "XXXX", + Topic: "U1BZWHF7F9/dev_01/data" +} +``` \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/index.js b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/index.js new file mode 100644 index 00000000..51393eeb --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/index.js @@ -0,0 +1,38 @@ +const tencentcloud = require("tencentcloud-sdk-nodejs"); + +const IotcloudClient = tencentcloud.iotcloud.v20180614.Client; +const models = tencentcloud.iotcloud.v20180614.Models; + +const Credential = tencentcloud.common.Credential; +const ClientProfile = tencentcloud.common.ClientProfile; +const HttpProfile = tencentcloud.common.HttpProfile; + +// 云函数入口函数 +exports.main = async (event, context) => { + let cred = new Credential(event.SecretId, event.SecretKey); + let httpProfile = new HttpProfile(); + httpProfile.endpoint = "iotcloud.tencentcloudapi.com"; + let clientProfile = new ClientProfile(); + clientProfile.httpProfile = httpProfile; + let client = new IotcloudClient(cred, "ap-guangzhou", clientProfile); + let req = new models.PublishMessageRequest(); + // req.Topic = "U1BZWHF7F9/dev_01/data"; + // req.DeviceName = "dev_01"; + // req.ProductId = "U1BZWHF7F9"; + req.Topic = event.Topic + req.DeviceName = event.DeviceName; + req.ProductId = event.ProductId; + req.Payload = event.Payload; + + return new Promise((resolve, reject)=>{ + client.PublishMessage(req, function (errMsg, response) { + if (errMsg) { + console.log(errMsg); + reject(errMsg) + return; + } + console.log(response.to_json_string()); + resolve(response) + }); + }) +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/package.json b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/package.json new file mode 100644 index 00000000..80f0014c --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-publish/package.json @@ -0,0 +1,14 @@ +{ + "name": "iothub-publish", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "wx-server-sdk": "latest" + } +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/index.js b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/index.js new file mode 100644 index 00000000..1d9921b2 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/index.js @@ -0,0 +1,34 @@ +const tencentcloud = require("tencentcloud-sdk-nodejs"); + +const IotcloudClient = tencentcloud.iotcloud.v20180614.Client; +const models = tencentcloud.iotcloud.v20180614.Models; + +const Credential = tencentcloud.common.Credential; +const ClientProfile = tencentcloud.common.ClientProfile; +const HttpProfile = tencentcloud.common.HttpProfile; + +exports.main = async (event, context) => { + let cred = new Credential(event.SecretId, event.SecretKey); + let httpProfile = new HttpProfile(); + httpProfile.endpoint = "iotcloud.tencentcloudapi.com"; + let clientProfile = new ClientProfile(); + clientProfile.httpProfile = httpProfile; + let client = new IotcloudClient(cred, "ap-guangzhou", clientProfile); + let req = new models.DescribeDeviceShadowRequest(); + req.DeviceName = event.DeviceName; + req.ProductId = event.ProductId; + + return new Promise((resolve, reject)=>{ + client.DescribeDeviceShadow(req, function (errMsg, response) { + + if (errMsg) { + console.log(errMsg); + reject(errMsg) + return; + } + + console.log(response.to_json_string()); + resolve(response) + }); + }) +} diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/package.json b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/package.json new file mode 100644 index 00000000..f346ef44 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/iothub-shadow-query/package.json @@ -0,0 +1,15 @@ +{ + "name": "iotexplorer", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "tencentcloud-sdk-nodejs": "^3.0.77", + "wx-server-sdk": "latest" + } +} diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/package-lock.json b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/package-lock.json new file mode 100644 index 00000000..9113190b --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/package-lock.json @@ -0,0 +1,549 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@cloudbase/database": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/@cloudbase/database/download/@cloudbase/database-0.1.1.tgz", + "integrity": "sha1-yf/JK3HhkAVxljkL6odpZ3f/l/8=", + "requires": { + "bson": "^4.0.2" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/@protobufjs/aspromise/download/@protobufjs/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/@protobufjs/base64/download/@protobufjs/base64-1.1.2.tgz", + "integrity": "sha1-TIVzDlm5ofHzSQR9vyQpYDS7JzU=" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/@protobufjs/codegen/download/@protobufjs/codegen-2.0.4.tgz", + "integrity": "sha1-fvN/DQEPsCitGtWXIuUG2SYoFcs=" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/@protobufjs/eventemitter/download/@protobufjs/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/@protobufjs/fetch/download/@protobufjs/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/@protobufjs/float/download/@protobufjs/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/@protobufjs/inquire/download/@protobufjs/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/@protobufjs/path/download/@protobufjs/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/@protobufjs/pool/download/@protobufjs/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/@protobufjs/utf8/download/@protobufjs/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "@types/long": { + "version": "4.0.0", + "resolved": "https://registry.npm.taobao.org/@types/long/download/@types/long-4.0.0.tgz", + "integrity": "sha1-cZVR0jUtMBrIuB23Mqy2vcKNve8=" + }, + "@types/node": { + "version": "10.14.13", + "resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-10.14.13.tgz?cache=0&sync_timestamp=1563391049881&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-10.14.13.tgz", + "integrity": "sha1-rHhtYjhgrfOaP1HWKUgKrNam7sc=" + }, + "ajv": { + "version": "6.10.2", + "resolved": "http://mirrors.cloud.tencent.com/npm/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "http://registry.npm.taobao.org/asn1/download/asn1-0.2.4.tgz", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "http://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "http://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "http://registry.npm.taobao.org/aws4/download/aws4-1.8.0.tgz", + "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=" + }, + "base64-js": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/base64-js/download/base64-js-1.3.0.tgz", + "integrity": "sha1-yrHmEY8FEJXli1KBrqjBzSK/wOM=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "http://mirrors.cloud.tencent.com/npm/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bson": { + "version": "4.0.2", + "resolved": "https://registry.npm.taobao.org/bson/download/bson-4.0.2.tgz", + "integrity": "sha1-KSn9/tGhRbHDYZCKK5UKbPS+0sI=", + "requires": { + "buffer": "^5.1.0", + "long": "^4.0.0" + } + }, + "buffer": { + "version": "5.2.1", + "resolved": "http://registry.npm.taobao.org/buffer/download/buffer-5.2.1.tgz", + "integrity": "sha1-3Vf6DxCaxZxgJHkETcp7iz0LcdY=", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "http://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npm.taobao.org/combined-stream/download/combined-stream-1.0.8.tgz", + "integrity": "sha1-w9RaizT9cwYxoRCoolIGgrMdWn8=", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "http://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "http://registry.npm.taobao.org/ecc-jsbn/download/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "http://registry.npm.taobao.org/extend/download/extend-3.0.2.tgz", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "http://registry.npm.taobao.org/extsprintf/download/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-2.0.1.tgz?cache=0&sync_timestamp=1562517919182&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/fast-json-stable-stringify/download/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "http://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz?cache=0&sync_timestamp=1562216133657&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fform-data%2Fdownload%2Fform-data-2.3.3.tgz", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "http://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "http://registry.npm.taobao.org/getpass/download/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "http://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "http://mirrors.cloud.tencent.com/npm/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "http://registry.npm.taobao.org/has/download/has-1.0.3.tgz", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", + "requires": { + "function-bind": "^1.1.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "http://mirrors.cloud.tencent.com/npm/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "http://registry.npm.taobao.org/ieee754/download/ieee754-1.1.13.tgz", + "integrity": "sha1-7BaFWOlaoYH9h9N/VcMrvLZwi4Q=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "http://registry.npm.taobao.org/is-regex/download/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "http://registry.npm.taobao.org/is-typedarray/download/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "http://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "http://registry.npm.taobao.org/jsbn/download/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "http://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "http://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "http://mirrors.cloud.tencent.com/npm/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npm.taobao.org/lodash.merge/download/lodash.merge-4.6.2.tgz", + "integrity": "sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo=" + }, + "long": { + "version": "4.0.0", + "resolved": "http://registry.npm.taobao.org/long/download/long-4.0.0.tgz", + "integrity": "sha1-mntxz7fTYaGU6lVSQckvdGjVvyg=" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npm.taobao.org/mime-db/download/mime-db-1.40.0.tgz", + "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI=" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.24.tgz", + "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=", + "requires": { + "mime-db": "1.40.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "http://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz", + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "http://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "protobufjs": { + "version": "6.8.8", + "resolved": "http://mirrors.cloud.tencent.com/npm/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + } + }, + "psl": { + "version": "1.2.0", + "resolved": "http://mirrors.cloud.tencent.com/npm/psl/-/psl-1.2.0.tgz", + "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-2.1.1.tgz", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" + }, + "qs": { + "version": "6.5.2", + "resolved": "http://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz", + "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" + }, + "request": { + "version": "2.88.0", + "resolved": "http://mirrors.cloud.tencent.com/npm/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.0.tgz", + "integrity": "sha1-t02uxJsRSPiMZLaNSbHoFcHy9Rk=" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "http://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" + }, + "sax": { + "version": "1.2.4", + "resolved": "http://registry.npm.taobao.org/sax/download/sax-1.2.4.tgz", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "http://mirrors.cloud.tencent.com/npm/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "tcb-admin-node": { + "version": "1.9.0", + "resolved": "http://mirrors.cloud.tencent.com/npm/tcb-admin-node/-/tcb-admin-node-1.9.0.tgz", + "integrity": "sha512-TYoBo66CEIIw1QzgK4Jq43G45zvBE6ZB35LbDV8wwLQvg6CiZHlmOTVZkgj2YZ8O87ELi+ZE3UBVNZM3nFa6lQ==", + "requires": { + "@cloudbase/database": "0.1.1", + "is-regex": "^1.0.4", + "lodash.merge": "^4.6.1", + "request": "^2.87.0", + "xml2js": "^0.4.19" + } + }, + "tencentcloud-sdk-nodejs": { + "version": "3.0.77", + "resolved": "http://mirrors.cloud.tencent.com/npm/tencentcloud-sdk-nodejs/-/tencentcloud-sdk-nodejs-3.0.77.tgz", + "integrity": "sha512-DULk7IFdR8BQnvC1iRuj+GrYnuU72Lj3oyIO1U2pubf71GtN9PeZ/9km2T7lsCeySU/J6jCTvwVPZaIhip/H1g==", + "requires": { + "request": "^2.85.0" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "http://mirrors.cloud.tencent.com/npm/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "http://mirrors.cloud.tencent.com/npm/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "http://mirrors.cloud.tencent.com/npm/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "http://mirrors.cloud.tencent.com/npm/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "http://mirrors.cloud.tencent.com/npm/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "http://mirrors.cloud.tencent.com/npm/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "http://mirrors.cloud.tencent.com/npm/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "http://mirrors.cloud.tencent.com/npm/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wx-server-sdk": { + "version": "0.8.1", + "resolved": "http://mirrors.cloud.tencent.com/npm/wx-server-sdk/-/wx-server-sdk-0.8.1.tgz", + "integrity": "sha512-mE7O3E7GtRhqk1ukWw2+NiykaO5DaJYiMFCeHrwvekYTVL5Z2nFXnSrUx6nPg/vZ7LWWQbCgQlGOygBjj2+hkQ==", + "requires": { + "protobufjs": "6.8.8", + "tcb-admin-node": "1.9.0", + "tslib": "^1.9.3" + } + }, + "xml2js": { + "version": "0.4.19", + "resolved": "http://mirrors.cloud.tencent.com/npm/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "http://mirrors.cloud.tencent.com/npm/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } +} diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/index.js b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/index.js new file mode 100644 index 00000000..be1647ca --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/index.js @@ -0,0 +1,37 @@ +// 云函数入口文件 +const cloud = require('wx-server-sdk') +const tencentcloud = require("tencentcloud-sdk-nodejs"); + +const IotexplorerClient = tencentcloud.iotexplorer.v20190423.Client; +const models = tencentcloud.iotexplorer.v20190423.Models; + +const Credential = tencentcloud.common.Credential; +const ClientProfile = tencentcloud.common.ClientProfile; +const HttpProfile = tencentcloud.common.HttpProfile; +cloud.init() + +// 云函数入口函数 +exports.main = async(event, context) => { + console.log("event:", event); + let cred = new Credential(event.SecretId, event.SecretKey); + let httpProfile = new HttpProfile(); + httpProfile.endpoint = "iotexplorer.tencentcloudapi.com"; + let clientProfile = new ClientProfile(); + clientProfile.httpProfile = httpProfile; + let client = new IotexplorerClient(cred, "ap-guangzhou", clientProfile); + + let req = new models.DescribeDeviceDataRequest(); + req.ProductId = event.ProductId; + req.DeviceName = event.DeviceName; + console.log("req:", req); + return new Promise((resolve, reject) => { + client.DescribeDeviceData(req, function(errMsg, response) { + if (errMsg) { + console.log(errMsg); + reject(errMsg); + } + console.log(response); + resolve(response) + }); + }) +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/package.json b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/package.json new file mode 100644 index 00000000..f346ef44 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/cloudfunctions/query/package.json @@ -0,0 +1,15 @@ +{ + "name": "iotexplorer", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "tencentcloud-sdk-nodejs": "^3.0.77", + "wx-server-sdk": "latest" + } +} diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.js b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.js new file mode 100644 index 00000000..d1823fd2 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.js @@ -0,0 +1,22 @@ +//app.js +App({ + globalData: { + }, + onLaunch: function () { + wx.setKeepScreenOn({ + keepScreenOn: true + }) + if (!wx.cloud) { + console.error('请使用 2.2.3 或以上的基础库以使用云能力') + } else { + wx.cloud.init({ + // env 参数说明: + // env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会默认请求到哪个云环境的资源 + // 此处请填入环境 ID, 环境 ID 可打开云控制台查看 + // 如不填则使用默认环境(第一个创建的环境) + env: "tos-demo", + traceUser: true, + }) + } + } +}) diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.json b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.json new file mode 100644 index 00000000..810369f4 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.json @@ -0,0 +1,13 @@ +{ + "pages": [ + "pages/index/index" + ], + "window": { + "backgroundColor": "#F6F6F6", + "backgroundTextStyle": "light", + "navigationBarBackgroundColor": "#F6F6F6", + "navigationBarTitleText": "设备联动Demo", + "navigationBarTextStyle": "black" + }, + "sitemapLocation": "sitemap.json" +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.wxss b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.wxss new file mode 100644 index 00000000..82678d67 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/app.wxss @@ -0,0 +1,156 @@ +/**app.wxss**/ +.container { + display: flex; + flex-direction: column; + align-items: center; + box-sizing: border-box; +} + +button { + background: initial; +} + +button:focus{ + outline: 0; +} + +button::after{ + border: none; +} + + +page { + background: #f6f6f6; + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.userinfo, .uploader, .tunnel { + margin-top: 40rpx; + height: 140rpx; + width: 100%; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.1); + border-left: none; + border-right: none; + display: flex; + flex-direction: row; + align-items: center; + transition: all 300ms ease; +} + +.userinfo-avatar { + width: 100rpx; + height: 100rpx; + margin: 20rpx; + border-radius: 50%; + background-size: cover; + background-color: white; +} + +.userinfo-avatar:after { + border: none; +} + +.userinfo-nickname { + font-size: 32rpx; + color: #007aff; + background-color: white; + background-size: cover; +} + +.userinfo-nickname::after { + border: none; +} + +.uploader, .tunnel { + height: auto; + padding: 0 0 0 40rpx; + flex-direction: column; + align-items: flex-start; + box-sizing: border-box; +} + +.uploader-text, .tunnel-text { + width: 100%; + line-height: 52px; + font-size: 34rpx; + color: #007aff; +} + +.uploader-container { + width: 100%; + height: 400rpx; + padding: 20rpx 20rpx 20rpx 0; + display: flex; + align-content: center; + justify-content: center; + box-sizing: border-box; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +.uploader-image { + width: 100%; + height: 360rpx; +} + +.tunnel { + padding: 0 0 0 40rpx; +} + +.tunnel-text { + position: relative; + color: #222; + display: flex; + flex-direction: row; + align-content: center; + justify-content: space-between; + box-sizing: border-box; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +.tunnel-text:first-child { + border-top: none; +} + +.tunnel-switch { + position: absolute; + right: 20rpx; + top: -2rpx; +} + +.disable { + color: #888; +} + +.service { + position: fixed; + right: 40rpx; + bottom: 40rpx; + width: 140rpx; + height: 140rpx; + border-radius: 50%; + background: linear-gradient(#007aff, #0063ce); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); + display: flex; + align-content: center; + justify-content: center; + transition: all 300ms ease; +} + +.service-button { + position: absolute; + top: 40rpx; +} + +.service:active { + box-shadow: none; +} + +.request-text { + padding: 20rpx 0; + font-size: 24rpx; + line-height: 36rpx; + word-break: break-all; +} diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/config.js b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/config.js new file mode 100644 index 00000000..c3a7f0a8 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/config.js @@ -0,0 +1,24 @@ +// 传感设备配置 +const SensorDevice = { + // 腾讯云物联网通信平台中获取 产品ID和设备名称 + ProductId: "ZLN6Q20QHU", // 产品ID + DeviceName: "dev001", // 设备名称 + // 腾讯云控制台-访问管理-访问密钥-API密钥管理中获取 SecretId, SecretKey + SecretId: "XXXX", + SecretKey: "XXXX", +} + +// 执行设备配置 +const ExecuteDevice = { + ProductId: "U1BZWHF7F9", // 产品ID + DeviceName: "dev_01", // 设备名称 + // 腾讯云控制台-访问管理-访问密钥-API密钥管理中获取 SecretId, SecretKey + SecretId: "XXXX", + SecretKey: "XXXX", + Topic: "U1BZWHF7F9/dev_01/data" +} + +module.exports = { + SensorDevice: SensorDevice, + ExecuteDevice: ExecuteDevice, +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/images/TencentOS_tiny_log.png b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/images/TencentOS_tiny_log.png new file mode 100644 index 0000000000000000000000000000000000000000..54f21405c7a4fe3a6130ed5a1dcf1b0f4a72587f GIT binary patch literal 48034 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 literal 0 HcmV?d00001 diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.js b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.js new file mode 100644 index 00000000..62eb148f --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.js @@ -0,0 +1,266 @@ +const app = getApp() +const config = require('../../config.js') +var util = require('../../utils/util.js'); +var wxCharts = require('../../utils/wxcharts.js'); +var lineChart = null; +var startPos = null; + +Page({ + data: { + tempHigh: 20, + sensorDevice: {}, + executeDevice: {}, + chartDataTemp: [], + chartDataHumi: [], + chartCategories: [], + }, + onLoad: async function(options) { + console.log("index onLoad") + while (true) { + let executor = await this.queryExecuteDeviceStatus() + let sensor = await this.querySensorDeviceStatus() + this.renderChart(sensor.temperature.Value, sensor.humidity.Value) + let temp = sensor.temperature.Value + console.log(temp) + if (temp >= this.data.tempHigh) { + if (executor.light == 0) { + wx.showModal({ + title: '提示', + content: `温度达到门限(${this.data.tempHigh}),打开风扇和灯`, + showCancel: false, + }) + this.turnOnLight() + this.turnOnFan() + } + } else { + // this.turnOffLight() + // this.turnOffFan() + } + await util.delayMs(5000) + } + }, + sliderChange(e) { + console.log(e.detail) + this.setData({ + tempHigh: e.detail.value + }) + }, + renderChart(temperature, humidity) { + let epoch = Math.round(new Date().getTime() / 1000) + let timestamp = util.formatEpoch(epoch) + if (this.data.chartDataTemp.length > 5) { + this.data.chartDataTemp.pop(); + this.data.chartDataHumi.pop(); + this.data.chartCategories.pop(); + } + this.data.chartDataTemp.unshift(temperature) + this.data.chartDataHumi.unshift(humidity) + this.data.chartCategories.unshift(timestamp.substring(11)) + this.setData({ + timestamp: timestamp, + chartDataTemp: this.data.chartDataTemp, + chartDataHumi: this.data.chartDataHumi, + chartCategories: this.data.chartCategories, + }) + this.renderChartTemp(400, 160) + this.renderChartHumi(400, 160) + }, + // 查询传感器设备状态 + async querySensorDeviceStatus() { + let res = await wx.cloud.callFunction({ + name: 'query', + data: config.SensorDevice + }) + console.log("sensorDevice status", res.result.Data) + let data = JSON.parse(res.result.Data) + this.setData({ + sensorDevice: data + }) + return this.data.sensorDevice + }, + // 查询执行设备状态 + async queryExecuteDeviceStatus() { + let res = await wx.cloud.callFunction({ + name: 'iothub-shadow-query', + data: config.ExecuteDevice, + }) + console.log("executeDevice status", res.result) + let deviceData = JSON.parse(res.result.Data) + // this.setData({ + // executeDevice: deviceData.payload.state.reported + // }) + return deviceData.payload.state.reported + }, + async turnOnLight() { + await this.control({ + light: 1 + }) + this.setData({ + [`executeDevice.light`]: 1 + }) + }, + async turnOffLight() { + await this.control({ + light: 0 + }) + this.setData({ + [`executeDevice.light`]: 0 + }) + }, + async turnOnFan() { + await this.control({ + motor: 1 + }) + this.setData({ + [`executeDevice.motor`]: 1 + }) + }, + async turnOffFan() { + await this.control({ + motor: 0 + }) + this.setData({ + [`executeDevice.motor`]: 0 + }) + }, + async control(obj) { + let data = config.ExecuteDevice + data.Payload = JSON.stringify(obj) + await wx.cloud.callFunction({ + name: 'iothub-publish', + data: data + }) + }, + async switchChange(e) { + let value = 0 + if (e.detail.value == true) { + value = 1 + } + let item = e.currentTarget.dataset.item + let obj = { + [`${item}`]: value + } + wx.showLoading() + let data = config.ExecuteDevice + data.Payload = JSON.stringify(obj) + console.log(data) + await wx.cloud.callFunction({ + name: 'iothub-publish', + data: data + }) + wx.hideLoading() + }, + touchHandler: function(e) { + if (!lineChart) { + return; + } + lineChart.scrollStart(e); + }, + moveHandler: function(e) { + if (!lineChart) { + return; + } + lineChart.scroll(e); + }, + touchEndHandler: function(e) { + if (!lineChart) { + return; + } + lineChart.scrollEnd(e); + lineChart.showToolTip(e, { + format: function(item, category) { + return category + ' ' + item.name + ':' + item.data + } + }); + }, + renderChartHumi: function(windowWidth, windowHeight) { + lineChart = new wxCharts({ + canvasId: 'chartHumi', + type: 'line', + categories: this.data.chartCategories, // simulationData.categories, + animation: false, + series: [{ + name: '湿度', + data: this.data.chartDataHumi, // simulationData.data2, + format: function(val, name) { + return val.toFixed(2) + "%"; + // return val + "%"; + } + }], + xAxis: { + disableGrid: false, + fontColor: '#666666', + }, + yAxis: { + // title: '湿度 (%)', + // format: function (val) { + // return val.toFixed(2); + // }, + max: 100, + min: 50, + disabled: false, + }, + width: windowWidth, + height: windowHeight, + legend: true, + dataLabel: true, + dataPointShape: true, + enableScroll: true, + extra: { + lineStyle: 'curve' + }, + }); + }, + renderChartTemp: function(windowWidth, windowHeight) { + lineChart = new wxCharts({ + canvasId: 'chartTemp', + type: 'line', + categories: this.data.chartCategories, // simulationData.categories, + animation: false, + series: [{ + name: '温度', + data: this.data.chartDataTemp, //simulationData.data, + format: function(val, name) { + return val.toFixed(2) + "℃"; + // return val + "℃"; + } + }], + xAxis: { + disableGrid: false + }, + yAxis: { + // title: '温度 (℃)', + // format: function (val) { + // return val.toFixed(2); + // }, + min: 15, + max: 25, + disabled: false, + }, + width: windowWidth, + height: windowHeight, + legend: true, + dataLabel: true, + dataPointShape: true, + enableScroll: true, + extra: { + lineStyle: 'curve' + }, + }); + }, + createSimulationData: function() { + var categories = []; + var data = []; + var data2 = []; + for (var i = 0; i < 10; i++) { + categories.push('201620162-' + (i + 1)); + data.push(Math.random() * (20 - 10) + 10); + data2.push(Math.random() * (20 - 10) + 30); + } + return { + categories: categories, + data: data, + data2: data2 + } + }, +}) \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.json b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxml b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxml new file mode 100644 index 00000000..69c61535 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxml @@ -0,0 +1,66 @@ + + + + + + + +

+ + + 传感设备 + + + {{timestamp}} + + + + + 温度 + + {{sensorDevice.temperature.Value}} + + + + + + 湿度 + + {{sensorDevice.humidity.Value}} + + % + + + + +
+ +
+ + + 执行设备 + + + + + 照明 + + + + + + 风扇 + + + + + + 温度门限 + + + +
+ + + + \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxss b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxss new file mode 100644 index 00000000..893c8b2c --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/pages/index/index.wxss @@ -0,0 +1,32 @@ +.body { + margin: 10rpx 20rpx; +} + +.box { + padding: 0rpx 20rpx; + border-top: 2px solid #000; +} + +.cell { + margin-top: 10rpx; + margin-bottom: 20rpx; + display: flex; + justify-content: space-between; + align-items: center; +} + +.left { + width: 50%; + text-align: left; +} + +.right { + width: 40%; + text-align: right; +} + +.unit { + width: 10%; + text-align: right; +} + diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/sitemap.json b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/sitemap.json new file mode 100644 index 00000000..ca02add2 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", + "rules": [{ + "action": "allow", + "page": "*" + }] +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/timeutil.wxs b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/timeutil.wxs new file mode 100644 index 00000000..2119a646 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/timeutil.wxs @@ -0,0 +1,48 @@ +var formatNumber = function (n) { + n = n.toString() + return n[1] ? n : '0' + n +} + +var regYear = getRegExp("(y+)", "i"); + +var dateFormat = function (timestamp, format) { + if (!format) { + format = "yyyy-MM-dd hh:mm:ss"; + } + timestamp = parseInt(timestamp); + var realDate = getDate(timestamp); + function timeFormat(num) { + return num < 10 ? '0' + num : num; + } + var date = [ + ["M+", timeFormat(realDate.getMonth() + 1)], + ["d+", timeFormat(realDate.getDate())], + ["h+", timeFormat(realDate.getHours())], + ["m+", timeFormat(realDate.getMinutes())], + ["s+", timeFormat(realDate.getSeconds())], + ["q+", Math.floor((realDate.getMonth() + 3) / 3)], + ["S+", realDate.getMilliseconds()], + ]; + var reg1 = regYear.exec(format); + // console.log(reg1[0]); + if (reg1) { + + format = format.replace(reg1[1], (realDate.getFullYear() + '').substring(4 - reg1[1].length)); + } + for (var i = 0; i < date.length; i++) { + var k = date[i][0]; + var v = date[i][1]; + + var reg2 = getRegExp("(" + k + ")").exec(format); + if (reg2) { + format = format.replace(reg2[1], reg2[1].length == 1 + ? v : ("00" + v).substring(("" + v).length)); + } + } + return format; +} + + +module.exports = { + dateFormat: dateFormat +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/util.js b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/util.js new file mode 100644 index 00000000..114726c3 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/util.js @@ -0,0 +1,33 @@ +// await util.delayMs(1000) +const delayMs = (ms) => { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +function Appendzero(obj) { + if (obj < 10) return "0" + "" + obj; + else return obj; +} + +// epoch => datetime str +// 1578450878 => 2019-10-23 18:05:30 +function formatEpoch(epoch) { + var dateTime = new Date(parseInt(epoch) * 1000) + var year = dateTime.getFullYear(); + var month = dateTime.getMonth() + 1; + var day = dateTime.getDate(); + var hour = dateTime.getHours(); + var minute = dateTime.getMinutes(); + var second = dateTime.getSeconds(); + var now = new Date(); + var now_new = Date.parse(now.toDateString()); //typescript转换写法 + var milliseconds = now_new - dateTime; + var timeSpanStr = year + '-' + Appendzero(month) + '-' + Appendzero(day) + ' ' + Appendzero(hour) + ':' + Appendzero(minute) + ':' + Appendzero(second); + return timeSpanStr; +} + +module.exports = { + delayMs: delayMs, + formatEpoch: formatEpoch, +} \ No newline at end of file diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/wxcharts.js b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/wxcharts.js new file mode 100644 index 00000000..e222a8d8 --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/miniprogram/utils/wxcharts.js @@ -0,0 +1,2048 @@ +/* + * charts for WeChat small app v1.0 + * + * https://github.com/xiaolin3303/wx-charts + * 2016-11-28 + * + * Designed and built with all the love of Web + */ + +'use strict'; + +var config = { + yAxisWidth: 15, + yAxisSplit: 5, + xAxisHeight: 15, + xAxisLineHeight: 15, + legendHeight: 15, + yAxisTitleWidth: 15, + padding: 12, + columePadding: 0, //3 + fontSize: 10, + dataPointShape: ['diamond', 'circle', 'triangle', 'rect'], + colors: ['#7cb5ec', '#f7a35c', '#434348', '#90ed7d', '#f15c80', '#8085e9'], + pieChartLinePadding: 0, //25 + pieChartTextPadding: 0, //15 + xAxisTextPadding: 3, + titleColor: '#333333', + titleFontSize: 20, + subtitleColor: '#999999', + subtitleFontSize: 15, + toolTipPadding: 3, + toolTipBackground: '#000000', + toolTipOpacity: 0.7, + toolTipLineHeight: 14, + radarGridCount: 3, + radarLabelTextMargin: 0 // 15 +}; + +// Object.assign polyfill +// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign +function assign(target, varArgs) { + if (target == null) { + // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { + // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; +} + +var util = { + toFixed: function toFixed(num, limit) { + limit = limit || 2; + if (this.isFloat(num)) { + num = num.toFixed(limit); + } + return num; + }, + isFloat: function isFloat(num) { + return num % 1 !== 0; + }, + approximatelyEqual: function approximatelyEqual(num1, num2) { + return Math.abs(num1 - num2) < 1e-10; + }, + isSameSign: function isSameSign(num1, num2) { + return Math.abs(num1) === num1 && Math.abs(num2) === num2 || Math.abs(num1) !== num1 && Math.abs(num2) !== num2; + }, + isSameXCoordinateArea: function isSameXCoordinateArea(p1, p2) { + return this.isSameSign(p1.x, p2.x); + }, + isCollision: function isCollision(obj1, obj2) { + obj1.end = {}; + obj1.end.x = obj1.start.x + obj1.width; + obj1.end.y = obj1.start.y - obj1.height; + obj2.end = {}; + obj2.end.x = obj2.start.x + obj2.width; + obj2.end.y = obj2.start.y - obj2.height; + var flag = obj2.start.x > obj1.end.x || obj2.end.x < obj1.start.x || obj2.end.y > obj1.start.y || obj2.start.y < obj1.end.y; + + return !flag; + } +}; + +function findRange(num, type, limit) { + if (isNaN(num)) { + throw new Error('[wxCharts] unvalid series data!'); + } + limit = limit || 10; + type = type ? type : 'upper'; + var multiple = 1; + while (limit < 1) { + limit *= 10; + multiple *= 10; + } + if (type === 'upper') { + num = Math.ceil(num * multiple); + } else { + num = Math.floor(num * multiple); + } + while (num % limit !== 0) { + if (type === 'upper') { + num++; + } else { + num--; + } + } + + return num / multiple; +} + +function calValidDistance(distance, chartData, config, opts) { + + var dataChartAreaWidth = opts.width - config.padding - chartData.xAxisPoints[0]; + var dataChartWidth = chartData.eachSpacing * opts.categories.length; + var validDistance = distance; + if (distance >= 0) { + validDistance = 0; + } else if (Math.abs(distance) >= dataChartWidth - dataChartAreaWidth) { + validDistance = dataChartAreaWidth - dataChartWidth; + } + return validDistance; +} + +function isInAngleRange(angle, startAngle, endAngle) { + function adjust(angle) { + while (angle < 0) { + angle += 2 * Math.PI; + } + while (angle > 2 * Math.PI) { + angle -= 2 * Math.PI; + } + + return angle; + } + + angle = adjust(angle); + startAngle = adjust(startAngle); + endAngle = adjust(endAngle); + if (startAngle > endAngle) { + endAngle += 2 * Math.PI; + if (angle < startAngle) { + angle += 2 * Math.PI; + } + } + + return angle >= startAngle && angle <= endAngle; +} + +function calRotateTranslate(x, y, h) { + var xv = x; + var yv = h - y; + + var transX = xv + (h - yv - xv) / Math.sqrt(2); + transX *= -1; + + var transY = (h - yv) * (Math.sqrt(2) - 1) - (h - yv - xv) / Math.sqrt(2); + + return { + transX: transX, + transY: transY + }; +} + +function createCurveControlPoints(points, i) { + + function isNotMiddlePoint(points, i) { + if (points[i - 1] && points[i + 1]) { + return points[i].y >= Math.max(points[i - 1].y, points[i + 1].y) || points[i].y <= Math.min(points[i - 1].y, points[i + 1].y); + } else { + return false; + } + } + + var a = 0.2; + var b = 0.2; + var pAx = null; + var pAy = null; + var pBx = null; + var pBy = null; + if (i < 1) { + pAx = points[0].x + (points[1].x - points[0].x) * a; + pAy = points[0].y + (points[1].y - points[0].y) * a; + } else { + pAx = points[i].x + (points[i + 1].x - points[i - 1].x) * a; + pAy = points[i].y + (points[i + 1].y - points[i - 1].y) * a; + } + + if (i > points.length - 3) { + var last = points.length - 1; + pBx = points[last].x - (points[last].x - points[last - 1].x) * b; + pBy = points[last].y - (points[last].y - points[last - 1].y) * b; + } else { + pBx = points[i + 1].x - (points[i + 2].x - points[i].x) * b; + pBy = points[i + 1].y - (points[i + 2].y - points[i].y) * b; + } + + // fix issue https://github.com/xiaolin3303/wx-charts/issues/79 + if (isNotMiddlePoint(points, i + 1)) { + pBy = points[i + 1].y; + } + if (isNotMiddlePoint(points, i)) { + pAy = points[i].y; + } + + return { + ctrA: { x: pAx, y: pAy }, + ctrB: { x: pBx, y: pBy } + }; +} + +function convertCoordinateOrigin(x, y, center) { + return { + x: center.x + x, + y: center.y - y + }; +} + +function avoidCollision(obj, target) { + if (target) { + // is collision test + while (util.isCollision(obj, target)) { + if (obj.start.x > 0) { + obj.start.y--; + } else if (obj.start.x < 0) { + obj.start.y++; + } else { + if (obj.start.y > 0) { + obj.start.y++; + } else { + obj.start.y--; + } + } + } + } + return obj; +} + +function fillSeriesColor(series, config) { + var index = 0; + return series.map(function (item) { + if (!item.color) { + item.color = config.colors[index]; + index = (index + 1) % config.colors.length; + } + return item; + }); +} + +function getDataRange(minData, maxData) { + var limit = 0; + var range = maxData - minData; + if (range >= 10000) { + limit = 1000; + } else if (range >= 1000) { + limit = 100; + } else if (range >= 100) { + limit = 10; + } else if (range >= 10) { + limit = 5; + } else if (range >= 1) { + limit = 1; + } else if (range >= 0.1) { + limit = 0.1; + } else { + limit = 0.01; + } + return { + minRange: findRange(minData, 'lower', limit), + maxRange: findRange(maxData, 'upper', limit) + }; +} + +function measureText(text) { + var fontSize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10; + + // wx canvas 未实现measureText方法, 此处自行实现 + text = String(text); + var text = text.split(''); + var width = 0; + text.forEach(function (item) { + if (/[a-zA-Z]/.test(item)) { + width += 7; + } else if (/[0-9]/.test(item)) { + width += 5.5; + } else if (/\./.test(item)) { + width += 2.7; + } else if (/-/.test(item)) { + width += 3.25; + } else if (/[\u4e00-\u9fa5]/.test(item)) { + width += 10; + } else if (/\(|\)/.test(item)) { + width += 3.73; + } else if (/\s/.test(item)) { + width += 2.5; + } else if (/%/.test(item)) { + width += 8; + } else { + width += 10; + } + }); + return width * fontSize / 10; +} + +function dataCombine(series) { + return series.reduce(function (a, b) { + return (a.data ? a.data : a).concat(b.data); + }, []); +} + +function getSeriesDataItem(series, index) { + var data = []; + series.forEach(function (item) { + if (item.data[index] !== null && typeof item.data[index] !== 'undefinded') { + var seriesItem = {}; + seriesItem.color = item.color; + seriesItem.name = item.name; + seriesItem.data = item.format ? item.format(item.data[index]) : item.data[index]; + data.push(seriesItem); + } + }); + + return data; +} + + + +function getMaxTextListLength(list) { + var lengthList = list.map(function (item) { + return measureText(item); + }); + return Math.max.apply(null, lengthList); +} + +function getRadarCoordinateSeries(length) { + var eachAngle = 2 * Math.PI / length; + var CoordinateSeries = []; + for (var i = 0; i < length; i++) { + CoordinateSeries.push(eachAngle * i); + } + + return CoordinateSeries.map(function (item) { + return -1 * item + Math.PI / 2; + }); +} + +function getToolTipData(seriesData, calPoints, index, categories) { + var option = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; + + var textList = seriesData.map(function (item) { + return { + text: option.format ? option.format(item, categories[index]) : item.name + ': ' + item.data, + color: item.color + }; + }); + var validCalPoints = []; + var offset = { + x: 0, + y: 0 + }; + calPoints.forEach(function (points) { + if (typeof points[index] !== 'undefinded' && points[index] !== null) { + validCalPoints.push(points[index]); + } + }); + validCalPoints.forEach(function (item) { + offset.x = Math.round(item.x); + offset.y += item.y; + }); + + offset.y /= validCalPoints.length; + return { textList: textList, offset: offset }; +} + +function findCurrentIndex(currentPoints, xAxisPoints, opts, config) { + var offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; + + var currentIndex = -1; + if (isInExactChartArea(currentPoints, opts, config)) { + xAxisPoints.forEach(function (item, index) { + if (currentPoints.x + offset > item) { + currentIndex = index; + } + }); + } + + return currentIndex; +} + +function isInExactChartArea(currentPoints, opts, config) { + return currentPoints.x < opts.width - config.padding && currentPoints.x > config.padding + config.yAxisWidth + config.yAxisTitleWidth && currentPoints.y > config.padding && currentPoints.y < opts.height - config.legendHeight - config.xAxisHeight - config.padding; +} + +function findRadarChartCurrentIndex(currentPoints, radarData, count) { + var eachAngleArea = 2 * Math.PI / count; + var currentIndex = -1; + if (isInExactPieChartArea(currentPoints, radarData.center, radarData.radius)) { + var fixAngle = function fixAngle(angle) { + if (angle < 0) { + angle += 2 * Math.PI; + } + if (angle > 2 * Math.PI) { + angle -= 2 * Math.PI; + } + return angle; + }; + + var angle = Math.atan2(radarData.center.y - currentPoints.y, currentPoints.x - radarData.center.x); + angle = -1 * angle; + if (angle < 0) { + angle += 2 * Math.PI; + } + + var angleList = radarData.angleList.map(function (item) { + item = fixAngle(-1 * item); + + return item; + }); + + angleList.forEach(function (item, index) { + var rangeStart = fixAngle(item - eachAngleArea / 2); + var rangeEnd = fixAngle(item + eachAngleArea / 2); + if (rangeEnd < rangeStart) { + rangeEnd += 2 * Math.PI; + } + if (angle >= rangeStart && angle <= rangeEnd || angle + 2 * Math.PI >= rangeStart && angle + 2 * Math.PI <= rangeEnd) { + currentIndex = index; + } + }); + } + + return currentIndex; +} + +function findPieChartCurrentIndex(currentPoints, pieData) { + var currentIndex = -1; + if (isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) { + var angle = Math.atan2(pieData.center.y - currentPoints.y, currentPoints.x - pieData.center.x); + angle = -angle; + for (var i = 0, len = pieData.series.length; i < len; i++) { + var item = pieData.series[i]; + if (isInAngleRange(angle, item._start_, item._start_ + item._proportion_ * 2 * Math.PI)) { + currentIndex = i; + break; + } + } + } + + return currentIndex; +} + +function isInExactPieChartArea(currentPoints, center, radius) { + return Math.pow(currentPoints.x - center.x, 2) + Math.pow(currentPoints.y - center.y, 2) <= Math.pow(radius, 2); +} + +function splitPoints(points) { + var newPoints = []; + var items = []; + points.forEach(function (item, index) { + if (item !== null) { + items.push(item); + } else { + if (items.length) { + newPoints.push(items); + } + items = []; + } + }); + if (items.length) { + newPoints.push(items); + } + + return newPoints; +} + +function calLegendData(series, opts, config) { + if (opts.legend === false) { + return { + legendList: [], + legendHeight: 0 + }; + } + var padding = 5; + var marginTop = 8; + var shapeWidth = 15; + var legendList = []; + var widthCount = 0; + var currentRow = []; + series.forEach(function (item) { + var itemWidth = 3 * padding + shapeWidth + measureText(item.name || 'undefinded'); + if (widthCount + itemWidth > opts.width) { + legendList.push(currentRow); + widthCount = itemWidth; + currentRow = [item]; + } else { + widthCount += itemWidth; + currentRow.push(item); + } + }); + if (currentRow.length) { + legendList.push(currentRow); + } + + return { + legendList: legendList, + legendHeight: legendList.length * (config.fontSize + marginTop) + padding + }; +} + +function calCategoriesData(categories, opts, config) { + var result = { + angle: 0, + xAxisHeight: config.xAxisHeight + }; + + var _getXAxisPoints = getXAxisPoints(categories, opts, config), + eachSpacing = _getXAxisPoints.eachSpacing; + + // get max length of categories text + + + var categoriesTextLenth = categories.map(function (item) { + return measureText(item); + }); + + var maxTextLength = Math.max.apply(this, categoriesTextLenth); + + if (maxTextLength + 2 * config.xAxisTextPadding > eachSpacing) { + result.angle = 45 * Math.PI / 180; + result.xAxisHeight = 2 * config.xAxisTextPadding + maxTextLength * Math.sin(result.angle); + } + + return result; +} + +function getRadarDataPoints(angleList, center, radius, series, opts) { + var process = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1; + + var radarOption = opts.extra.radar || {}; + radarOption.max = radarOption.max || 0; + var maxData = Math.max(radarOption.max, Math.max.apply(null, dataCombine(series))); + + var data = []; + series.forEach(function (each) { + var listItem = {}; + listItem.color = each.color; + listItem.data = []; + each.data.forEach(function (item, index) { + var tmp = {}; + tmp.angle = angleList[index]; + + tmp.proportion = item / maxData; + tmp.position = convertCoordinateOrigin(radius * tmp.proportion * process * Math.cos(tmp.angle), radius * tmp.proportion * process * Math.sin(tmp.angle), center); + listItem.data.push(tmp); + }); + + data.push(listItem); + }); + + return data; +} + +function getPieDataPoints(series) { + var process = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; + + var count = 0; + var _start_ = 0; + series.forEach(function (item) { + item.data = item.data === null ? 0 : item.data; + count += item.data; + }); + series.forEach(function (item) { + item.data = item.data === null ? 0 : item.data; + item._proportion_ = item.data / count * process; + }); + series.forEach(function (item) { + item._start_ = _start_; + _start_ += 2 * item._proportion_ * Math.PI; + }); + + return series; +} + +function getPieTextMaxLength(series) { + series = getPieDataPoints(series); + var maxLength = 0; + series.forEach(function (item) { + var text = item.format ? item.format(+item._proportion_.toFixed(2)) : util.toFixed(item._proportion_ * 100) + '%'; + maxLength = Math.max(maxLength, measureText(text)); + }); + + return maxLength; +} + +function fixColumeData(points, eachSpacing, columnLen, index, config, opts) { + return points.map(function (item) { + if (item === null) { + return null; + } + item.width = (eachSpacing - 2 * config.columePadding) / columnLen; + + if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) { + // customer column width + item.width = Math.min(item.width, +opts.extra.column.width); + } else { + // default width should less tran 25px + // don't ask me why, I don't know + item.width = Math.min(item.width, 25); + } + item.x += (index + 0.5 - columnLen / 2) * item.width; + + return item; + }); +} + +function getXAxisPoints(categories, opts, config) { + var yAxisTotalWidth = config.yAxisWidth + config.yAxisTitleWidth; + var spacingValid = opts.width - 2 * config.padding - yAxisTotalWidth; + var dataCount = opts.enableScroll ? Math.min(5, categories.length) : categories.length; + var eachSpacing = spacingValid / dataCount; + + var xAxisPoints = []; + var startX = config.padding + yAxisTotalWidth; + var endX = opts.width - config.padding; + categories.forEach(function (item, index) { + xAxisPoints.push(startX + index * eachSpacing); + }); + if (opts.enableScroll === true) { + xAxisPoints.push(startX + categories.length * eachSpacing); + } else { + xAxisPoints.push(endX); + } + + return { xAxisPoints: xAxisPoints, startX: startX, endX: endX, eachSpacing: eachSpacing }; +} + +function getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config) { + var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1; + + var points = []; + var validHeight = opts.height - 2 * config.padding - config.xAxisHeight - config.legendHeight; + data.forEach(function (item, index) { + if (item === null) { + points.push(null); + } else { + var point = {}; + point.x = xAxisPoints[index] + Math.round(eachSpacing / 2); + var height = validHeight * (item - minRange) / (maxRange - minRange); + height *= process; + point.y = opts.height - config.xAxisHeight - config.legendHeight - Math.round(height) - config.padding; + points.push(point); + } + }); + + return points; +} + +function getYAxisTextList(series, opts, config) { + var data = dataCombine(series); + // remove null from data + data = data.filter(function (item) { + return item !== null; + }); + var minData = Math.min.apply(this, data); + var maxData = Math.max.apply(this, data); + if (typeof opts.yAxis.min === 'number') { + minData = Math.min(opts.yAxis.min, minData); + } + if (typeof opts.yAxis.max === 'number') { + maxData = Math.max(opts.yAxis.max, maxData); + } + + // fix issue https://github.com/xiaolin3303/wx-charts/issues/9 + if (minData === maxData) { + var rangeSpan = maxData || 1; + minData -= rangeSpan; + maxData += rangeSpan; + } + + var dataRange = getDataRange(minData, maxData); + var minRange = dataRange.minRange; + var maxRange = dataRange.maxRange; + + var range = []; + var eachRange = (maxRange - minRange) / config.yAxisSplit; + + for (var i = 0; i <= config.yAxisSplit; i++) { + range.push(minRange + eachRange * i); + } + return range.reverse(); +} + +function calYAxisData(series, opts, config) { + + var ranges = getYAxisTextList(series, opts, config); + var yAxisWidth = config.yAxisWidth; + var rangesFormat = ranges.map(function (item) { + item = util.toFixed(item, 2); + item = opts.yAxis.format ? opts.yAxis.format(Number(item)) : item; + yAxisWidth = Math.max(yAxisWidth, measureText(item) + 5); + return item; + }); + if (opts.yAxis.disabled === true) { + yAxisWidth = 0; + } + + return { rangesFormat: rangesFormat, ranges: ranges, yAxisWidth: yAxisWidth }; +} + +function drawPointShape(points, color, shape, context) { + context.beginPath(); + context.setStrokeStyle("#ffffff"); + context.setLineWidth(1); + context.setFillStyle(color); + + if (shape === 'diamond') { + points.forEach(function (item, index) { + if (item !== null) { + context.moveTo(item.x, item.y - 4.5); + context.lineTo(item.x - 4.5, item.y); + context.lineTo(item.x, item.y + 4.5); + context.lineTo(item.x + 4.5, item.y); + context.lineTo(item.x, item.y - 4.5); + } + }); + } else if (shape === 'circle') { + points.forEach(function (item, index) { + if (item !== null) { + context.moveTo(item.x + 3.5, item.y); + context.arc(item.x, item.y, 4, 0, 2 * Math.PI, false); + } + }); + } else if (shape === 'rect') { + points.forEach(function (item, index) { + if (item !== null) { + context.moveTo(item.x - 3.5, item.y - 3.5); + context.rect(item.x - 3.5, item.y - 3.5, 7, 7); + } + }); + } else if (shape === 'triangle') { + points.forEach(function (item, index) { + if (item !== null) { + context.moveTo(item.x, item.y - 4.5); + context.lineTo(item.x - 4.5, item.y + 4.5); + context.lineTo(item.x + 4.5, item.y + 4.5); + context.lineTo(item.x, item.y - 4.5); + } + }); + } + context.closePath(); + context.fill(); + context.stroke(); +} + +function drawRingTitle(opts, config, context) { + var titlefontSize = opts.title.fontSize || config.titleFontSize; + var subtitlefontSize = opts.subtitle.fontSize || config.subtitleFontSize; + var title = opts.title.name || ''; + var subtitle = opts.subtitle.name || ''; + var titleFontColor = opts.title.color || config.titleColor; + var subtitleFontColor = opts.subtitle.color || config.subtitleColor; + var titleHeight = title ? titlefontSize : 0; + var subtitleHeight = subtitle ? subtitlefontSize : 0; + var margin = 5; + if (subtitle) { + var textWidth = measureText(subtitle, subtitlefontSize); + var startX = (opts.width - textWidth) / 2 + (opts.subtitle.offsetX || 0); + var startY = (opts.height - config.legendHeight + subtitlefontSize) / 2; + if (title) { + startY -= (titleHeight + margin) / 2; + } + context.beginPath(); + context.setFontSize(subtitlefontSize); + context.setFillStyle(subtitleFontColor); + context.fillText(subtitle, startX, startY); + context.stroke(); + context.closePath(); + } + if (title) { + var _textWidth = measureText(title, titlefontSize); + var _startX = (opts.width - _textWidth) / 2 + (opts.title.offsetX || 0); + var _startY = (opts.height - config.legendHeight + titlefontSize) / 2; + if (subtitle) { + _startY += (subtitleHeight + margin) / 2; + } + context.beginPath(); + context.setFontSize(titlefontSize); + context.setFillStyle(titleFontColor); + context.fillText(title, _startX, _startY); + context.stroke(); + context.closePath(); + } +} + +function drawPointText(points, series, config, context) { + // 绘制数据文案 + var data = series.data; + + context.beginPath(); + context.setFontSize(config.fontSize); + context.setFillStyle('#666666'); + points.forEach(function (item, index) { + if (item !== null) { + var formatVal = series.format ? series.format(data[index]) : data[index]; + context.fillText(formatVal, item.x - measureText(formatVal) / 2, item.y - 2); + } + }); + context.closePath(); + context.stroke(); +} + +function drawRadarLabel(angleList, radius, centerPosition, opts, config, context) { + var radarOption = opts.extra.radar || {}; + radius += config.radarLabelTextMargin; + context.beginPath(); + context.setFontSize(config.fontSize); + context.setFillStyle(radarOption.labelColor || '#666666'); + angleList.forEach(function (angle, index) { + var pos = { + x: radius * Math.cos(angle), + y: radius * Math.sin(angle) + }; + var posRelativeCanvas = convertCoordinateOrigin(pos.x, pos.y, centerPosition); + var startX = posRelativeCanvas.x; + var startY = posRelativeCanvas.y; + if (util.approximatelyEqual(pos.x, 0)) { + startX -= measureText(opts.categories[index] || '') / 2; + } else if (pos.x < 0) { + startX -= measureText(opts.categories[index] || ''); + } + context.fillText(opts.categories[index] || '', startX, startY + config.fontSize / 2); + }); + context.stroke(); + context.closePath(); +} + +function drawPieText(series, opts, config, context, radius, center) { + var lineRadius = radius + config.pieChartLinePadding; + var textObjectCollection = []; + var lastTextObject = null; + + var seriesConvert = series.map(function (item) { + var arc = 2 * Math.PI - (item._start_ + 2 * Math.PI * item._proportion_ / 2); + var text = item.format ? item.format(+item._proportion_.toFixed(2)) : util.toFixed(item._proportion_ * 100) + '%'; + var color = item.color; + return { arc: arc, text: text, color: color }; + }); + seriesConvert.forEach(function (item) { + // line end + var orginX1 = Math.cos(item.arc) * lineRadius; + var orginY1 = Math.sin(item.arc) * lineRadius; + + // line start + var orginX2 = Math.cos(item.arc) * radius; + var orginY2 = Math.sin(item.arc) * radius; + + // text start + var orginX3 = orginX1 >= 0 ? orginX1 + config.pieChartTextPadding : orginX1 - config.pieChartTextPadding; + var orginY3 = orginY1; + + var textWidth = measureText(item.text); + var startY = orginY3; + + if (lastTextObject && util.isSameXCoordinateArea(lastTextObject.start, { x: orginX3 })) { + if (orginX3 > 0) { + startY = Math.min(orginY3, lastTextObject.start.y); + } else if (orginX1 < 0) { + startY = Math.max(orginY3, lastTextObject.start.y); + } else { + if (orginY3 > 0) { + startY = Math.max(orginY3, lastTextObject.start.y); + } else { + startY = Math.min(orginY3, lastTextObject.start.y); + } + } + } + + if (orginX3 < 0) { + orginX3 -= textWidth; + } + + var textObject = { + lineStart: { + x: orginX2, + y: orginY2 + }, + lineEnd: { + x: orginX1, + y: orginY1 + }, + start: { + x: orginX3, + y: startY + }, + width: textWidth, + height: config.fontSize, + text: item.text, + color: item.color + }; + + lastTextObject = avoidCollision(textObject, lastTextObject); + textObjectCollection.push(lastTextObject); + }); + + textObjectCollection.forEach(function (item) { + var lineStartPoistion = convertCoordinateOrigin(item.lineStart.x, item.lineStart.y, center); + var lineEndPoistion = convertCoordinateOrigin(item.lineEnd.x, item.lineEnd.y, center); + var textPosition = convertCoordinateOrigin(item.start.x, item.start.y, center); + context.setLineWidth(1); + context.setFontSize(config.fontSize); + context.beginPath(); + context.setStrokeStyle(item.color); + context.setFillStyle(item.color); + context.moveTo(lineStartPoistion.x, lineStartPoistion.y); + var curveStartX = item.start.x < 0 ? textPosition.x + item.width : textPosition.x; + var textStartX = item.start.x < 0 ? textPosition.x - 5 : textPosition.x + 5; + context.quadraticCurveTo(lineEndPoistion.x, lineEndPoistion.y, curveStartX, textPosition.y); + context.moveTo(lineStartPoistion.x, lineStartPoistion.y); + context.stroke(); + context.closePath(); + context.beginPath(); + context.moveTo(textPosition.x + item.width, textPosition.y); + context.arc(curveStartX, textPosition.y, 2, 0, 2 * Math.PI); + context.closePath(); + context.fill(); + context.beginPath(); + context.setFillStyle('#666666'); + context.fillText(item.text, textStartX, textPosition.y + 3); + context.closePath(); + context.stroke(); + + context.closePath(); + }); +} + +function drawToolTipSplitLine(offsetX, opts, config, context) { + var startY = config.padding; + var endY = opts.height - config.padding - config.xAxisHeight - config.legendHeight; + context.beginPath(); + context.setStrokeStyle('#cccccc'); + context.setLineWidth(1); + context.moveTo(offsetX, startY); + context.lineTo(offsetX, endY); + context.stroke(); + context.closePath(); +} + +function drawToolTip(textList, offset, opts, config, context) { + var legendWidth = 4; + var legendMarginRight = 5; + var arrowWidth = 8; + var isOverRightBorder = false; + offset = assign({ + x: 0, + y: 0 + }, offset); + offset.y -= 8; + var textWidth = textList.map(function (item) { + return measureText(item.text); + }); + + var toolTipWidth = legendWidth + legendMarginRight + 4 * config.toolTipPadding + Math.max.apply(null, textWidth); + var toolTipHeight = 2 * config.toolTipPadding + textList.length * config.toolTipLineHeight; + + // if beyond the right border + if (offset.x - Math.abs(opts._scrollDistance_) + arrowWidth + toolTipWidth > opts.width) { + isOverRightBorder = true; + } + + // draw background rect + context.beginPath(); + context.setFillStyle(opts.tooltip.option.background || config.toolTipBackground); + context.setGlobalAlpha(config.toolTipOpacity); + if (isOverRightBorder) { + context.moveTo(offset.x, offset.y + 10); + context.lineTo(offset.x - arrowWidth, offset.y + 10 - 5); + context.lineTo(offset.x - arrowWidth, offset.y + 10 + 5); + context.moveTo(offset.x, offset.y + 10); + context.fillRect(offset.x - toolTipWidth - arrowWidth, offset.y, toolTipWidth, toolTipHeight); + } else { + context.moveTo(offset.x, offset.y + 10); + context.lineTo(offset.x + arrowWidth, offset.y + 10 - 5); + context.lineTo(offset.x + arrowWidth, offset.y + 10 + 5); + context.moveTo(offset.x, offset.y + 10); + context.fillRect(offset.x + arrowWidth, offset.y, toolTipWidth, toolTipHeight); + } + + context.closePath(); + context.fill(); + context.setGlobalAlpha(1); + + // draw legend + textList.forEach(function (item, index) { + context.beginPath(); + context.setFillStyle(item.color); + var startX = offset.x + arrowWidth + 2 * config.toolTipPadding; + var startY = offset.y + (config.toolTipLineHeight - config.fontSize) / 2 + config.toolTipLineHeight * index + config.toolTipPadding; + if (isOverRightBorder) { + startX = offset.x - toolTipWidth - arrowWidth + 2 * config.toolTipPadding; + } + context.fillRect(startX, startY, legendWidth, config.fontSize); + context.closePath(); + }); + + // draw text list + context.beginPath(); + context.setFontSize(config.fontSize); + context.setFillStyle('#ffffff'); + textList.forEach(function (item, index) { + var startX = offset.x + arrowWidth + 2 * config.toolTipPadding + legendWidth + legendMarginRight; + if (isOverRightBorder) { + startX = offset.x - toolTipWidth - arrowWidth + 2 * config.toolTipPadding + +legendWidth + legendMarginRight; + } + var startY = offset.y + (config.toolTipLineHeight - config.fontSize) / 2 + config.toolTipLineHeight * index + config.toolTipPadding; + context.fillText(item.text, startX, startY + config.fontSize); + }); + context.stroke(); + context.closePath(); +} + +function drawYAxisTitle(title, opts, config, context) { + var startX = config.xAxisHeight + (opts.height - config.xAxisHeight - measureText(title)) / 2; + context.save(); + context.beginPath(); + context.setFontSize(config.fontSize); + context.setFillStyle(opts.yAxis.titleFontColor || '#333333'); + context.translate(0, opts.height); + context.rotate(-90 * Math.PI / 180); + context.fillText(title, startX, config.padding + 0.5 * config.fontSize); + context.stroke(); + context.closePath(); + context.restore(); +} + +function drawColumnDataPoints(series, opts, config, context) { + var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1; + + var _calYAxisData = calYAxisData(series, opts, config), + ranges = _calYAxisData.ranges; + + var _getXAxisPoints = getXAxisPoints(opts.categories, opts, config), + xAxisPoints = _getXAxisPoints.xAxisPoints, + eachSpacing = _getXAxisPoints.eachSpacing; + + var minRange = ranges.pop(); + var maxRange = ranges.shift(); + context.save(); + if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) { + context.translate(opts._scrollDistance_, 0); + } + + series.forEach(function (eachSeries, seriesIndex) { + var data = eachSeries.data; + var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process); + points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts); + + // 绘制柱状数据图 + context.beginPath(); + context.setFillStyle(eachSeries.color); + points.forEach(function (item, index) { + if (item !== null) { + var startX = item.x - item.width / 2 + 1; + var height = opts.height - item.y - config.padding - config.xAxisHeight - config.legendHeight; + context.moveTo(startX, item.y); + context.rect(startX, item.y, item.width - 2, height); + } + }); + context.closePath(); + context.fill(); + }); + series.forEach(function (eachSeries, seriesIndex) { + var data = eachSeries.data; + var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process); + points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts); + if (opts.dataLabel !== false && process === 1) { + drawPointText(points, eachSeries, config, context); + } + }); + context.restore(); + return { + xAxisPoints: xAxisPoints, + eachSpacing: eachSpacing + }; +} + +function drawAreaDataPoints(series, opts, config, context) { + var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1; + + var _calYAxisData2 = calYAxisData(series, opts, config), + ranges = _calYAxisData2.ranges; + + var _getXAxisPoints2 = getXAxisPoints(opts.categories, opts, config), + xAxisPoints = _getXAxisPoints2.xAxisPoints, + eachSpacing = _getXAxisPoints2.eachSpacing; + + var minRange = ranges.pop(); + var maxRange = ranges.shift(); + var endY = opts.height - config.padding - config.xAxisHeight - config.legendHeight; + var calPoints = []; + + context.save(); + if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) { + context.translate(opts._scrollDistance_, 0); + } + + if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) { + drawToolTipSplitLine(opts.tooltip.offset.x, opts, config, context); + } + + series.forEach(function (eachSeries, seriesIndex) { + var data = eachSeries.data; + var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process); + calPoints.push(points); + + var splitPointList = splitPoints(points); + + splitPointList.forEach(function (points) { + // 绘制区域数据 + context.beginPath(); + context.setStrokeStyle(eachSeries.color); + context.setFillStyle(eachSeries.color); + context.setGlobalAlpha(0.6); + context.setLineWidth(2); + if (points.length > 1) { + var firstPoint = points[0]; + var lastPoint = points[points.length - 1]; + + context.moveTo(firstPoint.x, firstPoint.y); + if (opts.extra.lineStyle === 'curve') { + points.forEach(function (item, index) { + if (index > 0) { + var ctrlPoint = createCurveControlPoints(points, index - 1); + context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y); + } + }); + } else { + points.forEach(function (item, index) { + if (index > 0) { + context.lineTo(item.x, item.y); + } + }); + } + + context.lineTo(lastPoint.x, endY); + context.lineTo(firstPoint.x, endY); + context.lineTo(firstPoint.x, firstPoint.y); + } else { + var item = points[0]; + context.moveTo(item.x - eachSpacing / 2, item.y); + context.lineTo(item.x + eachSpacing / 2, item.y); + context.lineTo(item.x + eachSpacing / 2, endY); + context.lineTo(item.x - eachSpacing / 2, endY); + context.moveTo(item.x - eachSpacing / 2, item.y); + } + context.closePath(); + context.fill(); + context.setGlobalAlpha(1); + }); + + if (opts.dataPointShape !== false) { + var shape = config.dataPointShape[seriesIndex % config.dataPointShape.length]; + drawPointShape(points, eachSeries.color, shape, context); + } + }); + if (opts.dataLabel !== false && process === 1) { + series.forEach(function (eachSeries, seriesIndex) { + var data = eachSeries.data; + var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process); + drawPointText(points, eachSeries, config, context); + }); + } + + context.restore(); + + return { + xAxisPoints: xAxisPoints, + calPoints: calPoints, + eachSpacing: eachSpacing + }; +} + +function drawLineDataPoints(series, opts, config, context) { + var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1; + + var _calYAxisData3 = calYAxisData(series, opts, config), + ranges = _calYAxisData3.ranges; + + var _getXAxisPoints3 = getXAxisPoints(opts.categories, opts, config), + xAxisPoints = _getXAxisPoints3.xAxisPoints, + eachSpacing = _getXAxisPoints3.eachSpacing; + + var minRange = ranges.pop(); + var maxRange = ranges.shift(); + var calPoints = []; + + context.save(); + if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) { + context.translate(opts._scrollDistance_, 0); + } + + if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) { + drawToolTipSplitLine(opts.tooltip.offset.x, opts, config, context); + } + + series.forEach(function (eachSeries, seriesIndex) { + var data = eachSeries.data; + var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process); + calPoints.push(points); + var splitPointList = splitPoints(points); + + splitPointList.forEach(function (points, index) { + context.beginPath(); + context.setStrokeStyle(eachSeries.color); + context.setLineWidth(2); + if (points.length === 1) { + context.moveTo(points[0].x, points[0].y); + context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI); + } else { + context.moveTo(points[0].x, points[0].y); + if (opts.extra.lineStyle === 'curve') { + points.forEach(function (item, index) { + if (index > 0) { + var ctrlPoint = createCurveControlPoints(points, index - 1); + context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y); + } + }); + } else { + points.forEach(function (item, index) { + if (index > 0) { + context.lineTo(item.x, item.y); + } + }); + } + context.moveTo(points[0].x, points[0].y); + } + context.closePath(); + context.stroke(); + }); + + if (opts.dataPointShape !== false) { + var shape = config.dataPointShape[seriesIndex % config.dataPointShape.length]; + drawPointShape(points, eachSeries.color, shape, context); + } + }); + if (opts.dataLabel !== false && process === 1) { + series.forEach(function (eachSeries, seriesIndex) { + var data = eachSeries.data; + var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process); + drawPointText(points, eachSeries, config, context); + }); + } + + context.restore(); + + return { + xAxisPoints: xAxisPoints, + calPoints: calPoints, + eachSpacing: eachSpacing + }; +} + +function drawToolTipBridge(opts, config, context, process) { + context.save(); + if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) { + context.translate(opts._scrollDistance_, 0); + } + if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) { + drawToolTip(opts.tooltip.textList, opts.tooltip.offset, opts, config, context); + } + context.restore(); +} + +function drawXAxis(categories, opts, config, context) { + var _getXAxisPoints4 = getXAxisPoints(categories, opts, config), + xAxisPoints = _getXAxisPoints4.xAxisPoints, + startX = _getXAxisPoints4.startX, + endX = _getXAxisPoints4.endX, + eachSpacing = _getXAxisPoints4.eachSpacing; + + var startY = opts.height - config.padding - config.xAxisHeight - config.legendHeight; + var endY = startY + config.xAxisLineHeight; + + context.save(); + if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) { + context.translate(opts._scrollDistance_, 0); + } + + context.beginPath(); + context.setStrokeStyle(opts.xAxis.gridColor || "#cccccc"); + + if (opts.xAxis.disableGrid !== true) { + if (opts.xAxis.type === 'calibration') { + xAxisPoints.forEach(function (item, index) { + if (index > 0) { + context.moveTo(item - eachSpacing / 2, startY); + context.lineTo(item - eachSpacing / 2, startY + 4); + } + }); + } else { + xAxisPoints.forEach(function (item, index) { + context.moveTo(item, startY); + context.lineTo(item, endY); + }); + } + } + context.closePath(); + context.stroke(); + + // 对X轴列表做抽稀处理 + var validWidth = opts.width - 2 * config.padding - config.yAxisWidth - config.yAxisTitleWidth; + var maxXAxisListLength = Math.min(categories.length, Math.ceil(validWidth / config.fontSize / 1.5)); + var ratio = Math.ceil(categories.length / maxXAxisListLength); + + categories = categories.map(function (item, index) { + return index % ratio !== 0 ? '' : item; + }); + + if (config._xAxisTextAngle_ === 0) { + context.beginPath(); + context.setFontSize(config.fontSize); + context.setFillStyle(opts.xAxis.fontColor || '#666666'); + categories.forEach(function (item, index) { + var offset = eachSpacing / 2 - measureText(item) / 2; + context.fillText(item, xAxisPoints[index] + offset, startY + config.fontSize + 5); + }); + context.closePath(); + context.stroke(); + } else { + categories.forEach(function (item, index) { + context.save(); + context.beginPath(); + context.setFontSize(config.fontSize); + context.setFillStyle(opts.xAxis.fontColor || '#666666'); + var textWidth = measureText(item); + var offset = eachSpacing / 2 - textWidth; + + var _calRotateTranslate = calRotateTranslate(xAxisPoints[index] + eachSpacing / 2, startY + config.fontSize / 2 + 5, opts.height), + transX = _calRotateTranslate.transX, + transY = _calRotateTranslate.transY; + + context.rotate(-1 * config._xAxisTextAngle_); + context.translate(transX, transY); + context.fillText(item, xAxisPoints[index] + offset, startY + config.fontSize + 5); + context.closePath(); + context.stroke(); + context.restore(); + }); + } + + context.restore(); +} + +function drawYAxisGrid(opts, config, context) { + var spacingValid = opts.height - 2 * config.padding - config.xAxisHeight - config.legendHeight; + var eachSpacing = Math.floor(spacingValid / config.yAxisSplit); + var yAxisTotalWidth = config.yAxisWidth + config.yAxisTitleWidth; + var startX = config.padding + yAxisTotalWidth; + var endX = opts.width - config.padding; + + var points = []; + for (var i = 0; i < config.yAxisSplit; i++) { + points.push(config.padding + eachSpacing * i); + } + points.push(config.padding + eachSpacing * config.yAxisSplit + 2); + + context.beginPath(); + context.setStrokeStyle(opts.yAxis.gridColor || "#cccccc"); + context.setLineWidth(1); + points.forEach(function (item, index) { + context.moveTo(startX, item); + context.lineTo(endX, item); + }); + context.closePath(); + context.stroke(); +} + +function drawYAxis(series, opts, config, context) { + if (opts.yAxis.disabled === true) { + return; + } + + var _calYAxisData4 = calYAxisData(series, opts, config), + rangesFormat = _calYAxisData4.rangesFormat; + + var yAxisTotalWidth = config.yAxisWidth + config.yAxisTitleWidth; + + var spacingValid = opts.height - 2 * config.padding - config.xAxisHeight - config.legendHeight; + var eachSpacing = Math.floor(spacingValid / config.yAxisSplit); + var startX = config.padding + yAxisTotalWidth; + var endX = opts.width - config.padding; + var endY = opts.height - config.padding - config.xAxisHeight - config.legendHeight; + + // set YAxis background + context.setFillStyle(opts.background || '#ffffff'); + if (opts._scrollDistance_ < 0) { + context.fillRect(0, 0, startX, endY + config.xAxisHeight + 5); + } + context.fillRect(endX, 0, opts.width, endY + config.xAxisHeight + 5); + + var points = []; + for (var i = 0; i <= config.yAxisSplit; i++) { + points.push(config.padding + eachSpacing * i); + } + + context.stroke(); + context.beginPath(); + context.setFontSize(config.fontSize); + context.setFillStyle(opts.yAxis.fontColor || '#666666'); + rangesFormat.forEach(function (item, index) { + var pos = points[index] ? points[index] : endY; + context.fillText(item, config.padding + config.yAxisTitleWidth, pos + config.fontSize / 2); + }); + context.closePath(); + context.stroke(); + + if (opts.yAxis.title) { + drawYAxisTitle(opts.yAxis.title, opts, config, context); + } +} + +function drawLegend(series, opts, config, context) { + if (!opts.legend) { + return; + } + // each legend shape width 15px + // the spacing between shape and text in each legend is the `padding` + // each legend spacing is the `padding` + // legend margin top `config.padding` + + var _calLegendData = calLegendData(series, opts, config), + legendList = _calLegendData.legendList; + + var padding = 5; + var marginTop = 8; + var shapeWidth = 15; + legendList.forEach(function (itemList, listIndex) { + var width = 0; + itemList.forEach(function (item) { + item.name = item.name || 'undefined'; + width += 3 * padding + measureText(item.name) + shapeWidth; + }); + var startX = (opts.width - width) / 2 + padding; + var startY = opts.height - config.padding - config.legendHeight + listIndex * (config.fontSize + marginTop) + padding + marginTop; + + context.setFontSize(config.fontSize); + itemList.forEach(function (item) { + switch (opts.type) { + case 'line': + context.beginPath(); + context.setLineWidth(1); + context.setStrokeStyle(item.color); + context.moveTo(startX - 2, startY + 5); + context.lineTo(startX + 17, startY + 5); + context.stroke(); + context.closePath(); + context.beginPath(); + context.setLineWidth(1); + context.setStrokeStyle('#ffffff'); + context.setFillStyle(item.color); + context.moveTo(startX + 7.5, startY + 5); + context.arc(startX + 7.5, startY + 5, 4, 0, 2 * Math.PI); + context.fill(); + context.stroke(); + context.closePath(); + break; + case 'pie': + case 'ring': + context.beginPath(); + context.setFillStyle(item.color); + context.moveTo(startX + 7.5, startY + 5); + context.arc(startX + 7.5, startY + 5, 7, 0, 2 * Math.PI); + context.closePath(); + context.fill(); + break; + default: + context.beginPath(); + context.setFillStyle(item.color); + context.moveTo(startX, startY); + context.rect(startX, startY, 15, 10); + context.closePath(); + context.fill(); + } + startX += padding + shapeWidth; + context.beginPath(); + context.setFillStyle(opts.extra.legendTextColor || '#333333'); + context.fillText(item.name, startX, startY + 9); + context.closePath(); + context.stroke(); + startX += measureText(item.name) + 2 * padding; + }); + }); +} +function drawPieDataPoints(series, opts, config, context) { + var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1; + + var pieOption = opts.extra.pie || {}; + series = getPieDataPoints(series, process); + var centerPosition = { + x: opts.width / 2, + y: (opts.height - config.legendHeight) / 2 + }; + var radius = Math.min(centerPosition.x - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, centerPosition.y - config.pieChartLinePadding - config.pieChartTextPadding); + if (opts.dataLabel) { + radius -= 10; + } else { + radius -= 2 * config.padding; + } + series = series.map(function (eachSeries) { + eachSeries._start_ += (pieOption.offsetAngle || 0) * Math.PI / 180; + return eachSeries; + }); + series.forEach(function (eachSeries) { + context.beginPath(); + context.setLineWidth(2); + context.setStrokeStyle('#ffffff'); + context.setFillStyle(eachSeries.color); + context.moveTo(centerPosition.x, centerPosition.y); + context.arc(centerPosition.x, centerPosition.y, radius, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI); + context.closePath(); + context.fill(); + if (opts.disablePieStroke !== true) { + context.stroke(); + } + }); + + if (opts.type === 'ring') { + var innerPieWidth = radius * 0.6; + if (typeof opts.extra.ringWidth === 'number' && opts.extra.ringWidth > 0) { + innerPieWidth = Math.max(0, radius - opts.extra.ringWidth); + } + context.beginPath(); + context.setFillStyle(opts.background || '#ffffff'); + context.moveTo(centerPosition.x, centerPosition.y); + context.arc(centerPosition.x, centerPosition.y, innerPieWidth, 0, 2 * Math.PI); + context.closePath(); + context.fill(); + } + + if (opts.dataLabel !== false && process === 1) { + // fix https://github.com/xiaolin3303/wx-charts/issues/132 + var valid = false; + for (var i = 0, len = series.length; i < len; i++) { + if (series[i].data > 0) { + valid = true; + break; + } + } + + if (valid) { + drawPieText(series, opts, config, context, radius, centerPosition); + } + } + + if (process === 1 && opts.type === 'ring') { + drawRingTitle(opts, config, context); + } + + return { + center: centerPosition, + radius: radius, + series: series + }; +} + +function drawRadarDataPoints(series, opts, config, context) { + var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1; + + var radarOption = opts.extra.radar || {}; + var coordinateAngle = getRadarCoordinateSeries(opts.categories.length); + var centerPosition = { + x: opts.width / 2, + y: (opts.height - config.legendHeight) / 2 + }; + + var radius = Math.min(centerPosition.x - (getMaxTextListLength(opts.categories) + config.radarLabelTextMargin), centerPosition.y - config.radarLabelTextMargin); + + radius -= config.padding; + + // draw grid + context.beginPath(); + context.setLineWidth(1); + context.setStrokeStyle(radarOption.gridColor || "#cccccc"); + coordinateAngle.forEach(function (angle) { + var pos = convertCoordinateOrigin(radius * Math.cos(angle), radius * Math.sin(angle), centerPosition); + context.moveTo(centerPosition.x, centerPosition.y); + context.lineTo(pos.x, pos.y); + }); + context.stroke(); + context.closePath(); + + // draw split line grid + + var _loop = function _loop(i) { + var startPos = {}; + context.beginPath(); + context.setLineWidth(1); + context.setStrokeStyle(radarOption.gridColor || "#cccccc"); + coordinateAngle.forEach(function (angle, index) { + var pos = convertCoordinateOrigin(radius / config.radarGridCount * i * Math.cos(angle), radius / config.radarGridCount * i * Math.sin(angle), centerPosition); + if (index === 0) { + startPos = pos; + context.moveTo(pos.x, pos.y); + } else { + context.lineTo(pos.x, pos.y); + } + }); + context.lineTo(startPos.x, startPos.y); + context.stroke(); + context.closePath(); + }; + + for (var i = 1; i <= config.radarGridCount; i++) { + _loop(i); + } + + var radarDataPoints = getRadarDataPoints(coordinateAngle, centerPosition, radius, series, opts, process); + radarDataPoints.forEach(function (eachSeries, seriesIndex) { + // 绘制区域数据 + context.beginPath(); + context.setFillStyle(eachSeries.color); + context.setGlobalAlpha(0.6); + eachSeries.data.forEach(function (item, index) { + if (index === 0) { + context.moveTo(item.position.x, item.position.y); + } else { + context.lineTo(item.position.x, item.position.y); + } + }); + context.closePath(); + context.fill(); + context.setGlobalAlpha(1); + + if (opts.dataPointShape !== false) { + var shape = config.dataPointShape[seriesIndex % config.dataPointShape.length]; + var points = eachSeries.data.map(function (item) { + return item.position; + }); + drawPointShape(points, eachSeries.color, shape, context); + } + }); + // draw label text + drawRadarLabel(coordinateAngle, radius, centerPosition, opts, config, context); + + return { + center: centerPosition, + radius: radius, + angleList: coordinateAngle + }; +} + +function drawCanvas(opts, context) { + context.draw(); +} + +var Timing = { + easeIn: function easeIn(pos) { + return Math.pow(pos, 3); + }, + + easeOut: function easeOut(pos) { + return Math.pow(pos - 1, 3) + 1; + }, + + easeInOut: function easeInOut(pos) { + if ((pos /= 0.5) < 1) { + return 0.5 * Math.pow(pos, 3); + } else { + return 0.5 * (Math.pow(pos - 2, 3) + 2); + } + }, + + linear: function linear(pos) { + return pos; + } +}; + +function Animation(opts) { + this.isStop = false; + opts.duration = typeof opts.duration === 'undefined' ? 1000 : opts.duration; + opts.timing = opts.timing || 'linear'; + + var delay = 17; + + var createAnimationFrame = function createAnimationFrame() { + if (typeof requestAnimationFrame !== 'undefined') { + return requestAnimationFrame; + } else if (typeof setTimeout !== 'undefined') { + return function (step, delay) { + setTimeout(function () { + var timeStamp = +new Date(); + step(timeStamp); + }, delay); + }; + } else { + return function (step) { + step(null); + }; + } + }; + var animationFrame = createAnimationFrame(); + var startTimeStamp = null; + var _step = function step(timestamp) { + if (timestamp === null || this.isStop === true) { + opts.onProcess && opts.onProcess(1); + opts.onAnimationFinish && opts.onAnimationFinish(); + return; + } + if (startTimeStamp === null) { + startTimeStamp = timestamp; + } + if (timestamp - startTimeStamp < opts.duration) { + var process = (timestamp - startTimeStamp) / opts.duration; + var timingFunction = Timing[opts.timing]; + process = timingFunction(process); + opts.onProcess && opts.onProcess(process); + animationFrame(_step, delay); + } else { + opts.onProcess && opts.onProcess(1); + opts.onAnimationFinish && opts.onAnimationFinish(); + } + }; + _step = _step.bind(this); + + animationFrame(_step, delay); +} + +// stop animation immediately +// and tigger onAnimationFinish +Animation.prototype.stop = function () { + this.isStop = true; +}; + +function drawCharts(type, opts, config, context) { + var _this = this; + + var series = opts.series; + var categories = opts.categories; + series = fillSeriesColor(series, config); + + var _calLegendData = calLegendData(series, opts, config), + legendHeight = _calLegendData.legendHeight; + + config.legendHeight = legendHeight; + + var _calYAxisData = calYAxisData(series, opts, config), + yAxisWidth = _calYAxisData.yAxisWidth; + + config.yAxisWidth = yAxisWidth; + if (categories && categories.length) { + var _calCategoriesData = calCategoriesData(categories, opts, config), + xAxisHeight = _calCategoriesData.xAxisHeight, + angle = _calCategoriesData.angle; + + config.xAxisHeight = xAxisHeight; + config._xAxisTextAngle_ = angle; + } + if (type === 'pie' || type === 'ring') { + config._pieTextMaxLength_ = opts.dataLabel === false ? 0 : getPieTextMaxLength(series); + } + + var duration = opts.animation ? 1000 : 0; + this.animationInstance && this.animationInstance.stop(); + switch (type) { + case 'line': + this.animationInstance = new Animation({ + timing: 'easeIn', + duration: duration, + onProcess: function onProcess(process) { + drawYAxisGrid(opts, config, context); + + var _drawLineDataPoints = drawLineDataPoints(series, opts, config, context, process), + xAxisPoints = _drawLineDataPoints.xAxisPoints, + calPoints = _drawLineDataPoints.calPoints, + eachSpacing = _drawLineDataPoints.eachSpacing; + + _this.chartData.xAxisPoints = xAxisPoints; + _this.chartData.calPoints = calPoints; + _this.chartData.eachSpacing = eachSpacing; + drawXAxis(categories, opts, config, context); + drawLegend(opts.series, opts, config, context); + drawYAxis(series, opts, config, context); + drawToolTipBridge(opts, config, context, process); + drawCanvas(opts, context); + }, + onAnimationFinish: function onAnimationFinish() { + _this.event.trigger('renderComplete'); + } + }); + break; + case 'column': + this.animationInstance = new Animation({ + timing: 'easeIn', + duration: duration, + onProcess: function onProcess(process) { + drawYAxisGrid(opts, config, context); + + var _drawColumnDataPoints = drawColumnDataPoints(series, opts, config, context, process), + xAxisPoints = _drawColumnDataPoints.xAxisPoints, + eachSpacing = _drawColumnDataPoints.eachSpacing; + + _this.chartData.xAxisPoints = xAxisPoints; + _this.chartData.eachSpacing = eachSpacing; + drawXAxis(categories, opts, config, context); + drawLegend(opts.series, opts, config, context); + drawYAxis(series, opts, config, context); + drawCanvas(opts, context); + }, + onAnimationFinish: function onAnimationFinish() { + _this.event.trigger('renderComplete'); + } + }); + break; + case 'area': + this.animationInstance = new Animation({ + timing: 'easeIn', + duration: duration, + onProcess: function onProcess(process) { + drawYAxisGrid(opts, config, context); + + var _drawAreaDataPoints = drawAreaDataPoints(series, opts, config, context, process), + xAxisPoints = _drawAreaDataPoints.xAxisPoints, + calPoints = _drawAreaDataPoints.calPoints, + eachSpacing = _drawAreaDataPoints.eachSpacing; + + _this.chartData.xAxisPoints = xAxisPoints; + _this.chartData.calPoints = calPoints; + _this.chartData.eachSpacing = eachSpacing; + drawXAxis(categories, opts, config, context); + drawLegend(opts.series, opts, config, context); + drawYAxis(series, opts, config, context); + drawToolTipBridge(opts, config, context, process); + drawCanvas(opts, context); + }, + onAnimationFinish: function onAnimationFinish() { + _this.event.trigger('renderComplete'); + } + }); + break; + case 'ring': + case 'pie': + this.animationInstance = new Animation({ + timing: 'easeInOut', + duration: duration, + onProcess: function onProcess(process) { + _this.chartData.pieData = drawPieDataPoints(series, opts, config, context, process); + drawLegend(opts.series, opts, config, context); + drawCanvas(opts, context); + }, + onAnimationFinish: function onAnimationFinish() { + _this.event.trigger('renderComplete'); + } + }); + break; + case 'radar': + this.animationInstance = new Animation({ + timing: 'easeInOut', + duration: duration, + onProcess: function onProcess(process) { + _this.chartData.radarData = drawRadarDataPoints(series, opts, config, context, process); + drawLegend(opts.series, opts, config, context); + drawCanvas(opts, context); + }, + onAnimationFinish: function onAnimationFinish() { + _this.event.trigger('renderComplete'); + } + }); + break; + } +} + +// simple event implement + +function Event() { + this.events = {}; +} + +Event.prototype.addEventListener = function (type, listener) { + this.events[type] = this.events[type] || []; + this.events[type].push(listener); +}; + +Event.prototype.trigger = function () { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var type = args[0]; + var params = args.slice(1); + if (!!this.events[type]) { + this.events[type].forEach(function (listener) { + try { + listener.apply(null, params); + } catch (e) { + console.error(e); + } + }); + } +}; + +var Charts = function Charts(opts) { + opts.title = opts.title || {}; + opts.subtitle = opts.subtitle || {}; + opts.yAxis = opts.yAxis || {}; + opts.xAxis = opts.xAxis || {}; + opts.extra = opts.extra || {}; + opts.legend = opts.legend === false ? false : true; + opts.animation = opts.animation === false ? false : true; + var config$$1 = assign({}, config); + config$$1.yAxisTitleWidth = opts.yAxis.disabled !== true && opts.yAxis.title ? config$$1.yAxisTitleWidth : 0; + config$$1.pieChartLinePadding = opts.dataLabel === false ? 0 : config$$1.pieChartLinePadding; + config$$1.pieChartTextPadding = opts.dataLabel === false ? 0 : config$$1.pieChartTextPadding; + + this.opts = opts; + this.config = config$$1; + this.context = wx.createCanvasContext(opts.canvasId); + // store calcuated chart data + // such as chart point coordinate + this.chartData = {}; + this.event = new Event(); + this.scrollOption = { + currentOffset: 0, + startTouchX: 0, + distance: 0 + }; + + drawCharts.call(this, opts.type, opts, config$$1, this.context); +}; + +Charts.prototype.updateData = function () { + var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.opts.series = data.series || this.opts.series; + this.opts.categories = data.categories || this.opts.categories; + + this.opts.title = assign({}, this.opts.title, data.title || {}); + this.opts.subtitle = assign({}, this.opts.subtitle, data.subtitle || {}); + + drawCharts.call(this, this.opts.type, this.opts, this.config, this.context); +}; + +Charts.prototype.stopAnimation = function () { + this.animationInstance && this.animationInstance.stop(); +}; + +Charts.prototype.addEventListener = function (type, listener) { + this.event.addEventListener(type, listener); +}; + +Charts.prototype.getCurrentDataIndex = function (e) { + var touches = e.touches && e.touches.length ? e.touches : e.changedTouches; + if (touches && touches.length) { + var _touches$ = touches[0], + x = _touches$.x, + y = _touches$.y; + + if (this.opts.type === 'pie' || this.opts.type === 'ring') { + return findPieChartCurrentIndex({ x: x, y: y }, this.chartData.pieData); + } else if (this.opts.type === 'radar') { + return findRadarChartCurrentIndex({ x: x, y: y }, this.chartData.radarData, this.opts.categories.length); + } else { + return findCurrentIndex({ x: x, y: y }, this.chartData.xAxisPoints, this.opts, this.config, Math.abs(this.scrollOption.currentOffset)); + } + } + return -1; +}; + +Charts.prototype.showToolTip = function (e) { + var option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + if (this.opts.type === 'line' || this.opts.type === 'area') { + var index = this.getCurrentDataIndex(e); + var currentOffset = this.scrollOption.currentOffset; + + var opts = assign({}, this.opts, { + _scrollDistance_: currentOffset, + animation: false + }); + if (index > -1) { + var seriesData = getSeriesDataItem(this.opts.series, index); + if (seriesData.length === 0) { + drawCharts.call(this, opts.type, opts, this.config, this.context); + } else { + var _getToolTipData = getToolTipData(seriesData, this.chartData.calPoints, index, this.opts.categories, option), + textList = _getToolTipData.textList, + offset = _getToolTipData.offset; + + opts.tooltip = { + textList: textList, + offset: offset, + option: option + }; + drawCharts.call(this, opts.type, opts, this.config, this.context); + } + } else { + drawCharts.call(this, opts.type, opts, this.config, this.context); + } + } +}; + +Charts.prototype.scrollStart = function (e) { + if (e.touches[0] && this.opts.enableScroll === true) { + this.scrollOption.startTouchX = e.touches[0].x; + } +}; + +Charts.prototype.scroll = function (e) { + // TODO throtting... + if (e.touches[0] && this.opts.enableScroll === true) { + var _distance = e.touches[0].x - this.scrollOption.startTouchX; + var currentOffset = this.scrollOption.currentOffset; + + var validDistance = calValidDistance(currentOffset + _distance, this.chartData, this.config, this.opts); + + this.scrollOption.distance = _distance = validDistance - currentOffset; + var opts = assign({}, this.opts, { + _scrollDistance_: currentOffset + _distance, + animation: false + }); + + drawCharts.call(this, opts.type, opts, this.config, this.context); + } +}; + +Charts.prototype.scrollEnd = function (e) { + if (this.opts.enableScroll === true) { + var _scrollOption = this.scrollOption, + currentOffset = _scrollOption.currentOffset, + distance = _scrollOption.distance; + + this.scrollOption.currentOffset = currentOffset + distance; + this.scrollOption.distance = 0; + } +}; + +module.exports = Charts; diff --git a/tools/Mini_Program/qcloud_device_linkage_demo/project.config.json b/tools/Mini_Program/qcloud_device_linkage_demo/project.config.json new file mode 100644 index 00000000..f909d1de --- /dev/null +++ b/tools/Mini_Program/qcloud_device_linkage_demo/project.config.json @@ -0,0 +1,52 @@ +{ + "miniprogramRoot": "miniprogram/", + "cloudfunctionRoot": "cloudfunctions/", + "setting": { + "urlCheck": false, + "es6": true, + "postcss": true, + "minified": true, + "newFeature": true, + "coverView": true, + "nodeModules": true, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "scopeDataCheck": false, + "checkInvalidKey": true, + "checkSiteMap": true, + "uploadWithSourceMap": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "enhance": true + }, + "appid": "wx394efa031612f9b5", + "projectname": "qcloud_device_linkage_demo", + "libVersion": "2.7.7", + "simulatorType": "wechat", + "simulatorPluginLibVersion": {}, + "cloudfunctionTemplateRoot": "cloudfunctionTemplate", + "compileType": "miniprogram", + "condition": { + "search": { + "current": -1, + "list": [] + }, + "conversation": { + "current": -1, + "list": [] + }, + "plugin": { + "current": -1, + "list": [] + }, + "game": { + "list": [] + }, + "miniprogram": { + "current": 0 + } + } +} \ No newline at end of file