Added way more modularity at the cost of non-negligible code complexity (still need a big clean up)

megaRefact
Sami Mokaddem 2018-09-19 14:56:43 +00:00
parent f5263e33df
commit edb1bee303
2 changed files with 384 additions and 95 deletions

View File

@ -9,28 +9,81 @@
(function($) {
'use strict';
var ProxyMapper = function(mapping, data, options) {
var ProxyMapper = function(mapping, constructionInstruction, data, options) {
var that = this;
this.itemsToBeMapped = Object.keys(constructionInstruction);
this.constructionInstruction = constructionInstruction;
this.mapping = mapping;
//this.mapping = {
// dates: ["l", 0],
// index: {
// '@labels': [0],
// '@dates': [0]
// },
// //labels: ["l", 1, "l", 0],
// labels: [],
// //values: ["@dates,l", "{1}", "@labels,l", "{1}"]
// values: ["@dates,l", "{1}", "@labels"]
//}
this.data = data;
this.result = {};
var funcs = {};
this.fillKey;
this.labelKey = [];
this.valueKey;
this.itemsToBeMapped.forEach(function(item) {
funcs[item] = new Function('value', 'datum', 'return value');
let ci = constructionInstruction[item];
if (ci.strategy !== undefined && ci.strategy == 'date') { // only 1 key can be a fill key
that.fillKey = item;
}
else if (ci.strategy !== undefined && ci.strategy == 'label') { // multiple keys can be label key
that.labelKey.push(item);
}
else if (ci.strategy !== undefined && ci.strategy == 'value') { // only 1 key can be value key
that.valueKey = item;
}
// init default type for wanted key
let s = ci.instructions.split('.');
if (s.length >= 2 && s[0] === '' && s[1] !== '') {
let p_res, p_key;
let c_res = that.result;
s.slice(1).forEach(function(k) {
if (k[0] === '@') {
return false;
}
c_res[k] = {};
p_res = c_res;
p_key = k;
c_res = c_res[k];
});
if (p_key !== undefined) {
p_res[p_key] = [];
}
}
});
this._default_options = {
fillValue: 0,
functions: {
dates: function (value, datum) {return value;},
labels: function (value, datum) {return value;},
values: function (value, datum) {return value;}
},
prefillData: {
dates: [],
labels: []
},
functions: funcs,
datum: false // the tree data to walk in parallel
};
this.options = $.extend({}, this._default_options, options);
this.result = {};
this.result.dates = [];
this.mappingI2 = {};
this.mappingToIndexes = {};
this.itemsToBeMapped.forEach(function(item) {
that.mappingToIndexes[item] = {};
});
this.perform_mapping();
return this.result;
};
@ -39,49 +92,95 @@
constructor: ProxyMapper,
perform_mapping: function(data) {
if (this.mapping.dates.length > 0) {
for (var x of this.options.prefillData.dates) { this.result['dates'].push(x); }
this.c_dates(this.data, this.mapping.dates); // probe and fetch all dates
var that = this;
var fk = this.fillKey;
var lk = this.labelKey;
var vk = this.valueKey;
/* fill key is processed first */
if (fk !== undefined && this.mapping[fk].length > 0) {
this.apply_strategy(fk);
}
// prepare fill array
let fillArray = [];
for (var i=0; i<this.result.dates.length; i++) {
if ((this.options.fillValue !== undefined && this.options.fillValue !== '')) {
fillArray.push(this.options.fillValue);
if (fk !== undefined) {
for (var i=0; i<this.result[fk].length; i++) {
if ((this.options.fillValue !== undefined && this.options.fillValue !== '')) {
fillArray.push(this.options.fillValue);
}
}
}
for (var x of this.options.prefillData.labels) {
this.result[x] = fillArray.slice(0);
this.i1_prefill = x;
}
if (this.mapping.labels.length > 0) {
this.c_labels(this.data, this.mapping.labels); // probe and fetch all labels
// inject prefill data
for (let keyname in this.options.prefillData) {
let p_data = this.options.prefillData[keyname];
let subkeys = {};
subkeys[keyname] = p_data;
this.addFromInstruction(keyname, fillArray.slice(0), subkeys, true);
//this.i1_prefill = x;
}
//if (this.mapping.labels.length > 0 && this.mapping.values.length > 0) {
if (Object.keys(this.result).length > 1 && this.mapping.values.length > 0) {
this.c_values(this.data, this.mapping.values); // fetch values and overwrite default values
for (var k in this.result) {
this.result[k] = this.result[k].filter(function(n){ return n != undefined });
// inject prefill data
//for (var x of this.options.prefillData.labels) {
// this.result[x] = fillArray.slice(0);
// this.i1_prefill = x;
//}
//if (this.mapping.labels.length > 0) {
// this.c_labels(this.data, this.mapping.labels); // probe and fetch all labels
//}
lk.forEach(function(labelK) {
if (that.mapping[labelK].length > 0) {
that.apply_strategy(labelK);
}
});
//if (Object.keys(this.result).length > 1 && this.mapping.values.length > 0) {
//if (Object.keys(that.result).length > 0 && that.mapping[vk].length > 0) {
if (Object.keys(that.result).length > 1 && that.mapping[vk].length > 0) {
that.apply_strategy(vk);
for (var k in that.result) { // filter out undefined value
let res = that.result[k];
if (res !== undefined && !that.isObject(res)) { // if object, picking is likely to be incoherent
let filtered = res.filter(function(n){ return n != undefined });
that.result[k] = filtered;
}
}
}
},
c_dates: function(intermediate, instructions) {
apply_strategy: function(keyname) {
let strategy = this.constructionInstruction[keyname].strategy;
if (strategy == 'date') {
this.c_dates(this.data, this.mapping[keyname], keyname); // probe and fetch all dates
} else if (strategy == 'label') {
this.c_labels(this.data, this.mapping[keyname], keyname); // probe and fetch all labels
} else if (strategy == 'value') {
this.c_values(this.data, this.mapping[keyname], keyname); // fetch values and overwrite default values
} else {
console.log('Invalid mapping strategy');
}
},
c_dates: function(intermediate, instructions, keyname) {
var that = this;
var matchingFun = function (intermediate, instructions, additionalData) {
let index = instructions;
let val = intermediate[index];
if (that.mappingI2[val] === undefined) {
that.mappingI2[val] = that.result['dates'].length;
let nval = that.options.functions.dates(val, additionalData.datum);
that.result['dates'].push(nval);
if (that.mappingToIndexes[keyname][val] === undefined) {
//that.mappingToIndexes[keyname][val] = that.result['dates'].length;
that.mappingToIndexes[keyname][val] = that.result[keyname].length;
//let nval = that.options.functions.dates(val, additionalData.datum);
let nval = that.options.functions[keyname](val, additionalData.datum);
that.addFromInstruction(keyname, nval, {}, false);
}
};
this.iter(intermediate, instructions, matchingFun, { datum: this.options.datum });
},
c_labels: function(intermediate, instructions, valuesLength) {
c_labels: function(intermediate, instructions, keyname) {
var that = this;
var matchingFun = function (intermediate, instructions, additionalData) {
let reg = /\{(\w+)\}/;
@ -90,7 +189,7 @@
instructions = res[1];
}
let index = instructions;
if (index == 'l') { // labels are the keys themself
if (index == 'l') { // labels are the keys themselves
for (let label in intermediate) {
let val = [];
for (var i=0; i<additionalData.valueLength; i++) {
@ -98,8 +197,11 @@
val.push(that.options.fillValue);
}
}
let nlabel = that.options.functions.labels(label, additionalData.datum);
that.result[nlabel] = val;
let nlabel = that.options.functions[keyname](label, additionalData.datum);
//that.result[nlabel] = val;
var subkeys = {};
subkeys[keyname] = nlabel;
that.addFromInstruction(keyname, val, subkeys, true);
}
} else {
let label = intermediate[index];
@ -109,14 +211,18 @@
val.push(that.options.fillValue);
}
}
let nlabel = that.options.functions.labels(label, additionalData.datum);
that.result[nlabel] = val;
let nlabel = that.options.functions[keyname](label, additionalData.datum);
var subkeys = {};
subkeys[keyname] = nlabel;
//that.result[nlabel] = val;
that.addFromInstruction(keyname, val, subkeys, true);
}
};
this.iter(intermediate, instructions, matchingFun, {valueLength: this.result.dates.length, datum: this.options.datum});
let valueLength = this.result[this.fillKey] !== undefined ? this.result[this.fillKey].length : 0;
this.iter(intermediate, instructions, matchingFun, {valueLength: valueLength, datum: this.options.datum});
},
c_values: function(intermediate, instructions) {
c_values: function(intermediate, instructions, keyname) {
var that = this;
var matchingFun = function (intermediate, instructions, additionalData) {
let val;
@ -131,13 +237,51 @@
let index = instructions;
val = intermediate[index];
}
let i1 = additionalData.i1;
i1 = i1 !== undefined ? i1 : that.i1_prefill;
let i2 = additionalData.i2;
let i2_adjusted = that.mappingI2[i2];
let ni1 = that.options.functions.labels(i1, additionalData.datum);
let nval = that.options.functions.values(val, additionalData.datum);
that.result[ni1][i2_adjusted] = nval;
let subkeyNames = that.get_subkeys_basename(keyname);
let subkeys = {};
let directValue = false;
subkeyNames.forEach(function(kn) {
// fetch index in array from the key
let v;
let kn_strip;
if (kn.substring(0, 2) === '@@') {
kn = kn.slice(1);
kn_strip = kn.slice(1);
v = additionalData[kn];
v = that.mappingToIndexes[kn_strip][v];
directValue = true;
} else {
kn_strip = kn.slice(1);
v = additionalData[kn];
v = v !== undefined ? v : instructions;
// apply transformation function, only for non-index
v = that.options.functions[kn_strip](v, additionalData.datum);
}
subkeys[kn_strip] = v;
});
//that.options.functions.labels(i1, additionalData.datum);
//that.options.functions.values(val, additionalData.datum);
//let i1 = additionalData.i1;
////i1 = i1 !== undefined ? i1 : that.i1_prefill;
//let i2 = additionalData.i2;
//// fetch index in array from the key
//let i2_adjusted = that.mappingI2[i2];
// fetch index in array from the key
// apply transformation function
//let ni1 = that.options.functions.labels(i1, additionalData.datum);
//let nval = that.options.functions.values(val, additionalData.datum);
//that.result[ni1][i2_adjusted] = nval;
//var subkeys = { labels: ni1, dates: i2_adjusted};
let nval = that.options.functions[keyname](val, additionalData.datum);
that.addFromInstruction(keyname, nval, subkeys, directValue);
};
this.iter(intermediate, instructions, matchingFun, {mapping: this.mapping, datum: this.options.datum});
},
@ -168,24 +312,30 @@
let tmp = new String(instructions[0]).split(',');
let record_inst = tmp[0]
let ind_inst = tmp.length == 2 ? tmp[1] : tmp[0];
switch (record_inst) {
case 'i1':
if (additionalData.mapping) {
flag_register_i = true;
i_type = 'i1';
}
break;
case 'i2':
if (additionalData.mapping) {
flag_register_i = true;
i_type = 'i2';
}
break;
case '':
break;
default:
break;
if (record_inst[0] == '@') {
if (additionalData.mapping) {
flag_register_i = true;
i_type = record_inst;
}
}
//switch (record_inst) {
// case 'i1':
// if (additionalData.mapping) {
// flag_register_i = true;
// i_type = 'i1';
// }
// break;
// case 'i2':
// if (additionalData.mapping) {
// flag_register_i = true;
// i_type = 'i2';
// }
// break;
// case '':
// break;
// default:
// break;
//}
let inst = ind_inst;
let reg = /\{(\w+)\}/;
@ -248,9 +398,62 @@
}
}
},
addFromInstruction: function(constructionKey, value, subkeys, trueValue) {
let inst = this.constructionInstruction[constructionKey].instructions;
let split = inst.split('.').slice(1); // split and remove the first entry
let cres = this.result;
let p_res, p_key;
split.forEach(function(inst) {
if (inst == '') {
return false;
} else if (inst[0] === '@') {
let subkeyName;
if (inst.substring(0, 2) === '@@') {
subkeyName = inst.slice(2);
} else {
subkeyName = inst.slice(1);
}
let subkey = subkeys[subkeyName];
if (!cres.hasOwnProperty(subkey)) {
cres[subkey] = {};
}
p_res = cres;
p_key = subkey;
cres = cres[subkey];
} else {
p_res = cres;
p_key = inst;
cres = cres[inst];
}
});
//cres.push(value);
//p_res[p_key].push(value);
if (trueValue) {
p_res[p_key] = value;
} else {
if (this.isObject(cres) && p_key !== undefined) {
p_res[p_key] = [];
}
p_res[p_key].push(value);
}
},
get_subkeys_basename: function(keyname) {
var list = [];
let instructions = this.constructionInstruction[keyname].instructions.split('.');
instructions = instructions.slice(1);
instructions.forEach(function(inst) {
if (inst[0] === '@') {
list.push(inst);
}
});
return list;
},
isObject: function(v) {
return v !== null && typeof v === 'object';
return v !== null && typeof v === 'object' && !Array.isArray(v);
},
update_datum: function(d, k, should_look_into_linkname) {

View File

@ -24,7 +24,8 @@
duration: 500,
interaction: true,
default_function: ' return value;',
toBeMapped: []
toBeMapped: {},
toBeMappedList: []
};
this.options = $.extend({}, this._default_options, options);
@ -66,13 +67,14 @@
this.root.y0 = 0;
// mapping table related
if (this.options.toBeMapped.length > 0 ) {
this.options.toBeMappedList = Object.keys(this.options.toBeMapped);
if (this.options.toBeMappedList.length > 0 ) {
this.instructions = {};
this.prefillData = {};
var that = this;
this.options.toBeMapped.forEach(function(item, index) {
this.options.toBeMappedList.forEach(function(item, index) {
that.instructions[item] = [];
that.prefillData[item] = [];
//that.prefillData[item] = [];
that.itemColors.set(item, that.options.itemColors[index]);
});
@ -612,10 +614,29 @@
//var instructions = this.compute_mapping_instructions(paths);
//this.add_instruction(instructions);
this.unset_related();
this.add_prefill_data([c_label]);
},
unset_related: function() {
var that = this;
let curPickingBackup = this.currentPicking;
let refKey = '@'+this.currentPicking;
this.options.toBeMappedList.forEach(function(itemName) {
if (itemName == curPickingBackup) {
return true;
}
let inst = that.options.toBeMapped[itemName].instructions;
let instS = inst.split('.');
if (instS.indexOf(refKey) > -1) {
that.set_current_mapping_item(itemName);
that.reset_selected();
}
});
this.set_current_mapping_item(curPickingBackup);
},
reset_selected: function() {
var that = this;
var resNode = that.svg.selectAll(".node circle, .node rect, .node polygon")
@ -682,7 +703,7 @@
var row1 = $('<tr></tr>');
var row2 = $('<tr style="height: 20px;"></tr>');
var valueHeader;
this.options.toBeMapped.forEach(function(item, index) {
this.options.toBeMappedList.forEach(function(item, index) {
var itemColor = that.options.itemColors[index];
var cellH = $('<th data-map="'+item+'">'+item+': <span id="'+item+'Cell" data-map="'+item+'" style="font-weight: normal; font-style: italic;"></span> </th>');
var cellB2 = $('<td id="'+item+'CellFun" class="cellFunInput" data-map="'+item+'"></td>');
@ -753,8 +774,9 @@
// if name is empty, select first item not having instructions
set_current_mapping_item: function(name) {
if (name === undefined) {
for (var entry of this.options.toBeMapped) {
if (this.instructions[entry].length == 0 && this.prefillData[entry].length == 0) {
for (var entry of this.options.toBeMappedList) {
let prefillLength = this.prefillData[entry] !== undefined ? this.prefillData[entry].length : 0;
if (this.instructions[entry].length == 0 && prefillLength == 0) {
name = entry;
break;
}
@ -780,8 +802,10 @@
add_instruction: function(instructions) {
this.instructions[this.currentPicking] = instructions;
this.currentPickingCell.text(instructions.toString());
this.set_current_mapping_item();
this.update_result_tree();
if (instructions.length != 0) {
this.set_current_mapping_item();
this.update_result_tree();
}
},
add_prefill_data: function(data) {
@ -819,10 +843,11 @@
fillValue: this.fillValueDomInput.val(),
functions: functions,
datum: this.root,
prefillData: this.prefillData
prefillData: this.prefillData,
};
var adjustedInstructions = this.adjust_instruction();
var result = new $.proxyMapper(adjustedInstructions, this.data, pm_options);
var constructionInstruction = this.options.toBeMapped;
var result = new $.proxyMapper(adjustedInstructions, constructionInstruction, this.data, pm_options);
// destroy and redraw
this.treeDivResult[0].innerHTML = '';
@ -830,22 +855,69 @@
},
adjust_instruction: function() {
var that = this;
var adjustedInstructions = $.extend(true, {}, this.instructions);
adjustedInstructions.index = {};
var matchingIndex = 0;
var l = this.instructions.labels;
var v = this.instructions.values;
var d = this.instructions.dates;
// convert integer index into {index}
for (var i=0; i<v.length; i++) {
if (Number.isInteger(v[i])) {
adjustedInstructions.values[i] = '{'+v[i]+'}';
this.options.toBeMappedList.forEach(function(item) {
if (that.options.toBeMapped[item].strategy === 'value') {
let arr = adjustedInstructions[item];
for (let i=0; i<arr.length; i++) {
if (Number.isInteger(arr[i])) {
arr[i] = '{'+arr[i]+'}';
}
}
return false;
}
});
// add labels and values tracking for value strategy only
var subkeys = {}
var v, l, d;
var v_keyname, l_keyname, d_keyname;
this.options.toBeMappedList.forEach(function(keyname) {
var item = that.options.toBeMapped[keyname];
if (item.strategy === 'value') {
v = that.instructions[keyname];
v_keyname = keyname;
let s = item.instructions.split('.');
if (s.length >= 2 && s[0] === '' && s[1] !== '') {
s.slice(1).forEach(function(k) {
if (k.substring(0, 2) === '@@') {
let k_sliced = k.slice(2);
subkeys[k_sliced] = that.instructions[k_sliced];
} else if (k[0] === '@') {
let k_sliced = k.slice(1);
subkeys[k_sliced] = that.instructions[k_sliced];
}
});
}
return false;
}
});
for (let keyname in subkeys) {
if (this.options.toBeMapped[keyname].strategy === 'date') {
var d = this.instructions[keyname];
var d_keyname = keyname;
} else if (this.options.toBeMapped[keyname].strategy === 'label') {
var l = this.instructions[keyname];
var l_keyname = keyname;
} else {
return false;
}
}
//var l = this.instructions.labels;
//var v = this.instructions.values;
//var d = this.instructions.dates;
// label & value
if (l.length != 0 && v.length != 0) {
if (l !== undefined && l.length != 0 && v.length != 0) {
var smaller_array = v.length < l.length ? v : l;
var has_matched = false;
for (var i=0; i<smaller_array.length; i++) {
@ -858,15 +930,19 @@
// in case no match, last one should be registered
matchingIndex = has_matched ? matchingIndex : smaller_array.length-1;
let inst = adjustedInstructions.values[matchingIndex];
//let inst = adjustedInstructions.values[matchingIndex];
let inst = adjustedInstructions[v_keyname][matchingIndex];
inst = inst == 'l' ? 'l' : '{'+inst+'}';
adjustedInstructions.values[matchingIndex] = 'i1,'+inst;
adjustedInstructions.index['i1'] = adjustedInstructions.labels.slice(matchingIndex+1);
//adjustedInstructions.values[matchingIndex] = 'i1,'+inst;
let kref = '@'+l_keyname;
adjustedInstructions[v_keyname][matchingIndex] = kref + ',' + inst;
//adjustedInstructions.index['i1'] = adjustedInstructions.labels.slice(matchingIndex+1);
adjustedInstructions.index[kref] = adjustedInstructions[l_keyname].slice(matchingIndex+1);
}
var matchingIndex = 0;
// date & value
if (d.length != 0 && v.length != 0) {
if (d !== undefined && d.length != 0 && v.length != 0) {
smaller_array = v.length < d.length ? v : d;
for (var i=0; i<smaller_array.length; i++) {
if (v[i] != d[i]) {
@ -874,16 +950,22 @@
break;
}
}
adjustedInstructions.values[matchingIndex] = 'i2,'+adjustedInstructions.values[matchingIndex];
adjustedInstructions.index['i2'] = adjustedInstructions.dates.slice(matchingIndex+1);
//adjustedInstructions.values[matchingIndex] = 'i2,'+adjustedInstructions.values[matchingIndex];
let kref = '@'+d_keyname;
adjustedInstructions[v_keyname][matchingIndex] = kref + ',' + adjustedInstructions[v_keyname][matchingIndex];
//adjustedInstructions.index['i2'] = adjustedInstructions.dates.slice(matchingIndex+1);
adjustedInstructions.index[kref] = adjustedInstructions[d_keyname].slice(matchingIndex+1);
// add '' at the end for value only
var end_i = adjustedInstructions.values.length-1;
var last_i = adjustedInstructions.values[end_i];
//var end_i = adjustedInstructions.values.length-1;
//var last_i = adjustedInstructions.values[end_i];
var end_i = adjustedInstructions[v_keyname].length-1;
var last_i = adjustedInstructions[v_keyname][end_i];
last_i = last_i.split(',');
last_i = last_i.length == 2 ? last_i[1] : last_i[0];
if (last_i == 'l') {
adjustedInstructions.values[end_i+1] = '';
//adjustedInstructions.values[end_i+1] = '';
adjustedInstructions[v_keyname][end_i+1] = '';
}
}
@ -1035,6 +1117,10 @@
return pts.join(', ');
},
objkeyToList: function(obj) {
}
}