├── .npmignore ├── .gitignore ├── html ├── clock.html ├── cursor.html ├── sudoku.html ├── coderain.html ├── flydots.html ├── circletext.html ├── the_last_janvas.html ├── taichi.html ├── about_edge.html ├── about_wheel.html ├── beziermaker.html ├── stats.html ├── about_antv_performance_test.html ├── hello_world.html └── img │ └── complex.svg ├── package.json ├── LICENSE ├── js ├── circletext.js ├── coordinate.js ├── aboutedge.js ├── aboutwheel.js ├── cursor.js ├── flydots.js ├── stats.js ├── taichi.js ├── clock.js ├── coderain.js ├── antv.js ├── beziermaker.js ├── thelastjanvas.js └── sudoku.js ├── README.md └── dist └── janvasexamples.min.js /.npmignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /node_modules 3 | 4 | -------------------------------------------------------------------------------- /html/clock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Clock 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /html/cursor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cursor 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /html/sudoku.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sudoku 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /html/coderain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CodeRain 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /html/flydots.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FlyDots 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /html/circletext.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CircleText 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /html/the_last_janvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TheLastJanvas 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /html/taichi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TaiChi 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /html/about_edge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About Edge 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /html/about_wheel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About Wheel 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /html/beziermaker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BezierMaker 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "janvasexamples", 3 | "version": "1.4.0", 4 | "description": "Examples created with janvas.", 5 | "main": "dist/janvasexamples.min.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/JarenChow/JanvasExamples.git" 12 | }, 13 | "keywords": [ 14 | "janvas", 15 | "examples", 16 | "javascript", 17 | "canvas", 18 | "svg", 19 | "2d" 20 | ], 21 | "author": "JarenChow", 22 | "license": "MIT", 23 | "dependencies": { 24 | "janvas": "2.9.7" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/JarenChow/JanvasExamples/issues" 28 | }, 29 | "homepage": "https://github.com/JarenChow/JanvasExamples#readme" 30 | } 31 | -------------------------------------------------------------------------------- /html/stats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stats 7 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 JarenChow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /html/about_antv_performance_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AboutAntVPerformanceTest 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 25 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /js/circletext.js: -------------------------------------------------------------------------------- 1 | var circleText = new janvas.Canvas({ 2 | container: "#app", 3 | duration: 2000, 4 | props: { 5 | back: void (0), 6 | str: "HELLOJANVAS", 7 | texts: [], 8 | length: 0, 9 | ease: janvas.Utils.ease.inout.bounce 10 | }, 11 | methods: { 12 | init: function () { 13 | this.back = new janvas.Rect(this.$ctx, 0, 0); 14 | this.back.getStyle().setFillStyle("gold"); 15 | this.setText(this.str); 16 | }, 17 | resize: function () { 18 | var w = this.$width, h = this.$height; 19 | this.back.setWidth(w).setHeight(h); 20 | for (var i = 0; i < this.length; i++) { 21 | this.texts[i].init(w / 2, h / 2 - Math.min(w, h) * 0.309, w / 2, h / 2); 22 | } 23 | }, 24 | setText: function (textString) { 25 | var w = this.$width, h = this.$height; 26 | this.length = textString.length; 27 | while (this.texts.length < this.length) { 28 | var text = new janvas.Text(this.$ctx).init(w / 2, h / 2 - Math.min(w, h) * 0.309, w / 2, h / 2); 29 | text.getStyle().setFont("small-caps bold 8em courier") 30 | .setTextAlign("center").setTextBaseline("middle"); 31 | this.texts.push(text); 32 | } 33 | for (var i = 0; i < this.length; i++) { 34 | this.texts[i].setText(textString[i]).targetAngle = Math.PI * 2 * i / this.length; 35 | } 36 | this.$raf.start(); 37 | }, 38 | update: function (ts) { 39 | for (var i = 0; i < this.length; i++) { 40 | var text = this.texts[i]; 41 | text.getMatrix().setAngle(text.targetAngle * this.ease(ts / this.$duration)); 42 | } 43 | }, 44 | draw: function () { 45 | this.back.fill(); 46 | for (var i = 0; i < this.length; i++) this.texts[i].fill(); 47 | } 48 | }, 49 | events: { 50 | keydown: function (ev) { 51 | var key = ev.key; 52 | if (key === "Backspace") this.str = this.str.substring(0, this.str.length - 1); 53 | else if (key.length === 1) this.str += key.toUpperCase(); 54 | this.setText(this.str); 55 | } 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /html/hello_world.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello World 7 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /js/coordinate.js: -------------------------------------------------------------------------------- 1 | var coordinate = new janvas.Canvas({ 2 | container: "#app", 3 | props: { 4 | _backgroundColor: "rgb(237, 237, 237)", 5 | _font: "12px Consolas", 6 | _span: 50, 7 | _dash: [10, 5], 8 | _dashColor: "rgba(0, 0, 0, 0.2)" 9 | }, 10 | methods: { 11 | init: function () { 12 | this.background = new janvas.Rect(this.$ctx, 0, 0, 0, 0); 13 | this.xAxis = new janvas.Line(this.$ctx, 0, 0, 0, 0); 14 | this.yAxis = new janvas.Line(this.$ctx, 0, 0, 0, 0); 15 | this.xLines = []; 16 | this.xTexts = []; 17 | this.yLines = []; 18 | this.yTexts = []; 19 | this.oText = new janvas.Text(this.$ctx, 0, 0, "0"); 20 | }, 21 | draw: function () { 22 | this.background.fill(); 23 | this.xAxis.stroke(); 24 | this.yAxis.stroke(); 25 | this.xLines.forEach(function (line) { 26 | line.stroke(); 27 | }); 28 | this.yLines.forEach(function (line) { 29 | line.stroke(); 30 | }); 31 | this.xTexts.forEach(function (text) { 32 | text.fill(); 33 | }); 34 | this.yTexts.forEach(function (text) { 35 | text.fill(); 36 | }); 37 | this.oText.fill(); 38 | } 39 | }, 40 | events: { 41 | resize: function () { 42 | this.background.setWidth(this.$width).setHeight(this.$height); 43 | this.xAxis.setEnd(this.$width, 0); 44 | this.yAxis.setEnd(0, this.$height); 45 | this.adjustLength(Math.floor(this.$width / this._span - 0.2), this.xTexts, this.xLines, true); 46 | this.adjustLength(Math.floor(this.$height / this._span - 0.2), this.yTexts, this.yLines, false); 47 | this.setStyles(); 48 | } 49 | }, 50 | functions: { 51 | setStyles: function () { 52 | this.background.getStyle().setFillStyle(this._backgroundColor); 53 | this.oText.getStyle().setFont(this._font).setTextAlign("left").setTextBaseline("top"); 54 | this.xLines.forEach(function (line) { 55 | line.getStyle().setStrokeStyle(this._dashColor).setLineDash(this._dash); 56 | }, this); 57 | this.yLines.forEach(function (line) { 58 | line.getStyle().setStrokeStyle(this._dashColor).setLineDash(this._dash); 59 | }, this); 60 | this.xTexts.forEach(function (text) { 61 | text.getStyle().setFont(this._font).setTextAlign("center").setTextBaseline("top"); 62 | }, this); 63 | this.yTexts.forEach(function (text) { 64 | text.getStyle().setFont(this._font).setTextAlign("left").setTextBaseline("middle"); 65 | }, this); 66 | }, 67 | adjustLength: function (count, texts, lines, inAxisX) { 68 | var len, pos; 69 | while ((len = lines.length) < count) { 70 | pos = (len + 1) * this._span; 71 | if (inAxisX) { 72 | texts.push(new janvas.Text(this.$ctx, pos, 0, pos + "")); 73 | lines.push(new janvas.Line(this.$ctx, pos, 0, pos, 0)); 74 | } else { 75 | texts.push(new janvas.Text(this.$ctx, 0, pos, pos + "")); 76 | lines.push(new janvas.Line(this.$ctx, 0, pos, 0, pos)); 77 | } 78 | } 79 | if (count >= 0) texts.length = lines.length = count; 80 | lines.forEach(function (line) { 81 | inAxisX ? line.setEndY(this.$height) : line.setEndX(this.$width); 82 | }, this); 83 | } 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /js/aboutedge.js: -------------------------------------------------------------------------------- 1 | var aboutEdge = new janvas.Canvas({ 2 | container: "#app", 3 | props: { 4 | points: [], 5 | current: void (0) 6 | }, 7 | methods: { 8 | init: function () { 9 | this.background = new janvas.Rect(this.$ctx, 0, 0, this.$width, this.$height); 10 | this.start = new janvas.Arc(this.$ctx, 200, 250, 5); 11 | this.start.getStyle().setFillStyle("hsl(0, 80%, 60%)"); 12 | this.end = new janvas.Arc(this.$ctx, 500, 250, 5); 13 | this.end.getStyle().setFillStyle("hsl(90, 80%, 60%)"); 14 | this.an = new janvas.Arc(this.$ctx, 525, 300, 5); 15 | this.an.getStyle().setFillStyle("hsl(180, 80%, 60%)"); 16 | this.points.push(this.start, this.end, this.an); 17 | this.edge = new janvas.Edge(this.$ctx); 18 | this.text = new janvas.Text(this.$ctx, 0, 0, "Janvas"); 19 | this.text.getStyle().setFont("12px sans-serif").setTextAlign("center") 20 | .setTextBaseline("middle"); 21 | this.edge.setEmptyLength(janvas.Utils.measureTextWidth(this.text.getText(), 22 | this.text.getStyle().getFont()) / 0.809); 23 | this.head = new janvas.Triangle(this.$ctx).setLength(12); 24 | this.head.getStyle().setFillStyle("hsl(270, 80%, 60%)"); 25 | this.setCurvePropsAndDraw(); 26 | }, 27 | draw: function () { 28 | this.$clear(); 29 | this.edge.stroke(); 30 | if (this.edge.ratioInRange()) { 31 | var an = this.edge.getAngle(); 32 | this.text.getMatrix().setAngle(an > -Math.PI / 2 && an < Math.PI / 2 ? an : an + Math.PI); 33 | this.text.init(this.edge.getTargetX(), this.edge.getTargetY(), 34 | this.edge.getTargetX(), this.edge.getTargetY()).fill(); 35 | } 36 | this.start.fill(); 37 | this.end.fill(); 38 | this.an.fill(); 39 | this.head.setRotation(this.edge.getAnchorAngle()).fill(); 40 | } 41 | }, 42 | events: { 43 | mousedown: function () { 44 | this._mousedown = true; 45 | this.points.forEach(function (point) { 46 | point.lastX = point.getStartX(); 47 | point.lastY = point.getStartY(); 48 | }); 49 | }, 50 | mousemove: function (ev) { 51 | if (this._mousedown) { 52 | if (ev.buttons === 2) { 53 | this.points.forEach(function (point) { 54 | point.setStart(point.lastX + ev.$moveX, point.lastY + ev.$moveY); 55 | }, this); 56 | this.setCurvePropsAndDraw(); 57 | } else { 58 | if (!this.current) return; 59 | this.current.setStart(this.current.lastX + ev.$moveX, 60 | this.current.lastY + ev.$moveY); 61 | this.setCurvePropsAndDraw(); 62 | } 63 | } else { 64 | this.current = void (0); 65 | this.points.forEach(function (point) { 66 | if (point.isPointInPath(ev.$x, ev.$y)) { 67 | this.current = point; 68 | this.setCursor("pointer"); 69 | } 70 | }, this); 71 | if (!this.current) this.setCursor(""); 72 | } 73 | }, 74 | mouseup: function () { 75 | this._mousedown = false; 76 | } 77 | }, 78 | functions: { 79 | setCurvePropsAndDraw: function () { 80 | this.edge.setStart(this.start.getStartX(), this.start.getStartY()) 81 | .setEnd(this.end.getStartX(), this.end.getStartY()) 82 | .setAnchor(this.an.getStartX(), this.an.getStartY()); 83 | this.head.setStart(this.end.getStartX(), this.end.getStartY()); 84 | this.draw(); 85 | }, 86 | setCursor: function (cursor) { 87 | if (this.$canvas.style.cursor !== cursor) this.$canvas.style.cursor = cursor; 88 | } 89 | } 90 | }); 91 | -------------------------------------------------------------------------------- /html/img/complex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /js/aboutwheel.js: -------------------------------------------------------------------------------- 1 | var aboutWheel = new janvas.Canvas({ 2 | container: "#app", 3 | duration: Infinity, 4 | interval: 16, 5 | props: { 6 | size: 50 7 | }, 8 | methods: { 9 | init: function () { 10 | this.background = new janvas.Rect(this.$ctx, 0, 0, this.$width, this.$height); 11 | var cx = this.$width / 2, cy = this.$height / 2; 12 | this.img = new janvas.Image(this.$ctx, cx - this.size, cy - this.size, 13 | "img/complex.svg", cx, cy, this.size * 2, this.size * 2); 14 | this.img.getStyle().setStrokeStyle("grey"); 15 | this.img.animationQueue = []; // 动画队列 16 | this.img.count = this.img.maxCount = Math.floor(256 / this.$interval); 17 | this.$raf.resume(); 18 | }, 19 | update: function (ts) { 20 | this.img.getMatrix().setAngle(Math.PI / 2000 * ts); 21 | this.scaleAnimation(); 22 | }, 23 | draw: function () { 24 | this.$clear(); 25 | this.img.draw(); 26 | if (this.img._mousein) this.img.stroke(); 27 | } 28 | }, 29 | events: { 30 | mousedown: function () { 31 | if (this.img._mousein) { 32 | this._mousedown = true; 33 | this.img.lastX = this.img.getStartX(); 34 | this.img.lastY = this.img.getStartY(); 35 | } 36 | }, 37 | mousemove: function (ev) { 38 | if (this._mousedown) { 39 | var mx = this.img.lastX + ev.$moveX, my = this.img.lastY + ev.$moveY; 40 | this.img.init(mx, my, mx + this.size, my + this.size); 41 | } else { 42 | this.img._mousein = this.img.isPointInPath(ev.$x, ev.$y); 43 | } 44 | }, 45 | mouseup: function () { 46 | this._mousedown = false; 47 | }, 48 | wheel: function (ev) { 49 | /** 50 | * 当我们缩放一个对象的时候,有两种情况 51 | * 1. 中心点为 0, 0 52 | * 这种情况只能去缩放对象的 startX, startY 53 | * 2. 中心点不为 0, 0 54 | * 这种情况可选择缩放对象的 startX, startY 55 | * 因为存在中心点,所以在缩放后,需校准一次 offset,值为 _sx|_sy*(1-scale) 56 | * 也可以优先选择缩放对象的 originX, originY 57 | * 这样子就无需校准对象的 offset 58 | * 以上为笛卡尔坐标系内对象的缩放内容,当存在中心点时优先缩放中心点而不是起始绘制点 59 | * 其实就是一个比例问题(以下内容:target为目标点,event为事件点,point为对象点) 60 | * 缩放:(target-event)/scale=(point-event)/lastScale 61 | * 偏移:_sx|sy/1 = offset/(scale-1) // 存在中心点且缩放起始绘制点导致缩放图形而产生的偏移量 62 | */ 63 | // 方式一(不推荐): 64 | /*var targetSx = this.x + (this.img.getStartX() - this.x) * this.scaling, 65 | targetSy = this.y + (this.img.getStartY() - this.y) * this.scaling; 66 | this.img.init(targetSx, targetSy, targetSx + this.size, targetSy + this.size); 67 | this.img.getMatrix().setScale(this.scale, this.scale).setOffset( 68 | this.img._sx * (1 - this.scale), // 校正偏移量,不推荐此做法 69 | this.img._sy * (1 - this.scale) 70 | );*/ 71 | // 方式二(推荐): 72 | // var targetCx = ev.$x + (this.img.getOriginX() - ev.$x) * ev.$scaling, 73 | // targetCy = ev.$y + (this.img.getOriginY() - ev.$y) * ev.$scaling; 74 | // this.img.init(targetCx - this.size, targetCy - this.size, targetCx, targetCy) 75 | // .getMatrix().setScale(ev.$scale, ev.$scale); 76 | // 方式三:方式二的简单动画版本,有需求时有必要写进 components 里进行实现,以便解耦 77 | ev.preventDefault(); 78 | if (ev.$scaling !== 1) this.img.animationQueue.unshift(ev); 79 | } 80 | }, 81 | functions: { 82 | scaleAnimation: function () { 83 | if (this.img.animationQueue.length && this.img.count === this.img.maxCount) { 84 | var ev = this.img.animationQueue.pop(); 85 | this.img.lastCx = this.img.getOriginX(); 86 | this.img.lastCy = this.img.getOriginY(); 87 | this.img.targetCx = ev.$x + (this.img.getOriginX() - ev.$x) * ev.$scaling; 88 | this.img.targetCy = ev.$y + (this.img.getOriginY() - ev.$y) * ev.$scaling; 89 | this.img.lastScale = ev.$lastScale; 90 | this.img.targetScale = ev.$scale; 91 | this.img.count = 0; 92 | } 93 | if (this.img.count < this.img.maxCount) { 94 | this.img.count++; 95 | var ratio = this.img.count / this.img.maxCount, 96 | stampCx = this.img.lastCx + (this.img.targetCx - this.img.lastCx) * ratio, 97 | stampCy = this.img.lastCy + (this.img.targetCy - this.img.lastCy) * ratio, 98 | scale = this.img.lastScale + (this.img.targetScale - this.img.lastScale) * ratio; 99 | this.img.init(stampCx - this.size, stampCy - this.size, stampCx, stampCy) 100 | .getMatrix().setScale(scale, scale); 101 | } 102 | } 103 | } 104 | }); 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JanvasExamples 2 | 3 | Examples created with [janvas](https://github.com/jarenchow/janvas). 4 | 5 | ## 使用示例 6 | 7 | - 方式一 8 | 1. 准备一个容器 div:`
` 9 | 2. 添加 janvas 库:`` 10 | 3. 添加 janvasexamples 库:`` 11 | 4. 填充 div:`var fly = janvasexamples.flydots("#app")` 12 | - 方式二(在 [vue](https://github.com/vuejs/vue) 中使用) 13 | 1. 准备一个容器 div:`
` 14 | 2. `npm install janvasexamples --save` 15 | 3. 填充 div:`var fly = janvasexamples.flydots(this.$refs.container);` 16 | 17 | ## [About AntV Performance Test](https://jarenchow.github.io/JanvasExamples/html/about_antv_performance_test.html) 18 | 19 | 原示例:[https://g6.antv.vision/zh/examples/performance/perf#moreData](https://g6.antv.vision/zh/examples/performance/perf#moreData),如其所说: 20 | 21 | > 对 G6 的性能测试,用来验证 G6 能够承载的数据量,分别使用 5000+ 图元、将近 20000 图元及 50000+ 图元的示例进行了测试,从结果来看,20000 左右图元时,G6 是可以正常交互的,当数据量达到 50000+ 时,交互就会出现一定的卡顿,但对于绝大部分业务来说,都不建议在画布上展示如此多的数据,具体的做法可以参考 AntV G6 团队的大图可视化方案,预计 1122 发布 22 | 23 | 而使用 **janvas** 从低抽象角度来定制,数据量即使达到 **50000\+** 时,依然可以**缩放**、**拖曳**以及**自定义更多交互**。 24 | 25 | ## [CodeRain](https://jarenchow.github.io/JanvasExamples/html/coderain.html) 26 | 27 | 基于 **janvas** 编写不到 *200* 行代码几乎 100% 还原 The Matrix 特效代码雨。 28 | 29 | 可以使用 `janvasexamples.coderain(document.body).$canvas.style.zIndex = "-1";` 来为自己的网页添加此特效。 30 | 31 | ## [TheLastJanvas](https://jarenchow.github.io/JanvasExamples/html/the_last_janvas.html) 32 | 33 | 在 **CodePen** 上非常出名的 [The Last Experience](https://codepen.io/ge1doot/pen/LkdOwj),使用 **janvas** 改写的示例。 34 | 35 | 原作者 [ge1doot](https://codepen.io/ge1doot) 使用了 `` 存储了每个关节的图片,然后用 `drawImage()` 的方式绘制出来,这样会导致每个关节都需要一个 `` 元素,而使用 **janvas** 开发: 36 | 37 | 1. 直接处理成了实时绘制的 `janvas.Line/Rect/Arc`,对象身体会被事件拉长 38 | 2. 并且直接处理变形,不再需要手动 `translate/rotate`,效率有较大提升 39 | 3. `interval` 设置为 16,直接处理了不同刷新率屏幕下的同一表现,减少代码体积 40 | 4. 自动适配高分屏,省去大量琐碎操作 41 | 42 | ## [Stats](https://jarenchow.github.io/JanvasExamples/html/stats.html) 43 | 44 | 原示例来源于接近 *7k* 赞的 [mrdoob](https://github.com/mrdoob) 的 [stats.js](https://github.com/mrdoob/stats.js),是一个小巧的性能监测组件。 45 | 46 | 使用 **janvas** 改写后,性能消耗更低十倍多,几乎成为一个毫不起眼毫不占用的小组件,有兴趣可对比自行测试 [stress.html](https://github.com/mrdoob/stats.js/blob/master/examples/stress.html)。 47 | 48 | 使用方式: 49 | 50 | 1. 先引入 **janvas** 及 **janvasexamles**。 51 | 2. `
` 52 | 3. `var stats = janvasexamples.stats("#stats");` 53 | 54 | 自定义显示: 55 | 56 | 1. `stats.addPanel("foo", "#ffff88", "#222211");` 57 | 2. `stats.showPanel(3);` 58 | 3. 循环调用:`stats.updatePanel("foo", [value], [maxValue]);` 59 | 60 | ## [Clock](https://jarenchow.github.io/JanvasExamples/html/clock.html) 61 | 62 | 秒针使用了 **janvas** 中自带的高阶贝塞尔曲线实现动画,阴影偏移量随时间偏移。 63 | 64 | [![image-20200316105934566](https://cdn.jsdelivr.net/gh/JarenChow/ImageHosting@master/image/janvas/clock.gif)](https://jarenchow.github.io/JanvasExamples/html/clock.html) 65 | 66 | ## [BezierMaker](https://jarenchow.github.io/JanvasExamples/html/beziermaker.html) 67 | 68 | - 鼠标点击生成一个数据点 69 | - 鼠标右键拖曳所有数据点 70 | - 响应键盘 wasd/方向键 控制节点位置 71 | - 响应键盘 q/e 切换当前节点 72 | - 响应键盘 Delete 删除节点 73 | - 响应 Enter 从控制台打印原始数据点与计算后的数据点 74 | 75 | ## [FlyDots](https://jarenchow.github.io/JanvasExamples/html/flydots.html) 76 | 77 | 使用 **janvas** 简单轻松绘制的不算特效的特效。 78 | 79 | ## [SVG Support](https://jarenchow.github.io/JanvasExamples/html/tiger.html) 80 | 81 | 依据 svg 数据生成的组合图形仍然具有范围检测、样式自定义及矩阵变形的功能。 82 | 83 | ## [Sudoku](https://jarenchow.github.io/JanvasExamples/html/sudoku.html) 84 | 85 | **janvas** 制作的数独,左键操作,右键随机,中键还原。 86 | 87 | ## [Cursor](https://jarenchow.github.io/JanvasExamples/html/cursor.html) 88 | 89 | HTML Element.style.cursor 样式对照示例。 90 | 91 | ## [CircleText](https://jarenchow.github.io/JanvasExamples/html/circletext.html) 92 | 93 | **janvas** v2.6.1 新增数十个 janvas.Utils.ease 默认动效函数。 94 | 95 | ## [TaiChi](https://jarenchow.github.io/JanvasExamples/html/taichi.html) 96 | 97 | 太极图可由外圆,左半圆,右半圆,上下中小圆,一共 **7** 个圆形组成,不到两百行代码构建太极屏保,包含旋转、渐变、碰撞检测等。 98 | 99 | ## [AboutWheel](https://jarenchow.github.io/JanvasExamples/html/about_wheel.html) 100 | 101 | 缩放公式:target = event + (source - event) * scale / lastScale; 102 | 103 | 在 **janvas** 中读取一张 SVG 图片,并随时间旋转,随鼠标响应范围检测并拖曳,随滚轮实现无损缩放的示例。 104 | 105 | ## [AboutEdge](https://jarenchow.github.io/JanvasExamples/html/about_edge.html) 106 | 107 | v2.1.0 新增绘制连线的 Edge 类,实现了图数据库中的连线的样式。 108 | 109 | ## 其他 110 | 111 | 其他一些小示例写在 [CSDN](https://blog.csdn.net/M3oM3oChong) 和 [CodePen](https://codepen.io/jarenchow) -------------------------------------------------------------------------------- /js/cursor.js: -------------------------------------------------------------------------------- 1 | var cursor = new janvas.Canvas({ 2 | container: "#app", 3 | props: { // URL, default, auto, initial, context-menu 4 | data: ["alias", "help", "copy", "progress", "wait", "crosshair", "no-drop", "not-allowed", "text", "vertical-text", "col-resize", "row-resize", "zoom-in", "zoom-out", "move", "all-scroll", "pointer", "grab", "grabbing", "cell", "n-resize", "s-resize", "ns-resize", "e-resize", "w-resize", "ew-resize", "ne-resize", "sw-resize", "nesw-resize", "nw-resize", "se-resize", "nwse-resize", "inherit", "revert", "unset", "none"] 5 | }, 6 | components: { 7 | Button: (function () { 8 | function Button(ctx, raf, text) { 9 | janvas.Animation.call(this, raf, 1000); 10 | this.rect = new janvas.Rect(ctx, 0, 0, 0, 0); 11 | this.text = new janvas.Text(ctx, 0, 0, text); 12 | this.rect.getStyle().setFillStyle("pink").setLineWidth(2); 13 | this.text.getStyle().setFillStyle("white").setFont("24px sans-serif") 14 | .setTextAlign("center").setTextBaseline("middle"); 15 | this.mousein = false; 16 | } 17 | 18 | janvas.Utils.inheritPrototype(Button, janvas.Animation); 19 | 20 | Button.prototype.draw = function () { 21 | this.mousein ? this.rect.fillStroke() : this.rect.fill(); 22 | this.text.fill(); 23 | }; 24 | Button.prototype.resize = function (x, y, size) { 25 | var cx = x + size / 2, cy = y + size / 2; 26 | this.rect.init(x, y, cx, cy).setWidth(size).setHeight(size); 27 | this.text.init(cx, cy, cx, cy); 28 | }; 29 | Button.prototype.setColor = function (color) { 30 | this.rect.getStyle().setFillStyle(color); 31 | }; 32 | Button.prototype.getText = function () { 33 | return this.text.getText(); 34 | }; 35 | Button.prototype.setFont = function (font) { 36 | this.text.getStyle().setFont(font); 37 | }; 38 | Button.prototype.onUpdate = function (ratio) { 39 | ratio = 0.236 * janvas.Utils.ease.out.elastic(ratio); 40 | this.rect.getMatrix().setScale(1 + ratio, 1 + ratio); 41 | this.text.getMatrix().setScale(1 + ratio, 1 + ratio); 42 | }; 43 | Button.prototype.eventmove = function (x, y) { 44 | if (this.rect.isPointInPath(x, y)) { 45 | if (!this.mousein) { 46 | if (this.isRunning()) this.reverse(); 47 | else this.start(); 48 | return this.mousein = true; 49 | } 50 | } else if (this.mousein) { 51 | this.reverse(); 52 | this.mousein = false; 53 | } 54 | }; 55 | 56 | return Button; 57 | }()) 58 | }, 59 | methods: { 60 | init: function () { 61 | var params = this.params = {}; 62 | params.center = new janvas.Point(); 63 | params.start = new janvas.Point(); 64 | params.hsl = new janvas.Hsl(0, 71, 62); 65 | var buttons = this.buttons = new Array(length), i; 66 | for (i = 0; i < this.data.length; i++) { 67 | buttons[i] = new this.Button(this.$ctx, this.$raf, this.data[i]); 68 | } 69 | }, 70 | resize: function () { 71 | var w = this.$width, h = this.$height, params = this.params; 72 | params.center.init(w, h).scale(0.5, 0.5); 73 | params.size = Math.min(w, h) * 0.809; 74 | params.start.init(-params.size / 2, -params.size / 2).add(params.center); 75 | params.start.init(Math.floor(params.start.x), Math.floor(params.start.y)); 76 | params.count = Math.ceil(Math.sqrt(this.data.length)); 77 | params.gridSize = Math.floor(params.size / params.count); 78 | var min = Infinity, fontFamily = "courier", 79 | i, button, fontSize; 80 | for (i = 0; i < this.buttons.length; i++) { 81 | button = this.buttons[i]; 82 | button.setColor(params.hsl.setHue(360 / (params.count - 1) * i).toHslString()); 83 | button.resize(params.start.x + (i % params.count) * params.gridSize, 84 | params.start.y + Math.floor(i / params.count) * params.gridSize, 85 | params.gridSize); 86 | fontSize = janvas.Utils.measureTextFontSize( 87 | button.getText(), params.gridSize, fontFamily); 88 | min = fontSize < min ? fontSize : min; 89 | } 90 | for (i = 0; i < this.buttons.length; i++) { 91 | this.buttons[i].setFont("bold " + min + "px " + fontFamily); 92 | } 93 | }, 94 | update: function (a, b) { 95 | for (var i = 0; i < this.buttons.length; i++) { 96 | this.buttons[i].update(b); 97 | } 98 | }, 99 | draw: function () { 100 | this.$clear(); 101 | for (var i = 0; i < this.buttons.length; i++) { 102 | this.buttons[i].draw(); 103 | } 104 | } 105 | }, 106 | events: { 107 | mousemove: function (ev) { 108 | var btns = this.buttons, btn; 109 | for (var i = 0; i < btns.length; i++) { 110 | btn = btns[i]; 111 | if (btn.eventmove(ev.$x, ev.$y)) { 112 | btns.push(btns.splice(i, 1)[0]); 113 | this.$canvas.style.cursor = btn.getText(); 114 | break; 115 | } 116 | } 117 | } 118 | } 119 | }); 120 | -------------------------------------------------------------------------------- /js/flydots.js: -------------------------------------------------------------------------------- 1 | var flyDots = new janvas.Canvas({ 2 | container: "#app", 3 | duration: Infinity, 4 | interval: 16, 5 | props: { 6 | dots: [], 7 | lines: [] 8 | }, 9 | components: { 10 | factory: (function () { 11 | function Dot($ctx, width, height) { 12 | this.setBounding(width, height); 13 | this._x = janvas.Utils.randInt(this._left, this._right, true); 14 | this._y = janvas.Utils.randInt(this._top, this._bottom, true); 15 | this._r = 3; 16 | this._lvx = this._vx = janvas.Utils.randSign() * janvas.Utils.randInt(10, 100, true) / 100; 17 | this._lvy = this._vy = janvas.Utils.randSign() * janvas.Utils.randInt(10, 100, true) / 100; 18 | this._relateStart = []; 19 | this._relateEnd = []; 20 | this.arc = new janvas.Arc($ctx, this._x, this._y, this._r); 21 | this.arc.getStyle().setFillStyle("hsl(0, 0%, 40%)"); 22 | } 23 | 24 | Dot.prototype = { 25 | setStart: function (x, y) { 26 | this._x = x; 27 | this._y = y; 28 | this.arc.setStart(x, y); 29 | this._relateStart.forEach(this.startCallback, this); 30 | this._relateEnd.forEach(this.endCallBack, this); 31 | }, 32 | closer: function (x, y) { 33 | this._vx = (x - this._x) / Math.abs(x - this._x); 34 | this._vy = (y - this._y) / Math.abs(y - this._y); 35 | }, 36 | restore: function () { 37 | this._vx = this._lvx; 38 | this._vy = this._lvy; 39 | }, 40 | relateStart: function (line) { 41 | this._relateStart.push(line); 42 | }, 43 | relateEnd: function (line) { 44 | this._relateEnd.push(line); 45 | }, 46 | startCallback: function (line) { 47 | line.setStart(this._x, this._y); 48 | }, 49 | endCallBack: function (line) { 50 | line.setEnd(this._x, this._y); 51 | }, 52 | setBounding: function (width, height) { 53 | this._left = this._top = -50; 54 | this._right = width + 50; 55 | this._bottom = height + 50; 56 | }, 57 | update: function () { 58 | this._x += this._vx; 59 | this._y += this._vy; 60 | this.setStart(this._x, this._y); 61 | if (this._x < this._left || this._x > this._right) this._lvx = this._vx *= -1; 62 | if (this._y < this._top || this._y > this._bottom) this._lvy = this._vy *= -1; 63 | }, 64 | draw: function () { 65 | this.arc.fill(); 66 | } 67 | }; 68 | 69 | function Line($ctx, source, target) { 70 | this.line = new janvas.Line($ctx); 71 | source.relateStart(this.line); 72 | target.relateEnd(this.line); 73 | // this._rgb = new janvas.Rgb(0, 0, 0, 0); 74 | } 75 | 76 | Line.prototype = { 77 | update: function () { 78 | var _ratio = 1 - janvas.Utils.pythagorean( 79 | this.line.getStartX() - this.line.getEndX(), 80 | this.line.getStartY() - this.line.getEndY()) / 100; 81 | this.line.getStyle().setAlpha(this._ratio = _ratio < 0 ? 0 : _ratio); 82 | // this.line.getStyle().setStrokeStyle(this._rgb.setAlpha(this._ratio).toRgbString(true)); 83 | }, 84 | draw: function () { 85 | if (this._ratio) this.line.stroke(); 86 | } 87 | }; 88 | 89 | return { 90 | Dot: Dot, 91 | Line: Line 92 | }; 93 | }()) 94 | }, 95 | methods: { 96 | init: function () { 97 | for (var i = 0; i < 100; i++) { 98 | var dot = new this.factory.Dot(this.$ctx, this.$width, this.$height); 99 | this.dots.forEach(function (target) { 100 | this.lines.push(new this.factory.Line(this.$ctx, dot, target)); 101 | }, this); 102 | this.dots.push(dot); 103 | } 104 | this.cursor = new this.factory.Dot(); 105 | this.dots.forEach(function (target) { 106 | this.lines.push(new this.factory.Line(this.$ctx, this.cursor, target)); 107 | }, this); 108 | }, 109 | update: function () { 110 | this.dots.forEach(function (dot) { 111 | dot.update(); 112 | }, this); 113 | this.lines.forEach(function (line) { 114 | line.update(); 115 | }); 116 | }, 117 | draw: function () { 118 | this.$clear(); 119 | this.lines.forEach(function (line) { 120 | line.draw(); 121 | }); 122 | this.dots.forEach(function (dot) { 123 | dot.draw(); 124 | }); 125 | } 126 | }, 127 | events: { 128 | mousedown: function (ev) { 129 | this.dots.forEach(function (dot) { 130 | dot.closer(ev.$x, ev.$y); 131 | }, this); 132 | }, 133 | mousemove: function (ev) { 134 | this.cursor.setStart(ev.$x, ev.$y); 135 | }, 136 | mouseup: function () { 137 | this.dots.forEach(function (dot) { 138 | dot.restore(); 139 | }); 140 | }, 141 | resize: function () { 142 | this.dots.forEach(function (dot) { 143 | dot.setBounding(this.$width, this.$height); 144 | }, this); 145 | }, 146 | visibilitychange: function (visible) { 147 | visible ? this.$raf.resume() : this.$raf.pause(); 148 | } 149 | } 150 | }); 151 | -------------------------------------------------------------------------------- /js/stats.js: -------------------------------------------------------------------------------- 1 | var stats = new janvas.Canvas({ 2 | container: "#stats", 3 | duration: Infinity, 4 | props: { 5 | fpsTimespan: 1000, // fps refresh timespan, default 1000ms 6 | mbTimespan: 500 7 | }, 8 | components: { 9 | factory: (function () { 10 | var WIDTH = 80, HEIGHT = 48, 11 | TEXT_X = 3, TEXT_Y = 2, 12 | GRAPH_X = 3, GRAPH_Y = 15, 13 | GRAPH_WIDTH = 74, GRAPH_HEIGHT = 30; 14 | 15 | var background, 16 | topBack, text, 17 | graph, graphAlpha, 18 | right, rightAlpha, 19 | image; 20 | 21 | function init(context) { 22 | var _ctx = context.$ctx, _dpr = context.$dpr; 23 | background = new janvas.Rect(_ctx, 0, 0, WIDTH, HEIGHT); 24 | topBack = new janvas.Rect(_ctx, 0, 0, WIDTH, GRAPH_Y); 25 | text = new janvas.Text(_ctx, TEXT_X, TEXT_Y); 26 | text.getStyle().setFont("bold 9px Helvetica,Arial,sans-serif").setTextBaseline("top"); 27 | graph = new janvas.Rect(_ctx, GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); 28 | graphAlpha = new janvas.Rect(_ctx, GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); 29 | right = new janvas.Rect(_ctx, GRAPH_X + GRAPH_WIDTH - 1, GRAPH_Y, 1, GRAPH_HEIGHT); 30 | rightAlpha = new janvas.Rect(_ctx, GRAPH_X + GRAPH_WIDTH - 1, GRAPH_Y, 1, GRAPH_HEIGHT); 31 | image = new janvas.Image(_ctx, GRAPH_X, GRAPH_Y, context.$canvas, 0, 0, 32 | GRAPH_WIDTH - 1, GRAPH_HEIGHT, 33 | (GRAPH_X + 1) * _dpr, GRAPH_Y * _dpr, (GRAPH_WIDTH - 1) * _dpr, GRAPH_HEIGHT * _dpr); 34 | } 35 | 36 | function Panel(name, fg, bg) { 37 | this.name = name; 38 | this.fg = fg; 39 | this.bg = bg; 40 | this.bgAlpha = new janvas.Rgb().fromHexString(this.bg).setAlpha(230).toHexString(true); 41 | this.min = Infinity; 42 | this.max = 0; 43 | } 44 | 45 | Panel.prototype.show = function () { 46 | background.getStyle().setFillStyle(this.bg); 47 | topBack.getStyle().setFillStyle(this.bg); 48 | text.setText(this.name).getStyle().setFillStyle(this.fg); 49 | graph.getStyle().setFillStyle(this.fg); 50 | graphAlpha.getStyle().setFillStyle(this.bgAlpha); 51 | right.getStyle().setFillStyle(this.fg); 52 | rightAlpha.getStyle().setFillStyle(this.bgAlpha); 53 | background.fill(); 54 | text.fill(); 55 | graph.fill(); 56 | graphAlpha.fill(); 57 | }; 58 | 59 | Panel.prototype.update = function (value, maxValue) { 60 | if (value < this.min) this.min = value; 61 | if (value > this.max) this.max = value; 62 | text.setText(Math.round(value) + " " + this.name + 63 | " (" + Math.round(this.min) + "-" + Math.round(this.max) + ")"); 64 | rightAlpha.setHeight(Math.round((1 - (value / maxValue)) * GRAPH_HEIGHT)); 65 | topBack.fill(); 66 | text.fill(); 67 | image.draw(); 68 | right.fill(); 69 | rightAlpha.fill(); 70 | }; 71 | 72 | return { 73 | init: init, 74 | Panel: Panel 75 | }; 76 | }()) 77 | }, 78 | methods: { 79 | init: function () { 80 | this.factory.init(this); 81 | this.panels = []; 82 | this.addPanel("FPS", "#00ffff", "#000022"); 83 | this.addPanel("MS", "#00ff00", "#002200"); 84 | if (performance && performance.memory) { 85 | this.addPanel("MB", "#ff0088", "#220011"); 86 | } 87 | this.showPanel(0); 88 | this.$raf.resume(); 89 | this.$wrapper.style.cursor = "pointer"; 90 | }, 91 | update: function () { 92 | var ts = performance.now(); 93 | if (this._ts === void (0)) return this._ts = ts; 94 | this.frames++; 95 | switch (this.panel.name) { 96 | case "FPS": 97 | if (ts >= this._ts + this.fpsTimespan) { 98 | this.panel.update((this.frames * 1000) / (ts - this._ts), 100); 99 | this._ts = ts; 100 | this.frames = 0; 101 | } 102 | break; 103 | case "MS": 104 | this.panel.update(ts - this._ts, 50); 105 | this._ts = ts; 106 | break; 107 | case "MB": 108 | if (ts >= this._ts + this.mbTimespan) { 109 | var memory = performance.memory; 110 | this.panel.update(memory.usedJSHeapSize / 1048576, memory.totalJSHeapSize / 1048576); 111 | this._ts = ts; 112 | } 113 | break; 114 | } 115 | }, 116 | showPanel: function (mode) { 117 | this.panel = this.panels[this.mode = mode]; 118 | this.panel.show(); 119 | this.frames = 0; 120 | this._ts = void (0); 121 | }, 122 | addPanel: function (name, fg, bg) { 123 | this.panels.push(new this.factory.Panel(name, fg, bg)); 124 | }, 125 | updatePanel: function (name, value, maxValue) { 126 | if (this.panel.name === name) this.panel.update(value, maxValue); 127 | }, 128 | removePanel: function () { 129 | if (this.panels.length > 1) { 130 | var index = this.panels.indexOf(this.panel); 131 | this.panels.splice(index, 1) 132 | this.showPanel(index < this.panels.length ? index : index - 1); 133 | } 134 | } 135 | }, 136 | events: { 137 | click: function () { 138 | this.showPanel(++this.mode % this.panels.length); 139 | } 140 | } 141 | }); 142 | // stats.addPanel("TEST1", "#ffff88", "#222211"); 143 | // stats.addPanel("TEST2", "#ff88ff", "#221122"); 144 | // stats.showPanel(3); 145 | // new AnimateITV(function (ts) { 146 | // stats.updatePanel("TEST1", 250 - Math.abs(ts / 10 - 250), 250); 147 | // }, 5000, 100).start(); 148 | 149 | // stress test https://github.com/mrdoob/stats.js 150 | // for (var i = 0; i < 500; i++) { 151 | // var div = document.createElement("div"); 152 | // div.style.cssText="float:left;width:80px;height:48px;opacity:0.9;"; 153 | // document.body.appendChild(div); 154 | // janvasexamples.stats(div); 155 | // } 156 | -------------------------------------------------------------------------------- /js/taichi.js: -------------------------------------------------------------------------------- 1 | var taichi = new janvas.Canvas({ 2 | container: "#app", 3 | duration: Infinity, 4 | interval: 16, 5 | props: { 6 | addCount: 0, 7 | taichi: [] 8 | }, 9 | components: { 10 | Taichi: (function () { 11 | function Taichi($ctx, x, y, r) { 12 | var outer = new janvas.Arc($ctx, x, y, r); 13 | outer.getStyle().setLineWidth(r / 8); 14 | var left = new janvas.Arc($ctx, x, y, r, Math.PI / 2, -Math.PI / 2, false, x, y); 15 | var right = new janvas.Arc($ctx, x, y, r, Math.PI / 2, -Math.PI / 2, true, x, y); 16 | right.getStyle().setFillStyle("white"); 17 | var top = new janvas.Arc($ctx, x, y - r / 2, r / 2, 0, Math.PI * 2, false, x, y); 18 | var bottom = new janvas.Arc($ctx, x, y + r / 2, r / 2, 0, Math.PI * 2, false, x, y); 19 | bottom.getStyle().setFillStyle("white"); 20 | var topSmall = new janvas.Arc($ctx, x, y - r / 2, r / 8, 0, Math.PI * 2, false, x, y); 21 | var bottomSmall = new janvas.Arc($ctx, x, y + r / 2, r / 8, 0, Math.PI * 2, false, x, y); 22 | topSmall.getStyle().setFillStyle("white"); 23 | this.x = x; 24 | this.y = y; 25 | this.r = r; 26 | this.rotateSpeed = janvas.Utils.randSign() * Math.PI / 2000 * 16 * 42 / r; 27 | this.cp = new janvas.Point(); 28 | this.outer = outer; 29 | this.left = left; 30 | this.right = right; 31 | this.top = top; 32 | this.bottom = bottom; 33 | this.topSmall = topSmall; 34 | this.bottomSmall = bottomSmall; 35 | this.grd = { // 颜色渐变的对象 36 | rgb: new janvas.Rgb(0, 0, 0), 37 | outerStart: new janvas.Rgb(255, 255, 255).sRgbInverseCompanding(), 38 | black: new janvas.Rgb(0, 0, 0).sRgbInverseCompanding(), 39 | white: new janvas.Rgb(255, 255, 255).sRgbInverseCompanding(), 40 | count: 0, 41 | maxCount: Math.ceil(1000 / 16) 42 | }; 43 | } 44 | 45 | Taichi.prototype = { 46 | init: function (x, y) { 47 | this.x = x; 48 | this.y = y; 49 | this.outer.setStart(x, y); 50 | this.left.init(x, y, x, y); 51 | this.right.init(x, y, x, y); 52 | this.top.init(x, y - this.r / 2, x, y); 53 | this.bottom.init(x, y + this.r / 2, x, y); 54 | this.topSmall.init(x, y - this.r / 2, x, y); 55 | this.bottomSmall.init(x, y + this.r / 2, x, y); 56 | }, 57 | update: function () { 58 | this.rotate(); 59 | if (!this.gradient()) this.update = this._update; 60 | }, 61 | _update: function () { 62 | this.rotate(); 63 | }, 64 | draw: function () { 65 | this.outer.stroke(); 66 | this.left.fill(); 67 | this.right.fill(); 68 | this.top.fill(); 69 | this.bottom.fill(); 70 | this.topSmall.fill(); 71 | this.bottomSmall.fill(); 72 | }, 73 | rotate: function () { 74 | var angle = this.left.getMatrix().getAngle() + this.rotateSpeed; 75 | this.left.getMatrix().setAngle(angle); 76 | this.right.getMatrix().setAngle(angle); 77 | this.top.getMatrix().setAngle(angle); 78 | this.bottom.getMatrix().setAngle(angle); 79 | this.topSmall.getMatrix().setAngle(angle); 80 | this.bottomSmall.getMatrix().setAngle(angle); 81 | }, 82 | gradient: function () { 83 | var grd = this.grd; 84 | if (grd.count < grd.maxCount) { 85 | var ratio = grd.count / grd.maxCount; 86 | janvas.Rgb.sRgbGammaMixing(grd.outerStart, grd.black, ratio, grd.rgb); 87 | this.outer.getStyle().setStrokeStyle(grd.rgb.sRgbCompanding().toRgbString()); 88 | this._grd(this.left, grd.white, grd.black, ratio); 89 | this.top.getStyle().setFillStyle(this.left.getStyle().getFillStyle()); 90 | this.bottomSmall.getStyle().setFillStyle(this.left.getStyle().getFillStyle()); 91 | this._grd(this.right, grd.black, grd.white, ratio); 92 | this.bottom.getStyle().setFillStyle(this.right.getStyle().getFillStyle()); 93 | this.topSmall.getStyle().setFillStyle(this.right.getStyle().getFillStyle()); 94 | grd.count++; 95 | return true; 96 | } else { 97 | this.outer.getStyle().setStrokeStyle("#000000"); 98 | this.left.getStyle().setFillStyle("#000000"); 99 | this.top.getStyle().setFillStyle("#000000"); 100 | this.bottomSmall.getStyle().setFillStyle("#000000"); 101 | this.right.getStyle().setFillStyle("white"); 102 | this.bottom.getStyle().setFillStyle("white"); 103 | this.topSmall.getStyle().setFillStyle("white"); 104 | return false; 105 | } 106 | }, 107 | _grd: function (obj, start, end, ratio) { 108 | janvas.Rgb.sRgbGammaMixing(start, end, ratio, this.grd.rgb); 109 | obj.getStyle().setFillStyle(this.grd.rgb.sRgbCompanding().toRgbString()); 110 | }, 111 | collide: function (taichi) { 112 | var x1 = this.x, y1 = this.y, r1 = this.r * 17 / 16, // 添加了 outer linewidth 的一半 113 | x2 = taichi.x, y2 = taichi.y, r2 = taichi.r * 17 / 16, 114 | cp1 = this.cp, cp2 = taichi.cp, r = Math.min(r1, r2); // collision point 115 | if (janvas.Collision.arc(x1, y1, r1, x2, y2, r2)) { 116 | cp1.init(x1, y1).subtract(cp2.init(x2, y2)).unit().scale(r, r); 117 | if (isNaN(cp1.x)) { 118 | Math.random() > 0.5 ? cp1.init(r, 0) : cp1.init(0, r); 119 | } 120 | this.init(x1 + cp1.x / r1, y1 + cp1.y / r1); 121 | cp1.inverse(); 122 | taichi.init(x2 + cp1.x / r2, y2 + cp1.y / r2); 123 | } 124 | }, 125 | inRect: function (rect) { 126 | return janvas.Collision.arcRect(this.x, this.y, this.r, 127 | rect.getStartX(), rect.getStartY(), rect.getWidth(), rect.getHeight()); 128 | } 129 | }; 130 | 131 | return Taichi; 132 | }()) 133 | }, 134 | methods: { 135 | init: function () { 136 | this.background = new janvas.Rect(this.$ctx, 0, 0, this.$width, this.$height); 137 | }, 138 | update: function (ts) { 139 | if (ts > this.addCount * 1000) { 140 | this.addCount++; 141 | this.add(); 142 | } 143 | var flag = false; 144 | this.taichi.forEach(function (tc1) { 145 | tc1.update(); 146 | this.taichi.forEach(function (tc2) { 147 | if (tc1 === tc2) return; 148 | tc1.collide(tc2); 149 | }); 150 | tc1.in = tc1.inRect(this.background); 151 | if (!tc1.in) flag = true; 152 | }, this); 153 | if (flag) this.taichi = this.taichi.filter(function (tc) { 154 | return tc.in; 155 | }); 156 | }, 157 | draw: function () { 158 | this.$clear(); 159 | this.taichi.forEach(function (tc) { 160 | tc.draw(); 161 | }); 162 | } 163 | }, 164 | events: { 165 | mousedown: function (ev) { 166 | this.add(ev.$x, ev.$y); 167 | }, 168 | visibilitychange: function (visible) { 169 | visible ? this.$raf.resume() : this.$raf.pause(); 170 | }, 171 | resize: function () { 172 | this.background.setWidth(this.$width).setHeight(this.$height); 173 | } 174 | }, 175 | functions: { 176 | add: function (x, y) { 177 | this.taichi.push(new this.Taichi( 178 | this.$ctx, 179 | x || janvas.Utils.randInt(0, this.$width), 180 | y || janvas.Utils.randInt(0, this.$height), 181 | janvas.Utils.randInt(20, 100) 182 | )); 183 | } 184 | } 185 | }); 186 | -------------------------------------------------------------------------------- /js/clock.js: -------------------------------------------------------------------------------- 1 | var clock = new janvas.Canvas({ 2 | container: "#app", 3 | duration: Infinity, 4 | methods: { 5 | init: function () { 6 | var bezier = new janvas.Bezier(this.$ctx, 0, 0, [0, 0, 100, 1457, 200, -460, 300, 989, 400, 405, 500, 500]); 7 | bezier.getMatrix().setScale(1 / 500, 1 / 500); 8 | this.animation = bezier.getTransformedPoints().filter(function () { 9 | return arguments[1] % 2 === 1; 10 | }); 11 | this.background = new janvas.Rect(this.$ctx); // 背景 12 | this.bottom = new janvas.RoundRect(this.$ctx); // 时钟底 13 | this.bottom.border = new janvas.RoundRect(this.$ctx); 14 | this.outer = new janvas.Arc(this.$ctx); // 主圆 15 | this.outer.border = new janvas.Arc(this.$ctx); // 主圆阴影 16 | this.second = new janvas.RoundRect(this.$ctx); 17 | this.minute = new janvas.RoundRect(this.$ctx); 18 | this.hour = new janvas.RoundRect(this.$ctx); 19 | this.dot = new janvas.Arc(this.$ctx); 20 | this.initStyles(); 21 | }, 22 | initStyles: function () { 23 | this._setShadow("Color", "hsla(0, 0%, 0%, 0.8)"); 24 | this.shadowBasis = new janvas.Point(0, 0); 25 | this.shadowCursor = new janvas.Point(0, 0); 26 | this.background.getStyle().setFillStyle("hsl(0, 0%, 63%)"); // 背景 27 | this.bottom.border.getStyle().setShadowColor("hsla(0, 0%, 0%, 0.5)"); 28 | this.outer.border.getStyle().setShadowColor("hsla(0, 0%, 0%, 0.5)"); 29 | this.dot.getStyle().setStrokeStyle("hsl(0, 0%, 63%)"); 30 | }, 31 | _setShadow: function (type, value) { 32 | this.bottom.getStyle()["setShadow" + type](value); 33 | this.hour.getStyle()["setShadow" + type](value); 34 | this.minute.getStyle()["setShadow" + type](value); 35 | this.second.getStyle()["setShadow" + type](value); 36 | this.dot.getStyle()["setShadow" + type](value); 37 | }, 38 | resizeStyles: function () { 39 | var min = Math.min(this.$width, this.$height); 40 | this._setShadow("Blur", min / 140); 41 | this.shadowBasis.init(min / 35, 0); 42 | this.gradient("hsl(0, 0%, 100%)", "hsl(0, 0%, 90%)", 43 | this.bottom, this.hour, this.minute, this.dot); 44 | this.bottom.border.getStyle().setLineWidth(min / 35) 45 | .setShadowBlur(min / 140 * 3).setShadowOffsetX(-this.$width); 46 | this.gradient("hsl(0, 0%, 40%)", "hsl(0, 0%, 23%)", this.outer); 47 | this.outer.border.getStyle().setLineWidth(min / 35) 48 | .setShadowBlur(min / 70).setShadowOffsetX(-this.$width); 49 | this.gradient("hsl(0, 80%, 70%)", "hsl(0, 80%, 50%)", this.second); 50 | this.dot.getStyle().setLineWidth(1 / this.$dpr); 51 | }, 52 | update: function (ts) { 53 | ts += this.milliseconds; 54 | if (ts > this._secondIncrement * 1000) { 55 | this._secondIncrement++; 56 | if (++this.seconds % 60 === 0) { 57 | this.seconds = 0; 58 | if (++this.minutes % 60 === 0) { 59 | this.minutes = 0; 60 | if (++this.hours % 24 === 0) this.hours = 0; 61 | } 62 | } 63 | this._allSeconds++; // 维护一个总秒数,避免重复操作 hours/minutes/seconds 64 | this.setAngle(); 65 | this.onEvent(this.hours, this.minutes, this.seconds); 66 | } else if (ts % 1000 > 600) { // 动画间隔为 400,动画效果为五阶贝塞尔曲线 67 | this.second.getMatrix().setAngle(this._secondLastAngle 68 | + Math.PI / 30 // secondIncrementAngle: 1/60*2PI 69 | * this.animation[Math.floor((ts % 1000 - 600) / 400 * this.animation.length)]); 70 | } 71 | }, 72 | draw: function () { 73 | this.background.fill(); 74 | this.bottom.fill(); 75 | this.bottom.clip().border.stroke().restore(); 76 | this.outer.fill().border.stroke(); 77 | this.hour.fill(); 78 | this.minute.fill(); 79 | this.second.fill(); 80 | this.dot.strokeFill(); 81 | } 82 | }, 83 | events: { 84 | resize: function () { 85 | var goldenRatio = 0.809, 86 | size = Math.min(this.$width, this.$height) * goldenRatio, 87 | cx = this.$width / 2, cy = this.$height / 2; 88 | this._sizeBy2 = size / 2; 89 | this.background.setStart(0, 0).setWidth(this.$width).setHeight(this.$height); 90 | this.bottom.init(cx - this._sizeBy2, cy - this._sizeBy2, cx, cy) 91 | .setWidth(size).setHeight(size).setRadius(size / 4); 92 | this.bottom.border.setStart(this.bottom.getStartX() + this.$width, this.bottom.getStartY()) 93 | .setWidth(size).setHeight(size).setRadius(size / 4); 94 | this.outer.setStart(cx, cy).setRadius(size * goldenRatio / 2); 95 | this.outer.border.setStart(cx + this.$width, cy).setRadius(this.outer.getRadius()); 96 | var offset = this.outer.getRadius() * Math.pow(goldenRatio, 18); 97 | this.second.init(cx - offset * 8, cy - offset, cx, cy) 98 | .setWidth(this.outer.getRadius() * goldenRatio + offset * 8) 99 | .setHeight(offset * 2).setRadius(offset); 100 | offset /= Math.pow(goldenRatio, 2); 101 | this.minute.init(cx - offset, cy - offset, cx, cy) 102 | .setWidth(this.outer.getRadius() * Math.pow(goldenRatio, 2) + offset) 103 | .setHeight(offset * 2).setRadius(offset); 104 | offset /= Math.pow(goldenRatio, 2); 105 | this.hour.init(cx - offset, cy - offset, cx, cy) 106 | .setWidth(this.outer.getRadius() * Math.pow(goldenRatio, 4) + offset) 107 | .setHeight(offset * 2).setRadius(offset); 108 | this.dot.setStart(cx, cy).setRadius(offset / Math.pow(goldenRatio, 2)); 109 | this.resizeStyles(); 110 | }, 111 | visibilitychange: function (visible) { 112 | if (visible) { 113 | this.resetTime(); 114 | this.$raf.start(); 115 | } else { 116 | this.$raf.stop(); 117 | } 118 | }, 119 | onEvent: janvas.Utils.noop 120 | }, 121 | functions: { 122 | gradient: function (start, stop) { 123 | var grd = janvas.Utils.createLinearGradient(-this._sizeBy2, -this._sizeBy2, 124 | this._sizeBy2, this._sizeBy2, arguments[2]); 125 | grd.addColorStop(0, start); 126 | grd.addColorStop(1, stop); 127 | for (var i = 2; i < arguments.length; i++) arguments[i].getStyle().setFillStyle(grd); 128 | }, 129 | resetTime: function () { 130 | var date = new Date(); 131 | this.hours = date.getHours(); 132 | this.minutes = date.getMinutes(); 133 | this.seconds = date.getSeconds(); 134 | this.milliseconds = date.getMilliseconds(); 135 | this._allSeconds = this.hours * 3600 + this.minutes * 60 + this.seconds; 136 | this._secondIncrement = 1; 137 | this.setAngle(); 138 | }, 139 | setAngle: function () { 140 | this.hour.getMatrix().setAngle(this._allSeconds / 3600 / 12 * Math.PI * 2 - Math.PI / 2); 141 | this.minute.getMatrix().setAngle(this._allSeconds / 60 / 60 * Math.PI * 2 - Math.PI / 2); 142 | this.second.getMatrix().setAngle(this._allSeconds / 60 * Math.PI * 2 - Math.PI / 2); 143 | this._secondLastAngle = this.second.getMatrix().getAngle(); 144 | // if (this.hours === 5) { 145 | // if (this.minutes >= 45) this.shadowCursor.copy(this.shadowBasis).scaleX(this._allSeconds % 900 / 900); 146 | // } else 147 | // if (this.hours >= 6 && this.hours < 18) { // 设置 shadow 随时间变化的“角度” 148 | this.shadowCursor.copy(this.shadowBasis).rotate((this._allSeconds - 6 * 3600) % 43200 / (12 * 3600) * Math.PI); 149 | // } 150 | // else if (this.hours === 18) { 151 | // if (this.minutes < 15) this.shadowCursor.copy(this.shadowBasis).inverseX().scaleX(1 - this._allSeconds % 900 / 900); 152 | // } 153 | this._setShadow("OffsetX", this.shadowCursor.getX()); 154 | this._setShadow("OffsetY", this.shadowCursor.getY()); 155 | } 156 | } 157 | }); 158 | clock.onEvent = function (hours, minutes, seconds) { 159 | console.log(pad(hours) + ":" + pad(minutes) + ":" + pad(seconds)); 160 | 161 | function pad(num) { 162 | return num < 10 ? "0" + num : "" + num 163 | } 164 | }; 165 | -------------------------------------------------------------------------------- /js/coderain.js: -------------------------------------------------------------------------------- 1 | var codeRain = new janvas.Canvas({ 2 | container: "#app", 3 | duration: Infinity, 4 | interval: 50, 5 | props: { 6 | chars: [], // can define your own string array 7 | fontSize: 0, // can define your own preferred font size, 8 | colors: { 9 | background: "rgb(0, 0, 0)", 10 | head: "rgb(255, 255, 255)", 11 | tail: "rgb(0, 255, 0)", 12 | saturRange: [60, 90], // default: [60, 90], range: 0 ~ 100 13 | lightRange: [30, 40], // default: [30, 40], range: 0 ~ 100 14 | count: 3 // gradient count 15 | } 16 | }, 17 | methods: { 18 | init: function () { 19 | this._initChars(); 20 | this._initColors(); 21 | this.fontSize = this.fontSize || Math.ceil(this.$width / 120); 22 | this.font = "bold " + this.fontSize + "px sans-serif"; 23 | this.offsetY = janvas.Utils.measureTextWidth("M", this.font); 24 | this.halfY = this.offsetY / 2; 25 | this.background = new janvas.Rect(this.$ctx, 0, 0); 26 | this.background.getStyle().setFillStyle(this.colors.background); 27 | this.heads = []; 28 | this.pinMap = {}; 29 | this.pinStack = []; 30 | this.serialIdIndex = 0; 31 | }, 32 | resize: function () { 33 | var w = this.$width, h = this.$height; 34 | this.background.setWidth(w).setHeight(h); 35 | this.column = Math.floor(w / this.fontSize); 36 | this.count = Math.floor(this.column / 4); 37 | while (this.heads.length < this.count) { 38 | var x = this.getRandomX(), head = this.getDefaultText(); 39 | head.init(x, -this.halfY, x, -this.halfY) 40 | .getMatrix().setOffsetY(-this.rand(h * 2)); 41 | head.getStyle().setFillStyle(this.colors.head); 42 | head.timespan = this.rand(50, 100, true); 43 | head.timestamp = this.timestamp || 0; 44 | head.tails = new Array(Math.floor(head.timespan / 2) + 1); 45 | head.tails[0] = this.getDefaultText(); 46 | for (var i = 1; i < head.tails.length; i++) { 47 | head.tails[i] = this.getDefaultText(); 48 | head.tails[i].getStyle().setFillStyle(i < this.colors.count ? 49 | this.colors.gradient[i] : this.colors.tail); 50 | } 51 | this.heads.push(head); 52 | } 53 | this.heads.length = this.count; 54 | }, 55 | update: function (timestamp) { 56 | this.timestamp = timestamp; 57 | this.heads.forEach(function (head) { 58 | if (timestamp >= head.timestamp) { 59 | head.timestamp += head.timespan; 60 | var tails = head.tails, last = tails[tails.length - 1]; 61 | if (this.inBackground(last)) { // 新增 pin 以绘制拖尾效果 62 | var pin = this.pinStack.pop() || this.getDefaultText(); 63 | this.setTextConfig(pin, last.getStartX(), last.getStartY(), 64 | last.getText(), last.getMatrix()); 65 | pin.getStyle().setFillStyle(last.getStyle().getFillStyle()); 66 | pin.alpha = 255; 67 | pin.decre = last.decre; 68 | this.pinMap[pin.serialId] = pin; 69 | } 70 | var mat = head.getMatrix(), y = head.getStartY() + mat.getOffsetY(); 71 | this.setTextConfig(tails[0], head.getStartX(), y, head.getText(), mat); 72 | head.setText(this.getRandomChar()); // 将 head 的属性设置给 tails[0] 并变动 head 73 | mat.setScale(janvas.Utils.randSign(), janvas.Utils.randSign()) 74 | .setOffsetY(mat.getOffsetY() + this.offsetY); 75 | for (var i = tails.length - 1; i > 0; i--) { 76 | var next = tails[i], prev = tails[i - 1]; 77 | this.setTextConfig(next, prev.getStartX(), prev.getStartY(), 78 | Math.random() < 0.1 ? this.getRandomChar() : prev.getText(), 79 | prev.getMatrix()); // 将更靠近头部的前者属性设置给后者 80 | if (i > this.colors.count) { // 将更靠近头部的前者的颜色设置给后者 81 | next.getStyle().setFillStyle(prev.getStyle().getFillStyle()); 82 | next.decre = prev.decre; 83 | } 84 | } 85 | var range = this.colors.lightRange[1] - this.colors.lightRange[0], 86 | rand = this.rand(range), tail = tails[this.colors.count]; 87 | tail.getStyle().setFillStyle( // 随机取颜色 88 | this.colors.tailHsls[this.rand(this.colors.saturRange[1] - 89 | this.colors.saturRange[0])][rand]); 90 | tail.decre = rand < range / 2 ? 8 : 6; // alpha 减小幅度 91 | if (y >= this.$height + this.halfY) { // 当头部超出界面时 92 | mat.setOffsetY(0); 93 | var x = this.getRandomX(); 94 | head.init(x, -this.halfY, x, -this.halfY); 95 | } 96 | } 97 | }, this); 98 | }, 99 | draw: function () { 100 | this.background.fill(); // 背景绘制 101 | this.heads.forEach(function (head) { 102 | head.fill(); // 文字头部绘制 103 | for (var i = 1; i < head.tails.length; i++) { // 头部所带的尾巴绘制 104 | var tail = head.tails[i]; 105 | if (this.inBackground(tail)) { 106 | tail.fill(); 107 | } 108 | } 109 | }, this); 110 | for (var serialId in this.pinMap) { // 尾巴遗留处留下一个文本并渐变消失 111 | var pin = this.pinMap[serialId]; 112 | if (pin.alpha > pin.decre) { 113 | pin.getStyle().setAlpha((pin.alpha -= pin.decre) / 255); // 设置全局透明度 114 | pin.fill(); 115 | } else { 116 | delete this.pinMap[pin.serialId]; 117 | this.pinStack.push(pin); // 使用字典和栈回收管理这些 Text 文本 118 | } 119 | } 120 | }, 121 | _initChars: function () { // 初始化字符数组 122 | if (this.chars.length) return; 123 | var i; 124 | for (i = 0x0030; i < 0x003A; i++) { 125 | this.chars.push(String.fromCharCode(i)); 126 | } 127 | for (i = 0x30A0; i < 0x30FF; i++) { 128 | this.chars.push(String.fromCharCode(i)); 129 | } 130 | }, 131 | _initColors: function () { // 初始化颜色相关,包括随机颜色以及渐变颜色数组 132 | var tailRgb = new janvas.Rgb().fromRgbString(this.colors.tail), 133 | tailHsl = new janvas.Hsl().fromRgb(tailRgb), i, j, hsls; 134 | this.colors.tailHsls = []; 135 | for (i = this.colors.saturRange[0]; i <= this.colors.saturRange[1]; i++) { 136 | this.colors.tailHsls.push(hsls = []); 137 | for (j = this.colors.lightRange[0]; j <= this.colors.lightRange[1]; j++) { 138 | hsls.push(tailHsl.setSaturation(i).setLightness(j).toHslString()); 139 | } 140 | } 141 | this.colors.gradient = new Array(this.colors.count); 142 | var headSRgb = new janvas.Rgb().fromRgbString(this.colors.head) 143 | .sRgbInverseCompanding(), 144 | tailSRgb = tailRgb.sRgbInverseCompanding(), 145 | mix = new janvas.Rgb(); 146 | for (i = 1; i < this.colors.count; i++) { 147 | janvas.Rgb.sRgbGammaMixing(headSRgb, tailSRgb, i / this.colors.count, mix); 148 | this.colors.gradient[i] = mix.sRgbCompanding().toRgbString(); 149 | } 150 | } 151 | }, 152 | events: { 153 | visibilitychange: function (visible) { 154 | visible ? this.$raf.resume() : this.$raf.pause(); 155 | } 156 | }, 157 | functions: { 158 | rand: janvas.Utils.randInt, 159 | getDefaultText: function () { // 获取一个默认样式并且带 serialId 的 Text 160 | var text = new janvas.Text(this.$ctx, -this.halfY - 1, -this.halfY - 1, ""); 161 | text.serialId = this.serialIdIndex++; 162 | text.getStyle().setFont(this.font) 163 | .setTextAlign("center").setTextBaseline("middle"); 164 | return text; 165 | }, 166 | getRandomChar: function () { 167 | return this.chars[this.rand(this.chars.length)]; 168 | }, 169 | getRandomX: function () { // 获取一个与所有 heads 的 startX 不同的随机 x 值 170 | var x; 171 | while ((x = this.rand(this.column) * this.fontSize + this.halfY)) { 172 | if (this.heads.every(function (head) { 173 | return x !== head.getStartX(); 174 | })) { 175 | return x; 176 | } 177 | } 178 | }, 179 | setTextConfig: function (tail, x, y, text, mat) { // 设置 Text 文本的一些属性 180 | tail.init(x, y, x, y).setText(text) 181 | .getMatrix().setScale(mat.getScaleX(), mat.getScaleY()); 182 | }, 183 | inBackground: function (tail) { // 判断 Text 是否在背景可视范围内 184 | var x = tail.getStartX(), y = tail.getStartY(), hy = this.halfY, 185 | b = this.background; 186 | return janvas.Collision.rect(x - hy, y - hy, x + hy, y + hy, 187 | b.getStartX(), b.getStartY(), b.getWidth(), b.getHeight()); 188 | } 189 | } 190 | }); 191 | -------------------------------------------------------------------------------- /js/antv.js: -------------------------------------------------------------------------------- 1 | var antv = new janvas.Canvas({ 2 | container: "#app", 3 | components: { 4 | factory: (function () { 5 | function Node($ctx, id, x, y, label) { 6 | this._defaultRadius = 1; 7 | this._scale = 1; 8 | this._showText = false; 9 | this.arc = new janvas.FixedArc($ctx, 0, 0, this._defaultRadius); 10 | this._arc = new janvas.FixedRect($ctx, 11 | -this._defaultRadius, -this._defaultRadius, 12 | this._defaultRadius * 2, this._defaultRadius * 2); 13 | this._arc.setMatrix(this.arc.getMatrix()); 14 | this._arc.setStyle(this.arc.getStyle()); 15 | this.text = new janvas.Text($ctx, 0, 0, label || id); 16 | this.text.getStyle().setFont("1.5px sans-serif").setTextBaseline("middle"); 17 | this.setXY(x, y); 18 | this.highlight(false); 19 | this.draw = this.fillStroke; 20 | } 21 | 22 | Node.prototype = { 23 | setXY: function (x, y) { 24 | this.arc.getMatrix().setOffset(this.x = x, this.y = y); 25 | this.text.init(x + this._defaultRadius * this._scale * 2, y, 26 | x + this._defaultRadius * this._scale * 2, y); 27 | }, 28 | getX: function () { 29 | return this.x; 30 | }, 31 | getY: function () { 32 | return this.y; 33 | }, 34 | getLabel: function () { 35 | return this.text.getText(); 36 | }, 37 | setScale: function (scale) { 38 | this._scale = scale; 39 | this.arc.getMatrix().setScale(scale, scale); 40 | this.text.getMatrix().setScale(scale, scale); 41 | }, 42 | fillStroke: function () { 43 | this.arc.fillStroke(); 44 | if (this._showText) this.text.fill(); 45 | }, 46 | onlyFill: function () { 47 | this._arc.fill(); 48 | }, 49 | in: function (rect) { 50 | return janvas.Collision.rect( 51 | this.x - this._defaultRadius * this._scale, 52 | this.y - this._defaultRadius * this._scale, 53 | this.x + this._defaultRadius * this._scale, 54 | this.y + this._defaultRadius * this._scale, 55 | rect.getLeft(), rect.getTop(), rect.getRight(), rect.getBottom()); 56 | }, 57 | isPointInPath: function (x, y) { 58 | return this._arc.isPointInPath(x, y); 59 | }, 60 | highlight: function (flag) { 61 | if (flag) this.arc.getStyle().setFillStyle("#FF0000") 62 | .setStrokeStyle("#000000").setLineWidth(1); 63 | else this.arc.getStyle().setFillStyle("#C6E5FF") 64 | .setStrokeStyle("#5B8FF9").setLineWidth(0.3); 65 | }, 66 | showText: function (flag) { 67 | this._showText = flag; 68 | }, 69 | mark: function () { 70 | this.lastX = this.x; 71 | this.lastY = this.y; 72 | }, 73 | drag: function (moveX, moveY) { 74 | this.setXY(this.lastX + moveX, this.lastY + moveY); 75 | }, 76 | wheel: function (x, y, scaling, scale) { 77 | this.setScale(scale); 78 | this.setXY(x + (this.x - x) * scaling, y + (this.y - y) * scaling); 79 | this.draw = scale * this._defaultRadius < 1 ? this.onlyFill : this.fillStroke; 80 | } 81 | }; 82 | 83 | function Edge($ctx, source, target) { 84 | this._show = true; 85 | this.source = source; 86 | this.target = target; 87 | this.line = new janvas.Line($ctx, 0, 0, 0, 0); 88 | this.line.getStyle().setStrokeStyle("#333333").setLineWidth(0.1); 89 | this.refresh(); 90 | } 91 | 92 | Edge.prototype = { 93 | draw: function () { 94 | if (this._show) this.line.stroke(); 95 | }, 96 | refresh: function () { 97 | this.line.setStart(this.source.getX(), this.source.getY()) 98 | .setEndX(this.target.getX()).setEndY(this.target.getY()); 99 | }, 100 | setLineWidth: function (scale) { 101 | this.line.getStyle().setLineWidth(scale / 10); 102 | }, 103 | show: function (flag) { 104 | this._show = flag; 105 | } 106 | }; 107 | 108 | function Hint($ctx) { 109 | this._show = false; 110 | this._of = this._pdt = 3; // offset, paddingLeft, paddingTop 111 | this._pdl = 5; 112 | this.roundRect = new janvas.RoundRect($ctx, 0, 0, 0, 0, this._pdl); 113 | this.roundRect.getStyle().setFillStyle("white").setStrokeStyle("white") 114 | .setShadowBlur(20).setShadowColor("grey"); 115 | this.text = new janvas.Text($ctx, 0, 0, ""); 116 | this.text.getStyle().setFont("12px sans-serif").setTextBaseline("bottom"); 117 | } 118 | 119 | Hint.prototype = { 120 | draw: function () { 121 | if (this._show) { 122 | this.roundRect.fillStroke(); 123 | this.text.fill(); 124 | } 125 | }, 126 | setXY: function (x, y) { 127 | x += this._of; 128 | y -= this._of; 129 | this.text.setStart(x + this._pdl, y - this._pdt); 130 | this.roundRect.setStart(x, y); 131 | }, 132 | setLabel: function (label) { 133 | if (this.label === label) return; 134 | this.label = label; 135 | this.text.setText(label); 136 | this.roundRect.setWidth(this.text.getActualBoundingBoxWidth() + this._pdl * 2) 137 | .setHeight(-(this.text.getActualBoundingBoxHeight() + this._pdt * 2)); 138 | }, 139 | show: function (flag) { 140 | this._show = flag; 141 | } 142 | }; 143 | 144 | return { 145 | nodesMap: new Map(), 146 | newNode: function ($ctx, id, x, y, label) { 147 | var node = new Node($ctx, id, x, y, label); 148 | this.nodesMap.set(id, node); 149 | return node; 150 | }, 151 | newEdge: function ($ctx, source, target) { 152 | source = this.nodesMap.get(source); 153 | target = this.nodesMap.get(target); 154 | return source && target ? new Edge($ctx, source, target) : null; 155 | }, 156 | Hint: Hint 157 | }; 158 | }()) 159 | }, 160 | methods: { 161 | init: function () { 162 | this.nodes = []; 163 | this.edges = []; 164 | this.hint = new this.factory.Hint(this.$ctx); 165 | this.background = new janvas.Rect(this.$ctx, 0, 0, this.$width, this.$height); 166 | }, 167 | draw: function () { 168 | this.$clear(); 169 | this.edges.forEach(function (edge) { 170 | edge.draw(); 171 | }); 172 | this.nodes.forEach(function (node) { 173 | if (node.in(this.background)) node.draw(); 174 | }, this); 175 | this.hint.draw(); 176 | }, 177 | data: function (res) { 178 | this.nodes.length = this.edges.length = 0; 179 | res.nodes.forEach(function (node) { 180 | this.nodes.push(this.factory.newNode(this.$ctx, 181 | node.id, node.x, node.y, node.olabel)); 182 | }, this); 183 | res.edges.forEach(function (edge) { 184 | edge = this.factory.newEdge(this.$ctx, edge.source, edge.target); 185 | if (edge) this.edges.push(edge); 186 | }, this); 187 | this.draw(); 188 | } 189 | }, 190 | events: { 191 | mousedown: function () { 192 | this._mousedown = true; 193 | this.nodes.forEach(function (node) { 194 | node.mark(); 195 | }); 196 | if (this._current === void (0)) { 197 | this.edges.forEach(function (edge) { 198 | edge.show(false); 199 | }); 200 | } 201 | this.draw(); 202 | }, 203 | mousemove: function (ev) { 204 | if (this._mousedown) { 205 | if (this._current === void (0)) { 206 | this.nodes.forEach(function (node) { 207 | node.drag(ev.$moveX, ev.$moveY); 208 | }, this); 209 | } else { 210 | this._current.drag(ev.$moveX, ev.$moveY); 211 | this.hint.setXY(ev.$x, ev.$y); 212 | this.edges.forEach(function (edge) { 213 | edge.refresh(); 214 | }); 215 | } 216 | } else { 217 | var mousein = true; 218 | this.nodes.forEach(function (node) { 219 | if (node.isPointInPath(ev.$x, ev.$y)) { 220 | this._current = node; 221 | mousein = false; 222 | } 223 | node.highlight(false); 224 | }, this); 225 | if (mousein) { 226 | this._current = void (0); 227 | this.hint.show(false); 228 | } else { 229 | this._current.highlight(true); 230 | this.hint.setLabel(this._current.getLabel()); 231 | this.hint.setXY(ev.$x, ev.$y); 232 | this.hint.show(true); 233 | } 234 | } 235 | this.draw(); 236 | }, 237 | mouseup: function () { 238 | this._mousedown = false; 239 | this.edges.forEach(function (edge) { 240 | edge.show(true); 241 | edge.refresh(); 242 | }); 243 | this.draw(); 244 | }, 245 | wheel: function (ev) { 246 | ev.preventDefault(); 247 | this.nodes.forEach(function (node) { 248 | node.wheel(ev.$x, ev.$y, ev.$scaling, ev.$scale) 249 | }, this); 250 | this.edges.forEach(function (edge) { 251 | edge.refresh(); 252 | edge.setLineWidth(ev.$scale); 253 | }, this); 254 | this.draw(); 255 | }, 256 | resize: function () { 257 | this.background.setWidth(this.$width).setHeight(this.$height); 258 | } 259 | } 260 | }); 261 | // 原示例:https://g6.antv.vision/zh/examples/performance/perf#moreData 262 | fetch("https://gw.alipayobjects.com/os/bmw-prod/f1565312-d537-4231-adf5-81cb1cd3a0e8.json") 263 | .then(function (res) { 264 | return res.json(); 265 | }).then(function (res) { 266 | antv.data(res); 267 | }); 268 | -------------------------------------------------------------------------------- /js/beziermaker.js: -------------------------------------------------------------------------------- 1 | var bezierMaker = new janvas.Canvas({ 2 | container: "#app", 3 | duration: Infinity, 4 | interval: 16, 5 | props: { 6 | position: 0, // 指示器运行位置 7 | size: Math.floor(10000 / 16), // 10000ms/16ms,10秒运行次数 8 | keys: ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Delete", "Enter", "w", "s", "a", "d", "q", "e"] 9 | }, 10 | components: { 11 | factory: (function () { 12 | function Dot($ctx, x, y, index) { 13 | this._index = index; 14 | this._r = 5; 15 | this.arc = new janvas.Arc($ctx, 0, 0, this._r); 16 | this.text = new janvas.Text($ctx); 17 | this.setStart(x, y); 18 | this.initStyles(); 19 | this.highlight(true); 20 | } 21 | 22 | Dot.prototype = { 23 | setStart: function (x, y) { 24 | this._x = x; 25 | this._y = y; 26 | this.arc.setStart(x, y); 27 | this.text.setStart(x + this._r * 2, y).setText(this.getReadableText()); 28 | }, 29 | getReadableText: function () { 30 | return "p" + this._index + "(" + this._x + "," + this._y + ")"; 31 | }, 32 | initStyles: function () { 33 | this.arc.getStyle().setStrokeStyle("hsl(0, 80%, 50%)").setLineWidth(2); 34 | this.text.getStyle().setFont("12px sans-serif").setTextAlign("start").setTextBaseline("middle"); 35 | }, 36 | drawHighlight: function () { 37 | this.arc.fillStroke(); 38 | this.text.fill(); 39 | }, 40 | drawDefault: function () { 41 | this.arc.fill(); 42 | this.text.fill(); 43 | }, 44 | highlight: function (flag) { 45 | if (this._highlight !== flag) { 46 | this._highlight = flag; 47 | this.draw = flag ? this.drawHighlight : this.drawDefault; 48 | } 49 | }, 50 | mark: function () { 51 | this._lastX = this._x; 52 | this._lastY = this._y; 53 | }, 54 | onmove: function (moveX, moveY) { 55 | this.setStart(this._lastX + moveX, this._lastY + moveY); 56 | }, 57 | getX: function () { 58 | return this._x; 59 | }, 60 | getY: function () { 61 | return this._y; 62 | }, 63 | getIndex: function () { 64 | return this._index; 65 | }, 66 | decreaseIndex: function () { 67 | this._index--; 68 | this.text.setText(this.getReadableText()); 69 | }, 70 | isPointInPath: function (x, y) { 71 | return this.arc.isPointInPath(x, y); 72 | } 73 | }; 74 | 75 | return { 76 | _index: 0, 77 | newDot: function ($ctx, x, y) { 78 | return new Dot($ctx, x, y, this._index++); 79 | }, 80 | decreaseAutoIndex: function () { 81 | this._index--; 82 | } 83 | }; 84 | }()) 85 | }, 86 | methods: { 87 | init: function () { 88 | this.dots = []; 89 | this.polyline = new janvas.Polyline(this.$ctx, 0, 0, []); 90 | this.bezier = new janvas.Bezier(this.$ctx, 0, 0, this.polyline.getPoints(), this.size * 2); 91 | this.bezier.getStyle().setStrokeStyle("hsl(0, 80%, 50%)"); 92 | this.transformedPoints = this.bezier.getTransformedPoints(); 93 | this.hint = new janvas.Text(this.$ctx, 0, 0, ""); 94 | this.hint.getStyle().setFillStyle("rgba(0, 0, 0, 0.5)") 95 | .setFont("12px sans-serif").setTextAlign("end").setTextBaseline("middle"); 96 | this.cursor = new janvas.Triangle(this.$ctx, 0, 0); 97 | this.cursor.getStyle().setFillStyle("hsl(270, 80%, 50%)"); 98 | }, 99 | draw: function () { 100 | this.$clear(); 101 | this.polyline.stroke(); 102 | this.bezier.stroke(); 103 | this.dots.forEach(function (dot) { 104 | dot.draw(); 105 | }); 106 | this.hint.fill(); 107 | this.updateCursor(); 108 | this.cursor.fill(); 109 | } 110 | }, 111 | events: { 112 | mousedown: function (ev) { 113 | if (this._autoResize) { 114 | // var _dispatch; 115 | if (ev.$x > this.$width * 0.875) this.$wrapper.style.width = this.$width * 1.5 + "px"; 116 | if (ev.$y > this.$height * 0.875) this.$wrapper.style.height = this.$height * 1.5 + "px"; 117 | // if (_dispatch) dispatchEvent(new Event("resize")); 118 | // if (_dispatch) { 119 | // var ev = document.createEvent("Event"); 120 | // ev.initEvent("resize", true, true); 121 | // dispatchEvent(ev); 122 | // } 123 | } 124 | if (ev.buttons === 4) { 125 | return; 126 | } else if (ev.buttons === 2) { // 鼠标右键 127 | this.dots.forEach(function (dot) { 128 | dot.mark(); 129 | }); 130 | } else { // 非右键,默认行为 131 | if (this.current) { 132 | this.toggleLocked(this.current); 133 | } else { 134 | if (this.locked) this.locked.highlight(false); 135 | this.dots.push(this.locked = this.current = this.factory.newDot(this.$ctx, ev.$x, ev.$y)); 136 | this.polyline.append(ev.$x, ev.$y); 137 | } 138 | this.locked.mark(); 139 | } 140 | }, 141 | mousemove: function (ev) { 142 | if (ev.buttons === 2) { // 鼠标右键 143 | this.dots.forEach(function (dot) { 144 | dot.onmove(ev.$moveX, ev.$moveY); 145 | this.polyline.update(dot.getIndex(), dot.getX(), dot.getY()); 146 | }, this); 147 | } else if (ev.buttons === 1) { // 鼠标左键 148 | this.locked.onmove(ev.$moveX, ev.$moveY); 149 | this.polyline.update(this.locked.getIndex(), this.locked.getX(), this.locked.getY()); 150 | } else { // 无按键,默认行为 151 | if (this.current) this.current.highlight(false); 152 | this.current = void (0); 153 | this.dots.forEach(function (dot) { 154 | if (dot.isPointInPath(ev.$x, ev.$y)) this.current = dot; 155 | }, this); 156 | if (this.current) { 157 | this.locked.highlight(false); 158 | this.current.highlight(true); 159 | } else { 160 | if (this.locked) this.locked.highlight(true); 161 | } 162 | } 163 | this.hint.setStart(ev.$x, ev.$y).setText("(" + ev.$x + "," + ev.$y + ")"); 164 | }, 165 | mouseover: function () { 166 | this.$raf.resume(); 167 | }, 168 | mouseout: function () { 169 | this.$raf.pause(); 170 | }, 171 | keydown: function (ev) { 172 | if (this.locked === void (0)) return; 173 | if (this.keys.some(function (value) { 174 | return value === ev.key; 175 | })) ev.preventDefault(); 176 | var increase = ev.repeat ? 5 : 1; 177 | switch (ev.key) { 178 | case "ArrowUp": 179 | case "w": 180 | this.locked.setStart(this.locked.getX(), this.locked.getY() - increase); 181 | this.polyline.update(this.locked.getIndex(), this.locked.getX(), this.locked.getY()); 182 | break; 183 | case "ArrowDown": 184 | case "s": 185 | this.locked.setStart(this.locked.getX(), this.locked.getY() + increase); 186 | this.polyline.update(this.locked.getIndex(), this.locked.getX(), this.locked.getY()); 187 | break; 188 | case "ArrowLeft": 189 | case "a": 190 | this.locked.setStart(this.locked.getX() - increase, this.locked.getY()); 191 | this.polyline.update(this.locked.getIndex(), this.locked.getX(), this.locked.getY()); 192 | break; 193 | case "ArrowRight": 194 | case "d": 195 | this.locked.setStart(this.locked.getX() + increase, this.locked.getY()); 196 | this.polyline.update(this.locked.getIndex(), this.locked.getX(), this.locked.getY()); 197 | break; 198 | case "Delete": 199 | if (this.dots.length > 0) { 200 | this.dots.forEach(function (dot, index) { 201 | if (index > this.locked.getIndex()) dot.decreaseIndex(); 202 | }, this); 203 | this.factory.decreaseAutoIndex(); 204 | this.dots.splice(this.dots.indexOf(this.locked), 1); 205 | this.polyline.delete(this.locked.getIndex()); 206 | this.locked = this.dots[this.locked.getIndex()] 207 | || this.dots[this.locked.getIndex() - 1]; 208 | if (this.locked) this.locked.highlight(true); 209 | } 210 | break; 211 | case "Enter": 212 | console.log("SOURCEDOTS: " + this.polyline.getTransformedPoints().toString()); 213 | console.log("CALCULATED: " + this.bezier.getTransformedPoints().toString()); 214 | break; 215 | case "q": 216 | this.toggleLocked(this.dots[this.locked.getIndex() - 1] || this.dots[this.dots.length - 1]); 217 | break; 218 | case "e": 219 | this.toggleLocked(this.dots[this.locked.getIndex() + 1] || this.dots[0]); 220 | break; 221 | } 222 | }, 223 | autoResize: function (flag) { 224 | this._autoResize = flag; 225 | } 226 | }, 227 | functions: { 228 | toggleLocked: function (dot) { 229 | this.locked.highlight(false); 230 | this.locked = dot; 231 | this.locked.highlight(true); 232 | }, 233 | updateCursor: function () { 234 | var position = this.position, transformedPoints = this.transformedPoints; 235 | var x1 = transformedPoints[position], y1 = transformedPoints[position + 1], x2, y2; 236 | if (position !== 0) { 237 | x2 = transformedPoints[position - 2]; 238 | y2 = transformedPoints[position - 1]; 239 | } else { 240 | x2 = transformedPoints[position + 2]; 241 | y2 = transformedPoints[position + 3]; 242 | } 243 | this.position += 2; 244 | if (this.position === transformedPoints.length) this.position = 0; 245 | this.cursor.setStart(x1, y1).setRotation(Math.atan2(y1 - y2, x1 - x2)); 246 | } 247 | } 248 | }); 249 | bezierMaker.autoResize(true); 250 | -------------------------------------------------------------------------------- /js/thelastjanvas.js: -------------------------------------------------------------------------------- 1 | var theLastJanvas = new janvas.Canvas({ 2 | container: "#app", 3 | duration: Infinity, 4 | interval: 16, 5 | components: { 6 | Dancer: (function () { 7 | function Dancer($ctx, hsl, size, x, y, struct) { 8 | this.hsl = hsl; 9 | this.size = size; 10 | this._size = 16 * Math.sqrt(size); 11 | this.x = x; 12 | this.points = []; 13 | this.links = []; 14 | this.frame = 0; 15 | this.dir = 1; 16 | struct.points.forEach(function (point) { 17 | this.points.push(new Dancer.Point(size * point.x + x, size * point.y + y, point.fn)); 18 | }, this); 19 | struct.links.forEach(function (link) { 20 | this.links.push(new Dancer.Link($ctx, 21 | hsl.clone().setLightness(hsl.getLightness() * link.lum), 22 | link.size * size / 3, 23 | this.points[link.p0], 24 | this.points[link.p1], 25 | link.force, 26 | link.disk 27 | )); 28 | }, this); 29 | } 30 | 31 | Dancer.prototype.update = function (pointer) { 32 | if (++this.frame % 20 === 0) this.dir = -this.dir; 33 | if (this === pointer.dancerDrag && this.frame > 600 && this.size < 16) { 34 | this.onDragEnd.call(pointer.context, this); 35 | } 36 | this.links.forEach(function (link) { 37 | link.update(); 38 | }); 39 | this.points.forEach(function (point) { 40 | if (this === pointer.dancerDrag) { 41 | if (point === pointer.pointDrag) { 42 | point.x += (pointer.x - point.x) * 0.1; 43 | point.y += (pointer.y - point.y) * 0.1; 44 | } 45 | } else { 46 | point.fn(this._size, this.dir); 47 | } 48 | point.update(); 49 | }, this); 50 | this.links.forEach(function (link) { 51 | var p1 = link.p1; 52 | if (p1.y > pointer.ground - link.size / 2) { 53 | p1.y = pointer.ground - link.size / 2; 54 | p1.x -= p1.vx; 55 | p1.vx = p1.vy = 0; 56 | } 57 | }); 58 | this.points[3].x += (this.x - this.points[3].x) * 0.001; 59 | }; 60 | 61 | Dancer.prototype.draw = function () { 62 | this.links.forEach(function (link) { 63 | link.draw(); 64 | }); 65 | }; 66 | 67 | Dancer.Link = function ($ctx, hsl, size, p0, p1, force, isHead) { 68 | this.hsl = hsl; 69 | this.size = size; 70 | this._offset = size / 10; 71 | this.p0 = p0; 72 | this.p1 = p1; 73 | this.distance = janvas.Utils.pythagorean(p1.x - p0.x, p1.y - p0.y); 74 | this.force = force || 0.5; 75 | this.isHead = isHead; 76 | this.startRect = new janvas.Rect($ctx, 0, 0, size / 5, size / 5); 77 | this.endRect = new janvas.Rect($ctx, 0, 0, size / 5, size / 5); 78 | this.body = new janvas.Line($ctx); 79 | this.body.getStyle().setStrokeStyle(this.hsl.toHslString()) 80 | .setLineWidth(size).setLineCap("round") 81 | .setShadowColor("rgba(0, 0, 0, 0.5)") 82 | .setShadowOffsetX(size / 4).setShadowOffsetY(size / 4); 83 | }; 84 | 85 | Dancer.Link.prototype = { 86 | update: function () { 87 | var p0 = this.p0, p1 = this.p1, 88 | dx = p1.x - p0.x, dy = p1.y - p0.y, 89 | dist = janvas.Utils.pythagorean(dx, dy), 90 | tw = p0.w + p1.w, r0 = p0.w / tw, r1 = p1.w / tw, 91 | dz = (this.distance - dist) * this.force, 92 | sx = dx / dist * dz, sy = dy / dist * dz; 93 | p1.x += sx * r0; 94 | p1.y += sy * r0; 95 | p0.x -= sx * r1; 96 | p0.y -= sy * r1; 97 | }, 98 | draw: function () { 99 | var p0 = this.p0, p1 = this.p1, o = this._offset; 100 | if (this.isHead) this.body.setStart(p1.x, p1.y); 101 | else this.body.setStart(p0.x, p0.y); 102 | this.body.setEnd(p1.x, p1.y).stroke(); 103 | this.startRect.getMatrix().setAngle(Math.atan2(p1.y - p0.y, p1.x - p0.x)); 104 | this.endRect.getMatrix().setAngle(this.startRect.getMatrix().getAngle()); 105 | this.startRect.init(p0.x - o, p0.y - o, p0.x, p0.y).fill(); 106 | this.endRect.init(p1.x - o, p1.y - o, p1.x, p1.y).fill(); 107 | } 108 | }; 109 | 110 | Dancer.Point = function (x, y, fn, w) { 111 | this.x = x; 112 | this.y = y; 113 | this.w = w || 0.5; 114 | this.fn = fn || janvas.Utils.noop; 115 | this.px = x; 116 | this.py = y; 117 | this.vx = 0; 118 | this.vy = 0; 119 | }; 120 | 121 | Dancer.Point.prototype.update = function () { 122 | this.vx = this.x - this.px; 123 | this.vy = this.y - this.py; 124 | this.px = this.x; 125 | this.py = this.y; 126 | this.vx *= 0.995; 127 | this.vy *= 0.995; 128 | this.x += this.vx; 129 | this.y += this.vy + 0.01; 130 | }; 131 | 132 | return Dancer; 133 | }()) 134 | }, 135 | props: { 136 | struct: { 137 | points: [ 138 | { 139 | x: 0, 140 | y: -4, 141 | fn: function (s, d) { 142 | this.y -= 0.01 * s; 143 | } 144 | }, 145 | { 146 | x: 0, 147 | y: -16, 148 | fn: function (s, d) { 149 | this.y -= 0.02 * s * d; 150 | } 151 | }, 152 | { 153 | x: 0, 154 | y: 12, 155 | fn: function (s, d) { 156 | this.y += 0.02 * s * d; 157 | } 158 | }, 159 | {x: -12, y: 0}, 160 | {x: 12, y: 0}, 161 | { 162 | x: -3, 163 | y: 34, 164 | fn: function (s, d) { 165 | if (d > 0) { 166 | this.x += 0.01 * s; 167 | this.y -= 0.015 * s; 168 | } else { 169 | this.y += 0.02 * s; 170 | } 171 | } 172 | }, 173 | { 174 | x: 3, 175 | y: 34, 176 | fn: function (s, d) { 177 | if (d > 0) { 178 | this.y += 0.02 * s; 179 | } else { 180 | this.x -= 0.01 * s; 181 | this.y -= 0.015 * s; 182 | } 183 | } 184 | }, 185 | { 186 | x: -28, 187 | y: 0, 188 | fn: function (s, d) { 189 | this.x += this.vx * 0.025; 190 | this.y -= 0.001 * s; 191 | } 192 | }, 193 | { 194 | x: 28, 195 | y: 0, 196 | fn: function (s, d) { 197 | this.x += this.vx * 0.025; 198 | this.y -= 0.001 * s; 199 | } 200 | }, 201 | { 202 | x: -3, 203 | y: 64, 204 | fn: function (s, d) { 205 | this.y += 0.015 * s; 206 | if (d > 0) { 207 | this.y -= 0.01 * s; 208 | } else { 209 | this.y += 0.05 * s; 210 | } 211 | } 212 | }, 213 | { 214 | x: 3, 215 | y: 64, 216 | fn: function (s, d) { 217 | this.y += 0.015 * s; 218 | if (d > 0) { 219 | this.y += 0.05 * s; 220 | } else { 221 | this.y -= 0.01 * s; 222 | } 223 | } 224 | } 225 | ], 226 | links: [ 227 | {p0: 3, p1: 7, size: 12, lum: 0.5}, // 左小臂 228 | {p0: 1, p1: 3, size: 24, lum: 0.5}, // 左大臂 229 | {p0: 1, p1: 0, size: 60, lum: 0.5, disk: 1}, // 头 230 | {p0: 5, p1: 9, size: 16, lum: 0.5}, // 左小腿 231 | {p0: 2, p1: 5, size: 32, lum: 0.5}, // 左大腿 232 | {p0: 1, p1: 2, size: 50, lum: 1}, // 身体 233 | {p0: 6, p1: 10, size: 16, lum: 1.5}, // 右小腿 234 | {p0: 2, p1: 6, size: 32, lum: 1.5}, // 右大腿 235 | {p0: 4, p1: 8, size: 12, lum: 1.5}, // 右小臂 236 | {p0: 1, p1: 4, size: 24, lum: 1.5} // 右大臂 237 | ] 238 | } 239 | }, 240 | methods: { 241 | init: function () { 242 | this.background = new janvas.Rect(this.$ctx, 0, 0); 243 | this.header = new janvas.Rect(this.$ctx, 0, 0); 244 | this.header.getStyle().setFillStyle("#222"); 245 | this.footer = new janvas.Rect(this.$ctx, 0); 246 | this.footer.getStyle().setFillStyle("#222"); 247 | this.dancers = []; 248 | this.pointer = {x: 0, y: 0, dancerDrag: null, pointDrag: null, ground: 0, context: this}; 249 | this._initDancer(); 250 | }, 251 | resize: function () { 252 | var w = this.$width, h = this.$height; 253 | this.background.setWidth(w).setHeight(h); 254 | this.header.setWidth(w).setHeight(h * 0.15); 255 | this.footer.setStartY(h * 0.85).setWidth(w).setHeight(this.header.getHeight()); 256 | this.pointer.ground = h > 500 ? this.footer.getStartY() : h; 257 | for (var i = 0; i < this.dancers.length; i++) { 258 | this.dancers[i].x = (i + 2) * w / 9; 259 | } 260 | }, 261 | draw: function () { 262 | this.background.fill(); 263 | this.header.fill(); 264 | this.footer.fill(); 265 | this.dancers.forEach(function (dancer) { 266 | dancer.update(this.pointer); 267 | dancer.draw(); 268 | }, this); 269 | }, 270 | _initDancer: function () { 271 | var w = this.$width, h = this.$height; 272 | for (var i = 0; i < 6; i++) { 273 | this._pushDancer( 274 | new janvas.Hsl(i * 360 / 7, 30, 80), 275 | Math.sqrt(Math.min(w, h)) / 6, 276 | (i + 2) * w / 9, 277 | h * 0.5 - 100 278 | ); 279 | } 280 | }, 281 | _pushDancer: function (hsl, size, x, y) { 282 | var dancer = new this.Dancer(this.$ctx, hsl, size, x, y, this.struct); 283 | dancer.onDragEnd = this._onDancerDragEnd; 284 | this.dancers.push(dancer); 285 | }, 286 | _onDancerDragEnd: function (dancer) { 287 | var hsl = dancer.hsl, pointer = this.pointer; 288 | pointer.dancerDrag = null; 289 | this._pushDancer( 290 | hsl.clone().setHue(hsl.getHue() + 90).setLightness(hsl.getLightness() * 1.25), 291 | dancer.size * 2, 292 | pointer.x, 293 | pointer.y - 100 * dancer.size * 2 294 | ); 295 | this.dancers.sort(function (d0, d1) { 296 | return d0.size - d1.size; 297 | }); 298 | } 299 | }, 300 | events: { 301 | eventdown: function (ev) { 302 | var pointer = this.pointer; 303 | pointer.x = ev.$x; 304 | pointer.y = ev.$y; 305 | this.dancers.forEach(function (dancer) { 306 | dancer.points.forEach(function (point) { 307 | if (janvas.Utils.pythagorean(ev.$x - point.x, ev.$y - point.y) < 60) { 308 | pointer.dancerDrag = dancer; 309 | pointer.pointDrag = point; 310 | dancer.frame = 0; 311 | } 312 | }); 313 | }); 314 | }, 315 | eventmove: function (ev) { 316 | this.pointer.x = ev.$x; 317 | this.pointer.y = ev.$y; 318 | }, 319 | eventup: function () { 320 | this.pointer.dancerDrag = null; 321 | }, 322 | visibilitychange: function (visible) { 323 | visible ? this.$raf.resume() : this.$raf.pause(); 324 | }, 325 | mousedown: function (ev) { 326 | this.eventdown(ev); 327 | }, 328 | mousemove: function (ev) { 329 | this.eventmove(ev); 330 | }, 331 | mouseup: function () { 332 | this.eventup(); 333 | }, 334 | touchstart: function (ev) { 335 | ev.preventDefault(); 336 | this.eventdown(ev.targetTouches[0]); 337 | }, 338 | touchmove: function (ev) { 339 | ev.preventDefault(); 340 | this.eventmove(ev.targetTouches[0]); 341 | }, 342 | touchend: function () { 343 | this.eventup(); 344 | } 345 | } 346 | }); 347 | -------------------------------------------------------------------------------- /js/sudoku.js: -------------------------------------------------------------------------------- 1 | var sudoku = new janvas.Canvas({ 2 | container: "#app", 3 | components: { 4 | Grid: (function () { 5 | function _error(error) { 6 | this._error = error; 7 | this.getStyle().setFillStyle(error ? 8 | "#ed1524" : this.getStyle()._lastFillStyle); 9 | } 10 | 11 | function Grid($ctx) { 12 | this.rects = []; 13 | this.texts = []; 14 | this.numbers = []; 15 | for (var i = 0; i < 3; i++) { 16 | var t1 = [], t2 = [], t3 = new Array(3); 17 | for (var j = 0; j < 3; j++) { 18 | var t = new janvas.Rect($ctx); 19 | t.getStyle().setFillStyle("#fefefe").setStrokeStyle("#bdc4d3"); 20 | t.i = i; 21 | t.j = j; 22 | t1.push(t); 23 | t = new janvas.Text($ctx, 0, 0, ""); 24 | t.error = _error; 25 | t.getStyle().setTextAlign("center").setTextBaseline("middle"); 26 | t2.push(t); 27 | t3[j] = 0; 28 | } 29 | this.rects.push(t1); 30 | this.texts.push(t2); 31 | this.numbers.push(t3); 32 | } 33 | this.border = new janvas.Rect($ctx); 34 | this.border.getStyle().setStrokeStyle("#000000"); 35 | } 36 | 37 | Grid.prototype = { 38 | setStart: function (sx, sy) { 39 | var s = this._size / 3, o = s / 2; 40 | for (var i = 0; i < 3; i++) { 41 | for (var j = 0; j < 3; j++) { 42 | var x = sx + j * s, y = sy + i * s; 43 | this.rects[i][j].setStart(x, y); 44 | this.texts[i][j].setStart(x + o, y + o); 45 | } 46 | } 47 | this.border.setStart(sx, sy); 48 | }, 49 | resize: function (sx, sy, size) { 50 | this._size = size; 51 | var s = size / 3, o = s / 2, ff = "sans-serif", 52 | font = janvas.Utils.measureTextFontSize("M", s * 0.7, ff) + "px " + ff; 53 | for (var i = 0; i < 3; i++) { 54 | for (var j = 0; j < 3; j++) { 55 | var x = sx + j * s, y = sy + i * s; 56 | this.rects[i][j].setStart(x, y).setWidth(s).setHeight(s) 57 | .getStyle().setLineWidth(Math.round(size / 120)); 58 | this.texts[i][j].setStart(x + o, y + o) 59 | .getStyle().setFont(font); 60 | } 61 | } 62 | this.border.setStart(sx, sy).setWidth(size).setHeight(size) 63 | .getStyle().setLineWidth(Math.round(size / 60)); 64 | }, 65 | draw: function () { 66 | for (var i = 0; i < 3; i++) { 67 | for (var j = 0; j < 3; j++) { 68 | this.rects[i][j].fillStroke(); 69 | this.texts[i][j].fill(); 70 | } 71 | } 72 | this.border.stroke(); 73 | }, 74 | setNumber: function (i, j, value, input) { 75 | this.numbers[i][j] = value; 76 | this.texts[i][j].setText(value ? value + "" : ""); 77 | this._setFillStyle(this.texts[i][j].getStyle(), 78 | input ? "#325aaf" : "#292a2f"); 79 | }, 80 | getNumber: function (i, j) { 81 | return this.numbers[i][j]; 82 | }, 83 | getText: function (i, j) { 84 | return this.texts[i][j]; 85 | }, 86 | setNumbers: function (numbers, input) { 87 | for (var i = 0; i < 3; i++) { 88 | for (var j = 0; j < 3; j++) { 89 | this.setNumber(i, j, numbers[i][j], input); 90 | } 91 | } 92 | }, 93 | clear: function () { 94 | for (var i = 0; i < 3; i++) { 95 | for (var j = 0; j < 3; j++) { 96 | this.setNumber(i, j, 0); 97 | } 98 | } 99 | }, 100 | isPointInPath: function (x, y) { 101 | for (var i = 0; i < 3; i++) { 102 | for (var j = 0; j < 3; j++) { 103 | if (this.rects[i][j].isPointInPath(x, y) && this._isInput(i, j)) { 104 | return this.rects[i][j]; 105 | } 106 | } 107 | } 108 | }, 109 | eventmove: function (x, y) { 110 | if (this._lastRect) this._restoreLastRect(); 111 | outer: 112 | for (var i = 0; i < 3; i++) { 113 | for (var j = 0; j < 3; j++) { 114 | var rect = this.rects[i][j]; 115 | if (rect.isPointInPath(x, y) && this._isInput(i, j)) { 116 | rect.getStyle().setFillStyle("#bddbfe"); 117 | this._lastRect = rect; 118 | break outer; 119 | } 120 | } 121 | } 122 | }, 123 | getEventNumber: function () { 124 | if (this._lastRect) { 125 | var number = this.getNumber(this._lastRect.i, this._lastRect.j); 126 | this._restoreLastRect(); 127 | return number; 128 | } 129 | }, 130 | check: function () { 131 | for (var i = 0; i < 3; i++) { 132 | for (var j = 0; j < 3; j++) { 133 | this.texts[i][j].error(this._check(i, j, this.numbers[i][j])); 134 | } 135 | } 136 | }, 137 | _check: function (i, j, number) { 138 | for (var k = 0; k < 3; k++) { 139 | for (var l = 0; l < 3; l++) { 140 | if (k === i && l === j) continue; 141 | if (this.numbers[k][l] === number) return true; 142 | } 143 | } 144 | return false; 145 | }, 146 | isComplete: function () { 147 | for (var i = 0; i < 3; i++) { 148 | for (var j = 0; j < 3; j++) { 149 | if (this.texts[i][j]._error || this.numbers[i][j] === 0) return false; 150 | } 151 | } 152 | return true; 153 | }, 154 | _restoreLastRect: function () { 155 | this._lastRect.getStyle().setFillStyle("#fefefe"); 156 | this._lastRect = null; 157 | }, 158 | _setFillStyle: function (style, fillStyle) { 159 | style.setFillStyle(fillStyle)._lastFillStyle = fillStyle; 160 | }, 161 | _isInput: function (i, j) { 162 | return this.numbers[i][j] === 0 || 163 | this.texts[i][j].getStyle()._lastFillStyle === "#325aaf"; 164 | } 165 | }; 166 | 167 | return Grid; 168 | }()) 169 | }, 170 | methods: { 171 | init: function () { 172 | this.grids = []; 173 | for (var i = 0; i < 3; i++) { 174 | var t = []; 175 | for (var j = 0; j < 3; j++) { 176 | t.push(new this.Grid(this.$ctx)); 177 | } 178 | this.grids.push(t); 179 | } 180 | this.selector = new this.Grid(this.$ctx); 181 | this.selectorNumbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; 182 | this.selector.setNumbers(this.selectorNumbers, true); 183 | this.background = new janvas.Rect(this.$ctx, 0, 0); 184 | this.background.getStyle().setFillStyle("#ffffff"); 185 | this.mainground = new janvas.Rect(this.$ctx); 186 | this._cache = new Array(9); 187 | for (i = 0; i < 9; i++) this._cache[i] = {}; 188 | }, 189 | resize: function () { 190 | var w = this.$width, h = this.$height, 191 | sy = Math.min(w, h) * 0.191 / 2, 192 | sx = Math.abs(w - h) / 2 + sy, 193 | t, size; 194 | if (w < h) { 195 | t = sy; 196 | sy = sx; 197 | sx = t; 198 | } 199 | size = w - sx * 2; 200 | this.mainground.setStart(sx, sy).setWidth(size).setHeight(size); 201 | this._offset = (size /= 3) / 2; 202 | for (var i = 0; i < 3; i++) { 203 | for (var j = 0; j < 3; j++) { 204 | this.grids[i][j].resize(sx + j * size, sy + i * size, size); 205 | } 206 | } 207 | this.selector.resize(0, 0, size); 208 | this.background.setWidth(w).setHeight(h); 209 | }, 210 | draw: function () { 211 | this.background.fill(); 212 | for (var i = 0; i < 3; i++) { 213 | for (var j = 0; j < 3; j++) { 214 | this.grids[i][j].draw(); 215 | } 216 | } 217 | if (this.selector.activate) this.selector.draw(); 218 | }, 219 | setNumber: function (row, column, value) { 220 | this.getGrid(row, column).setNumber(this.mod(row), this.mod(column), value); 221 | this.check(); 222 | this.draw(); 223 | }, 224 | getNumber: function (row, column) { 225 | return this.getGrid(row, column).getNumber(this.mod(row), this.mod(column)); 226 | }, 227 | setNumbers: function (numbers) { 228 | for (var i = 0; i < 3; i++) { 229 | for (var j = 0; j < 3; j++) { 230 | this.grids[i][j].setNumbers(numbers[i][j]); 231 | } 232 | } 233 | this.check(); 234 | this.draw(); 235 | }, 236 | clear: function () { 237 | for (var i = 0; i < 3; i++) { 238 | for (var j = 0; j < 3; j++) { 239 | this.grids[i][j].clear(); 240 | } 241 | } 242 | this.draw(); 243 | }, 244 | inverseSelector: function () { 245 | var numbers = this.selectorNumbers, nums = numbers[0]; 246 | numbers[0] = numbers[2]; 247 | numbers[2] = nums; 248 | this.selector.setNumbers(numbers, true); 249 | }, 250 | getNumberArray: function () { 251 | var numbers = new Array(81); 252 | for (var r = 1; r < 10; r++) { 253 | for (var c = 1; c < 10; c++) { 254 | numbers[(r - 1) * 9 + c - 1] = this.getNumber(r, c); 255 | } 256 | } 257 | return numbers; 258 | }, 259 | setNumberArray: function (numberArray) { 260 | for (var i = 0; i < 81; i++) { 261 | this.setNumber(Math.floor(i / 9) + 1, i % 9 + 1, numberArray[i]); 262 | } 263 | }, 264 | check: function () { 265 | for (var i = 0; i < 3; i++) { 266 | for (var j = 0; j < 3; j++) { 267 | this.grids[i][j].check(); 268 | } 269 | } 270 | for (i = 0; i < 3; i++) { 271 | this._checkRow(this.grids[i][0], this.grids[i][1], this.grids[i][2]); 272 | } 273 | for (j = 0; j < 3; j++) { 274 | this._checkColumn(this.grids[0][j], this.grids[1][j], this.grids[2][j]); 275 | } 276 | }, 277 | /** 278 | * 完整适配解决更多情况数独的源码在: 279 | * https://github.com/JarenChow/Sudoku 280 | */ 281 | solve: function () { 282 | var numberArray = this.getNumberArray(), 283 | startTime = performance.now(), 284 | numbers = [], 285 | numbersBackup = [], 286 | possibleValue = [], 287 | possibleValueBackup = [], 288 | length = 81, 289 | width = 9, 290 | height = 9, 291 | subGridWidth = 3, 292 | subGridHeight = 3, 293 | standard = [1, 2, 3, 4, 5, 6, 7, 8, 9], 294 | loopCount = 0, 295 | maxCount = 62, 296 | maxCountStep = maxCount, 297 | operations = ["original", "asc", "desc", "random"], 298 | depth = -1, 299 | stopLoop = false, 300 | message = []; 301 | 302 | for (var index = 0; index < length; index++) { 303 | numbers[index] = 0; 304 | possibleValue[index] = standard.slice(0, width); 305 | } 306 | 307 | function updateState(index, value, type) { 308 | numbers[index] = value; 309 | possibleValue[index] = 0; 310 | if (type) message.push(type + " R" + (Math.floor(index / width) + 1) + 311 | "C" + (index % width + 1) + "=" + value); 312 | 313 | var row = Math.floor(index / width), 314 | rowIndex = row * width, 315 | rowEndIndex = rowIndex + width; 316 | for (var i = rowIndex; i < rowEndIndex; i++) { 317 | update(i); 318 | } 319 | 320 | var column = index % width, 321 | columnEndIndex = width * height; 322 | for (var j = column; j < columnEndIndex; j += width) { 323 | update(j); 324 | } 325 | 326 | var subGridRow = Math.floor(row / subGridHeight), 327 | subGridColumn = Math.floor(column / subGridWidth), 328 | subGridIndex = subGridRow * subGridHeight * width + 329 | subGridColumn * subGridWidth; 330 | for (i = 0; i < subGridHeight; i++) { 331 | for (j = 0; j < subGridWidth; j++) { 332 | update(subGridIndex + i * width + j); 333 | } 334 | } 335 | 336 | function update(cursor) { 337 | if (numbers[cursor] !== 0) return; 338 | var hasValue = possibleValue[cursor].indexOf(numbers[index]); 339 | if (hasValue > -1) { 340 | possibleValue[cursor].splice(hasValue, 1); 341 | } 342 | } 343 | } 344 | 345 | for (index = 0; index < length; index++) { 346 | var value = parseInt(numberArray[index]); 347 | if (value === 0) continue; 348 | updateState(index, value); 349 | } 350 | 351 | function nakedSingle() { 352 | for (var index = 0; index < length; index++) { 353 | if (possibleValue[index].length === 1) { 354 | updateState(index, possibleValue[index][0], "唯余解法"); 355 | } 356 | } 357 | } 358 | 359 | function hiddenSingle() { 360 | var isUniqueNumber; 361 | for (var index = 0; index < length; index++) { 362 | if (numbers[index] !== 0) continue; 363 | var row = Math.floor(index / width), 364 | column = index % width; 365 | 366 | rowOrColumn(index, row * width, (row + 1) * width, 1); 367 | 368 | if (isUniqueNumber) continue; 369 | 370 | rowOrColumn(index, column, column + width * height, width); 371 | 372 | if (isUniqueNumber) continue; 373 | 374 | var subGridIndex = Math.floor(row / subGridHeight) * subGridHeight * width + 375 | Math.floor(column / subGridWidth) * subGridWidth; 376 | for (var i = 0; i < possibleValue[index].length; i++) { 377 | isUniqueNumber = true; 378 | UniqueFlag: 379 | for (var j = 0; j < subGridHeight; j++) { 380 | for (var k = 0; k < subGridWidth; k++) { 381 | var c = subGridIndex + j * width + k; 382 | if (numbers[c] !== 0 || c === index) continue; 383 | if (possibleValue[c].indexOf(possibleValue[index][i]) > -1) { 384 | isUniqueNumber = false; 385 | break UniqueFlag; 386 | } 387 | } 388 | } 389 | if (isUniqueNumber) { 390 | updateState(index, possibleValue[index][i], "宫摒除法"); 391 | } 392 | } 393 | } 394 | 395 | function rowOrColumn(index, startIndex, endIndex, step) { 396 | for (var i = 0; i < possibleValue[index].length; i++) { 397 | isUniqueNumber = true; 398 | for (var j = startIndex; j < endIndex; j += step) { 399 | if (numbers[j] !== 0 || j === index) continue; 400 | if (possibleValue[j].indexOf(possibleValue[index][i]) > -1) { 401 | isUniqueNumber = false; 402 | break; 403 | } 404 | } 405 | if (isUniqueNumber) { 406 | updateState(index, possibleValue[index][i], (step === 1 ? "行" : "列") + "摒除法"); 407 | break; 408 | } 409 | } 410 | } 411 | } 412 | 413 | function loopSolveSudoku() { 414 | while (true) { 415 | var temp = numbers.slice(); 416 | loopSolve(nakedSingle); 417 | loopSolve(hiddenSingle); 418 | if (unchanged(numbers, temp)) { 419 | break; 420 | } 421 | } 422 | 423 | function loopSolve(code) { 424 | if (stopLoop) return; 425 | var temp = numbers.slice(); 426 | code(); 427 | if (!unchanged(numbers, temp)) { 428 | loopCount++; 429 | if (loopCount === maxCount) stopLoop = true; 430 | loopSolve(code); 431 | } 432 | } 433 | 434 | function unchanged(arr1, arr2) { 435 | return arr1.every(function (value, index) { 436 | return value === arr2[index]; 437 | }); 438 | } 439 | } 440 | 441 | loopSolveSudoku(); 442 | 443 | var complete = sudokuComplete(); 444 | 445 | if (!complete) { 446 | maxCount += loopCount; 447 | depth = -1; 448 | stopLoop = false; 449 | 450 | for (var i = 0; i < operations.length - 1; i++) { 451 | tryNumber(operations[i]); 452 | if (!complete) { 453 | restoreData(); 454 | } else { 455 | break; 456 | } 457 | } 458 | 459 | if (!complete) { 460 | for (i = 0; i < 618; i++) { 461 | tryNumber("random"); 462 | if (!complete) { 463 | restoreData(); 464 | } else { 465 | break; 466 | } 467 | } 468 | } 469 | } 470 | 471 | function tryNumber(operation) { 472 | depth++; 473 | numbersBackup[depth] = numbers.slice(); 474 | possibleValueBackup[depth] = arrayDeepCopy(possibleValue); 475 | var possibleArray = getPossibleArray(operation); 476 | for (var i = 0; i < possibleArray.length && !stopLoop; i++) { 477 | var index = possibleArray[i].index, 478 | arr = possibleValue[index], 479 | error = 0; 480 | for (var j = 0; j < arr.length && !stopLoop; j++) { 481 | updateState(index, arr[j], "假设法假设"); 482 | loopSolveSudoku(); 483 | if (!possibleValue.some(function (arr) { 484 | if (arr instanceof Array) return arr.length === 0; 485 | })) { 486 | if (sudokuComplete()) { 487 | complete = true; 488 | } else { 489 | tryNumber(operation); 490 | depth--; 491 | } 492 | } else { 493 | error++; 494 | } 495 | if (complete) break; 496 | numbers = numbersBackup[depth].slice(); 497 | possibleValue = arrayDeepCopy(possibleValueBackup[depth]); 498 | } 499 | if (error === arr.length) break; 500 | if (complete) break; 501 | } 502 | 503 | function getPossibleArray(operation) { 504 | var arr = []; 505 | possibleValue.forEach(function (value, index) { 506 | if (value instanceof Array) { 507 | arr.push({ 508 | index: index, 509 | length: value.length 510 | }); 511 | } 512 | }); 513 | switch (operation) { 514 | case "original": 515 | break; 516 | case "asc": 517 | case "desc": 518 | var asc = operation === "asc" ? -1 : 1; 519 | arr.sort(function (o1, o2) { 520 | if (o1.length < o2.length) { 521 | return asc; 522 | } else if (o1.length > o2.length) { 523 | return -asc; 524 | } else { 525 | return 0; 526 | } 527 | }); 528 | break; 529 | case "random": 530 | arr.sort(function () { 531 | return 0.5 - Math.random(); 532 | }); 533 | break; 534 | default: 535 | break; 536 | } 537 | return arr; 538 | } 539 | } 540 | 541 | function restoreData() { 542 | maxCount += maxCountStep; 543 | depth = -1; 544 | stopLoop = false; 545 | numbers = numbersBackup[0].slice(); 546 | possibleValue = arrayDeepCopy(possibleValueBackup[0]); 547 | } 548 | 549 | function arrayDeepCopy(source) { 550 | var copy = []; 551 | loopCopy(copy, source); 552 | return copy; 553 | 554 | function loopCopy(copy, source) { 555 | source.forEach(function (item, index) { 556 | if (item instanceof Array) { 557 | copy[index] = []; 558 | loopCopy(copy[index], item); 559 | } else { 560 | copy[index] = item; 561 | } 562 | }); 563 | } 564 | } 565 | 566 | function sudokuComplete() { 567 | return numbers.indexOf(0) === -1; 568 | } 569 | 570 | return { 571 | complete: complete, 572 | loopCount: loopCount, 573 | duration: Math.floor(performance.now() - startTime), 574 | numberArray: numbers, 575 | message: message 576 | }; 577 | }, 578 | isComplete: function () { 579 | for (var i = 0; i < 3; i++) { 580 | for (var j = 0; j < 3; j++) { 581 | if (!this.grids[i][j].isComplete()) return false; 582 | } 583 | } 584 | return true; 585 | }, 586 | simpleRandom: function (ratio) { 587 | ratio = ratio || 0.4; 588 | var i, j, k, number; 589 | this.clear(); 590 | for (j = 1; j < 10; j++) { 591 | while (number = this._rand(1, 10)) { 592 | for (k = Math.floor((j - 1) / 3) * 3 + 1; number && k < j; k++) { 593 | if (this.getNumber(k, k) === number) number = 0; 594 | } 595 | if (number) break; 596 | } 597 | this.setNumber(j, j, number); 598 | } 599 | for (j = 1; j < 10; j++) { 600 | while (number = this._rand(1, 10)) { 601 | for (k = Math.floor((j - 1) / 3) * 3 + 1; number && k < j; k++) { 602 | if (this.getNumber(k, 10 - k) === number) number = 0; 603 | } 604 | if (number && number !== this.getNumber(j, j) 605 | && number !== this.getNumber(10 - j, 10 - j)) { 606 | break; 607 | } 608 | } 609 | this.setNumber(j, 10 - j, number); 610 | if (j === 3) j += 3; 611 | } 612 | var numberArray = this.solve().numberArray; 613 | for (i = 0; i < numberArray.length; i++) { 614 | if (Math.random() < ratio) numberArray[i] = 0; 615 | } 616 | this.setNumberArray(numberArray); 617 | }, 618 | _rand: janvas.Utils.randInt, 619 | _checkRow: function (left, center, right) { 620 | for (var k = 0; k < 3; k++) { 621 | for (var i = 0; i < 3; i++) { 622 | for (var j = 0; j < 3; j++) { 623 | var c = this._cache[i * 3 + j], grid = arguments[i]; 624 | c.number = grid.getNumber(k, j); 625 | c.text = grid.getText(k, j); 626 | } 627 | } 628 | this._check(); 629 | } 630 | }, 631 | _checkColumn: function (top, middle, bottom) { 632 | for (var k = 0; k < 3; k++) { 633 | for (var i = 0; i < 3; i++) { 634 | for (var j = 0; j < 3; j++) { 635 | var c = this._cache[i * 3 + j], grid = arguments[i]; 636 | c.number = grid.getNumber(j, k); 637 | c.text = grid.getText(j, k); 638 | } 639 | } 640 | this._check(); 641 | } 642 | }, 643 | _check: function () { 644 | for (var i = 0; i < 8; i++) { 645 | for (var j = i + 1; j < 9; j++) { 646 | var c1 = this._cache[i], c2 = this._cache[j]; 647 | if (c1.number === c2.number) { 648 | c1.text.error(true); 649 | c2.text.error(true); 650 | } 651 | } 652 | } 653 | } 654 | }, 655 | events: { 656 | eventdown: function (ev) { 657 | var rect; 658 | outer: 659 | for (var i = 0; i < 3; i++) { 660 | for (var j = 0; j < 3; j++) { 661 | this._grid = this.grids[i][j]; 662 | if ((rect = this._grid.isPointInPath(ev.$x, ev.$y))) { 663 | this.selector.setStart(ev.$x - this._offset, ev.$y - this._offset); 664 | this.selector.activate = true; 665 | this.selector.eventmove(ev.$x, ev.$y); 666 | this._rect = rect; 667 | break outer; 668 | } 669 | } 670 | } 671 | this.draw(); 672 | }, 673 | eventmove: function (ev) { 674 | if (this.selector.activate) { 675 | this.selector.eventmove(ev.$x, ev.$y); 676 | } else if (this.mainground.isPointInPath(ev.$x, ev.$y)) { 677 | for (var i = 0; i < 3; i++) { 678 | for (var j = 0; j < 3; j++) { 679 | this.grids[i][j].eventmove(ev.$x, ev.$y); 680 | } 681 | } 682 | } 683 | this.draw(); 684 | }, 685 | eventup: function () { 686 | var number = this.selector.getEventNumber(); 687 | if (number) { 688 | this._grid.setNumber(this._rect.i, this._rect.j, number, true); 689 | if (this.isComplete()) this.onComplete(); 690 | } else if (this.selector.activate) { 691 | this._grid.setNumber(this._rect.i, this._rect.j, 0); 692 | } 693 | this.selector.activate = false; 694 | this.check(); 695 | this.draw(); 696 | }, 697 | onComplete: janvas.Utils.noop, 698 | mousedown: function (ev) { 699 | switch (ev.button) { 700 | case 0: 701 | this.eventdown(ev); 702 | break; 703 | case 1: 704 | this.setNumberArray(this.solve().numberArray); 705 | break; 706 | case 2: 707 | this.simpleRandom(); 708 | break; 709 | } 710 | }, 711 | mousemove: function (ev) { 712 | this.eventmove(ev); 713 | }, 714 | mouseup: function () { 715 | this.eventup(); 716 | }, 717 | touchstart: function (ev) { 718 | ev.preventDefault(); 719 | switch (ev.targetTouches.length) { 720 | case 1: 721 | this.eventdown(ev.targetTouches[0]); 722 | break; 723 | case 2: 724 | if (this._fingerOut(ev)) this.setNumberArray(this.solve().numberArray); 725 | break; 726 | case 3: 727 | if (this._fingerOut(ev)) this.simpleRandom(); 728 | break; 729 | } 730 | }, 731 | _fingerOut: function (ev) { 732 | for (var i = 0; i < ev.targetTouches.length; i++) { 733 | var _ev = ev.targetTouches[i]; 734 | if (this.mainground.isPointInPath(_ev.$x, _ev.$y)) return false; 735 | } 736 | return true; 737 | }, 738 | touchmove: function (ev) { 739 | this.eventmove(ev.targetTouches[0]); 740 | }, 741 | touchend: function () { 742 | this.eventup(); 743 | } 744 | }, 745 | functions: { 746 | getGrid: function (row, column) { 747 | return this.grids[Math.floor((row - 1) / 3)][Math.floor((column - 1) / 3)]; 748 | }, 749 | mod: function (num) { 750 | return (num - 1) % 3; 751 | } 752 | } 753 | }); 754 | 755 | // 附:二次开发额外的一些用法 756 | var numbers = [ // 默认数据输入格式 757 | [[[3, 0, 4], [0, 0, 2], [8, 0, 0]], // 需要注意是以宫为单位 758 | [[0, 2, 0], [0, 5, 1], [0, 0, 0]], // 数据被设置时 759 | [[0, 0, 1], [0, 4, 9], [2, 0, 0]]], 760 | [[[9, 3, 0], [0, 7, 8], [0, 0, 0]], 761 | [[5, 0, 8], [3, 0, 0], [0, 0, 0]], 762 | [[0, 2, 0], [9, 0, 0], [0, 8, 0]]], 763 | [[[0, 0, 0], [0, 2, 6], [0, 0, 0]], 764 | [[0, 0, 0], [7, 0, 0], [0, 0, 5]], 765 | [[7, 6, 0], [0, 9, 8], [0, 0, 2]]] 766 | ]; 767 | sudoku.setNumbers(numbers); // 除了这种默认格式还有 setNumberArray 一维数组格式 768 | sudoku.setNumber(2, 2, 6); // 在第 2 行第 2 列填充 6 769 | sudoku.inverseSelector(); // 翻转选择器的数字排列顺序 770 | var number = sudoku.getNumber(2, 5); // 获取第 2 行第 5 列的数值 771 | var numberArray = sudoku.getNumberArray(); // 将数独转为一维数组 772 | console.log(sudoku.isComplete()); // 判断数独是否还原 773 | var solved = sudoku.solve(); // 直接解决任意数独 774 | sudoku.setNumberArray(solved.numberArray); // 将解决后的一维数据设置进数独 775 | sudoku.onComplete = function () { // 数独还原的回调(限事件触发) 776 | console.log("isComplete:", this.isComplete()); // 判断数独是否还原 777 | }; 778 | sudoku.clear(); // 清空 779 | sudoku.simpleRandom(0.4); // 随机一个数独 puzzle,传递的值表示挖空概率 780 | -------------------------------------------------------------------------------- /dist/janvasexamples.min.js: -------------------------------------------------------------------------------- 1 | // https://github.com/JarenChow/Janvas Created by JarenChow in 2020 janvas.js v1.3.8 2 | !function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory(require("janvas")):"function"==typeof define&&define.amd?define(["janvas"],factory):(global=global||self).janvasexamples=factory(global.janvas)}(this,function(janvas){"use strict";function antv(container){return new janvas.Canvas({container:container,components:{factory:(Node.prototype={setXY:function(x,y){this.arc.getMatrix().setOffset(this.x=x,this.y=y),this.text.init(x+this._defaultRadius*this._scale*2,y,x+this._defaultRadius*this._scale*2,y)},getX:function(){return this.x},getY:function(){return this.y},getLabel:function(){return this.text.getText()},setScale:function(scale){this._scale=scale,this.arc.getMatrix().setScale(scale,scale),this.text.getMatrix().setScale(scale,scale)},fillStroke:function(){this.arc.fillStroke(),this._showText&&this.text.fill()},onlyFill:function(){this._arc.fill()},in:function(rect){return janvas.Collision.rect(this.x-this._defaultRadius*this._scale,this.y-this._defaultRadius*this._scale,this.x+this._defaultRadius*this._scale,this.y+this._defaultRadius*this._scale,rect.getLeft(),rect.getTop(),rect.getRight(),rect.getBottom())},isPointInPath:function(x,y){return this._arc.isPointInPath(x,y)},highlight:function(flag){flag?this.arc.getStyle().setFillStyle("#FF0000").setStrokeStyle("#000000").setLineWidth(1):this.arc.getStyle().setFillStyle("#C6E5FF").setStrokeStyle("#5B8FF9").setLineWidth(.3)},showText:function(flag){this._showText=flag},mark:function(){this.lastX=this.x,this.lastY=this.y},drag:function(moveX,moveY){this.setXY(this.lastX+moveX,this.lastY+moveY)},wheel:function(x,y,scaling,scale){this.setScale(scale),this.setXY(x+(this.x-x)*scaling,y+(this.y-y)*scaling),this.draw=scale*this._defaultRadius<1?this.onlyFill:this.fillStroke}},Edge.prototype={draw:function(){this._show&&this.line.stroke()},refresh:function(){this.line.setStart(this.source.getX(),this.source.getY()).setEndX(this.target.getX()).setEndY(this.target.getY())},setLineWidth:function(scale){this.line.getStyle().setLineWidth(scale/10)},show:function(flag){this._show=flag}},Hint.prototype={draw:function(){this._show&&(this.roundRect.fillStroke(),this.text.fill())},setXY:function(x,y){x+=this._of,y-=this._of,this.text.setStart(x+this._pdl,y-this._pdt),this.roundRect.setStart(x,y)},setLabel:function(label){this.label!==label&&(this.label=label,this.text.setText(label),this.roundRect.setWidth(this.text.getActualBoundingBoxWidth()+2*this._pdl).setHeight(-(this.text.getActualBoundingBoxHeight()+2*this._pdt)))},show:function(flag){this._show=flag}},{nodesMap:new Map,newNode:function($ctx,id,x,y,node){node=new Node($ctx,id,x,y,node);return this.nodesMap.set(id,node),node},newEdge:function($ctx,source,target){return source=this.nodesMap.get(source),target=this.nodesMap.get(target),source&&target?new Edge($ctx,source,target):null},Hint:Hint})},methods:{init:function(){this.nodes=[],this.edges=[],this.hint=new this.factory.Hint(this.$ctx),this.background=new janvas.Rect(this.$ctx,0,0,this.$width,this.$height)},draw:function(){this.$clear(),this.edges.forEach(function(edge){edge.draw()}),this.nodes.forEach(function(node){node.in(this.background)&&node.draw()},this),this.hint.draw()},data:function(res){this.nodes.length=this.edges.length=0,res.nodes.forEach(function(node){this.nodes.push(this.factory.newNode(this.$ctx,node.id,node.x,node.y,node.olabel))},this),res.edges.forEach(function(edge){(edge=this.factory.newEdge(this.$ctx,edge.source,edge.target))&&this.edges.push(edge)},this),this.draw()}},events:{mousedown:function(){this._mousedown=!0,this.nodes.forEach(function(node){node.mark()}),void 0===this._current&&this.edges.forEach(function(edge){edge.show(!1)}),this.draw()},mousemove:function(ev){var mousein;this._mousedown?void 0===this._current?this.nodes.forEach(function(node){node.drag(ev.$moveX,ev.$moveY)},this):(this._current.drag(ev.$moveX,ev.$moveY),this.hint.setXY(ev.$x,ev.$y),this.edges.forEach(function(edge){edge.refresh()})):(mousein=!0,this.nodes.forEach(function(node){node.isPointInPath(ev.$x,ev.$y)&&(this._current=node,mousein=!1),node.highlight(!1)},this),mousein?(this._current=void 0,this.hint.show(!1)):(this._current.highlight(!0),this.hint.setLabel(this._current.getLabel()),this.hint.setXY(ev.$x,ev.$y),this.hint.show(!0))),this.draw()},mouseup:function(){this._mousedown=!1,this.edges.forEach(function(edge){edge.show(!0),edge.refresh()}),this.draw()},wheel:function(ev){ev.preventDefault(),this.nodes.forEach(function(node){node.wheel(ev.$x,ev.$y,ev.$scaling,ev.$scale)},this),this.edges.forEach(function(edge){edge.refresh(),edge.setLineWidth(ev.$scale)},this),this.draw()},resize:function(){this.background.setWidth(this.$width).setHeight(this.$height)}}});function Node($ctx,id,x,y,label){this._defaultRadius=1,this._scale=1,this._showText=!1,this.arc=new janvas.FixedArc($ctx,0,0,this._defaultRadius),this._arc=new janvas.FixedRect($ctx,-this._defaultRadius,-this._defaultRadius,2*this._defaultRadius,2*this._defaultRadius),this._arc.setMatrix(this.arc.getMatrix()),this._arc.setStyle(this.arc.getStyle()),this.text=new janvas.Text($ctx,0,0,label||id),this.text.getStyle().setFont("1.5px sans-serif").setTextBaseline("middle"),this.setXY(x,y),this.highlight(!1),this.draw=this.fillStroke}function Edge($ctx,source,target){this._show=!0,this.source=source,this.target=target,this.line=new janvas.Line($ctx,0,0,0,0),this.line.getStyle().setStrokeStyle("#333333").setLineWidth(.1),this.refresh()}function Hint($ctx){this._show=!1,this._of=this._pdt=3,this._pdl=5,this.roundRect=new janvas.RoundRect($ctx,0,0,0,0,this._pdl),this.roundRect.getStyle().setFillStyle("white").setStrokeStyle("white").setShadowBlur(20).setShadowColor("grey"),this.text=new janvas.Text($ctx,0,0,""),this.text.getStyle().setFont("12px sans-serif").setTextBaseline("bottom")}}function tiger(container){return new janvas.Canvas({container:container,methods:{init:function(){this.background=new janvas.Rect(this.$ctx,0,0,this.$width,this.$height),this.shapes=[]},data:function(svgXML){for(var shape,fill,stroke,lineWidth,style,paths=(new DOMParser).parseFromString(svgXML,"text/xml").getElementsByTagName("path"),i=0;i1e3*this._secondIncrement?(this._secondIncrement++,++this.seconds%60==0&&(this.seconds=0,++this.minutes%60==0&&(this.minutes=0,++this.hours%24==0&&(this.hours=0))),this._allSeconds++,this.setAngle(),this.onEvent(this.hours,this.minutes,this.seconds)):600o2.length?-asc:0});break;case"random":arr.sort(function(){return.5-Math.random()})}return arr}(operation),i=0;i1e3*this.addCount&&(this.addCount++,this.add());var flag=!1;this.taichi.forEach(function(tc1){tc1.update(),this.taichi.forEach(function(tc2){tc1!==tc2&&tc1.collide(tc2)}),tc1.in=tc1.inRect(this.background),tc1.in||(flag=!0)},this),flag&&(this.taichi=this.taichi.filter(function(tc){return tc.in}))},draw:function(){this.$clear(),this.taichi.forEach(function(tc){tc.draw()})}},events:{mousedown:function(ev){this.add(ev.$x,ev.$y)},visibilitychange:function(visible){visible?this.$raf.resume():this.$raf.pause()},resize:function(){this.background.setWidth(this.$width).setHeight(this.$height)}},functions:{add:function(x,y){this.taichi.push(new this.Taichi(this.$ctx,x||janvas.Utils.randInt(0,this.$width),y||janvas.Utils.randInt(0,this.$height),janvas.Utils.randInt(20,100)))}}});function Taichi(bottomSmall,x,y,r){var outer=new janvas.Arc(bottomSmall,x,y,r);outer.getStyle().setLineWidth(r/8);var left=new janvas.Arc(bottomSmall,x,y,r,Math.PI/2,-Math.PI/2,!1,x,y),right=new janvas.Arc(bottomSmall,x,y,r,Math.PI/2,-Math.PI/2,!0,x,y);right.getStyle().setFillStyle("white");var top=new janvas.Arc(bottomSmall,x,y-r/2,r/2,0,2*Math.PI,!1,x,y),bottom=new janvas.Arc(bottomSmall,x,y+r/2,r/2,0,2*Math.PI,!1,x,y);bottom.getStyle().setFillStyle("white");var topSmall=new janvas.Arc(bottomSmall,x,y-r/2,r/8,0,2*Math.PI,!1,x,y),bottomSmall=new janvas.Arc(bottomSmall,x,y+r/2,r/8,0,2*Math.PI,!1,x,y);topSmall.getStyle().setFillStyle("white"),this.x=x,this.y=y,this.r=r,this.rotateSpeed=janvas.Utils.randSign()*Math.PI/2e3*16*42/r,this.cp=new janvas.Point,this.outer=outer,this.left=left,this.right=right,this.top=top,this.bottom=bottom,this.topSmall=topSmall,this.bottomSmall=bottomSmall,this.grd={rgb:new janvas.Rgb(0,0,0),outerStart:new janvas.Rgb(255,255,255).sRgbInverseCompanding(),black:new janvas.Rgb(0,0,0).sRgbInverseCompanding(),white:new janvas.Rgb(255,255,255).sRgbInverseCompanding(),count:0,maxCount:Math.ceil(62.5)}}},tiger:tiger,clock:clock,beziermaker:function(container){return new janvas.Canvas({container:container,duration:1/0,interval:16,props:{position:0,size:Math.floor(625),keys:["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Delete","Enter","w","s","a","d","q","e"]},components:{factory:(Dot.prototype={setStart:function(x,y){this._x=x,this._y=y,this.arc.setStart(x,y),this.text.setStart(x+2*this._r,y).setText(this.getReadableText())},getReadableText:function(){return"p"+this._index+"("+this._x+","+this._y+")"},initStyles:function(){this.arc.getStyle().setStrokeStyle("hsl(0, 80%, 50%)").setLineWidth(2),this.text.getStyle().setFont("12px sans-serif").setTextAlign("start").setTextBaseline("middle")},drawHighlight:function(){this.arc.fillStroke(),this.text.fill()},drawDefault:function(){this.arc.fill(),this.text.fill()},highlight:function(flag){this._highlight!==flag&&(this._highlight=flag,this.draw=flag?this.drawHighlight:this.drawDefault)},mark:function(){this._lastX=this._x,this._lastY=this._y},onmove:function(moveX,moveY){this.setStart(this._lastX+moveX,this._lastY+moveY)},getX:function(){return this._x},getY:function(){return this._y},getIndex:function(){return this._index},decreaseIndex:function(){this._index--,this.text.setText(this.getReadableText())},isPointInPath:function(x,y){return this.arc.isPointInPath(x,y)}},{_index:0,newDot:function($ctx,x,y){return new Dot($ctx,x,y,this._index++)},decreaseAutoIndex:function(){this._index--}})},methods:{init:function(){this.dots=[],this.polyline=new janvas.Polyline(this.$ctx,0,0,[]),this.bezier=new janvas.Bezier(this.$ctx,0,0,this.polyline.getPoints(),2*this.size),this.bezier.getStyle().setStrokeStyle("hsl(0, 80%, 50%)"),this.transformedPoints=this.bezier.getTransformedPoints(),this.hint=new janvas.Text(this.$ctx,0,0,""),this.hint.getStyle().setFillStyle("rgba(0, 0, 0, 0.5)").setFont("12px sans-serif").setTextAlign("end").setTextBaseline("middle"),this.cursor=new janvas.Triangle(this.$ctx,0,0),this.cursor.getStyle().setFillStyle("hsl(270, 80%, 50%)")},draw:function(){this.$clear(),this.polyline.stroke(),this.bezier.stroke(),this.dots.forEach(function(dot){dot.draw()}),this.hint.fill(),this.updateCursor(),this.cursor.fill()}},events:{mousedown:function(ev){this._autoResize&&(ev.$x>.875*this.$width&&(this.$wrapper.style.width=1.5*this.$width+"px"),ev.$y>.875*this.$height&&(this.$wrapper.style.height=1.5*this.$height+"px")),4===ev.buttons||(2===ev.buttons?this.dots.forEach(function(dot){dot.mark()}):(this.current?this.toggleLocked(this.current):(this.locked&&this.locked.highlight(!1),this.dots.push(this.locked=this.current=this.factory.newDot(this.$ctx,ev.$x,ev.$y)),this.polyline.append(ev.$x,ev.$y)),this.locked.mark()))},mousemove:function(ev){2===ev.buttons?this.dots.forEach(function(dot){dot.onmove(ev.$moveX,ev.$moveY),this.polyline.update(dot.getIndex(),dot.getX(),dot.getY())},this):1===ev.buttons?(this.locked.onmove(ev.$moveX,ev.$moveY),this.polyline.update(this.locked.getIndex(),this.locked.getX(),this.locked.getY())):(this.current&&this.current.highlight(!1),this.current=void 0,this.dots.forEach(function(dot){dot.isPointInPath(ev.$x,ev.$y)&&(this.current=dot)},this),this.current?(this.locked.highlight(!1),this.current.highlight(!0)):this.locked&&this.locked.highlight(!0)),this.hint.setStart(ev.$x,ev.$y).setText("("+ev.$x+","+ev.$y+")")},mouseover:function(){this.$raf.resume()},mouseout:function(){this.$raf.pause()},keydown:function(ev){if(void 0!==this.locked){this.keys.some(function(value){return value===ev.key})&&ev.preventDefault();var increase=ev.repeat?5:1;switch(ev.key){case"ArrowUp":case"w":this.locked.setStart(this.locked.getX(),this.locked.getY()-increase),this.polyline.update(this.locked.getIndex(),this.locked.getX(),this.locked.getY());break;case"ArrowDown":case"s":this.locked.setStart(this.locked.getX(),this.locked.getY()+increase),this.polyline.update(this.locked.getIndex(),this.locked.getX(),this.locked.getY());break;case"ArrowLeft":case"a":this.locked.setStart(this.locked.getX()-increase,this.locked.getY()),this.polyline.update(this.locked.getIndex(),this.locked.getX(),this.locked.getY());break;case"ArrowRight":case"d":this.locked.setStart(this.locked.getX()+increase,this.locked.getY()),this.polyline.update(this.locked.getIndex(),this.locked.getX(),this.locked.getY());break;case"Delete":0this.locked.getIndex()&&dot.decreaseIndex()},this),this.factory.decreaseAutoIndex(),this.dots.splice(this.dots.indexOf(this.locked),1),this.polyline.delete(this.locked.getIndex()),this.locked=this.dots[this.locked.getIndex()]||this.dots[this.locked.getIndex()-1],this.locked&&this.locked.highlight(!0));break;case"Enter":console.log("SOURCEDOTS: "+this.polyline.getTransformedPoints().toString()),console.log("CALCULATED: "+this.bezier.getTransformedPoints().toString());break;case"q":this.toggleLocked(this.dots[this.locked.getIndex()-1]||this.dots[this.dots.length-1]);break;case"e":this.toggleLocked(this.dots[this.locked.getIndex()+1]||this.dots[0])}}},autoResize:function(flag){this._autoResize=flag}},functions:{toggleLocked:function(dot){this.locked.highlight(!1),this.locked=dot,this.locked.highlight(!0)},updateCursor:function(){var x2,y2=this.position,transformedPoints=this.transformedPoints,x1=transformedPoints[y2],y1=transformedPoints[y2+1],y2=0!==y2?(x2=transformedPoints[y2-2],transformedPoints[y2-1]):(x2=transformedPoints[y2+2],transformedPoints[y2+3]);this.position+=2,this.position===transformedPoints.length&&(this.position=0),this.cursor.setStart(x1,y1).setRotation(Math.atan2(y1-y2,x1-x2))}}});function Dot($ctx,x,y,index){this._index=index,this._r=5,this.arc=new janvas.Arc($ctx,0,0,this._r),this.text=new janvas.Text($ctx),this.setStart(x,y),this.initStyles(),this.highlight(!0)}},flydots:function(container){return new janvas.Canvas({container:container,duration:1/0,interval:16,props:{dots:[],lines:[]},components:{factory:(Dot.prototype={setStart:function(x,y){this._x=x,this._y=y,this.arc.setStart(x,y),this._relateStart.forEach(this.startCallback,this),this._relateEnd.forEach(this.endCallBack,this)},closer:function(x,y){this._vx=(x-this._x)/Math.abs(x-this._x),this._vy=(y-this._y)/Math.abs(y-this._y)},restore:function(){this._vx=this._lvx,this._vy=this._lvy},relateStart:function(line){this._relateStart.push(line)},relateEnd:function(line){this._relateEnd.push(line)},startCallback:function(line){line.setStart(this._x,this._y)},endCallBack:function(line){line.setEnd(this._x,this._y)},setBounding:function(width,height){this._left=this._top=-50,this._right=width+50,this._bottom=height+50},update:function(){this._x+=this._vx,this._y+=this._vy,this.setStart(this._x,this._y),(this._xthis._right)&&(this._lvx=this._vx*=-1),(this._ythis._bottom)&&(this._lvy=this._vy*=-1)},draw:function(){this.arc.fill()}},Line.prototype={update:function(){var _ratio=1-janvas.Utils.pythagorean(this.line.getStartX()-this.line.getEndX(),this.line.getStartY()-this.line.getEndY())/100;this.line.getStyle().setAlpha(this._ratio=_ratio<0?0:_ratio)},draw:function(){this._ratio&&this.line.stroke()}},{Dot:Dot,Line:Line})},methods:{init:function(){for(var i=0;i<100;i++){var dot=new this.factory.Dot(this.$ctx,this.$width,this.$height);this.dots.forEach(function(target){this.lines.push(new this.factory.Line(this.$ctx,dot,target))},this),this.dots.push(dot)}this.cursor=new this.factory.Dot,this.dots.forEach(function(target){this.lines.push(new this.factory.Line(this.$ctx,this.cursor,target))},this)},update:function(){this.dots.forEach(function(dot){dot.update()},this),this.lines.forEach(function(line){line.update()})},draw:function(){this.$clear(),this.lines.forEach(function(line){line.draw()}),this.dots.forEach(function(dot){dot.draw()})}},events:{mousedown:function(ev){this.dots.forEach(function(dot){dot.closer(ev.$x,ev.$y)},this)},mousemove:function(ev){this.cursor.setStart(ev.$x,ev.$y)},mouseup:function(){this.dots.forEach(function(dot){dot.restore()})},resize:function(){this.dots.forEach(function(dot){dot.setBounding(this.$width,this.$height)},this)},visibilitychange:function(visible){visible?this.$raf.resume():this.$raf.pause()}}});function Dot($ctx,width,height){this.setBounding(width,height),this._x=janvas.Utils.randInt(this._left,this._right,!0),this._y=janvas.Utils.randInt(this._top,this._bottom,!0),this._r=3,this._lvx=this._vx=janvas.Utils.randSign()*janvas.Utils.randInt(10,100,!0)/100,this._lvy=this._vy=janvas.Utils.randSign()*janvas.Utils.randInt(10,100,!0)/100,this._relateStart=[],this._relateEnd=[],this.arc=new janvas.Arc($ctx,this._x,this._y,this._r),this.arc.getStyle().setFillStyle("hsl(0, 0%, 40%)")}function Line($ctx,source,target){this.line=new janvas.Line($ctx),source.relateStart(this.line),target.relateEnd(this.line)}},aboutwheel:function(container){return new janvas.Canvas({container:container,duration:1/0,interval:16,props:{size:50},methods:{init:function(){this.background=new janvas.Rect(this.$ctx,0,0,this.$width,this.$height);var cx=this.$width/2,cy=this.$height/2;this.img=new janvas.Image(this.$ctx,cx-this.size,cy-this.size,"img/complex.svg",cx,cy,2*this.size,2*this.size),this.img.getStyle().setStrokeStyle("grey"),this.img.animationQueue=[],this.img.count=this.img.maxCount=Math.floor(256/this.$interval),this.$raf.resume()},update:function(ts){this.img.getMatrix().setAngle(Math.PI/2e3*ts),this.scaleAnimation()},draw:function(){this.$clear(),this.img.draw(),this.img._mousein&&this.img.stroke()}},events:{mousedown:function(){this.img._mousein&&(this._mousedown=!0,this.img.lastX=this.img.getStartX(),this.img.lastY=this.img.getStartY())},mousemove:function(ev){var mx,my;this._mousedown?(mx=this.img.lastX+ev.$moveX,my=this.img.lastY+ev.$moveY,this.img.init(mx,my,mx+this.size,my+this.size)):this.img._mousein=this.img.isPointInPath(ev.$x,ev.$y)},mouseup:function(){this._mousedown=!1},wheel:function(ev){ev.preventDefault(),1!==ev.$scaling&&this.img.animationQueue.unshift(ev)}},functions:{scaleAnimation:function(){var stampCx,stampCy,scale;this.img.animationQueue.length&&this.img.count===this.img.maxCount&&(stampCy=this.img.animationQueue.pop(),this.img.lastCx=this.img.getOriginX(),this.img.lastCy=this.img.getOriginY(),this.img.targetCx=stampCy.$x+(this.img.getOriginX()-stampCy.$x)*stampCy.$scaling,this.img.targetCy=stampCy.$y+(this.img.getOriginY()-stampCy.$y)*stampCy.$scaling,this.img.lastScale=stampCy.$lastScale,this.img.targetScale=stampCy.$scale,this.img.count=0),this.img.count-Math.PI/2&&an=head.timestamp){head.timestamp+=head.timespan;var tails=head.tails,rand=tails[tails.length-1];this.inBackground(rand)&&(tail=this.pinStack.pop()||this.getDefaultText(),this.setTextConfig(tail,rand.getStartX(),rand.getStartY(),rand.getText(),rand.getMatrix()),tail.getStyle().setFillStyle(rand.getStyle().getFillStyle()),tail.alpha=255,tail.decre=rand.decre,this.pinMap[tail.serialId]=tail);var x=head.getMatrix(),y=head.getStartY()+x.getOffsetY();this.setTextConfig(tails[0],head.getStartX(),y,head.getText(),x),head.setText(this.getRandomChar()),x.setScale(janvas.Utils.randSign(),janvas.Utils.randSign()).setOffsetY(x.getOffsetY()+this.offsetY);for(var i=tails.length-1;0this.colors.count&&(next.getStyle().setFillStyle(prev.getStyle().getFillStyle()),next.decre=prev.decre)}var range=this.colors.lightRange[1]-this.colors.lightRange[0],rand=this.rand(range),tail=tails[this.colors.count];tail.getStyle().setFillStyle(this.colors.tailHsls[this.rand(this.colors.saturRange[1]-this.colors.saturRange[0])][rand]),tail.decre=rand=this.$height+this.halfY&&(x.setOffsetY(0),x=this.getRandomX(),head.init(x,-this.halfY,x,-this.halfY))}},this)},draw:function(){for(var serialId in this.background.fill(),this.heads.forEach(function(head){head.fill();for(var i=1;ipin.decre?(pin.getStyle().setAlpha((pin.alpha-=pin.decre)/255),pin.fill()):(delete this.pinMap[pin.serialId],this.pinStack.push(pin))}},_initChars:function(){if(!this.chars.length){for(var i=48;i<58;i++)this.chars.push(String.fromCharCode(i));for(i=12448;i<12543;i++)this.chars.push(String.fromCharCode(i))}},_initColors:function(){var j,hsls,tailRgb=(new janvas.Rgb).fromRgbString(this.colors.tail),tailHsl=(new janvas.Hsl).fromRgb(tailRgb);for(this.colors.tailHsls=[],i=this.colors.saturRange[0];i<=this.colors.saturRange[1];i++)for(this.colors.tailHsls.push(hsls=[]),j=this.colors.lightRange[0];j<=this.colors.lightRange[1];j++)hsls.push(tailHsl.setSaturation(i).setLightness(j).toHslString());this.colors.gradient=new Array(this.colors.count);for(var headSRgb=(new janvas.Rgb).fromRgbString(this.colors.head).sRgbInverseCompanding(),tailSRgb=tailRgb.sRgbInverseCompanding(),mix=new janvas.Rgb,i=1;ipointer.ground-link.size/2&&(p1.y=pointer.ground-link.size/2,p1.x-=p1.vx,p1.vx=p1.vy=0)}),this.points[3].x+=.001*(this.x-this.points[3].x)},Dancer.prototype.draw=function(){this.links.forEach(function(link){link.draw()})},Dancer.Link=function($ctx,hsl,size,p0,p1,force,isHead){this.hsl=hsl,this.size=size,this._offset=size/10,this.p0=p0,this.p1=p1,this.distance=janvas.Utils.pythagorean(p1.x-p0.x,p1.y-p0.y),this.force=force||.5,this.isHead=isHead,this.startRect=new janvas.Rect($ctx,0,0,size/5,size/5),this.endRect=new janvas.Rect($ctx,0,0,size/5,size/5),this.body=new janvas.Line($ctx),this.body.getStyle().setStrokeStyle(this.hsl.toHslString()).setLineWidth(size).setLineCap("round").setShadowColor("rgba(0, 0, 0, 0.5)").setShadowOffsetX(size/4).setShadowOffsetY(size/4)},Dancer.Link.prototype={update:function(){var p0=this.p0,p1=this.p1,sx=p1.x-p0.x,dy=p1.y-p0.y,dist=janvas.Utils.pythagorean(sx,dy),sy=p0.w+p1.w,r0=p0.w/sy,r1=p1.w/sy,sy=(this.distance-dist)*this.force,sx=sx/dist*sy,sy=dy/dist*sy;p1.x+=sx*r0,p1.y+=sy*r0,p0.x-=sx*r1,p0.y-=sy*r1},draw:function(){var p0=this.p0,p1=this.p1,o=this._offset;this.isHead?this.body.setStart(p1.x,p1.y):this.body.setStart(p0.x,p0.y),this.body.setEnd(p1.x,p1.y).stroke(),this.startRect.getMatrix().setAngle(Math.atan2(p1.y-p0.y,p1.x-p0.x)),this.endRect.getMatrix().setAngle(this.startRect.getMatrix().getAngle()),this.startRect.init(p0.x-o,p0.y-o,p0.x,p0.y).fill(),this.endRect.init(p1.x-o,p1.y-o,p1.x,p1.y).fill()}},Dancer.Point=function(x,y,fn,w){this.x=x,this.y=y,this.w=w||.5,this.fn=fn||janvas.Utils.noop,this.px=x,this.py=y,this.vx=0,this.vy=0},Dancer.Point.prototype.update=function(){this.vx=this.x-this.px,this.vy=this.y-this.py,this.px=this.x,this.py=this.y,this.vx*=.995,this.vy*=.995,this.x+=this.vx,this.y+=this.vy+.01},Dancer)},props:{struct:{points:[{x:0,y:-4,fn:function(s,d){this.y-=.01*s}},{x:0,y:-16,fn:function(s,d){this.y-=.02*s*d}},{x:0,y:12,fn:function(s,d){this.y+=.02*s*d}},{x:-12,y:0},{x:12,y:0},{x:-3,y:34,fn:function(s,d){0this.max&&(this.max=value),text.setText(Math.round(value)+" "+this.name+" ("+Math.round(this.min)+"-"+Math.round(this.max)+")"),rightAlpha.setHeight(Math.round(30*(1-value/maxValue))),topBack.fill(),text.fill(),image.draw(),right.fill(),rightAlpha.fill()},{init:function(context){var _ctx=context.$ctx,_dpr=context.$dpr;background=new janvas.Rect(_ctx,0,0,80,48),topBack=new janvas.Rect(_ctx,0,0,80,15),(text=new janvas.Text(_ctx,3,2)).getStyle().setFont("bold 9px Helvetica,Arial,sans-serif").setTextBaseline("top"),graph=new janvas.Rect(_ctx,3,15,74,30),graphAlpha=new janvas.Rect(_ctx,3,15,74,30),right=new janvas.Rect(_ctx,76,15,1,30),rightAlpha=new janvas.Rect(_ctx,76,15,1,30),image=new janvas.Image(_ctx,3,15,context.$canvas,0,0,73,30,4*_dpr,15*_dpr,73*_dpr,30*_dpr)},Panel:Panel})},methods:{init:function(){this.factory.init(this),this.panels=[],this.addPanel("FPS","#00ffff","#000022"),this.addPanel("MS","#00ff00","#002200"),performance&&performance.memory&&this.addPanel("MB","#ff0088","#220011"),this.showPanel(0),this.$raf.resume(),this.$wrapper.style.cursor="pointer"},update:function(){var memory,ts=performance.now();if(void 0===this._ts)return this._ts=ts;switch(this.frames++,this.panel.name){case"FPS":ts>=this._ts+this.fpsTimespan&&(this.panel.update(1e3*this.frames/(ts-this._ts),100),this._ts=ts,this.frames=0);break;case"MS":this.panel.update(ts-this._ts,50),this._ts=ts;break;case"MB":ts>=this._ts+this.mbTimespan&&(memory=performance.memory,this.panel.update(memory.usedJSHeapSize/1048576,memory.totalJSHeapSize/1048576),this._ts=ts)}},showPanel:function(mode){this.panel=this.panels[this.mode=mode],this.panel.show(),this.frames=0,this._ts=void 0},addPanel:function(name,fg,bg){this.panels.push(new this.factory.Panel(name,fg,bg))},updatePanel:function(name,value,maxValue){this.panel.name===name&&this.panel.update(value,maxValue)},removePanel:function(){var index;1