how to normalize data in d3 and plot the lines in the axis independent of other line ranges?

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.

Leave a Comment

Your email address will not be published.

Scroll to Top