import * as ECS from '../../../packages/ecs-js/index'
import {x} from '../../xapp/xworld'
import {XError} from '../../xutils/xcommon'
import XSys from '../../sys/xsys'
import {MorphingAnim} from '../../sys/tween/animizer'
import {CoordsGrid} from '../../xmath/chartgrid'
import {vec3} from '../../xmath/vec'
import {Obj3Type} from '../../component/obj3';
import {Sankey} from '../../component/ext/chart'
import {AssetType, ShaderFlag} from '../../component/visual';
/**
* Subsystem rendering 3d sankey chart
*
* @class XSankey
*/
export default class XSankey extends XSys {
/**
* create sankey objects
* @param {ECS} ecs
* @param {object} options
* options.chart: the json chart section defining chart grid space, {domain, range, grid, grid-space}
* @param {array} vectors the high dimensional vectors.<br>
* deprecated? XSankey assumes the last dimension as the y scale value as in
* original 2d sankey chart.
* @constructor XSankey
*/
constructor(ecs, options, json, vectors) {
super(ecs);
this.logged = false;
this.ecs = ecs;
this.cmds = {click: undefined};
/**extruding coordingate index
* @member XSankey#extrudingCoord
* @property {int} extrudingCoord - coordinate index
*/
this.extrudingCoord = -1;
/**Bars' pivoting (extruding) positions (grid index).
* @member XSankey#pivotings
* @property {array<array>} pivotings - major index: vector index;<br>
* minor index: coord index
*/
this.pivotings = undefined;
/**Bars' pivoting (extruding) positions (grid index).
* @member XSankey#barmap
* @property barmap */
this.barmap = undefined;
/**vectors
* @member XSankey#vectors
* @property vectors */
this.vectors = vectors;
ecs.registerComponent('Sankey', Sankey);
if (!json)
throw new XError('XSankey can only been created synchronously with json data for initializing');
if (ecs) {
// debug1(ecs, options, json, vectors);
if (!x.chart || !x.chart.grid) {
this.grid = new CoordsGrid(options.chart, json);
x.chart = Object.assign(x.chart || new Object(), {grid: this.grid});
}
else this.grid = x.chart.grid;
var {barmap, defs} = XSankey.sankey(ecs, this.grid, json, options);
this.barmap = barmap;
if (json.pivotings)
this.setPivoting(json.extruding.coord, json.extruding.pivoting);
}
// else testing?
this.camera = x.xcam.XCamera.cam;
}
/**Setup sankey chart with data.
* @param {ECS} ecs
* @param {CoordsGrid} grid
* @param {object} pivotings<br>
* @param {object} json where<br>
* json.grid-space<br>
* json.coordss: array of {label, range}, where range is the discret value serial.<br>
* json.vectors: array of hi-dimensional vectors, with last dimesion as value.
* @param {object} options<br>
* texture: string, bar texture, e.g. './tex/byr0.png', default is ram texture<br>
* geom: Obj3Type<br> otpinal, default Cylinder<br>
* box: geometry parameters, [radiusTop, radiusBottom, height(ignored), radialSegments = 4]
* @return {object} {barmap, defs} where <br>
* barmap: map of [coord-index => [bar-entity]]<br>
* defs: array of entity definition of sankey bars
*
* @member XSankey.sankey
* @function
*/
static sankey( ecs, grid, json, options ) {
var geom = options.geom === undefined ?
Obj3Type.Cylinder : options.geom;
var asset = json.texture || options.texture;
var vectors = json.vectors;
var scl = json["grid-space"] || 10;
var ixVal = json.coordinates.length;
var defs = [];
var barmap = new Array(json.bars.length);
if (Array.isArray(json.geometry))
// handle inconvenient of json file - turn string like 'Math.PI' into number
for (var bx = 0; bx < json.geometry.length; bx++) {
if (typeof json.geometry[bx] === 'string')
json.geometry[bx] = eval(json.geometry[bx]);
}
for (var vidx = 0; vidx < json.bars.length; vidx++) {
var vect = json.bars[vidx];
barmap[vidx] = new Array(vect.length);
for (var bidx = 0; bidx < vect.length; bidx++) {
// bar = [ coord-idx, enum-val, h, y0 ], e.g. [0, 1, 6, 15]
var bar = vect[bidx];
// init pos: x, y, z=0
var {pos0, h} = grid.coordPos( bar );
// radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength
var box = Array.from(json.geometry);
if (!box)
box = [grid.space(0.1), grid.space(0.1), h, 4];
else {
box[0] = grid.space(box[0]);
box[1] = grid.space(box[1]);
box[2] = h;
}
var animSeqs = [];
animSeqs.push( [{
mtype: xv.XComponent.AnimType.U_ALPHA,
paras: {
start: Infinity,
duration: 0.3,
alpha: [0.3, 0.9] }
}] );
animSeqs.push( [{
mtype: xv.XComponent.AnimType.POSITION,
paras: { // start with some animation?
start: 0,
duration: 0.8,
// set tween object for extruding animation
translate: [ [0, 0, 0], [0, 0, 0] ] }
}] );
animSeqs.push( [{
mtype: xv.XComponent.AnimType.POSITION,
paras: {
start: Infinity,
duration: 0.7,
translate: [ [0, 0, 0], [0, 0, 0] ] }
}] );
var bar = ecs.createEntity({
id: skUId(),
Obj3: { geom,
// transform: [{translate: [-1 * scl * 4, y11, 0]}],
transform: [{ translate: pos0 }],
box },
Visual:{vtype: AssetType.mesh,
asset},
Sankey:{vecIx: vidx,
coordIx: bidx,
translated: [0, 0, 0],
onOver: 0, // tweens[0], blinking
onClick:[1, 2] }, // forth & back
GpuPickable: {},
ModelSeqs: { script: animSeqs },
CmpTweens: {}
});
barmap[vidx][bidx] = bar;
}
}
return {barmap, defs};
}
/**
* @param {int} tick
* @param {array<Entity>} entites
* @member XSankey#update
* @function
*/
update(tick, entities) {
if ( x.xview.flag < 0 ) {
return;
}
this.cmds.click = false;
for ( var cmd of x.xview.cmds ) {
if ( cmd.code === 'mouse' && cmd.cmd === 'mouseup' )
this.cmds.click = true;
break;
}
if ( !this.cmds.click )
return;
var e = x.xview.picked;
if (e && e.GpuPickable && e.GpuPickable.picked
&& e.Sankey)
this.extrudextr(e, entities);
}
/** Kept until delete branch temp-sankey-debug1 */
onMouse(cmd, e) {
if (e.CmpTweens) {
var twCmd;
switch (cmd) {
case 'mousemove':
twCmd = e.Sankey.onOver;
return true;
case 'click':
case 'mouseup':
twCmd = e.Sankey.onClick;
if (twCmd !== undefined)
sankeyClick(e, twCmd);
return true;
default:
}
}
else {
if (!this.logged) {
console.error('XSankey.onMouse(): No such tween. eid: ', e.id);
this.logged = true;
}
}
return false;
}
/**Extrude / de-extrude the selected coordinates.
*
* **Note:** In x-visual 1.0, all sankey bars can only move back and forth,
* without moving elsewhere, e.g. from z = 1 to z = 2.
*
* TODO ignore new translate when tweening
* @param {Sankey} e the selected entity
* @member XSankey.extrudextr
* @function
*/
extrudextr(e, entities) {
if ( this.extrudingCoord >= 0 ) {
// de-extruding
// var vix = this.pivotings[this.extrudingCoord];
// var extrudeds = this.barmap[this.extrudingCoord];
for (const en of entities) {
// if ( en.Sankey && en.Sankey.pivotIx[2] > 0 )
if ( en.Sankey && !vec3.eq(en.Sankey.translated, [0, 0, 0]) )
en.CmpTweens.startCmds.push(2);
}
// TODO if not now, but when ?
this.extrudingCoord = -1;
}
else {
// extrude = this.pinvotings[this.extrudingCoord]
this.getPivotings(e.Sankey, (sys, extrude) => {
// var c = extrude.coordIx;
sys.extrudingCoord = extrude.coord;
var p = extrude.pivoting;
for (const e of entities) {
var sk = e.Sankey;
// some bars may not extrudable
if (sk.vecIx < p.length && sk.coordIx < p[sk.vecIx].length) {
var grdx = p[sk.vecIx][sk.coordIx];
var bufArr = e.Sankey.translated;
var val = sys.vectors[sk.vecIx][sys.vectors[sk.vecIx].length - 1];
sys.grid.extrudePos( val, grdx, bufArr );
MorphingAnim.set1stPos( e.CmpTweens.tweens[1], bufArr );
vec3.mulArr( bufArr, -1, bufArr );
MorphingAnim.set1stPos( e.CmpTweens.tweens[2], bufArr );
e.CmpTweens.startCmds.push(1);
}
}
});
}
}
setPivoting(extruding) {
// TODO load online data
if (!this.pivotings)
this.pivotings = new Map();
this.pivotings.set(extruding.coord, extruding);
}
getPivotings( sankey, onload ) {
// TODO manage buffer
if ( this.pivotings && this.pivotings.has(sankey.coordIx) )
onload( this, this.pivotings.get(sankey.coordIx) );
}
}
XSankey.query = {
any: ['Sankey']
};
function sankeyClick(e, twCmd) {
if (e.CmpTweens !== undefined) {
if (Array.isArray(twCmd)) {
if (!e.Sankey.cmd)
e.Sankey.cmd = {};
if (e.Sankey.cmd.click === twCmd[0])
e.Sankey.cmd.click = twCmd[1];
else
e.Sankey.cmd.click = twCmd[0];
}
e.CmpTweens.startCmds.push(e.Sankey.cmd.click);
}
}
/**Create some sankey bars.
* Should been kept. works together with update() in branch temp-sankey-debug1
* @memberof XSankey
*/
function debug1(ecs, options, json, vectors) {
var scl = options.xscale || 20;
var ixVal = json.coordinates.length;
var h11 = vectors[0][ixVal] * scl;
var h10 = vectors[1][ixVal] * scl;
var y11 = h10 + h11/2;
var y10 = h10/2;
var z1 = scl * 8;
var x1 = scl;
var n11 = ecs.createEntity({
id: 'n11',
Obj3: { geom: Obj3Type.Cylinder,
transform: [{translate: [-1 * scl * 4, y11, 0]}],
// radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength
box: [10, 10, h11] },
Visual:{vtype: AssetType.mesh,
asset: '../../assets/tex/byr0.png'},
Sankey:{
onOver: 0, // tweens[0], blinking
onClick:[1, 2] // forth & back
},
GpuPickable: {pickid: 4},
ModelSeqs: {script: [
[{ mtype: xv.XComponent.AnimType.U_ALPHA,
paras: {
start: Infinity,
duration: 0.3,
alpha: [0.3, 0.9]
} }],
[{ mtype: xv.XComponent.AnimType.POSITION,
paras: {
start: Infinity,
duration: 1.1,
translate: [[0., 0, 0.], [0, -h10, z1]]
} }],
[{ mtype: xv.XComponent.AnimType.POSITION,
paras: {
start: Infinity,
duration: 1.2,
translate: [[0, 0, 0], [0, h10, -z1]],
} }],
]},
CmpTweens: {}
});
var n10 = ecs.createEntity({
id: 'n10',
Obj3: { geom: Obj3Type.Cylinder,
transform: [{translate: [-1 * scl * 4, y10, 0]}],
box: [10, 10, h10] },
Visual:{vtype: AssetType.mesh,
},
GpuPickable: {pickid: 3},
Sankey:{onOver: 0},
ModelSeqs: {script: [
[{ mtype: xv.XComponent.AnimType.U_ALPHA,
paras: {
start: Infinity,
duration: 1.2,
alpha: [0.3, 0.9]
}}],
]},
CmpTweens: {}
});
var h01 = vectors[2][ixVal] * scl;
var h00 = vectors[3][ixVal] * scl;
var y01 = h01/2 + h00;
var y00 = h00/2;
var n01 = ecs.createEntity({
id: 'n01',
Obj3: { geom: Obj3Type.Cylinder,
transform: [{translate: [0, y01, 0]}],
box: [10, 10, h01] },
Visual:{vtype: AssetType.mesh,
asset: '../../assets/tex/byr0.png'},
Sankey:{
onOver: 0, // tweens[0], alpha
onClick:[1, 2] // forth & back
},
GpuPickable: {pickid: 2},
ModelSeqs: {script: [
[{ mtype: xv.XComponent.AnimType.U_ALPHA,
paras: {
start: Infinity,
duration: 0.32,
alpha: [0.3, 0.9]
} }],
[{ mtype: xv.XComponent.AnimType.POSITION,
paras: {
start: Infinity,
duration: 1.12, // seconds
// translate: [[0, 0, 0.], [0, -y01 + h01/2, z1]],
translate: [[0, 0, 0.], [0, -h00, z1]],
}}],
[{ mtype: xv.XComponent.AnimType.POSITION,
paras: {
start: Infinity,
duration: 1.22,
translate: [[0, 0, 0], [0, h00, -z1]],
}}],
]},
CmpTweens: {}
});
var n00 = ecs.createEntity({
id: 'n00',
Obj3: { geom: Obj3Type.Cylinder,
transform: [{translate: [0, y00, 0]}],
box: [10, 10, h00, 20] },
Visual:{vtype: AssetType.mesh,
},
GpuPickable: {pickid: 1},
Sankey:{onOver: 0},
ModelSeqs: {script: [
[{ mtype: xv.XComponent.AnimType.U_ALPHA,
paras: {
start: Infinity,
duration: 1.23,
alpha: [0.3, 0.9]
}}],
]},
CmpTweens: {}
});
}
/**For generating sankey element uuid.
* @memberof XSankey
*/
var skuuid = 0;
/**Get a uuid.
* @memberof XSankey */
function skUId() {
return `sk-${++skuuid}`;
}