├── .gitignore ├── LICENSE ├── README.md ├── main.js ├── package.json ├── panel ├── preview-webview.html ├── preview.html └── webview-index.html └── widget ├── linear-ticks.js ├── pixi-grid.html └── pixi-grid.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Fireball Game Engine 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pixi grid 2 | 3 | A grid scale widget renderred by pixi graphics. 4 | 5 | ![preview](https://cloud.githubusercontent.com/assets/174891/7783124/bf4915d2-0168-11e5-8a81-e1aa7e497933.png) 6 | 7 | ## API 8 | 9 | ### Method: setAnchor(x,y) 10 | 11 | - `x` number - Range of [0.0,1.0] 12 | - `y` number - Range of [0.0,1.0] 13 | 14 | Sets a scale anchor. 15 | 16 | ### Method: setScaleH( lods, minScale, maxScale[, type] ) 17 | 18 | - `lods` array 19 | - `minScale` number 20 | - `maxScale` number 21 | - `type` string - can be `frame` 22 | 23 | Sets the scale method for horizontal scalar. Example: 24 | 25 | ``` 26 | this.setScaleH( [5,2], 0.001, 1000 ); 27 | ``` 28 | 29 | ### Method: setMappingH( minValue, maxValue, pixelRange ) 30 | 31 | ### Method: setRangeH( minValue, maxValue ) 32 | 33 | ### Method: setScaleV( lods, minScale, maxScale, type ) 34 | 35 | ### Method: setMappingV( minValue, maxValue, pixelRange ) 36 | 37 | ### Method: setRangeV( minValue, maxValue ) 38 | 39 | ### Method: pan( deltaPixelX, deltaPixelY ) 40 | 41 | ### Method: panX( deltaPixelX ) 42 | 43 | ### Method: panY( deltaPixelX ) 44 | 45 | ### Method: xAxisScaleAt( pixelX, scale ) 46 | 47 | ### Method: yAxisScaleAt( pixelY, scale ) 48 | 49 | ### Method: xAxisSync( x, scaleX ) 50 | 51 | ### Method: yAxisSync( y, scaleY ) 52 | 53 | ### Method: resize( w, h ) 54 | 55 | ### Method: repaint() 56 | 57 | ### Method: scaleAction(event) 58 | 59 | ### Method: panAction(event) 60 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | load () { 5 | }, 6 | 7 | unload () { 8 | }, 9 | 10 | messages: { 11 | open () { 12 | Editor.Panel.open( 'ui-grid.preview' ); 13 | }, 14 | 15 | 'open-webview' () { 16 | Editor.Panel.open( 'ui-grid.preview-webview' ); 17 | }, 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui-grid", 3 | "version": "0.1.4", 4 | "description": "A Grid Widget", 5 | "author": "Firebox Technology", 6 | "main": "main.js", 7 | "widgets": { 8 | "pixi-grid": "widget/pixi-grid.html" 9 | }, 10 | "main-menu": { 11 | "i18n:MAIN_MENU.developer.title/UI Preview/ui-grid": { 12 | "message": "ui-grid:open", 13 | "dev": true 14 | }, 15 | "i18n:MAIN_MENU.developer.title/UI Preview/ui-grid-webview": { 16 | "message": "ui-grid:open-webview", 17 | "dev": true 18 | } 19 | }, 20 | "panel.preview": { 21 | "main": "panel/preview.html", 22 | "ui": "polymer", 23 | "type": "dockable", 24 | "title": "Grid", 25 | "width": 800, 26 | "height": 600, 27 | "min-width": 400, 28 | "min-height": 300, 29 | "messages": [] 30 | }, 31 | "panel.preview-webview": { 32 | "main": "panel/preview-webview.html", 33 | "ui": "polymer", 34 | "type": "dockable", 35 | "title": "Grid WebView", 36 | "width": 800, 37 | "height": 600, 38 | "min-width": 400, 39 | "min-height": 300, 40 | "messages": [] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /panel/preview-webview.html: -------------------------------------------------------------------------------- 1 | 2 | 30 | 31 | 35 | 36 | -------------------------------------------------------------------------------- /panel/preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | 167 | 168 | -------------------------------------------------------------------------------- /panel/webview-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /widget/linear-ticks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class LinearTicks { 4 | constructor () { 5 | this.ticks = []; 6 | this.tickLods = []; 7 | this.tickRatios = []; 8 | 9 | this.minScale = 0.1; 10 | this.maxScale = 1000.0; 11 | 12 | this.minValueScale = 1.0; 13 | this.maxValueScale = 1.0; 14 | 15 | this.minValue = -500; 16 | this.maxValue = 500; 17 | 18 | this.pixelRange = 500; 19 | 20 | this.minSpacing = 10; 21 | this.maxSpacing = 80; 22 | } 23 | 24 | initTicks ( lods, min, max ) { 25 | if ( min <= 0 ) { 26 | min = 1; 27 | } 28 | if ( max <= 0 ) { 29 | max = 1; 30 | } 31 | if ( max < min ) { 32 | max = min; 33 | } 34 | 35 | this.tickLods = lods; 36 | this.minScale = min; 37 | this.maxScale = max; 38 | 39 | // generate ticks 40 | this.ticks = []; 41 | 42 | let curTick = 1.0; 43 | let curIdx = 0; 44 | 45 | this.ticks.push(curTick); 46 | 47 | let minScale = min; 48 | let maxScale = max; 49 | let maxTickValue = 1; 50 | let minTickValue = 1; 51 | 52 | while ( curTick * this.tickLods[curIdx] <= maxScale ) { 53 | curTick = curTick * this.tickLods[curIdx]; 54 | curIdx = curIdx + 1 > this.tickLods.length-1 ? 0 : curIdx + 1; 55 | this.ticks.push(curTick); 56 | 57 | maxTickValue = curTick; 58 | } 59 | 60 | // NOTE: we need to leave two more level for both zoom-in, so enlarge 100 times here. 61 | this.minValueScale = 1.0/maxTickValue * 100; 62 | 63 | curIdx = this.tickLods.length-1; 64 | curTick = 1.0; 65 | while ( curTick / this.tickLods[curIdx] >= minScale ) { 66 | curTick = curTick / this.tickLods[curIdx]; 67 | curIdx = curIdx - 1 < 0 ? this.tickLods.length-1 : curIdx - 1; 68 | this.ticks.unshift(curTick); 69 | 70 | minTickValue = curTick; 71 | } 72 | 73 | // NOTE: we need to leave two more level for both zoom-out, so enlarge 100 times here. 74 | this.maxValueScale = 1.0/minTickValue * 100; 75 | 76 | return this; 77 | } 78 | 79 | spacing ( min, max ) { 80 | this.minSpacing = min; 81 | this.maxSpacing = max; 82 | 83 | return this; 84 | } 85 | 86 | range ( minValue, maxValue, pixelRange ) { 87 | // NOTE: Math.fround here to prevent label blinking 88 | this.minValue = Math.fround(Math.min(minValue,maxValue)); 89 | this.maxValue = Math.fround(Math.max(minValue,maxValue)); 90 | 91 | this.pixelRange = pixelRange; 92 | 93 | this.minTickLevel = 0; 94 | this.maxTickLevel = this.ticks.length-1; 95 | 96 | for ( let i = this.ticks.length-1; i >= 0; --i ) { 97 | let ratio = this.ticks[i] * this.pixelRange / (this.maxValue - this.minValue); 98 | this.tickRatios[i] = (ratio - this.minSpacing) / (this.maxSpacing - this.minSpacing); 99 | if ( this.tickRatios[i] >= 1.0 ) { 100 | this.maxTickLevel = i; 101 | } 102 | if ( ratio <= this.minSpacing ) { 103 | this.minTickLevel = i; 104 | break; 105 | } 106 | } 107 | 108 | for ( let j = this.minTickLevel; j <= this.maxTickLevel; ++j ) { 109 | this.tickRatios[j] = Editor.Math.clamp01(this.tickRatios[j]); 110 | } 111 | 112 | return this; 113 | } 114 | 115 | ticksAtLevel ( level, excludeHigherLevel ) { 116 | let results = []; 117 | let tick = this.ticks[level]; 118 | // NOTE: we use `Math.floor` and `<= end` for one more line 119 | // so that label draw will not cut off when at the edge of the viewport 120 | let start = Math.floor( this.minValue / tick ); 121 | let end = Math.ceil( this.maxValue / tick ); 122 | for ( let i = start; i <= end; ++i ) { 123 | if ( !excludeHigherLevel || 124 | level >= this.maxTickLevel || 125 | i % Math.round(this.ticks[level+1] / tick) !== 0 ) 126 | { 127 | results.push( i * tick ); 128 | } 129 | } 130 | 131 | return results; 132 | } 133 | 134 | levelForStep ( step ) { 135 | for ( let i = 0; i < this.ticks.length; ++i ) { 136 | let ratio = this.ticks[i] * this.pixelRange / (this.maxValue - this.minValue); 137 | if ( ratio >= step ) { 138 | return i; 139 | } 140 | } 141 | return -1; 142 | } 143 | } 144 | 145 | module.exports = LinearTicks; 146 | -------------------------------------------------------------------------------- /widget/pixi-grid.html: -------------------------------------------------------------------------------- 1 | 2 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /widget/pixi-grid.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 'use strict'; 3 | 4 | const Numeral = require('numeral'); 5 | const PIXI = require('pixi.js'); 6 | const LinearTicks = Editor.require('packages://ui-grid/widget/linear-ticks'); 7 | 8 | function _snapPixel (p) { 9 | return Math.floor(p); 10 | } 11 | 12 | function _uninterpolate(a, b) { 13 | b = (b -= a) || 1 / b; 14 | return function(x) { return (x - a) / b; }; 15 | } 16 | 17 | function _interpolate(a, b) { 18 | return function(t) { return a * (1 - t) + b * t; }; 19 | } 20 | 21 | // pixi config 22 | PIXI.utils._saidHello = true; 23 | 24 | Editor.polymerElement({ 25 | properties: { 26 | debugInfo: { 27 | type: Object, 28 | value: () => { return { 29 | xAxisScale: 0, 30 | xMinLevel: 0, 31 | xMaxLevel: 0, 32 | yAxisScale: 0, 33 | yMinLevel: 0, 34 | yMaxLevel: 0, 35 | }; }, 36 | }, 37 | 38 | showDebugInfo: { 39 | type: Boolean, 40 | value: false, 41 | reflectToAttribute: true 42 | }, 43 | 44 | showLabelH: { 45 | type: Boolean, 46 | value: false, 47 | reflectToAttribute: true 48 | }, 49 | 50 | showLabelV: { 51 | type: Boolean, 52 | value: false, 53 | reflectToAttribute: true 54 | }, 55 | }, 56 | 57 | hostAttributes: { 58 | tabindex: -1 59 | }, 60 | 61 | created () { 62 | this.canvasWidth = 0; 63 | this.canvasHeight = 0; 64 | this.worldPosition = [0, 0]; 65 | 66 | this.hticks = null; 67 | this.xAxisScale = 1.0; 68 | this.xAxisOffset = 0.0; 69 | this.xAnchor = 0.5; 70 | 71 | this.vticks = null; 72 | this.yAxisScale = 1.0; 73 | this.yAxisOffset = 0.0; 74 | this.yAnchor = 0.5; 75 | 76 | // this is generated in setMapping 77 | this._xAnchorOffset = 0.0; 78 | this._yAnchorOffset = 0.0; 79 | 80 | // init direction 81 | this.xDirection = 1.0; 82 | this.yDirection = 1.0; 83 | }, 84 | 85 | ready () { 86 | let rect = this.$.view.getBoundingClientRect(); 87 | this.renderer = new PIXI.WebGLRenderer( rect.width, rect.height, { 88 | view: this.$['pixi-grid-canvas'], 89 | transparent: true, 90 | antialias: false, 91 | forceFXAA: false, 92 | }); 93 | 94 | this.stage = new PIXI.Container(); 95 | 96 | // background 97 | let background = new PIXI.Container(); 98 | this.stage.addChild(background); 99 | 100 | this.bgGraphics = new PIXI.Graphics(); 101 | background.addChild(this.bgGraphics); 102 | 103 | // DISABLE 104 | // // scene 105 | // this.scene = new PIXI.Container(); 106 | // this.stage.addChild(this.scene); 107 | 108 | // // foreground 109 | // let foreground = new PIXI.Container(); 110 | // this.stage.addChild(foreground); 111 | 112 | // this.fgGraphics = new PIXI.Graphics(); 113 | // foreground.addChild(this.fgGraphics); 114 | }, 115 | 116 | attached () { 117 | this.async(() => { 118 | this.lightDomReady(); 119 | }); 120 | }, 121 | 122 | lightDomReady () { 123 | this.resize(); 124 | this.repaint(); 125 | }, 126 | 127 | // default 0.5, 0.5 128 | setAnchor ( x, y ) { 129 | this.xAnchor = Editor.Math.clamp( x, -1, 1 ); 130 | this.yAnchor = Editor.Math.clamp( y, -1, 1 ); 131 | }, 132 | 133 | // recommended: [5,2], 0.001, 1000 134 | setScaleH ( lods, minScale, maxScale, type ) { 135 | this.hticks = new LinearTicks() 136 | .initTicks( lods, minScale, maxScale ) 137 | .spacing ( 10, 80 ) 138 | ; 139 | this.xAxisScale = Editor.Math.clamp( 140 | this.xAxisScale, 141 | this.hticks.minValueScale, 142 | this.hticks.maxValueScale 143 | ); 144 | 145 | if ( type === 'frame' ) { 146 | this.hformat = frame => { 147 | return Editor.Utils.formatFrame( frame, 60.0 ); 148 | }; 149 | } 150 | 151 | this.pixelToValueH = x => { 152 | // return (x - this.canvasWidth * 0.5) / this.xAxisScale; 153 | return (x - this.xAxisOffset) / this.xAxisScale; 154 | }; 155 | 156 | this.valueToPixelH = x => { 157 | // return x * this.xAxisScale + this.canvasWidth * 0.5; 158 | return x * this.xAxisScale + this.xAxisOffset; 159 | }; 160 | }, 161 | 162 | setMappingH ( minValue, maxValue, pixelRange ) { 163 | this._xAnchorOffset = minValue / (maxValue - minValue); 164 | this.xDirection = (maxValue - minValue) > 0 ? 1 : -1; 165 | 166 | this.pixelToValueH = x => { 167 | let pixelOffset = this.xAxisOffset; 168 | 169 | let ratio = this.canvasWidth / pixelRange; 170 | let u = _uninterpolate( 0.0, this.canvasWidth ); 171 | let i = _interpolate( minValue * ratio, maxValue * ratio ); 172 | return i(u(x - pixelOffset)) / this.xAxisScale; 173 | }; 174 | 175 | this.valueToPixelH = x => { 176 | let pixelOffset = this.xAxisOffset; 177 | 178 | let ratio = this.canvasWidth / pixelRange; 179 | let u = _uninterpolate( minValue * ratio, maxValue * ratio ); 180 | let i = _interpolate( 0.0, this.canvasWidth ); 181 | return i(u(x * this.xAxisScale)) + pixelOffset; 182 | }; 183 | }, 184 | 185 | setRangeH ( minValue, maxValue ) { 186 | this.xMinRange = minValue; 187 | this.xMaxRange = maxValue; 188 | }, 189 | 190 | setScaleV ( lods, minScale, maxScale, type ) { 191 | this.vticks = new LinearTicks() 192 | .initTicks( lods, minScale, maxScale ) 193 | .spacing ( 10, 80 ) 194 | ; 195 | this.yAxisScale = Editor.Math.clamp( 196 | this.yAxisScale, 197 | this.vticks.minValueScale, 198 | this.vticks.maxValueScale 199 | ); 200 | 201 | if ( type === 'frame' ) { 202 | this.vformat = frame => { 203 | return Editor.Utils.formatFrame( frame, 60.0 ); 204 | }; 205 | } 206 | 207 | this.pixelToValueV = y => { 208 | // return (this.canvasHeight*0.5 - y) / this.yAxisScale; 209 | return (this.canvasHeight - y + this.yAxisOffset) / this.yAxisScale; 210 | }; 211 | 212 | this.valueToPixelV = y => { 213 | // return -y * this.yAxisScale + this.canvasHeight*0.5; 214 | return -y * this.yAxisScale + this.canvasHeight + this.yAxisOffset; 215 | }; 216 | }, 217 | 218 | setMappingV ( minValue, maxValue, pixelRange ) { 219 | this._yAnchorOffset = minValue / (maxValue - minValue); 220 | this.yDirection = (maxValue - minValue) > 0 ? 1 : -1; 221 | 222 | this.pixelToValueV = y => { 223 | let pixelOffset = this.yAxisOffset; 224 | 225 | let ratio = this.canvasHeight / pixelRange; 226 | let u = _uninterpolate( 0.0, this.canvasHeight ); 227 | let i = _interpolate( minValue * ratio, maxValue * ratio ); 228 | return i(u(y - pixelOffset)) / this.yAxisScale; 229 | }; 230 | 231 | this.valueToPixelV = y => { 232 | let pixelOffset = this.yAxisOffset; 233 | 234 | let ratio = this.canvasHeight / pixelRange; 235 | let u = _uninterpolate( minValue * ratio, maxValue * ratio ); 236 | let i = _interpolate( 0.0, this.canvasHeight ); 237 | return i(u(y * this.yAxisScale)) + pixelOffset; 238 | }; 239 | }, 240 | 241 | setRangeV ( minValue, maxValue ) { 242 | this.yMinRange = minValue; 243 | this.yMaxRange = maxValue; 244 | }, 245 | 246 | pan ( deltaPixelX, deltaPixelY ) { 247 | this.panX(deltaPixelX); 248 | this.panY(deltaPixelY); 249 | }, 250 | 251 | panX ( deltaPixelX ) { 252 | if ( !this.valueToPixelH ) { 253 | return; 254 | } 255 | 256 | let newOffset = this.xAxisOffset + deltaPixelX; 257 | this.xAxisOffset = 0.0; // calc range without offset 258 | 259 | let min, max; 260 | if ( this.xMinRange !== undefined && this.xMinRange !== null ) { 261 | min = this.valueToPixelH(this.xMinRange); 262 | } 263 | if ( this.xMaxRange !== undefined && this.xMaxRange !== null ) { 264 | max = this.valueToPixelH(this.xMaxRange); 265 | max = Math.max(0, max - this.canvasWidth); 266 | } 267 | 268 | this.xAxisOffset = newOffset; 269 | 270 | if ( min !== undefined && max !== undefined ) { 271 | this.xAxisOffset = Editor.Math.clamp( this.xAxisOffset, -max, -min ); 272 | return; 273 | } 274 | 275 | if ( min !== undefined ) { 276 | this.xAxisOffset = Math.min( this.xAxisOffset, -min ); 277 | return; 278 | } 279 | 280 | if ( max !== undefined ) { 281 | this.xAxisOffset = Math.max( this.xAxisOffset, -max ); 282 | return; 283 | } 284 | }, 285 | 286 | panY ( deltaPixelY ) { 287 | if ( !this.valueToPixelV ) { 288 | return; 289 | } 290 | 291 | let newOffset = this.yAxisOffset + deltaPixelY; 292 | this.yAxisOffset = 0.0; // calc range without offset 293 | 294 | let min, max; 295 | if ( this.yMinRange !== undefined && this.yMinRange !== null ) { 296 | min = this.valueToPixelV(this.yMinRange); 297 | } 298 | if ( this.yMaxRange !== undefined && this.yMaxRange !== null ) { 299 | max = this.valueToPixelV(this.yMaxRange); 300 | max = Math.max(0, max - this.canvasHeight); 301 | } 302 | 303 | this.yAxisOffset = newOffset; 304 | 305 | if ( min !== undefined && max !== undefined ) { 306 | this.yAxisOffset = Editor.Math.clamp( this.yAxisOffset, -max, -min ); 307 | return; 308 | } 309 | 310 | if ( min !== undefined ) { 311 | this.yAxisOffset = Math.min( this.yAxisOffset, -min ); 312 | return; 313 | } 314 | 315 | if ( max !== undefined ) { 316 | this.yAxisOffset = Math.max( this.yAxisOffset, -max ); 317 | return; 318 | } 319 | }, 320 | 321 | xAxisScaleAt ( pixelX, scale ) { 322 | let oldValueX = this.pixelToValueH(pixelX); 323 | this.xAxisScale = Editor.Math.clamp( scale, this.hticks.minValueScale, this.hticks.maxValueScale ); 324 | let newScreenX = this.valueToPixelH(oldValueX); 325 | this.pan( pixelX - newScreenX, 0 ); 326 | }, 327 | 328 | yAxisScaleAt ( pixelY, scale ) { 329 | let oldValueY = this.pixelToValueV(pixelY); 330 | this.yAxisScale = Editor.Math.clamp( scale, this.vticks.minValueScale, this.vticks.maxValueScale ); 331 | let newScreenY = this.valueToPixelV(oldValueY); 332 | this.pan( 0, pixelY - newScreenY ); 333 | }, 334 | 335 | xAxisSync ( x, scaleX ) { 336 | this.xAxisOffset = x; 337 | this.xAxisScale = scaleX; 338 | }, 339 | 340 | yAxisSync ( y, scaleY ) { 341 | this.yAxisOffset = y; 342 | this.yAxisScale = scaleY; 343 | }, 344 | 345 | resize ( w, h ) { 346 | if ( !w || !h ) { 347 | let rect = this.$.view.getBoundingClientRect(); 348 | w = w || rect.width; 349 | h = h || rect.height; 350 | 351 | w = Math.round(w); 352 | h = Math.round(h); 353 | } 354 | 355 | // adjust xAxisOffset by anchor x 356 | if ( this.canvasWidth !== 0 ) { 357 | this.panX((w - this.canvasWidth) * (this.xAnchor + this._xAnchorOffset)); 358 | } 359 | 360 | // adjust yAxisOffset by anchor y 361 | if ( this.canvasHeight !== 0 ) { 362 | this.panY((h - this.canvasHeight) * (this.yAnchor + this._yAnchorOffset)); 363 | } 364 | 365 | this.canvasWidth = w; 366 | this.canvasHeight = h; 367 | 368 | if ( this.renderer ) { 369 | this.renderer.resize( this.canvasWidth, this.canvasHeight ); 370 | } 371 | }, 372 | 373 | repaint () { 374 | if ( !this.renderer ) { 375 | return; 376 | } 377 | 378 | this._updateGrids(); 379 | if (!this._requestID) { 380 | this._requestID = window.requestAnimationFrame(() => { 381 | this._requestID = null; 382 | this.renderer.render(this.stage); 383 | }); 384 | } 385 | }, 386 | 387 | scaleAction ( event ) { 388 | event.stopPropagation(); 389 | 390 | let changeX = true; 391 | let changeY = true; 392 | 393 | if ( event.metaKey ) { 394 | changeX = true; 395 | changeY = false; 396 | } else if ( event.shiftKey ) { 397 | changeX = false; 398 | changeY = true; 399 | } 400 | 401 | let newScale; 402 | 403 | if ( changeX && this.hticks ) { 404 | newScale = Editor.Utils.smoothScale(this.xAxisScale, event.wheelDelta); 405 | this.xAxisScaleAt ( event.offsetX, newScale ); 406 | } 407 | 408 | if ( changeY && this.vticks ) { 409 | newScale = Editor.Utils.smoothScale(this.yAxisScale, event.wheelDelta); 410 | this.yAxisScaleAt ( event.offsetY, newScale ); 411 | } 412 | 413 | // TODO: smooth animate 414 | // var curScale = this.xAxisScale; 415 | // var nextScale = scale; 416 | // var start = window.performance.now(); 417 | // var duration = 300; 418 | // function animateScale ( time ) { 419 | // var requestId = window.requestAnimationFrame ( animateScale.bind(this) ); 420 | // var cur = time - start; 421 | // var ratio = cur/duration; 422 | // if ( ratio >= 1.0 ) { 423 | // this.xAxisScale = nextScale; 424 | // cancelAnimationFrame(requestId); 425 | // } 426 | // else { 427 | // this.xAxisScale = Editor.Math.lerp( curScale, nextScale, ratio ); 428 | // } 429 | // this.repaint(); 430 | // }; 431 | // animateScale.call(this,start); 432 | 433 | this.repaint(); 434 | }, 435 | 436 | panAction ( event ) { 437 | if ( event.which === 1 ) { 438 | this.style.cursor = '-webkit-grabbing'; 439 | Editor.UI.startDrag ( 440 | '-webkit-grabbing', 441 | event, 442 | 443 | // move 444 | ( event, dx, dy ) => { 445 | this.pan( dx, dy ); 446 | this.repaint(); 447 | }, 448 | 449 | // end 450 | () => { 451 | this.style.cursor = ''; 452 | } 453 | ); 454 | 455 | return; 456 | } 457 | }, 458 | 459 | // DISABLE 460 | // updateSelectRect ( x, y, w, h ) { 461 | // let lineColor = 0x09fff; 462 | 463 | // this.fgGraphics.clear(); 464 | // this.fgGraphics.beginFill(lineColor, 0.2); 465 | // this.fgGraphics.lineStyle(1, lineColor, 1.0); 466 | // this.fgGraphics.drawRect(x,y,w,h); 467 | // this.fgGraphics.endFill(); 468 | // }, 469 | 470 | // clearSelectRect: function () { 471 | // this.fgGraphics.clear(); 472 | // this.fgGraphics.endFill(); 473 | // }, 474 | 475 | _updateGrids () { 476 | let lineColor = 0x555555; 477 | let ticks, ratio; 478 | let screen_x, screen_y; 479 | 480 | this.bgGraphics.clear(); 481 | this.bgGraphics.beginFill(lineColor); 482 | 483 | // draw h ticks 484 | if ( this.hticks ) { 485 | let left = this.pixelToValueH(0); 486 | let right = this.pixelToValueH(this.canvasWidth); 487 | this.hticks.range( left, right, this.canvasWidth ); 488 | 489 | for ( let i = this.hticks.minTickLevel; i <= this.hticks.maxTickLevel; ++i ) { 490 | ratio = this.hticks.tickRatios[i]; 491 | if ( ratio > 0 ) { 492 | this.bgGraphics.lineStyle(1, lineColor, ratio * 0.5); 493 | ticks = this.hticks.ticksAtLevel(i,true); 494 | for ( let j = 0; j < ticks.length; ++j ) { 495 | screen_x = this.valueToPixelH(ticks[j]); 496 | this.bgGraphics.moveTo( _snapPixel(screen_x), -1.0 ); 497 | this.bgGraphics.lineTo( _snapPixel(screen_x), this.canvasHeight ); 498 | } 499 | } 500 | } 501 | } 502 | 503 | // draw v ticks 504 | if ( this.vticks ) { 505 | let top = this.pixelToValueV(0); 506 | let bottom = this.pixelToValueV(this.canvasHeight); 507 | this.vticks.range( top, bottom, this.canvasHeight ); 508 | 509 | for ( let i = this.vticks.minTickLevel; i <= this.vticks.maxTickLevel; ++i ) { 510 | ratio = this.vticks.tickRatios[i]; 511 | if ( ratio > 0 ) { 512 | this.bgGraphics.lineStyle(1, lineColor, ratio * 0.5); 513 | ticks = this.vticks.ticksAtLevel(i,true); 514 | for ( let j = 0; j < ticks.length; ++j ) { 515 | screen_y = this.valueToPixelV( ticks[j] ); 516 | this.bgGraphics.moveTo( 0.0, _snapPixel(screen_y) ); 517 | this.bgGraphics.lineTo( this.canvasWidth, _snapPixel(screen_y) ); 518 | } 519 | } 520 | } 521 | } 522 | 523 | this.bgGraphics.endFill(); 524 | 525 | // draw label 526 | if ( this.showLabelH || this.showLabelV ) { 527 | var minStep = 50, labelLevel, labelEL, tickValue; 528 | var decimals, fmt; 529 | 530 | this._resetLabels(); 531 | 532 | // draw hlabel 533 | if ( this.showLabelH && this.hticks ) { 534 | labelLevel = this.hticks.levelForStep(minStep); 535 | ticks = this.hticks.ticksAtLevel(labelLevel,false); 536 | 537 | tickValue = this.hticks.ticks[labelLevel]; 538 | decimals = Math.max( 0, -Math.floor(Math.log10(tickValue)) ); 539 | fmt = '0,' + Number(0).toFixed(decimals); 540 | 541 | var hlabelsDOM = Polymer.dom(this.$.hlabels); 542 | 543 | let j; 544 | for ( j = 0; j < ticks.length; ++j ) { 545 | screen_x = _snapPixel(this.valueToPixelH(ticks[j])) + 5; 546 | 547 | if ( j < hlabelsDOM.children.length ) { 548 | labelEL = hlabelsDOM.children[j]; 549 | } else { 550 | labelEL = this._createLabel(); 551 | hlabelsDOM.appendChild(labelEL); 552 | } 553 | 554 | if ( this.hformat ) { 555 | labelEL.innerText = this.hformat(ticks[j]); 556 | } else { 557 | labelEL.innerText = Numeral(ticks[j]).format(fmt); 558 | } 559 | 560 | labelEL.style.display = 'block'; 561 | labelEL.style.left = _snapPixel(screen_x) + 'px'; 562 | labelEL.style.bottom = '0px'; 563 | labelEL.style.right = ''; 564 | labelEL.style.top = ''; 565 | // labelEL.style.transform = 'translate3d(' + screen_x + 'px,' + '-15px,' + '0px)'; 566 | } 567 | this._hlabelIdx = j; 568 | } 569 | 570 | // draw vlabel 571 | if ( this.showLabelV && this.vticks ) { 572 | labelLevel = this.vticks.levelForStep(minStep); 573 | ticks = this.vticks.ticksAtLevel(labelLevel,false); 574 | 575 | tickValue = this.vticks.ticks[labelLevel]; 576 | decimals = Math.max( 0, -Math.floor(Math.log10(tickValue)) ); 577 | fmt = '0,' + Number(0).toFixed(decimals); 578 | 579 | var vlabelsDOM = Polymer.dom(this.$.vlabels); 580 | 581 | let j; 582 | for ( j = 0; j < ticks.length; ++j ) { 583 | screen_y = _snapPixel(this.valueToPixelV(ticks[j])) - 15; 584 | 585 | if ( j < vlabelsDOM.children.length ) { 586 | labelEL = vlabelsDOM.children[j]; 587 | } else { 588 | labelEL = this._createLabel(); 589 | vlabelsDOM.appendChild(labelEL); 590 | } 591 | 592 | if ( this.vformat ) { 593 | labelEL.innerText = this.vformat(ticks[j]); 594 | } else { 595 | labelEL.innerText = Numeral(ticks[j]).format(fmt); 596 | } 597 | 598 | labelEL.style.display = 'block'; 599 | labelEL.style.left = '0px'; 600 | labelEL.style.top = _snapPixel(screen_y) + 'px'; 601 | labelEL.style.bottom = ''; 602 | labelEL.style.right = ''; 603 | // labelEL.style.transform = 'translate3d(0px,' + screen_y + 'px,' + '0px)'; 604 | } 605 | this._vlabelIdx = j; 606 | } 607 | 608 | // 609 | this._hideUnusedLabels(); 610 | } 611 | 612 | // DEBUG 613 | if ( this.showDebugInfo ) { 614 | this.set('debugInfo.xAxisScale', this.xAxisScale.toFixed(3)); 615 | this.set('debugInfo.xAxisOffset', this.xAxisOffset.toFixed(3)); 616 | 617 | if ( this.hticks ) { 618 | this.set('debugInfo.xMinLevel', this.hticks.minTickLevel); 619 | this.set('debugInfo.xMaxLevel', this.hticks.maxTickLevel); 620 | } 621 | 622 | this.set('debugInfo.yAxisScale', this.yAxisScale.toFixed(3)); 623 | this.set('debugInfo.yAxisOffset', this.yAxisOffset.toFixed(3)); 624 | 625 | if ( this.vticks ) { 626 | this.set('debugInfo.yMinLevel', this.vticks.minTickLevel); 627 | this.set('debugInfo.yMaxLevel', this.vticks.maxTickLevel); 628 | } 629 | } 630 | }, 631 | 632 | _resetLabels () { 633 | this._hlabelIdx = 0; 634 | this._vlabelIdx = 0; 635 | }, 636 | 637 | _createLabel () { 638 | let el = document.createElement('div'); 639 | el.classList.add('label'); 640 | return el; 641 | }, 642 | 643 | _hideUnusedLabels () { 644 | let hlabelsDOM = Polymer.dom(this.$.hlabels); 645 | let vlabelsDOM = Polymer.dom(this.$.vlabels); 646 | 647 | for ( let i = this._hlabelIdx; i < hlabelsDOM.children.length; ++i ) { 648 | let el = hlabelsDOM.children[i]; 649 | el.style.display = 'none'; 650 | } 651 | 652 | for ( let i = this._vlabelIdx; i < vlabelsDOM.children.length; ++i ) { 653 | let el = vlabelsDOM.children[i]; 654 | el.style.display = 'none'; 655 | } 656 | }, 657 | }); 658 | 659 | })(); 660 | --------------------------------------------------------------------------------