简体   繁体   English

如何规范化 d3 中的数据并绘制与其他线范围无关的轴中的线

[英]How to normalize data in d3 and plot the lines in the axis independent of other line ranges

I have a parallel coordinates plot visualized in d3.js, my problem lies within the data, the different lines for "car" "bicycle" "boat" and "airplane" have different ranges of numbers, therefore the lines that have low numbers can't be seen and are represented as a flat line instead.我有一个在 d3.js 中可视化的平行坐标图,我的问题在于数据,“汽车”“自行车”“船”和“飞机”的不同线具有不同的数字范围,因此数字低的线可以'不会被看到,而是被表示为一条平线。 This ocurs with "car" line for example because the airplane data is much greater.例如,这出现在“汽车”线中,因为飞机数据要大得多。

I can't seem to figure out how can one normalize data so that when "type" is "car" then normalize from 0 to 1, and when type is "bicycle" or "boat" normalize with 0 to max.我似乎无法弄清楚如何规范化数据,以便当“类型”是“汽车”时从 0 规范化到 1,而当类型是“自行车”或“船”时用 0 规范化到最大值。 But show these data in the axis independent of the others.但是在独立于其他轴的轴中显示这些数据。 As you can see the axis don't have labels for ranges.如您所见,轴没有范围标签。 Instead I plan to show the exact data when hovering over a line.相反,我计划在悬停在一条线上时显示确切的数据。

