@labvis-ufpa/vistechlib
Version:
Information Visualization Technique Library
267 lines (222 loc) • 9.44 kB
JavaScript
let d3 = require("d3");
let Visualization = require("./Visualization.js");
let utils = require("./Utils.js");
class ParallelCoordinates extends Visualization{
constructor(parentElement, settings){
super(parentElement, settings);
this.name = "ParallelCoordinates";
this.color = 'dimgray'
this.lineFunction = (d) => {
return d3.line()(this.keys.map((key) => {
return [this.x(key), this.y[key](d[key])];
}))
};
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;
if(this.x)
this.x.range([0, this.svg.node().getBoundingClientRect().width-pl-pr]);
else
this.x = d3.scalePoint().range([0, this.svg.node().getBoundingClientRect().width-pl-pr]);
if(this.y) {
for (let prop of this.keys) {
//TODO: verificar como diferenciar entre scalePonint e Linear Contínuo.
// if (typeof this.y[prop].padding === "function")
this.y[prop].range([this.svg.node().getBoundingClientRect().height-pt-pb, 0]);
// else
// this.y[prop].range([$(this.svg.node()).height() -pt-pb, 0]);
}
}
console.log("redraw");
this.redraw();
this.linesCoords = [];
for(let d of this.d){
this.linesCoords.push(this.keys.map((key) => {
return [this.x(key), this.y[key](d[key])];
}));
}
return this;
}
data(d){
let pt = this.settings.paddingTop;
let pb = this.settings.paddingBottom;
super.data(d);
this.x.domain(this.keys);
this.y = {};
for(let k of this.keys){
if(this.domainType[k] === "Categorical") {
this.y[k] = d3.scalePoint()
}else if(this.domainType[k] === "Time"){
//TODO: Melhorar a escala para o tempo.
this.y[k] = d3.scaleTime();
} else {
this.y[k] = d3.scaleLinear();
}
this.y[k]
.domain(this.domain[k])
.range([this.svg.node().getBoundingClientRect().height-pt-pb, 0]);
}
this.linesCoords = [];
for(let d of this.d){
this.linesCoords.push(this.keys.map((key) => {
return [this.x(key), this.y[key](d[key])];
}));
}
return this;
}
redraw(){
if(!this.hasData)
return this;
// let axis = d3.svg.axis().orient("left");
//Atualiza os Eixos
let y_axes = this.y;
let self = this;
let dataUpdate = this.foreground.selectAll("path.data").data(this.d);
dataUpdate.exit().remove();
let dataEnter = dataUpdate
.enter()
.append("path")
.attr("class", "data")
.attr("data-index", function(d,i){ return i; });
this._bindDataMouseEvents(dataEnter);
dataEnter.merge(dataUpdate)
.attr("d", this.lineFunction)
.style("stroke", this.color);
let axisUpdate = this.overlay.selectAll(".axis").data(this.keys);
axisUpdate.exit().remove();
axisUpdate.selectAll("*").remove();
let axisEnter = axisUpdate
.enter()
.append("g")
.attr("class", "axis");
axisEnter.merge(axisUpdate)
.attr("transform", (d) => { return "translate(" + this.x(d) + ")"; })
.each(function(d) { d3.select(this).call(d3.axisLeft(y_axes[d])); })
.append("text")
.style("text-anchor", "middle")
.attr("class", "column_label")
.attr("y", -9)
.on('click', d => {
this.event.call("dimensiontitleclick",{about: "I am a context object"},d);
})
.text(function(d) { return d; })
.style("fill", "black");
this.axis = this.overlay.selectAll(".axis");
this.event.apply("draw");
return this;
}
updateColors(){
if(this.domainType[this.focus] !== 'Categorical') this.color = "dimgray"
else {
this.colorScale = d3.scaleOrdinal().domain(this.domain[this.focus]).range(
['firebrick', 'mediumseagreen', 'steelblue', 'gold', 'chocolate', 'magenta']
)
this.color = d => {
let category = d[this.focus]
return this.colorScale(category)
}
}
this.redraw()
}
highlight(...args){
let parallelcoordinates = this;
let highlighted;
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])
highlighted = this.foreground
.selectAll('path.data[data-index="'+args[1]+'"]')
.style("stroke", this.settings.highlightColor)
.style("stroke-width", "2")
.each(function(){
this.parentNode.appendChild(this);
});
}
if(highlighted)
super.highlight(highlighted.nodes(), args[0], args[1], args[2]);
}
removeHighlight(...args){
if(args[1] instanceof SVGElement){
}else if(typeof args[1] === "number" && args[1] >= 0 && args[1] < this.d.length){
let dataSelect = this.foreground.selectAll('path.data[data-index="'+args[1]+'"]')
.style("stroke", this.color)
.style("stroke-width", "1");
// this.overlay.selectAll(".lineHighlight").remove();
super.removeHighlight(dataSelect.node(), dataSelect.datum(), args[1]);
// this.event.apply("highlightend", dataSelect.node(), [dataSelect.datum(), args[1]]);
}
}
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;
}
select(selection){
console.log("entrou");
let result = [];
if(Array.isArray(selection)){
if(Array.isArray(selection[0])){
for(let k=0; k<this.linesCoords.length; k++){
data_block: {
let polyLine = this.linesCoords[k];
for (let j = 0; j < polyLine.length - 1; j++) {
for (let i = 0; i < selection.length - 1; i++) {
this.foreground.append("line")
.attr("x1", selection[i][0]-this.settings.paddingLeft)
.attr("x2", selection[i + 1][0]-this.settings.paddingLeft)
.attr("y1", selection[i][1]-this.settings.paddingTop)
.attr("y2", selection[i + 1][1]-this.settings.paddingTop)
.style("fill", "none")
.style("stroke", "red");
let intersect = utils.lineIntersects(selection[i][0]-this.settings.paddingLeft,
selection[i][1]-this.settings.paddingTop,
selection[i + 1][0]-this.settings.paddingLeft,
selection[i + 1][1]-this.settings.paddingTop,
polyLine[j][0], polyLine[j][1],
polyLine[j + 1][0], polyLine[j + 1][1]);
if (intersect) {
result.push(
this.foreground.select('path.data[data-index="' + k + '"]').node());
break data_block;
}
}
}
}
}
}else if(typeof selection[0] === "number"){
//seleção através de índices.
for(let i of selection){
result.push(
this.foreground.select('path.data[data-index="'+i+'"]').node());
}
}else if(selection[0] instanceof SVGPathElement){
for(let i of selection){
result.push(i);
}
}
}
console.log(result);
super.select(result);
}
getSelected(){
return this.selectionLayer.selectAll("*").nodes();
}
}
module.exports = ParallelCoordinates;