UNPKG

c2

Version:

d3 component canvas

714 lines (651 loc) 24.6 kB
const d3 = global.d3 || require("d3"); const Types = require('./Types'), float = Types.float, int = Types.int; module.exports = function (selection) { return new c2_Animate(selection); }; module.exports.remove = function (item) { const transition = item._c2_transition; var p,tweens,tween_group,i,ln; for (p in transition) { tweens = transition[p]; tween_group = tweens.tween_group; //TODO if tweens onEnd/remove - remove tweens from eventGroup if (tweens.end_index !== -1) { tweens.end_group[tweens.end_index] =0; } for (i=0,ln=tweens.length;i<ln;i++) { tween_group[tweens[i]] = 0; } if (tween_group.cnt !== 0 && (tween_group.cnt -= ln) <= 0 ) { tween_group.cnt=tween_group.length=0; } } item._c2_transition = false; return module.exports; } //default d3 ease,delay,duration function const invalidate = require('./Invalidate'), DEFAULT_EASE = function easeCubicInOut(t) { return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; }, DEFAULT_DELAY = 0, DEFAULT_DURATION = 250, isChrome = typeof navigator !== 'undefined' && /Chrome/.test(navigator.userAgent); //var animateIds=1; function c2_Animate (name) { function animation (selection) { var compiled; if (animation._to && (compiled = animation._compile(selection))) { //animation._id = Object.keys(animation._to).map() animation.tween('',compiled); } //if (animation._to && (compiled = animation._compile(selection))) { //animation.tween('',compiled); //} pending[pending_cnt++] = { 'selection' : selection, 'animation' : animation }; !c2_timer_running && (c2_timer_running = true,invalidate.nextCalculate(start_c2_timer)); //!c2_timer_running && (c2_timer_running = true,setTimeout(start_c2_timer)); //!c2_timer_running && (c2_timer_running = true,requestAnimationFrame(start_c2_timer)); } //animation._id = animateIds++; animation._name = name || ''; animation._ease = DEFAULT_EASE; animation._delay = DEFAULT_DELAY; animation._duration = DEFAULT_DURATION; animation._tween_map={}; //animation._to = {}; animation._compiled = {}; animation._compiled_fn = undefined; //TODO do we need from? //animation._from = {}; //TODO to should allow chaining animation._tweens = []; animation._compile = c2_compile; animation.duration = c2_duration; animation.delay = c2_delay; animation.ease = c2_ease; animation.tween = c2_tween; animation._remove = false; //animation.from = c2_from; animation.to = c2_to; animation.on = c2_on; animation.remove = c2_remove; //TODO for chaining //animation.animate; //TODO events + iteration //animation.each //this._selection = selection; //console.error(animation); return animation; } function c2_duration (duration) { if (arguments.length) { this._duration = duration; return this; } return this._duration; } function c2_delay (delay) { if (arguments.length) { this._delay = delay; return this; } return this._delay; } function c2_ease (ease) { if (arguments.length) { this._ease = ease; return this; } return this._ease; } function c2_remove () { this._remove = true; return this; } function c2_on (name,fn) { //i think we can just hook into event system by passing an additional tween that only does something //when t === 1, the only issue i see is that this woudl exist for all nodes -- if we use id like d3.transition, //because each node needs a delay and duration - which means every node could be removed at a different time if (name === 'end') { !this._on_end && (this._on_end = [fn]) || this._on_end.push(fn); } else if (name === 'start') { !this._on_start && (this._on_start = [fn]) || this._on_start.push(fn); } return this; } //further optimize by compiling animations per prototype function c2_compile_proto (item,id,keys,values) { if (item.constructor._compiled && item.constructor._compiled[id]) { return this._compiled[id]; } var vars=['var me=this'],vars2=[],interpolators=[],tween=[],compiled,key,value,attr,is_num,s,result, s_inner; if (!(compiled=item.constructor._compiled)) { compiled = item.constructor._compiled = {}; } for (var i=0,ln=keys.length;i<ln;i++) { key = keys[i]; value = values[i]; attr = item.constructor._attributes[key]; is_num = attr === Types.float || attr === Types.int; vars.push( `v${i}=${typeof value === 'function' ? attr.animateValue`to["${key}"].call(this,d,i)` : attr.animateValue`${value}`}`, //`v${cnt}=typeof to["${key}"] === function ? ${attr.animateValue`to["${key}"].call(this,d,i)`} : ${attr.animateValue`to["${key}"]`}`, `s${i}=${attr.animateValue`this["${key}"]`}` ); vars2.push(`v${i}`,`s${i}`); interpolators.push( is_num ? `s${i}=s${i}||0;v${i}-=s${i};` : `v${i}=${attr.animateValue`d3.interpolate(s${i},v${i});`}` ); tween.push( is_num ? `n=(s${i}+v${i}*t);` : `n=v${i}(t);`, //is_num ? `me["${key}"]=s${i}+v${i}*t;` : `me["${key}"]=${attr.animateValue`v${i}(t)`}` `n!==me["${key}"] && (me["${key}"]=${attr.animateValue`n`},!c && (c=true));` ); } s_inner = `var n,c=false; ${tween.join('')}; c && ((me.children && me._not_invalid_) || (me.parentNode && me.parentNode._not_invalid_)) && me.invalidate();`; if (isChrome) { //s = new Function('t','me',...vars2,s_inner); s = (0,eval)(`(function () { return function (t,me,${vars2.join(',')}) { ${s_inner}; } })`)(); } //problem we are seeing is the amount of garbage accrued in high volume due to scoped functions //result = (0,eval)(`(function (${isChrome && 's,' || '' }d3) { //result = (eval)(`(function (s) { //result = new Function('s',` result = (0,eval)(`(function (s) { return function (to,d,i) { ${vars.join(',')}; ${interpolators.join('')} return function (t) { ${isChrome && `s(t,me,${vars2.join(',')})` || s_inner}; }; } })`)(s) //result.s = s; //window.values = values; //window.s = s; //compiled[id] = result; //result = isChrome && result(s,d3) || result(d3); //result.values = values; //console.error(result); return (compiled[id] = result); } function c2_compile () { const to = this._to,keys = Object.keys(to), values = Object.values(to); var id = keys.length ? `${keys[0]}=${typeof values[0] === 'function' && '@' || values[0]}` : ''; for (var i=1,ln=keys.length;i<ln;i++) { id += `&${keys[i]}=${typeof values[i] === 'function' && '@' || values[i]}` } return function (d,i) { var fn; if (this.constructor._compiled && this.constructor._compiled[id]) { fn = this.constructor._compiled[id]; return this.constructor._compiled[id].call(this,to,d,i); } else { return c2_compile_proto(this,id,keys,values).call(this,to,d,i); } return fn.call(this,to,d,i); }; } //TODO we need to separate the on_end and remove logic from compile and either put it into tween or hook it directly into //the scheduler function c2_compile3 () { var p,to=this._to,result='(function (to,s,d3) {return function (d,i) {', vars = ['var me=this'], vars2 = [], interpolators = [], tween = [], compiled = this._compiled, not_same = false, cnt=0; if (compiled) { for (p in to) { if (compiled[p] !== to[p]) { not_same = true; break; } } if (not_same === false) { return this._compiled_fn; } } compiled = this._compiled = {}; //right now tween is a function -- we want to try the following //[ln,property,v,t,s,instance,] //instance comes from parent -- shoudl be uneeded for (p in to) { compiled[p] = to[p]; vars.push( 'v'+(cnt)+'='+ (typeof to[p] === 'function' && 'to["'+p+'"].call(this,d,i)'||'to["'+p+'"]'), 't'+(cnt)+'=typeof v'+cnt+' === "number"', 's'+cnt+'=this["'+p+'"]' ); vars2.push('v'+cnt,'t'+cnt,'s'+cnt); interpolators.push( 'if (t'+cnt+') {s'+cnt+'=s'+cnt+'||0;v'+cnt+'-=s'+cnt+';} else {v'+cnt+'=d3.interpolate(s'+cnt+',v'+cnt+');}' ); //tween.push ('if (t'+cnt+') me["'+p+'"]=s'+cnt+'+v'+cnt+'*t; else me["'+p+'"]=v'+cnt+'(t);'); tween.push ('if (t'+cnt+') n=s'+cnt+'+v'+cnt+'*t; else n=v'+cnt+'(t); n!==me["'+p+'"] && (me["'+p+'"]=n,!c && (c=true));'); cnt++; } var s = (0,eval)(` (function (t,me,d3,${vars2.join(',')}) {var n,c=false; ${tween.join('')}; c && ((me.children && me._not_invalid_) || (me.parentNode && me.parentNode._not_invalid_)) && me.invalidate(); }) `); /* *s2 = (0,eval)(`(function (t) { * s(t,this,${vars2.join(',')}); *})`) */ //shared2 = function (t,); result += vars.join(',') + ';'; result += interpolators.join(''); result += isChrome ? `return function (t) {s(t,me,d3,${vars2.join(',')})};` : `return function (t) {var n,c=false; ${tween.join('')}; c && ((me.children && me._not_invalid_) || (me.parentNode && me.parentNode._not_invalid_)) && me.invalidate(); }` //result += 'return function (t) {'; //vars2.join(',') + ';'; //result += tween.join(''); //result += '(me.children && me._not_invalid_) || (me.parentNode && me.parentNode._not_invalid_) && me._invalidate();' //result += 'm._not_invalid_&&m.parentNode &&m._invalidate();'; //result += 'm._invalidate();'; //result += '}'; //result += `return shared.bind(me,t,me,${vars2.join(',')})` //result += 'return function () {}' result += '}})'; return this._compiled_fn = (0,eval)(result)(to,s,c2.d3); } function c2_compile2 () { var p,v, to = this._to,tod=[Object.keys(to).length*4,'this']; for (p in to) { v = to[p]; tod.push( //t '(v=' + (typeof v === 'function' && `to["${p}"].call(this,d,i)` || `to["${p}"]`) + `,t=(typeof v === 'number'))`, //s `s = this["${p}"]`, //v `t?(v-=s):d3.interpolate(s,v)`, //t `"${p}"` ); } return this._compiled_fn = eval(`(function (d,i) { var s,v,t; return [${tod.join(',\n')}]; })`) } function c2_to (name,value) { if (!this._to) { this._to = {}; } if (arguments.length > 1) { this._to[name]=value; } else if (typeof name === 'object') { var p; for (p in name) { this._to[p] = name[p]; } } return this; } //TODO animation chaining function c2_animate_Animate () { //var animate = c2_Animate(); //this.on('end',function () { //add ended nodes //}) } //probably do some variation of to2 in each statement (groups of 1ish) function c2_to2 (name,value) { if (arguments.length > 1) { if (typeof value === 'function') { return this.tween('attr.'+name,function (d,i,g) { var me = this, v = value.call(this,d,i,g), s = this[name]; if (typeof v === 'number') { s = s || 0; v -= s; return function (t) { var m = me; m[name] = s+v*t; m._invalid_ === false && m.invalidate(); }; } else { v = d3.interpolate(s,v); return function (t) { var m = me; m[name] = v(t); m._invalid_ === false && m.invalidate(); }; } }); } else { return this.tween('attr.'+ name,function () { var me = this, v = value, s = this[name]; if (typeof v === 'number' && typeof s === 'number') { return function (t) { var m = me; m[name] = s+v*t; m._invalid_ === false && m.invalidate(); }; } else { v = d3.interpolate(s,v); return function (t) { var m = me; m[name] = v(t); m._invalid_ === false && m.invalidate(); }; } }); } } else { if (typeof name === 'object') { var p; for (p in name) { this.to(p,name[p]); } return this; } return this.tween(name); } } function c2_tween (name,tween) { var index; if (arguments.length > 1) { if (index = this._tween_map[name]) { this._tweens[index] = tween; } else { this._tween_map[name] = this._tweens.push(tween) - 1; } return this; } else { return this._tweens[this._tween_map[name]] || null; } } var start_durations=[], available_start_indices=[], available_invalid = true, ordered_available = [], ordered_available_cnt=0, ordered_available_start=0, start_duration_end_index=-1, start_duration_start_index=0, ease_groups = [], pending=[], pending_cnt = 0, start_duration_map = {}, c2_timer_running = false; function add_pending (date) { var i,ln,j,jln,k,kln,m,mln,groups,group,item,tween_group,tweens, delay,duration,ease,_delay,_duration,delay_is_fn,duration_is_fn, _ease_groups = ease_groups, _pending = pending, _tweens,_name, _remove, _end, ease_group, names, endGroup, start_duration_key, index, stamp = date, animation, bundle, selection; if (start_duration_end_index === -1) { if (available_invalid) { available_invalid = false; ordered_available_start=ordered_available_cnt=0; } } else if (available_invalid) { available_invalid = false; if (start_duration_end_index===-1) { ordered_available_start=ordered_available_cnt=0; } else { ordered_available_start=ordered_available_cnt=0; for (i=start_duration_start_index/2,ln=(start_duration_end_index+1)/2;i<ln;i++) { if (available_start_indices[i]) { ordered_available[ordered_available_cnt++]=i*2; } } } } for (i=0,ln=pending_cnt;i<ln;i++) { bundle = _pending[i]; animation = bundle.animation; selection = bundle.selection; selection._started = true; groups = selection._groups; _delay = animation._delay; _duration = animation._duration; _tweens = animation._tweens; _name = animation._name; _remove = animation._remove; _end = animation._on_end; ease = animation._ease; delay_is_fn = typeof _delay === 'function'; duration_is_fn = typeof _duration === 'function'; for (j=0,jln=groups.length;j<jln;j++) { group = groups[j]; for (k=0,kln=group.length;k<kln;k++) { if (item = group[k]) { if (names = item._c2_transition) { if (tweens = names[_name]) { tween_group = tweens.tween_group; //TODO if tweens onEnd/remove - remove tweens from eventGroup if (tweens.end_index !== -1) { tweens.end_group[tweens.end_index]=0; } for (m=0,mln=tweens.length;m<mln;m++) { tween_group[tweens[m]] = 0; } if (tween_group.cnt !== 0 && (tween_group.cnt -= mln) <= 0 ) { tween_group.cnt=tween_group.length=0; } } } if (delay_is_fn) { delay = stamp+ _delay.call(item,item.__data__,k,group); } else { delay = stamp+_delay; } if (duration_is_fn) { duration = _duration.call(item,item.__data__,k,group); } else { duration = _duration; } start_duration_key = delay + '-' + duration; if ( !(ease_group = start_duration_map[start_duration_key])) { if (ordered_available_cnt) { index = ordered_available[ordered_available_start++]; ordered_available_cnt--; available_start_indices[index/2]=0; } else if (start_duration_start_index > 0) { index = start_duration_start_index-=2; available_start_indices[index/2]=0; } else { index = (start_duration_end_index+=2)-1; available_start_indices[index/2]=0; } //undefined is for the event group, which may or may not exist ease_group = _ease_groups[index/2] = start_duration_map[start_duration_key] = [undefined,ease,tween_group=[]]; tween_group.cnt =0; start_durations[index] = delay; start_durations[++index] = duration; //if (index > start_duration_end_index) { //start_duration_end_index = index; //} } else { index = ease_group.indexOf(ease); if (index !== -1) { tween_group = ease_group[index+1]; } else { ease_group.push(ease,tween_group=[]); tween_group.cnt=0; } } names = item._c2_transition; if (!names) { names = item._c2_transition = {}; } tweens = names[_name] = new Array(_tweens.length); tweens.tween_group = tween_group; tweens.node = item; if (_remove || _end) { tweens.remove = _remove; tweens.end = _end; tweens.index = k; if (!(endGroup = ease_group[0])) { tweens.end_group = ease_group[0] = [tweens]; tweens.end_index = 0; } else { tweens.end_index = endGroup.push(tweens)-1; tweens.end_group = endGroup; } } else { tweens.end_index=-1; } //TODO -- //} for (m=0,mln=_tweens.length;m<mln;m++) { tweens[m] = tween_group.push(_tweens[m].call(item,item.__data__,k,group))-1; } tween_group.cnt += mln; } } } _pending[i] = undefined; } pending_cnt=0; //console.error(new Date()-stamp); } function start_c2_timer () { var i,ln,j,jln,group,g, k,kln,end, item, date = Date.now(), start = 0, t=0,e=0, ease_value, duration,tween, tweens, cleanup = 0, cleanup_start,cleanup_end, n=0; //sd=Date.now() //first check pending if ( pending_cnt) { //console.error('add'); add_pending(date); } //console.error(start_duration_start_index,start_duration_end_index); if (start_duration_end_index > 0) { for (i=start_duration_start_index,j=0,g=i/2,ln=start_duration_end_index;i<ln;g++,i=g<<1) { n=i+1; //,n=i+1 start = start_durations[i]; if (start !== -1 && date > start) { duration = start_durations[n]; if (duration > 0) { t = (date-start) / duration; (t >= 1) && (e = t = 1); if (group = ease_groups[g]) { for (j=1,jln=group.length;j<jln;j+=2) { tweens = group[j+1] if (kln=tweens.length) { ease_value = group[j](t); for (k=0,kln=tweens.length;k<kln;k++) { if (tween = tweens[k]) { tween(ease_value) } //(tween = tweens[k]) && tween(ease_value); } } } } } else { e=1; } if (e) { e=0; !available_invalid && (available_invalid = true); available_start_indices[g]=1; if (group=group[0]) { for (j=0,jln=group.length;j<jln;j++) { if (tweens=group[j]) { item = tweens.node; item._c2_transition = null; if (end=tweens.end) { for (k=0,kln=end.length;k<kln;k++) { end[k].call(item,item.__data__,tweens.index); } } if (tweens.remove) { item.parentNode && item.parentNode.removeChild(item); } } } } delete start_duration_map[start_durations[i]+'-'+start_durations[n]]; start_durations[i] = start_durations[n] = -1; ease_groups[g] = null; if (n === start_duration_end_index) { cleanup = cleanup_end = true; } if (i === start_duration_start_index) { cleanup = cleanup_start = true; } } } } if (cleanup) { if (cleanup_end) { for (i=start_duration_end_index-1;i>=start_duration_start_index;i-=2) { if (start_durations[i] !== -1) { start_duration_end_index = i+1; break; } } if (i<start_duration_start_index) { start_duration_end_index=-1;console.error('done')}; } //update start index if (cleanup_start) { for (i=start_duration_start_index;i<=start_duration_end_index;i+=2) { if (start_durations[i] !== -1) { //console.error('updated?',i); start_duration_start_index = i; break; } } if (i>start_duration_end_index || start_duration_start_index < 0) start_duration_start_index =0; } } } //console.error(new Date() - date); if (pending_cnt > 0 || start_duration_end_index > 0) { //requestAnimationFrame(start_c2_timer); invalidate.nextCalculate(start_c2_timer); } else { c2_timer_running = false; } }