README
ray-visutil
vis tools
author
ilex.h
methods
ColorUtils
SvgUtils
fullscreen
memorize 缓存方法执行结果
deepEqual
shallowEqual
resizeEvent [bindResize, unbindResize]
createInterface
const MyInterface = Interface.create('myMethodA', 'myMethodB')
// or
const MyInterface = new Interface('myMethodA', 'myMethodB')
class MyClass extends MyInterface {
constructor () {
super()
}
myMethodA () {
// ...implementation goes here
}
}
const instance = new MyClass()
// throws a new error with the message:
// 'The following function(s) need to be implemented for class MyClass: myMethodB'
class MySubClass extends MyClass {
constructor () {
super()
}
myMethodA () {
// override 'myMethodA'
}
}
const instance = new MySubClass()
// still throws an error with the message:
// 'The following function(s) need to be implemented for class MyClass: myMethodB'
isImplementedBy
const MyInterface = new Interface('myMethod')
class MyClass {
myMethod () {
// some implementation
}
}
class MyOtherClass {
myOtherMethod () {
// some implementation
}
}
const instanceA = new MyClass()
const instanceB = new MyOtherClass()
MyInterface.isImplementedBy(instanceA) // returns true
MyInterface.isImplementedBy(instanceB) // returns false
transition
过渡动画
移植于 @tweenjs/tween.js, or tween.js
用于简单动画的JavaScript补间引擎,结合了优化的 Robert Penner 方程。
example
import TG from 'ray-visutil/lib/transition';
function render(){
const box = document.createElement('div');
box.style.setProperty('background-color', '#008800');
box.style.setProperty('width', '100px');
box.style.setProperty('height', '100px');
document.body.appendChild(box);
// Setup the animation loop.
function animate(time) {
requestAnimationFrame(animate);
TG.update(time);
}
requestAnimationFrame(animate);
const coords = { x: 0, y: 0 }; // 起始点 (0, 0)
const transition = new TG.Transition(coords) // 创建一个新的transition用来改变 'coords'
.to({ x: 300, y: 200 }, 1000) // 在1s内移动至 (300, 200)
.easing(TG.Easing.Quadratic.Out) // 使用缓动功能使的动画更加平滑
.onUpdate(function() { // Called after TG updates 'coords'.
// 将 'box' 移动到 'coords' 所描述的位置,配合 CSS 过渡
box.style.setProperty('transform', 'translate(' + coords.x + 'px, ' + coords.y + 'px)');
})
.start(); // Start the transition immediately.
}
render();
options
TransitionGroup
import TG from 'ray-visutil/lib/transition/TransitionGroup';
用于 添加、删除、更新动画
- getAll()
- removeAll()
- add(transition)
- remove(transition)
- update(time, preserve)
TransitionCore
import TG from 'ray-visutil/lib/transition/TransitionCore';
动画核心库,用于实现动画
- constructor(object, group) :
object 为初始属性
- getId():
获取动画id
- isPlaying():
判断是否正在执行动画
- to(properties, duration):
从初属性转化为目标属性,duration 为耗时时间,默认 1000
- start(time):
开启动画
- stop():
停止动画
- end():
终止动画,设置 update(time)中的,time = startTime + duration
- stopChainedTransitions():
停止 chain 动画
- group():
重新设置 TG
- delay(amount):
延时时间
- repeat(times):
重复执行次数
- repeatDelay(amount):
设置重复延时
- kickBack(kb):
设置回弹
- easing(eas):
设置动画速度函数,默认为 [Easing.Linear.None]
- interpolation(inter):
插入动画,默认为 [Interpolation.Linear]
- chain():
用于多个动画之间连续交换
- onStart(callback):
start 时回调
- onUpdate(callback):
更新时回调,回调函数中的参数为constructor中具体的 object 值
- onComplete(callback):
动画执行完成时的回调,回调函数中的参数为constructor中具体的 object 值
- onStop(callback):
设置停止动画时的回调,回调函数中的参数为constructor中具体的 object 值
- update(time):
更新动画
TG
import TG from 'ray-visutil/lib/transition';
实质是一个 TransitionGroup
实例。
- TG.TransitionGroup
- TG.Transition
- TG.Easing
- TG.Interpolation
polylabel
Given polygon coordinates in
GeoJSON-like format
and precision (1.0
by default),
Polylabel returns the pole of inaccessibility coordinate in [x, y]
format.
- 基本使用
import polylabel from 'ray-visutil/lib/polylabel';
const polygon = [
[[3116, 3071], [3118, 3068], [3108, 3102], [3100, 3105]],
[[4016, 1878], [4016, 1864], [4029, 1859], [4024, 1850], [4008, 1839]],
[([3315, 1339], [3327, 1332], [3331, 1324], [3323, 1329])]
];
const p = polylabel(polygon, 1.0); // [3106.1875, 3098.9375]
const p = polylabel(polygon, 50); // [3108.8702290076335,3090.8778625954196]
- test
var p = polylabel([[[0, 0], [1, 0], [2, 0], [0, 0]]]); // [0, 0]
p = polylabel([[[0, 0], [1, 0], [1, 1], [1, 0], [0, 0]]]); // [0, 0]
TinyQueue
- 基本使用
import TinyQueue from 'ray-visutil/lib/polylabel/TinyQueue';
// create an empty priority queue
var queue = new TinyQueue();
// add some items
queue.push(7);
queue.push(5);
queue.push(10);
// remove the top item
var top = queue.pop(); // returns 7
// return the top item (without removal)
top = queue.peep(); // returns 7
// get queue length
queue.length; // returns 3
// create a priority queue from an existing array (modifies the array)
queue = new TinyQueue([7, 5, 10]);
// pass a custom item comparator as a second argument
queue = new TinyQueue([{value: 5}, {value: 7}], (a, b) => a.value - b.value);
// turn a queue into a sorted array
var array = [];
while (queue.length) {
array.push(queue.pop());
}
- 性能测试
import TinyQueue from 'ray-visutil/lib/polylabel/TinyQueue';
const N = 1000000;
const data = [];
for (let i = 0; i < N; i++) data[i] = {value: Math.random()};
const q = new TinyQueue(null, compare);
function compare(a, b) {
return a.value - b.value;
}
console.time(`push ${N}`);
for (let i = 0; i < 1000000; i++) q.push(data[i]);
console.timeEnd(`push ${N}`);
console.time(`pop ${N}`);
for (let i = 0; i < 1000000; i++) q.pop();
console.timeEnd(`pop ${N}`);
Store
since 2019-01-14 v1.0.10
indexedDB 使用
还可以使用另一个比较好的开源库 localforage
import Store from 'ray-visutil/lib/Store';
const store = new Store({ name: 'ray-3dobj', storeName: 'models' });
store.init(() => {
});
const data = {};
store.set(data, () => {});
store.get((data) => {
});
store.clear();
tools
- isArray(val: any): boolean;
- isObject(val: any): boolean;
- isString(val: any): boolean;
- isNumber(val: any): boolean;
- isFunction(val: any): boolean;
- isRegExp(val: any): boolean;
- isDom(val: any): boolean;
- isNull(val: any): boolean; 判断 undefined 和 null
- isBlank(val: any): boolean; 判断空白
- isEmptyObj(val: any): boolean;
- isEmptyArray(val: any): boolean;
- clone(o: any): any; 克隆数据, 采用 JSON.parse 实现
- merge(target: any, source: any, overwrite: boolean): any;
- hasSameProperties(o1: Object, o2: Object): boolean;
- parseValue(o: any, defaultValue: any): any; 解析数据,当 o 为空时,则取默认值,反之则是 o
- omit(obj: Object, fields: String | String[]): Object; 删除不需要的属性
import { tools } from 'ray-visutil';
// or import tools from 'ray-visutil/lib/tools';
str2html
将 string 转化为 html
import str2html from 'ray-visutil/lib/str2html';
var ss = str2html('<li>aaaa</li>');
var ss2 = str2html('<li>aaaa</li><li>aaaa</li><li>aaaa</li>');
var ss3 = str2html('<ul><li>aaaa</li><li>aaaa</li><li>aaaa</li></ul>');
// [li]
// [li, li, li]
// [ul]
Pivot
@since 1.1.0
Pivot basic use
import Pivot from 'ray-visutil/lib/pivot';
//custom object that dispatch a `started` pivot
const myObject = {
changed : new Pivot(),
ended : new Pivot()
};
function onChange(param1, param2){
alert(param1 + param2);
}
myObject.changed.add(onStarted); //add listener
myObject.changed.dispatch('foo', 'bar'); //dispatch pivot passing custom parameters
myObject.changed.remove(onStarted); //remove a single listener
Pivot Multiple Listeners
function onStop(){
alert('ended');
}
function onStop2(){
alert('ended listener 2');
}
myObject.ended.add(onStop);
myObject.ended.add(onStop2);
myObject.ended.dispatch();
myObject.ended.removeAll(); //remove all listeners of the `ended` pivot
Stop/Halt Propagation (method 1)
myObject.changed.add(function(){
myObject.changed.halt(); //prevent next listeners on the queue from being executed
// return false; //if handler returns `false` will also stop propagation
});
myObject.changed.add(function(){
alert('second listener'); //won't be called since first listener stops propagation
});
myObject.changed.dispatch();
Set execution context of the listener handler
var foo = 'bar';
var obj = {
foo : 10
};
function handler1(){
alert(this.foo);
}
function handler2(){
alert(this.foo);
}
//note that you cannot add the same handler twice to the same pivot without removing it first
myObject.changed.add(handler1); //default execution context
myObject.changed.add(handler2, obj); //set a different execution context
myObject.changed.dispatch(); //first handler will alert "bar", second will alert "10".
Set listener priority/order
var handler1 = function(){
alert('foo');
};
var handler2 = function(){
alert('bar');
};
myObject.changed.add(handler1); //default priority is 0
myObject.changed.add(handler2, null, 2); //setting priority to 2 will make `handler2` execute before `handler1`
myObject.changed.dispatch(); //will alert "bar" than "foo"
GIF
@since v1.1.1
import GIF from 'ray-visutil/lib/gif';
function loadGIF(url, renderGIF){
const oReq = new window.XMLHttpRequest();
oReq.open('GET', url, true);
oReq.responseType = 'arraybuffer';
oReq.onload = function(oEvent) {
var arrayBuffer = oReq.response; // Note: not oReq.responseText
if (arrayBuffer) {
gif = new GIF(arrayBuffer);
var frames = gif.decompressFrames(true);
console.log(gif);
// render the gif
renderGIF(frames);
}
};
oReq.send(null);
}
let loadedFrames;
let frameIndex;
loadGIF('/example/res/car.gif', frames => {
loadedFrames = frames;
// user canvas
var c = document.getElementById('mycanvas');
var ctx = c.getContext('2d');
// gif patch canvas
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
// full gif canvas
var gifCanvas = document.createElement('canvas');
var gifCtx = gifCanvas.getContext('2d');
c.width = frames[0].dims.width;
c.height = frames[0].dims.height;
gifCanvas.width = c.width;
gifCanvas.height = c.height;
renderFrame();
});
function renderFrame() {
// get the frame
var frame = loadedFrames[frameIndex];
var start = new Date().getTime();
gifCtx.clearRect(0, 0, c.width, c.height);
// draw the patch
drawPatch(frame);
// perform manipulation
manipulate();
// update the frame index
frameIndex++;
if (frameIndex >= loadedFrames.length) {
frameIndex = 0;
}
var end = new Date().getTime();
var diff = end - start;
if (playing) {
// delay the next gif frame
setTimeout(() => {
requestAnimationFrame(renderFrame);
//renderFrame();
}, Math.max(0, Math.floor(frame.delay - diff)));
}
}
function drawPatch(frame) {
var dims = frame.dims;
if (!frameImageData || dims.width !== frameImageData.width || dims.height !== frameImageData.height) {
tempCanvas.width = dims.width;
tempCanvas.height = dims.height;
frameImageData = tempCtx.createImageData(dims.width, dims.height);
}
// set the patch data as an override
frameImageData.data.set(frame.patch);
// draw the patch back over the canvas
tempCtx.putImageData(frameImageData, 0, 0);
gifCtx.drawImage(tempCanvas, dims.left, dims.top);
}
ImageProcessor
@since v1.1.2
图片处理 颜色校正、高斯模糊
createInstance
: 创建 canvas instance,包含核心方法或属性: texture, draw, update, replace, contents, getPixelArray
挂载对象 $_$: { gl, isInitialized, texture, spareTexture, flippedShader }
使用完整版 processor
import imgProcessor from 'ray-visutil/lib/imageProcessor/processorAll';
function createImg(){
const img = new window.Image(300, 300);
img.src = '/example/res/landscape.jpg';
img.onload = imgOnload;
return img;
}
function imgOnload(){
const canvas = imgProcessor.canvas();
const texture = canvas.texture(this);
canvas.draw(texture).triangleBlur(50).update();
// replace the image with the canvas
document.body.appendChild(canvas);
}
createImg();
使用自定义滤镜
import { createInstance, wrap } from 'ray-visutil/lib/imageProcessor';
import triangleBlur from 'ray-visutil/lib/imageProcessor/filters/blur/triangleblur';
const imgProcessor = {
canvas(){
const canvas = createInstance();
canvas.triangleBlur = wrap(triangleBlur);
// 全局查看
window.$canvas = canvas;
return canvas;
}
};
// 使用 imgProcessor
screenLog
显示 log 内容。会将 console 日志打印在界面上
import screenLog from 'ray-visutil/screenLog';
// 初始化
screenLog.init('screen-log', { autoScroll: false });
screenLog.log('String: Hello world');
screenLog.log(`Numbers: ${124}`);
screenLog.log(21, 'multiple arguments');
screenLog.log('Arrays', [1, 2, 3]);
screenLog.log('Objects', { a: 3 });
console.log('console.log also gets logged.');
var i = 10;
function log() {
console.log('Future log', Date.now());
console.error('Future error', Date.now());
console.warn('Future warn', Date.now());
console.info('Future info', Date.now());
if (--i) {
setTimeout(log, 1000);
}
}
log();
screenlog options 说明
init(parent, options)
parent: 需要绑定的父节点,可以是 id选择器、或者具体的 DOM。
// 默认 options
const options = {
bgColor: '#232323',
logColor: 'lightgreen',
infoColor: 'blue',
warnColor: 'orange',
errorColor: 'red',
fontSize: '10px',
freeConsole: false,
css: `
z-index:99;
font-family:Helvetica,Arial,sans-serif;
padding:5px;
text-align:left;
opacity:0.8;
position:absolute;
bottom:0;
width:100%;
height:100%;
font-weight:bold;
overflow:auto;
box-sizing: border-box;
`,
autoScroll: true
};
log(...args)
通用日志,类似于console.log
info(...args)
信息类型,类似于console.info
warn(...args)
警告类型,类似于console.warn
error(...args)
错误类型,类似于console.error
clear()
清空日志destroy()
销毁
Heatmap
since@1.1.4
热力图
直接使用 Heatmap
import Heatmap from 'ray-visutil/lib/heatmap';
const hm = new Heatmap({
mapSize: 256,
width: 12, // 宽度 单位米
height: 8, // 长度 单位米
minValue: 10,
maxValue: 25,
radius: 1, // 单个点的热力影响半径
transparent: false // 未插值区域是否透明(默认为 false )
});
// 更新热图数值
setInterval(() => {
// 数据格式为
// [x坐标,y坐标,热力值]
// 坐标系以热力图平面中心为原点
hm.randomData();
}, 1000);
// document.body.appendChild(hm.getCanvas());
document.body.appendChild(hm.canvas);
使用 BaseHeatmap
import { BaseHeatmap } from 'ray-visutil/lib/heatmap';
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
function randomData(){
return [
[-0.91, -1.07, randomFloat(24.0, 28.0)],
[-2.11, -1.07, randomFloat(24.0, 28.0)],
[0, 0, randomFloat(24.0, 28.0)],
[-2.71, -1.07, randomFloat(24.0, 28.0)],
[-0.86, 1.13, randomFloat(24.0, 28.0)],
[-0.31, -1.07, randomFloat(24.0, 28.0)],
[2, -2, randomFloat(24.0, 28.0)],
[3, 3, randomFloat(24.0, 28.0)]
];
}
class HM {
constructor(param) {
this._shm = new BaseHeatmap();
this._params = {};
this.initParams(param);
}
initParams(param = {}) {
const params = this._params;
const { width = 10, height = 10, mapSize = 256, transparent, gradient, minValue = 10, maxValue = 50, radius = 0.8, blur = 0.8, data = [] } = param;
params.areaWidth = width; // area width
params.areaHeight = height;
params.mapSize = mapSize;
params.transparent = !!transparent;
params.gradient = gradient || {
0.4: 'blue',
0.6: 'cyan',
0.7: 'lime',
0.8: 'yellow',
1.0: 'red'
};
params.minValue = minValue;
params.maxValue = maxValue;
params.radius = radius;
params.blur = blur;
params.data = data;
this._refresh();
}
setData(data) {
this._params.data = data;
this._refresh();
}
_refresh() {
const params = this._params;
let rateX = 1;
let rateY = 1;
let rateRadius = params.mapSize / params.areaWidth;
if (params.areaWidth !== params.areaHeight) {
if (params.areaWidth > params.areaHeight) {
rateX = 1;
rateY = params.areaHeight / params.areaWidth;
} else {
rateX = params.areaWidth / params.areaHeight;
rateY = 1;
rateRadius = params.mapSize / params.areaHeight;
}
}
let scaler = 1;
if (params.radius * rateRadius < 1) {
scaler = (1 / params.radius) * rateRadius + 0.0001;
}
const data = params.data.map(item => {
return [item[0] * scaler, item[1] * scaler, item[2]];
});
this._shm
.setCanvasSize(Math.floor(params.mapSize * rateX), Math.floor(params.mapSize * rateY))
.setAreaSize(params.areaWidth * scaler, params.areaHeight * scaler)
.setMin(params.minValue)
.setMax(params.maxValue)
.setTransparent(params.transparent)
.setGradient(params.gradient)
.setRadius(Math.floor(params.radius * rateRadius * scaler), Math.floor(params.blur * rateRadius * scaler))
.data(data)
.draw();
}
get canvas(){
return this._shm.getCanvas();
}
}
// 创建热图
const heatMap01 = new HM({
mapSize: 512,
width: 12, // 宽度 单位米
height: 8, // 长度 单位米
minValue: 10,
maxValue: 25,
radius: 1, // 单个点的热力影响半径
transparent: false // 未插值区域是否透明(默认为 false )
});
// 更新热图数值
setInterval(() => {
const data = randomData();
heatMap01.setData(data);
}, 1000);
document.body.appendChild(hm.canvas);
UndoPool
前进/后退 池
直接使用:
const undoPool = new UndoPool();
undoPool.add({
undo() {
// ...
},
redo() {
// ...
}
});
使用案例
const undoPool = new UndoPool();
const people = {};
function addPerson(id, name) {
people[id] = name;
};
function removePerson(id) {
delete people[id];
};
function createPerson(id, name) {
// first creation
addPerson(id, name);
// make undo-able
undoPool.add({
undo() {
removePerson(id)
},
redo() {
addPerson(id, name);
}
});
}
createPerson(101, "John");
createPerson(102, "Mary");
console.log("people", people); // {101: "John", 102: "Mary"}
undoPool.undo();
console.log("people", people); // {101: "John"}
undoPool.undo();
console.log("people", people); // {}
undoPool.redo();
console.log("people", people); // {101: "John"}
UndoPool methods
new UndoPool($limit?: Number, cb?: Function)
: create undopool.undoPool.undo();
: Performs the undo action.undoPool.redo();
: Performs the redo action.undoPool.clear();
: Clears all stored states.undoPool.setLimit(limit);
: Set the maximum number of undo steps. Default: 0 (unlimited).var hasUndo = undoPool.hasUndo();
: Tests if any undo actions exist.var hasRedo = undoPool.hasRedo();
: Tests if any redo actions exist.undoPool.setCallback(myCallback);
: Get notified on changes.var index = undoPool.getIndex();
: Returns the index of the actions list.
toImage
imageUtils
toSvg(node, options)
toPng(node, options)
toJpeg(node, options)
toBlob(node, options)
toPixelData(node, options)
options:
- filter: func 过滤node节点,如
(node) => node.tagName !== 'i'
- bgcolor: background color, valid CSS color value.
- height, width: 设置 node 的宽高
- twidth, theight: 设置裁剪后自定义宽高
- style: 设置 node 样式
- quality: JPEG 时,设置 quality, 0 and 1,(e.g. 0.92 => 92%)
- cacheBust: 启用缓存
- imagePlaceholder: 获取图像失败时的默认url
changelog
2021-12-22 v1.3.7
修复
imageUtils
无法截取 svg use symbol 问题2021-5-7 v1.3.1
add
toos#utf8ArrayToStr
2021-2-23 v1.2.0
add
memorize
andmemoFastStringify
2021-2-23 v1.1.15
add
UndoPoll
ts support andUndoPool($limit?: Number, cb?: Function)
constructor2020-8-12 v1.1.12
add
imageUtils
2020-7-7 v1.1.10
add
QRTypes
2020-7-6 v1.1.9
add
VisCache
2020-5-25 v1.1.8
add
fastStringify
2020-5-9 v1.1.7
add
uuid、shortid
2020-5-8 v1.1.6
add
UndoPool
2020-4-27 v1.1.5
modify
tools
2019-12-31 v1.1.3
add screen log
2019-12-9 v1.1.2
add imageProcessor
2018-12-19 v1.0.9
add tools
2018-12-18 v1.0.8
add Transition
License
MIT