You’ve made things way more complicated than necessary, especially with the foreground/background logic. I’ve rewritten the logic to draw a group of lines per category, one dashed and one solid. The result is the same, only the data handling is easier:
var dataSet = [{
"type": "car",
"first": 0.65,
"second": 0.34,
"third": 0.55,
"fourth": 0.39
},
{
"type": "car",
"dataset": "train",
"first": 0.59,
"second": 0.33,
"third": 0.50,
"fourth": 0.40
},
{
"type": "bicycle",
"first": 200,
"second": 230,
"third": 250,
"fourth": 300
},
{
"type": "bicycle",
"dataset": "train",
"first": 200,
"second": 280,
"third": 225,
"fourth": 278
},
{
"type": "boat",
"first": 320,
"second": 324,
"third": 532,
"fourth": 321
},
{
"type": "boat",
"dataset": "train",
"first": 128,
"second": 179,
"third": 166,
"fourth": 234
},
{
"type": "airplane",
"first": 1500,
"second": 2000,
"third": 2321,
"fourth": 1793
},
{
"type": "airplane",
"dataset": "train",
"first": 1438,
"second": 2933,
"third": 2203,
"fourth": 2000
}
];
var processedData = [];
dataSet.forEach(function(d) {
var match = processedData.find(function(p) { return p.type === d.type; });
if(!match) {
match = {
type: d.type,
};
processedData.push(match);
}
var values = [d.first, d.second, d.third, d.fourth];
if(d.dataset === "train") {
match.train = values;
} else {
match.test = values;
}
});
processedData.forEach(function(d) {
// Normalise the values in the arrays
const min = Math.min(d3.min(d.train), d3.min(d.test));
const max = Math.max(d3.max(d.train), d3.max(d.test));
d.trainNormalised = d.train.map(function(v) {
return (v - min) / (max - min);
});
d.testNormalised = d.test.map(function(v) {
return (v - min) / (max - min);
});
});
var margin = {
top: 5,
right: 50,
bottom: 5,
left: 70
},
width = 600 - margin.left - margin.right,
height = 280 - margin.top - margin.bottom;
var categoryScale = d3.scale.ordinal()
.domain(processedData.map(function(d) { return d.type; }))
.rangePoints([0, height]);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var x = d3.scale.ordinal()
.domain(d3.range(5))
.rangePoints([0, width]);
var line = d3.svg.line()
.defined(function(d) {
return !isNaN(d[1]);
});
// CREATE A COLOR SCALE
var color = d3.scale.ordinal()
.range(["#4683b8", "#79add2", "#a6c9de", "#cadbed", "#9d9bc4", "#bcbed9", "#dadaea", "#f6d2a8", "#f2b076", "#ef914e", "#d65e2a"])
var svg = d3.select("#parallel_coor")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
svg.selectAll(".dimension.axis")
.data([categoryScale, y, y, y, y])
.enter()
.append("g")
.attr("class", "dimension axis")
.attr("transform", function(d, i) {
return "translate(" + x(i) + ")";
})
.each(function(d) {
const yAxis = d3.svg.axis()
.scale(d)
.ticks([])
.orient("left");
d3.select(this).call(yAxis);
});
function parallel(data) {
// Draw one line group per type (car, boat)
// Each line group consists of a train and a test line;
var lineGroup = svg.append("g")
.selectAll(".lineGroup")
.data(data)
.enter()
.append("g")
.attr("class", "lineGroup")
.each(function(d) {
if(d.train)
d3.select(this).append("path")
.datum([d, "train"]);
if(d.test)
d3.select(this).append("path")
.datum(function(d) { return [d, "test"]; });
})
lineGroup
.attr("stroke", function(d) {
var company = d.type.slice(0, d.type.indexOf(' '));
return color(company);
})
.selectAll("path")
.attr("class", function(d) { return d[1]; })
.attr("d", draw);
lineGroup
.on("mouseover", function(d) {
// show train when click others
d3.select(this).classed("active", true);
lineGroup
.filter(function(e) { return e.type !== d.type; })
.style('opacity', 0.2);
})
.on("mouseout", function(d) {
d3.select(this).classed("active", false);
lineGroup.style('opacity', null);
});
function draw(d) {
var data = d[0], type = d[1];
var points = data[type + "Normalised"].map(function(v, i) {
return [x(i + 1), y(v)];
});
points.unshift([x(0), categoryScale(data.type)]);
return line(points);
}
}
parallel(processedData);
svg {
font: 12px sans-serif;
}
.lineGroup path {
fill: none;
}
.lineGroup.active .train {
visibility: visible;
}
.train {
visibility: hidden;
stroke-dasharray: 5 5;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="parallel_coor"></div>
Edit to specify which categories to normalise together, you can add every type
to an array, grouping types you want to normalise together.
Then, loop over the groups and calculate the common min
and max
values. The rest of the code is identical to the one above.
var dataSet = [{
"type": "car",
"first": 0.65,
"second": 0.34,
"third": 0.55,
"fourth": 0.39
},
{
"type": "car",
"dataset": "train",
"first": 0.59,
"second": 0.33,
"third": 0.50,
"fourth": 0.40
},
{
"type": "bicycle",
"first": 200,
"second": 230,
"third": 250,
"fourth": 300
},
{
"type": "bicycle",
"dataset": "train",
"first": 200,
"second": 280,
"third": 225,
"fourth": 278
},
{
"type": "boat",
"first": 320,
"second": 324,
"third": 532,
"fourth": 321
},
{
"type": "boat",
"dataset": "train",
"first": 128,
"second": 179,
"third": 166,
"fourth": 234
},
{
"type": "airplane",
"first": 1500,
"second": 2000,
"third": 2321,
"fourth": 1793
},
{
"type": "airplane",
"dataset": "train",
"first": 1438,
"second": 2933,
"third": 2203,
"fourth": 2000
}
];
var normaliseTogether = [
["car"],
["bicycle", "boat"],
["airplane"],
];
var processedData = [];
dataSet.forEach(function(d) {
var match = processedData.find(function(p) {
return p.type === d.type;
});
if (!match) {
match = {
type: d.type,
};
processedData.push(match);
}
var values = [d.first, d.second, d.third, d.fourth];
if (d.dataset === "train") {
match.train = values;
} else {
match.test = values;
}
});
normaliseTogether.forEach(function(groups) {
var groupData = processedData.filter(function(d) {
return groups.includes(d.type);
});
// Normalise the values in the arrays
let min = Infinity,
max = -Infinity;
groupData.forEach(function(d) {
min = Math.min(min, d3.min(d.train), d3.min(d.test));
max = Math.max(max, d3.max(d.train), d3.max(d.test));
});
groupData.forEach(function(d) {
d.trainNormalised = d.train.map(function(v) {
return (v - min) / (max - min);
});
d.testNormalised = d.test.map(function(v) {
return (v - min) / (max - min);
});
});
});
var margin = {
top: 5,
right: 50,
bottom: 5,
left: 70
},
width = 600 - margin.left - margin.right,
height = 280 - margin.top - margin.bottom;
var categoryScale = d3.scale.ordinal()
.domain(processedData.map(function(d) {
return d.type;
}))
.rangePoints([0, height]);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var x = d3.scale.ordinal()
.domain(d3.range(5))
.rangePoints([0, width]);
var line = d3.svg.line()
.defined(function(d) {
return !isNaN(d[1]);
});
// CREATE A COLOR SCALE
var color = d3.scale.ordinal()
.range(["#4683b8", "#79add2", "#a6c9de", "#cadbed", "#9d9bc4", "#bcbed9", "#dadaea", "#f6d2a8", "#f2b076", "#ef914e", "#d65e2a"])
var svg = d3.select("#parallel_coor")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
svg.selectAll(".dimension.axis")
.data([categoryScale, y, y, y, y])
.enter()
.append("g")
.attr("class", "dimension axis")
.attr("transform", function(d, i) {
return "translate(" + x(i) + ")";
})
.each(function(d) {
const yAxis = d3.svg.axis()
.scale(d)
.ticks([])
.orient("left");
d3.select(this).call(yAxis);
});
function parallel(data) {
// Draw one line group per type (car, boat)
// Each line group consists of a train and a test line;
var lineGroup = svg.append("g")
.selectAll(".lineGroup")
.data(data)
.enter()
.append("g")
.attr("class", "lineGroup")
.each(function(d) {
if (d.train)
d3.select(this).append("path")
.datum([d, "train"]);
if (d.test)
d3.select(this).append("path")
.datum(function(d) {
return [d, "test"];
});
})
lineGroup
.attr("stroke", function(d) {
var company = d.type.slice(0, d.type.indexOf(' '));
return color(company);
})
.selectAll("path")
.attr("class", function(d) {
return d[1];
})
.attr("d", draw);
lineGroup
.on("mouseover", function(d) {
// show train when click others
d3.select(this).classed("active", true);
lineGroup
.filter(function(e) {
return e.type !== d.type;
})
.style('opacity', 0.2);
})
.on("mouseout", function(d) {
d3.select(this).classed("active", false);
lineGroup.style('opacity', null);
});
function draw(d) {
var data = d[0],
type = d[1];
var points = data[type + "Normalised"].map(function(v, i) {
return [x(i + 1), y(v)];
});
points.unshift([x(0), categoryScale(data.type)]);
return line(points);
}
}
parallel(processedData);
svg {
font: 12px sans-serif;
}
.lineGroup path {
fill: none;
}
.lineGroup.active .train {
visibility: visible;
}
.train {
visibility: hidden;
stroke-dasharray: 5 5;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="parallel_coor"></div>
CLICK HERE to find out more related problems solutions.