This is the d3 code:这是 d3 代码:

 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 margin = { top: 5, right: 50, bottom: 5, left: 70 }, width = 600 - margin.left - margin.right, height = 280 - margin.top - margin.bottom; var dimensions = [{ name: "type", scale: d3.scale.ordinal().rangePoints([0, height]), type: "string" }, { name: "first", scale: d3.scale.linear().range([height, 0]), type: "number" }, { name: "second", scale: d3.scale.linear().range([height, 0]), type: "number" }, { name: "third", scale: d3.scale.linear().range([height, 0]), type: "number" }, { name: "fourth", scale: d3.scale.linear().range([height, 0]), type: "number" } ]; var maxRange = d3.max(dataSet, function(d) { return Math.max(d.first, d.second, d.third, d.fourth); }); var x = d3.scale.ordinal() .domain(dimensions.map(function(d) { return d.name; })) .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 yAxis = d3.svg.axis() .orient("left"); 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 + ")"); var dimension = svg.selectAll(".dimension") .data(dimensions) .enter().append("g") .attr("class", "dimension") .attr("transform", function(d) { return "translate(" + x(d.name) + ")"; }); function parallel(dataSet) { dimensions.forEach(function(dimension) { dimension.scale.domain(dimension.type === "number" ? ([0, maxRange]) : dataSet.map(function(d) { return d[dimension.name]; })); }); svg.append("g") .attr("class", "background coorPath") .selectAll("path") .data(dataSet) .enter().append("path") .attr("d", draw); // USE THE COLOR SCALE TO SET THE STROKE BASED ON THE DATA foreground = svg.append("g") .attr("class", "foreground coorPath") .selectAll("path") .data(dataSet) .enter() .append("path") .attr("d", draw) .style("stroke", function(d) { var company = d.type.slice(0, d.type.indexOf(' ')); return color(company); }) dimension.append("g") .attr("class", "axis") .each(function(d) { d3.select(this).call(yAxis.scale(d.scale)); }) // remove axis numbers svg.selectAll(".tick") .each(function(d) { if (typeof d == 'number') { this.remove(); } }); var ordinal_labels = svg.selectAll(".axis text") .on("mouseover", mouseover) .on("mouseout", mouseout); var projection = svg.selectAll(".background path,.foreground path") .on("mouseover", mouseover) .on("mouseout", mouseout); // d3.selectAll("[dataset=train]").attr("visibility", "hidden"); let trainline = d3.selectAll(".coorPath path").filter(function(d) { return d.dataset == "train"; }) .attr("visibility", "hidden"); // On Click, we want to add data to the array and chart let lines = svg.selectAll(".coorPath path").on("mouseover", function(d) { // show train when click others trainline.attr("visibility", "visible") trainline.style("stroke-dasharray", ("5, 5")) lines.style('opacity', function(e) { return e.type === d.type ? 1 : 0.2; }); }); // On Click, we want to add data to the array and chart lines.on("mouseout", function(d) { // show train when click others lines.style('opacity', null); trainline.attr("visibility", "hidden") }); // making parallel coordinates plot interactive to highlight lines function mouseover(d) { svg.classed("active", true); if (typeof d === "string") { projection.classed("inactive", function(p) { return p.name !== d; }); projection.filter(function(p) { return p.name === d; }).each(moveToFront); ordinal_labels.classed("inactive", function(p) { return p !== d; }); ordinal_labels.filter(function(p) { return p === d; }).each(moveToFront); } else { projection.classed("inactive", function(p) { return p !== d; }); projection.filter(function(p) { return p === d; }).each(moveToFront); ordinal_labels.classed("inactive", function(p) { return p !== d.name; }); ordinal_labels.filter(function(p) { return p === d.name; }).each(moveToFront); } } function mouseout(d) { svg.classed("active", false); projection.classed("inactive", false); ordinal_labels.classed("inactive", false); } function moveToFront() { this.parentNode.appendChild(this); } function draw(d) { return line(dimensions.map(function(dimension) { return [x(dimension.name), dimension.scale(d[dimension.name])]; })); } } parallel(dataSet);
 svg { font: 12px sans-serif; } .background path { fill: none; stroke: none; stroke-width: 20px; pointer-events: stroke; } .foreground path { fill: none; stroke: steelblue; stroke-width: 1.5px; } .axis .title { font-size: 11px; font-weight: bold; text-transform: uppercase; } .axis line, .axis path { fill: none; stroke: #000; shape-rendering: crispEdges; } .axis.string { font-size: 6px; } .label { -webkit-transition: fill 125ms linear; } .active .label:not(.inactive) { font-weight: bold; font-size: 11px; } .label.inactive { fill: #ccc; } .foreground path.inactive { stroke: #ccc; stroke-opacity: .2; stroke-width: 1.2px; } /* body { background-color: grey; } */ .node circle { fill: #fff; stroke: steelblue; stroke-width: 2px; } .node circle.highlighted { stroke: #00477F; stroke-width: 5px; } .node text { font: 12px sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 2px; } { box-sizing: border-box; } /* Create four equal columns that floats next to each other */ #svg0 { float: left; width: 120px; margin: 15px; padding: 20px; } #svg1 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg2 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg3 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg4 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg5 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg6 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg7 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg8 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } #svg9 { float: left; width: auto; length: auto; border: 0.8px solid grey; margin: 5px; margin-top: 10px; } .treeClass { width: 5000px; height: 300px; } /* Clear floats after the columns */ .treeClass:after { content: ""; display: table; clear: both; } #scatterplots { width: 3000px; } #parallel_coor { /* float: right; */ /* margin: 50px; */ /* margin-right: 40%; */ margin-top: 2px; margin-bottom: 2px; } #scatterplot { float: left; margin-left: 40px; /*margin: 20px;*/ } #ROCPlot { float: left; margin-right: 300px; margin-left: 300px; } svg rect.borderline { fill: white; stroke-width: 0.8; stroke: grey; } .circle { fill: red; } .dot { fill: black; stroke: grey; } .axis path, .axis line { fill: none; stroke: #aaa; shape-rendering: crispEdges; } .axis text { font-family: sans-serif; font-size: 11px; } ul { height: 28px; list-style-type: none; margin: -8px; padding: 0; overflow: hidden; background-color: #333; } li { float: left; border-right: 1px solid #bbb; } li:last-child { border-right: none; } li a { text-size: 10px; display: block; color: white; text-align: center; padding: 5px 7px; text-decoration: none; } li a:hover:not(.active) { background-color: #111; } .active { background-color: #1679af; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> <div id="parallel_coor"></div>

any help or idea how to tackle this issue with javascript or d3.js would be highly appreciated.任何帮助或想法如何使用 javascript 或 d3.js 解决这个问题将不胜感激。 Thank you!谢谢!

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.编辑以指定要一起规范化的类别,您可以将每个type添加到数组中,将要规范化的类型分组在一起。

Then, loop over the groups and calculate the common min and max values.然后,遍历组并计算常见的minmax 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM