@labvis-ufpa/vistechlib
Version:
Information Visualization Technique Library
709 lines (623 loc) • 32.3 kB
JavaScript
let d3 = require("d3");
let _ = require("underscore");
let Visualization = require("./Visualization.js");
class ParallelBundling extends Visualization{
constructor(parentElement, settings){
super(parentElement, settings);
this.animation = true;
this.clusterOn = 'all';
this.colorScheme = ['firebrick', 'mediumseagreen', 'steelblue', 'gold', 'chocolate', 'magenta'];
this.clusterColor = someCluster => {
if (this.clusterTags.includes(someCluster)){
return this.colorScheme[this.clusterTags.indexOf(someCluster)];
} else {
return 'dimgrey';
}
};
let Path = require('d3-path').path;
this.lineFunction = d => {
let path = new Path();
let x,y,x1,y1,x2,y2;
let BOX1 = 1;
let BOX2 = 20;
let FACTOR = 10;
let cluster = (this.clusterOn === 'all') ? 'all' : d[this.clusterOn];
if (this.clusterOn === 'all') this.domain['all'] = ['all'];
const isCategorical = someKey => this.domainType[someKey] === "Categorical";
const map = (i, a, b, x, y) => {
let scale = d3.scaleLinear().domain([a, b]).range([x, y]);
return scale(i);
};
const keysExceptLast = this.keys.slice(0,-1);
keysExceptLast.forEach((key, keyIndex)=> {
const nextKey = this.keys[keyIndex + 1];
const co_co = !isCategorical(key) && !isCategorical(nextKey);
const co_ca = !isCategorical(key) && isCategorical(nextKey);
const ca_co = isCategorical(key) && !isCategorical(nextKey);
let displacement;
if (isCategorical(nextKey)) {
let ordination;
if(!isCategorical(key)) {
ordination = this.sortedClusters[cluster][key].filter(entry => {
return entry[nextKey] === d[nextKey];
});
}
else {
ordination = this.clusters.find(c => c.key === cluster).values.filter(entry => {
return entry[nextKey] === d[nextKey];
})
}
const indexOfData = ordination.indexOf(d);
const quantityOfMembers = ordination.length;
const relativeIndexPositionOfd = quantityOfMembers - indexOfData;
let auxiliaryDisplacement = 0;
const createDisplacementScale = () => {
for (let tag of this.clusterTags) {
if (tag === cluster) break;
auxiliaryDisplacement += this.reservedSpaceInCategoricalAxes[tag][nextKey][d[nextKey]];
}
return d3.scaleLinear()
.domain([0,quantityOfMembers])
.range([auxiliaryDisplacement, auxiliaryDisplacement + this.reservedSpaceInCategoricalAxes[cluster][nextKey][d[nextKey]]]);
};
const displacementScale = createDisplacementScale();
this.verticalPositionDisplacementOnAxis = displacementScale(relativeIndexPositionOfd);
displacement = displacementScale(relativeIndexPositionOfd);
};
let quantityOfMembersInCluster = this.clusters.find(c => c.key === cluster).values.length;
let location_in_array_thisKey = this.sortedClusters[cluster][key].indexOf(d);
let keyFactor = map(location_in_array_thisKey, 0, quantityOfMembersInCluster, 1, -1) * FACTOR;
let location_in_array_nextKey = this.sortedClusters[cluster][nextKey].indexOf(d);
let nextKeyFactor = map(location_in_array_nextKey, 0, quantityOfMembersInCluster, 1, -1) * FACTOR;
if (keyIndex === 0) { // Mover para o inicio
if (!isCategorical(key)) {
x = this.x(key);
y = this.y[key](d[key]);
path.moveTo(x, y);
}
else {
let ordination = this.sortedClusters[cluster][key].filter(entry => {
return entry[key] === d[key];
});
let indexOfd = ordination.indexOf(d);
let quantityOfMembers = ordination.length;
let relativeDisplacement = quantityOfMembers - indexOfd;
let sumOfReservedSpaces = 0;
for (let tag of this.clusterTags) {
if (tag === cluster) break;
sumOfReservedSpaces += this.reservedSpaceInCategoricalAxes[tag][key][d[key]];
}
let displacementScale = d3.scaleLinear()
.domain([0, quantityOfMembers])
.range([sumOfReservedSpaces, this.reservedSpaceInCategoricalAxes[cluster][key][d[key]] + sumOfReservedSpaces]);
let displacement = displacementScale(relativeDisplacement);
x = this.x(key);
y = this.y[key](d[key]) + displacement;
path.moveTo(x, y);
}
}
if (!isCategorical(key)) { // LINHA PARA BOX SE CO
x = this.x(key) + this.band(BOX1);
// y = this.y[key](data[key]);
path.lineTo(x, y);
}
else { // LINHA PARA BOX SE CA
x = this.x(key) + this.band(BOX1);
// y = this.y[key](data[key]);
path.lineTo(x, y);
}
// Bezier para a banda 25 se co
if (!isCategorical(key)) {
x1 = this.x(key) + this.band(BOX1) / 2 + this.band(10);
y1 = this.y[key](d[key]);
x2 = this.x(key) + this.band(BOX1) / 2;
y2 = this.y[key](this.means[key][cluster]) + keyFactor;
x = this.x(key) + this.band(25) + this.band(BOX1) / 2;
y = this.y[key](this.means[key][cluster]) + keyFactor;
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
// Cubica para banda 25 se ca
else {
x1 = this.x(key) + this.band(BOX1) / 2 + this.band(10);
y1 = y;
x2 = this.x(key) + this.band(BOX1) / 2;
y2 = this.y[key](this.modes[key][cluster]) + keyFactor + this.axesSizesWithPadding[key] / 2;
x = this.x(key) + this.band(25) + this.band(BOX1) / 2;
y = this.y[key](this.modes[key][cluster]) + keyFactor + this.axesSizesWithPadding[key] / 2;
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
// Cubica para a banda 75 co-co
if (co_co) {
x1 = this.x(key) + this.band(50);
y1 = this.y[key](this.means[key][cluster]) + keyFactor;
x2 = this.x(key) + this.band(50);
y2 = this.y[nextKey](this.means[nextKey][cluster]) + nextKeyFactor;
x = this.x(key) + this.band(75) - this.band(BOX1) / 2;
y = this.y[nextKey](this.means[nextKey][cluster]) + nextKeyFactor;
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
// Cubica para a banda 75 co-ca
else if (co_ca) {
x1 = this.x(key) + this.band(50);
y1 = this.y[key](this.means[key][cluster]) + keyFactor;
x2 = this.x(key) + this.band(50);
y2 = this.y[nextKey](this.modes[nextKey][cluster]) + nextKeyFactor + this.axesSizesWithPadding[nextKey] / 2;
x = this.x(key) + this.band(75) - this.band(BOX1) / 2;
y = this.y[nextKey](this.modes[nextKey][cluster]) + nextKeyFactor + this.axesSizesWithPadding[nextKey] / 2;
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
// Cubica para banda 75 ca-co
else if (ca_co) {
x1 = this.x(key) + this.band(50);
y1 = this.y[key](this.modes[key][cluster]) + keyFactor + this.axesSizesWithPadding[key] / 2;
x2 = this.x(key) + this.band(50);
y2 = this.y[nextKey](this.means[nextKey][cluster]) + nextKeyFactor;
x = this.x(key) + this.band(75) - this.band(BOX1) / 2;
y = this.y[nextKey](this.means[nextKey][cluster]) + nextKeyFactor;
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
// Cubica para banda 75 ca-ca
else {
x1 = this.x(key) + this.band(50);
y1 = this.y[key](this.modes[key][cluster]) + keyFactor + this.axesSizesWithPadding[key] / 2;
x2 = this.x(key) + this.band(50);
y2 = this.y[nextKey](this.modes[nextKey][cluster]) + nextKeyFactor + this.axesSizesWithPadding[nextKey] / 2;
x = this.x(key) + this.band(75) - this.band(BOX1) / 2;
y = this.y[nextKey](this.modes[nextKey][cluster]) + nextKeyFactor + this.axesSizesWithPadding[nextKey] / 2;
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
// Quadratica para next BOX se co
if (!isCategorical(nextKey)) {
x1 = this.x(nextKey) - this.band(BOX1) / 2;
y1 = this.y[nextKey](this.means[nextKey][cluster]) + nextKeyFactor;
x2 = this.x(nextKey) - this.band(BOX1) / 2 - this.band(10);
y2 = this.y[nextKey](d[nextKey]);
x = this.x(nextKey) - this.band(BOX1) / 2;
y = this.y[nextKey](d[nextKey]);
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
// Cubica para next this.band(BOX1) se ca
else {
x1 = this.x(nextKey) - this.band(BOX1) / 2;
y1 = this.y[nextKey](this.modes[nextKey][cluster]) + nextKeyFactor + this.axesSizesWithPadding[nextKey] / 2;
x2 = this.x(nextKey) - this.band(BOX1) / 2 - this.band(10);
y2 = this.y[nextKey](d[nextKey]) + displacement;
x = this.x(nextKey) - this.band(BOX1) / 2;
y = this.y[nextKey](d[nextKey]) + displacement;
path.bezierCurveTo(x1, y1, x2, y2, x, y);
}
//Linha para next dimension se co
if (!isCategorical(nextKey)) {
x = this.x(nextKey);
y = this.y[nextKey](d[nextKey]);
path.lineTo(x, y);
}
else { // se categorico
x = this.x(nextKey);
y = this.y[nextKey](d[nextKey]) + displacement;
path.lineTo(x, y);
}
});
return path.toString();
};
this.x = d3.scalePoint().range([
0,
this.svg.node().getBoundingClientRect().width
-this.settings.paddingLeft
-this.settings.paddingRight
], 0);
}
resize(){
let pl = this.settings.paddingLeft;
let pr = this.settings.paddingRight;
let pt = this.settings.paddingTop;
let pb = this.settings.paddingBottom;
let svgBounds = this.svg.node().getBoundingClientRect();
if(this.x)
this.x.range([0, svgBounds.width-pl-pr]);
else
this.x = d3.scalePoint().range([0, svgBounds.width-pl-pr]);
if(this.y) {
for (let prop of this.keys) {
this.y[prop].range([svgBounds.height -pt-pb, 0]);
}
}
console.log("redraw");
this.redraw();
return this;
}
data(d){
let pt = this.settings.paddingTop;
let pb = this.settings.paddingBottom;
super.data(d);
this.categoricalKeys = this.keys.filter(key => this.domainType[key]==='Categorical');
const mode = array => _.chain(array).countBy().pairs().max(_.last).head().value();
const clustering = () => {
const defineClusters = () => {
const validateClusterOnParameter = () => {
if (this.domainType[this.clusterOn] !== 'Categorical')
this.clusterOn = this.keys.find(key => this.domainType[key] === 'Categorical');
};
const defineClusterTags = () => {this.clusterTags = this.clusters.map(d=>d.key)};
const clusterDataByKey = key => {this.clusters = d3.nest().key(d => d[key]).entries(d);};
const clusterDataInOneGroup = () => {
this.clusters = [{key: 'all', values: d}];
this.clusterOn = 'all';
};
if(this.clusterOn === 'all'){
clusterDataInOneGroup();
} else {
validateClusterOnParameter();
clusterDataByKey(this.clusterOn);
}
defineClusterTags();
};
const defineSortedClusters = () => {
const sortClusterMembersByContinuousKey = (clusterTag,key) => {
//TODO: make this pass the test where there are no categorical keys
return this.clusters
.find(d => d.key === clusterTag).values.slice()
.sort((a, b) => a[key] - b[key]);
};
const sortClusterMembersByCategoricalKey = (clusterTag,key) => {
return this.clusters
.find(d => d.key === clusterTag).values.slice()
.sort((a,b) => this.domain[key].indexOf(a[key]) - this.domain[key].indexOf(b[key]));
};
this.sortedClusters = {};
this.clusterTags.forEach(clusterTag => {
this.sortedClusters[clusterTag] = {};
this.keys.forEach(key => {
if(this.domainType[key] !== "Categorical")
this.sortedClusters[clusterTag][key] = sortClusterMembersByContinuousKey(clusterTag, key);
else
this.sortedClusters[clusterTag][key] = sortClusterMembersByCategoricalKey(clusterTag, key);
})
})
};
const defineStatisticsFromClusters = () => {
const getMeansAndVariancesOfClustersOnKey = (key) => {
this.means[key] = {};
this.variances[key] = {};
this.clusterTags.forEach(clusterKey => {
let pluck = _.pluck(this.clusters.find(d => d.key === clusterKey).values, key);
this.means[key][clusterKey] = d3.mean(pluck);
this.variances[key][clusterKey] = d3.max(pluck) - d3.min(pluck)
});
};
const getModeOfClustersOnKey = (key) => {
this.modes[key] = {};
this.clusterTags.forEach(clusterKey => {
let pluck = _.pluck(this.clusters.find(d => d.key === clusterKey).values, key);
this.modes[key][clusterKey] = mode(pluck);
});
};
this.means = {};
this.variances = {};
this.modes = {};
this.keys.forEach(key => {
if (this.domainType[key] !== 'Categorical')
getMeansAndVariancesOfClustersOnKey(key);
else
getModeOfClustersOnKey(key);
});
};
const defineCategoricalOrdinationMetrics = () => {
const calculateClusterQuantity = (tag,key) => {
this.domain[key].forEach( category => {
let sum = 0;
this.clusters.find(c => c.key === tag).values.forEach( entry => {
if (entry[key] === category)
sum += 1;
});
this.quantityOfClusterMembersThatPassOn[tag][key][category] = sum;
});
};
const calculateTotalQuantity = (key,category) => {
let sum = 0;
this.clusterTags.forEach( tag=> {
sum += this.quantityOfClusterMembersThatPassOn[tag][key][category];
});
this.totalQuantityOfEntriesThatPassOn[key][category] = sum;
};
this.quantityOfClusterMembersThatPassOn = {};
this.clusterTags.forEach( tag => {
this.quantityOfClusterMembersThatPassOn[tag] = {};
this.categoricalKeys.forEach( key => {
this.quantityOfClusterMembersThatPassOn[tag][key]= {};
calculateClusterQuantity(tag, key);
})
});
this.totalQuantityOfEntriesThatPassOn = {};
this.categoricalKeys.forEach( key => {
this.totalQuantityOfEntriesThatPassOn[key] = {};
this.domain[key].forEach( category => {
calculateTotalQuantity(key, category);
});
});
};
defineClusters();
defineSortedClusters();
defineStatisticsFromClusters();
defineCategoricalOrdinationMetrics();
};
const defineScales = () => {
const updateScaleDomains = () => {
const createBandScaleFromXScale = () => {
this.band = p => (p/100)*(this.x(this.keys[1]) - this.x(this.keys[0]));
};
this.x.domain(this.keys);
createBandScaleFromXScale();
};
const defineYScales = () => {
// const createCategoricalScaleFor = key => {
// this.y[key] = d3.scalePoint()
// .domain(['*'].concat(this.domain[key]))
// .range([$(this.svg[0][0]).height()-pt-pb, 0]);
// };
// const createTimeScaleFor = key => {
// this.y[key] = d3.scaleTime()
// .domain(this.domain[key])
// .range([$(this.svg[0][0]).height()-pt-pb, 0]);
// };
// const createNumericScaleFor = key => {
// this.y[key] = d3.scaleLinear()
// .domain(this.domain[key])
// .range([$(this.svg[0][0]).height()-pt-pb, 0]);
// };
this.y = {};
this.keys.forEach(key => {
switch(this.domainType[key]){
case ('Categorical'): this.y[key] = d3.scalePoint(); break;
case ('Time'): this.y[key] = d3.scaleTime(); break;
default: this.y[key] = d3.scaleLinear();
}
this.y[key]
.domain(this.domainType[key] === 'Categorical' ?
['*'].concat(this.domain[key]) : this.domain[key])
.range([this.svg.node().getBoundingClientRect().height -pt -pb, 0]);
});
};
this.defineReservedSpaceInCategoricalAxes = () =>{
const calculateCategoricalAxesSizes = () => {
// TODO: remove hardcoded Padding in interface
this.axesPadding = 10;
const paddAxisSize = size => size - (((((this.axesPadding - 1) * 49 )/ 99) + 1)/100) * size;
this.axesSizesWithNoPadding = {};
this.axesSizesWithPadding = {};
this.categoricalKeys.forEach( key => {
let sizeWithNoPadding = this.y[key](this.domain[key][0]) - this.y[key](this.domain[key][1]);
this.axesSizesWithNoPadding[key] = sizeWithNoPadding;
this.axesSizesWithPadding[key] = paddAxisSize(sizeWithNoPadding);
})
};
const calculateReservedSpaceInCategoricalAxes = (tag,key,category) => {
const total = this.totalQuantityOfEntriesThatPassOn[key][category];
const size = this.axesSizesWithPadding[key];
const auxiliaryScale = d3.scaleLinear().domain([0, total]).range([0, size]);
const quantity = this.quantityOfClusterMembersThatPassOn[tag][key][category];
this.reservedSpaceInCategoricalAxes[tag][key][category] = auxiliaryScale(quantity);
};
calculateCategoricalAxesSizes();
this.reservedSpaceInCategoricalAxes = {};
this.clusterTags.forEach(tag => {
this.reservedSpaceInCategoricalAxes[tag] = {};
this.categoricalKeys.forEach( key => {
this.reservedSpaceInCategoricalAxes[tag][key] = {};
this.domain[key].forEach(category => {
calculateReservedSpaceInCategoricalAxes(tag,key,category);
})
})
})
};
updateScaleDomains();
defineYScales();
this.defineReservedSpaceInCategoricalAxes();
};
clustering();
defineScales();
}
redraw(withAnimation){
if(!this.hasData)
return;
let y_axes = this.y;
let self = this;
const calculateCategoricalAxesSizes = () => {
const paddAxisSize = size => size - (((((this.axesPadding - 1) * 49 )/ 99) + 1)/100) * size;
this.axesSizesWithNoPadding = {};
this.axesSizesWithPadding = {};
this.categoricalKeys.forEach( key => {
let sizeWithNoPadding = this.y[key](this.domain[key][0]) - this.y[key](this.domain[key][1]);
this.axesSizesWithNoPadding[key] = sizeWithNoPadding;
this.axesSizesWithPadding[key] = paddAxisSize(sizeWithNoPadding);
})
};
this.defineReservedSpaceInCategoricalAxes();
calculateCategoricalAxesSizes();
const drawLines = () => {
const linesUpdateSelection = this.foreground.selectAll('path.data').data(this.d);
const updateAllLinesWithAnimation = (selection) => {
const transitionScale = d3.scaleLinear().domain([0,this.d.length]).range([0,200]);
selection
.transition()
.attr("class", "data")
.attr("data-index", function(d,i){ return i; })
.attr("d", this.lineFunction)
.style("stroke", d=> this.clusterColor(d[this.clusterOn]))
.attr('cluster', d=> this.clusterOn === 'all' ? 'all': d[this.clusterOn]);
this._bindDataMouseEvents(selection);
};
const updateAllLinesWithoutAnimation = (selection) => {
selection
.attr("class", "data")
.attr("data-index", function(d,i){ return i; })
.attr("d", this.lineFunction)
.style('stroke-width',1)
.style("stroke", d=> this.clusterColor(d[this.clusterOn]))
.attr('cluster', d=> this.clusterOn === 'all' ? 'all': d[this.clusterOn]);
this._bindDataMouseEvents(selection);
};
const removeExtraLines = () => {
linesUpdateSelection
.exit()
.remove();
};
this.painterAlgorithm = () => {
const drawOrder = this.clusterTags.slice().sort( (a,b) => this.clusters.find(c => c.key === b).values.length - this.clusters.find(c => c.key === a).values.length);
drawOrder.forEach( tag => {
this.foreground.selectAll(`path[cluster=${tag}].data`).each(function() {
this.parentNode.appendChild(this)
})
});
};
let mergeSelection = linesUpdateSelection.enter().append("path")
.merge(linesUpdateSelection);
if (this.animation && withAnimation)
updateAllLinesWithAnimation(mergeSelection);
else
updateAllLinesWithoutAnimation(mergeSelection);
removeExtraLines();
setTimeout(this.painterAlgorithm, 250);
};
drawLines();
// const drawAxes = () => {
// const axesUpdateSelection = this.overlay.selectAll('.axis').data(this.keys);
//
// const appendNewAxes = () => {};
// const updateAllAxes = () => {};
// const removeExtraAxes = () => {};
// }
this.overlay.selectAll(".axis")
.data(this.keys)
.exit().remove();
this.axis = this.overlay.selectAll(".axis")
.data(this.keys)
.attr("transform", (d) => { return "translate(" + this.x(d) + ")"; })
.each(function(d) {
if(self.domainType[d] !== 'Categorical') d3.select(this).call(d3.axisLeft(y_axes[d]));
});
this.axis.select("text.column_label")
.text(function(d) { return d; });
let axisSelection = this.overlay.selectAll('.axis').data(this.keys);
let axisSelectionEnter = axisSelection
.enter()
.append("g")
.attr("class", "axis")
.each(function(key) {
if(self.domainType[key] !== 'Categorical') d3.select(this).call(d3.axisLeft(y_axes[key]));
else {
// let size = self.y[key](self.domain[key][0]) - self.y[key](self.domain[key][1]);
// let PADDING = size/50;
self.domain[key].forEach((category, index) => {
let g = d3.select(this).append('g').attr('class', 'categoricalAxis').attr('category', category)
.attr('transform','translate(0,'+self.y[key](category)+')');
let s = d3.scaleLinear().domain([0,1]).range([0, self.axesSizesWithPadding[key]]);
g.call(d3.axisLeft(s).ticks(0));
g.append('text').text(category).attr('x', -10).attr('y', self.axesSizesWithPadding[key]/2);
})
}
});
//insere label textual para cada eixo no enter().
axisSelectionEnter
.append("text")
.style("text-anchor", "middle")
.attr("class", "column_label")
.style("fill", "black")
.attr("y", -9)
.on('click', d=>this.setClusterKey(d))
.text(function(d) { return d; });
axisSelectionEnter
.merge(axisSelection)
.attr("transform", (key) => { return "translate(" + this.x(key) + ")"; })
.each(function(key) {
if(self.domainType[key] !== 'Categorical') d3.select(this).call(d3.axisLeft(y_axes[key]));
else {
// let size = self.y[key](self.domain[key][0]) - self.y[key](self.domain[key][1]);
// let PADDING = size/50;
let g = d3.select(this).selectAll('.categoricalAxis')
.attr('transform', function() { return 'translate(0,'+ self.y[key](d3.select(this).attr('category')) +')'});
let s = d3.scaleLinear().domain([0,1]).range([0, self.axesSizesWithPadding[key]]);
g.selectAll('*').remove();
g.call(d3.axisLeft(s).ticks(0));
g.append('text').text(function() {return d3.select(this.parentNode).attr('category')})
.attr('text-anchor', 'end')
.attr('x', -5)
.attr('y', self.axesSizesWithPadding[key]/2);
}
});
axisSelection.exit().remove();
this.event.apply("draw");
// Add an axis and title.
// let labelUpdate = axisSelection.selectAll("text.column_label").data(this.keys);
// labelUpdate.enter()
// .append("text")
// .style("text-anchor", "middle")
// .attr("class", "column_label")
// .attr("y", -9)
// .on('click', d=>this.setClusterKey(d))
// .merge(labelUpdate)
// .text(function(d) { return d; });
// let categoricalAxisSelection = this.overlay.selectAll('.categoricalAxis');
}
setClusterKey(key) {
if (this.domainType[key] !== 'Categorical')
this.clusterOn = 'all';
else if (this.clusterOn !== key)
this.clusterOn = key;
else
return;
this.data(this.d);
const WITH_ANIMATION = true;
this.redraw(WITH_ANIMATION);
}
highlight(...args){
let parallelcoordinates = this;
if(args[0] instanceof SVGElement){
}else if(typeof args[1] === "number" && args[1] >= 0 && args[1] < this.d.length){
// this.foreground.select
// d3.select(args[0])
this.foreground.selectAll('path.data[data-index="'+args[1]+'"]')
.style("stroke", parallelcoordinates.settings.highlightColor)
.style("stroke-width", "2")
.each(function(){
// parallelcoordinates.overlay.node()
// .appendChild(d3.select(this.cloneNode())
// .attr("class", "lineHighlight")
// .style("stroke", parallelcoordinates.settings.highlightColor)
// .style("stroke-width", "2")
// .style("fill", "none")
// .node());
this.parentNode.appendChild(this);
});
}
super.highlight.apply(this, args);
}
removeHighlight(...args){
let self = this;
if(args[1] instanceof SVGElement){
}else if(typeof args[1] === "number" && args[1] >= 0 && args[1] < this.d.length){
let elem = this.foreground.selectAll('path.data[data-index="'+args[1]+'"]')
.style("stroke", function(d) {return self.clusterColor(d[self.clusterOn])})
.style("stroke-width", 1);
// this.overlay.selectAll(".lineHighlight").remove();
// this.event.apply("highlightend", null, args);
super.removeHighlight(elem.node(), elem.datum(), args[1]);
}
this.painterAlgorithm();
}
getHighlightElement(i){
let parallelcoordinates = this;
let group = document.createElementNS("http://www.w3.org/2000/svg", "g");
d3.select(group).attr("class", "groupHighlight");
this.foreground.selectAll('path.data[data-index="'+i+'"]').each(function(){
group.appendChild(d3.select(this.cloneNode())
.attr("class", "lineHighlight")
.style("stroke", parallelcoordinates.settings.highlightColor)
.style("stroke-width", "2")
.style("fill", "none")
.node());
});
return group;
}
}
module.exports = ParallelBundling;