//## Boxplot with dots ## $.fn.boxPlotDotsLegend = function(d_in, inSettings) { var extraDefaults = { xLabelTransform: 'translate(0,5)', yAxisFormat: '.2s', boxLabels: true, showOutliers: true, xPadding: 0.4, yMin: false, yMax: false, yAxisLabelPos: "insideTop", medianTooltip: true, medianUnitTooltip: true, samplesTooltip: true, outlierFormat: '.1f', pointRadius:1, addColorbar:false }; var settings = addDefaultPlotSettings(inSettings, extraDefaults); var width = settings.dimension.w - settings.padding.l - settings.padding.r; var height = settings.dimension.h - settings.padding.t - settings.padding.b; var data = []; var c = 0; var yMin = 20; var yMax = -20; for (var i in d_in) { if (!d_in.hasOwnProperty(i)) { continue; } data[c] = []; data[c][1] = d_in[i].data; data[c][0] = d_in[i]; data[c][0].name = i; data[c][0].url = (d_in[i].url === undefined) ? '' : d_in[i].url; data[c][0].name_label = (d_in[i].name_label === undefined) ? i : d_in[i].name_label; data[c][0].lod_percentage = (d_in[i].lod_percentage === undefined) ? '' : d_in[i].lod_percentage c++; } var chart = d3.box() .whiskers(iqr(1.5)) .height(height) .showLabels(settings.boxLabels) .showOutliers(settings.showOutliers) .tickFormat(d3.format(settings.outlierFormat)) ; if (settings.yMin !== false) { chart.minVal(settings.yMin); } else { chart.minVal(yMin); } if (settings.yMax !== false) { chart.maxVal(settings.yMax); } else { chart.maxVal(yMax); } var svg = d3.select($(this).get(0)).append("svg") .attr("width", settings.dimension.w) .attr("height", settings.dimension.h) .attr("class", "boxplot") .append("g") .attr("transform", "translate(" + settings.padding.l + "," + settings.padding.t + ")"); // the x-axis var xScale = d3.scaleBand() .domain(data.map(function(d) { return d[0].name_label; })) .range([0, width]) .padding(settings.xPadding); var xAxis = d3.axisBottom(xScale) .tickSize(0); // draw the boxplots var tooltips = []; svg.selectAll(".boxplot") .data(data) .enter().append("g") .attr("class", function(d) { return 'group_' + d[0].color }) .attr("transform", function(d) { return "translate(" + xScale(d[0].name_label) + ")"; } ) .append('a') .call(chart.width(xScale.bandwidth())) .each(function(d, i) { var tooltip = d[0].tooltip? d[0].tooltip : d[0].name; if (settings.medianTooltip) tooltip += '<br>Median: '+Math.round(d['quartileData'][1]*10)/10; if (settings.medianUnitTooltip) tooltip += ' '+settings.yAxisLabel; if (settings.samplesTooltip) tooltip += '<br>Samples: '+d[1].length; if (d[0].lod_percentage) tooltip += '<br>' + Math.round(d[0].lod_percentage) +'% below LOD'; d3.select(this) .attr("title", tooltip) .attr("class", function(d) { return d[0].clss; }); if (d[0].color) { d3.select(this).selectAll('rect').style('fill', d[0].color); } tooltips[i] = tooltip; }); var name = ""; // the y-axis var y = d3.scaleLinear() .domain([ chart.minVal(), chart.maxVal() ]) .range([height, 0]); var yAxis; if (settings.yAxisFormat === 'auto') { yAxis = d3.axisLeft(y) .scale(y); } else { yAxis = d3.axisLeft(y) .tickFormat(d3.format(settings.yAxisFormat)); } // new var xPositions = {}; for (let i = 0; i < data.length; i++) { const name = data[i][0].name_label; xPositions[name] = xScale(name) + xScale.bandwidth() / 4; } svg.selectAll("indPoints") .data(data) .enter() .append('g') .each(function(d,i) { const groupName = d[0].name_label; const circles = d3.select(this).selectAll("circle") .data(d[0].data) .enter() .append("circle") .attr("cx", function(d) { return xPositions[groupName] + Math.random() * xScale.bandwidth() / 2; }) .attr("cy", function(d,i) { return y(d)}) .attr("r", settings.pointRadius) .attr("fill", d[0].color) .style("opacity",0.5) .attr("stroke", "black") .attr("stroke-width",.4) }) // new /* old svg.selectAll("indPoints") .data(data) .enter() .each(function(d,i) { name = d[0].name_label; d3.select(this).selectAll("indPoints") .data(d[0].data) .enter() .append("circle") .attr("cx", function(d) {return xScale(name)+ xScale.bandwidth()/4+Math.random()*xScale.bandwidth()/2}) .attr("cy", function(d,i) { return y(d)}) .attr("r", settings.pointRadius) .attr("fill", d[0].color) .style("opacity",0.5) .attr("stroke", "black") .attr("stroke-width",.4) }) */ var overlay = svg.selectAll(".overlay") .data(data) .enter().append("g") // .attr("class", "legend") //.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); overlay.append("rect") .attr("x", function(d, i) { return xScale(d[0].name_label)-xScale.bandwidth()/2}) .attr("y", 0) .attr("width", 2*xScale.bandwidth()) .attr("height", height) .style("fill", function(d, i) { if (d[0].name_label === 'Healthy') { return "rgb(196, 164, 132,0.15)"; // Fill for healthy status } else { return 'transparent'; // Fill for non-healthy status } }) .attr("title", function(d,i) { return tooltips[i]}) // Plot area background svg.insert("rect", ":first-child") .attr("class", "plotBG") .attr("width", xScale.step() * (data.length + xScale.paddingOuter())) .attr("height", y(settings.yMin!==false? settings.yMin : chart.minVal())); // draw y axis var yAxisDrawn = svg.append("g") .attr("class", "y axis") .call(yAxis); // draw y axis Label var textTransform = "rotate(-90)"; var textAnchor = "end"; var textPosX = 0; var textPosY = 10; if (settings.yAxisLabelPos === "outsideMiddle") { // Get the middle point of the axis from the range of the scale var yAxisRange = yAxis.scale().range(); textPosX = Math.abs(yAxisRange[0]-yAxisRange[1])/2 + Math.min(yAxisRange[0], yAxisRange[1]); textTransform = "translate("+yAxisRange[1]+","+yAxisRange[0]+") " + textTransform; textAnchor = "middle"; // Align the label outside the tick labels based on the tick labels bounding boxes and add some extra padding. textPosY = -10 - Math.ceil(d3.max(yAxisDrawn.selectAll("g.tick").nodes(), function(item) {return item.getBBox().width;})); } var yAxisLabel = yAxisDrawn.append("text") .attr("transform", textTransform) .attr("y", textPosY) .attr("x", textPosX) .style("text-anchor", textAnchor); if (settings.yAxisLabel.indexOf("\n") !== -1) { var yLabels = settings.yAxisLabel.split("\n"); var dy = -1 * (yLabels.length-1); for (var idx in yLabels) { yAxisLabel.append("tspan") .attr("x", textPosX) .attr("dy", dy+"em") .text(yLabels[idx]); dy = 1.2; } } else { yAxisLabel.text(settings.yAxisLabel); } // Legend if (settings.legend !== '') { var legend = svg.selectAll(".legend") .data(d3.map(data, function(d){return d[0].legend;}).values()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", settings.legend.x) .attr("y", settings.legend.y) .attr("width", 10) .attr("height", 10) .style("fill", function(d, i) {return d[0].color; }) .on("click", function (d) { // 'd' will be the color that was clicked console.log(d[0].color); toggleElementsByColor(d[0].color); }); legend.append("text") .attr("x", settings.legend.x+15) .attr("y", settings.legend.y+5) .attr("dy", ".35em") .style("text-anchor", settings.legend.txtAnchor) .text(function(d) { return d[0].legend; }); } // draw x axis svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("x", width) .attr("y", -2) .style("text-anchor", "end") .text(settings.xAxisLabel); svg.selectAll('.x.axis .tick text') .attr("transform", settings.xLabelTransform) .attr("text-anchor", settings.xLabelTransform.indexOf('rotate')!==-1? "end":"middle") .each(function (name, i) { var node = d3.select(this); if (tooltips[i]) { node.attr('title', tooltips[i]); } if (data[i][0].url) { var txt = node.text(); node.text(''); node.append('a').attr("xlink:href", data[i][0]['url']).text(txt); } }); if (settings.addColorbar) { svg.selectAll(".bar") .data(data) .enter().append("rect") .attr("class", "bar") .attr("x", function (d) { return xScale(d[0].name_label) - xScale.bandwidth() / 2; }) .attr("y", height ) .attr("width", xScale.bandwidth()*2) .attr("height", 5) .attr("fill", function (d) { return d[0].color; }); } $(this).on('sort', function(e, order_func) { var x0 = xScale.domain( data.sort(order_func) .map(function(d) {return d[0].name_label;}) ) .copy(); var comp = function(a, b) { return x0(a[0].name) - x0(b[0].name); }; svg.selectAll("g.group").sort(comp); svg.selectAll(".x.axis.tick").sort(comp); var transition = svg.transition().duration(50); transition.selectAll("g.group") .attr("transform", function(d) { return 'translate('+x0(d[0].name_label)+', 0)'; }); transition.select(".x.axis") .call(xAxis); // .selectAll(".tick text") }); return { vis: svg, settings: settings, h: height, w: width, x: xScale, xMin: xScale.domain()[0], xMax: xScale.domain()[1], y: y, yMin: y.domain()[0], yMax: y.domain()[1], color: settings.color, xAxis: xAxis, yAxis: yAxis, data: data }; }; $.fn.boxPlotDots = function(d_in, inSettings) { var extraDefaults = { xLabelTransform: 'translate(0,5)', yAxisFormat: '.2s', boxLabels: true, showOutliers: true, xPadding: 0.4, yMin: false, yMax: false, yAxisLabelPos: "insideTop", medianTooltip: true, medianUnitTooltip: true, samplesTooltip: true, outlierFormat: '.1f', pointRadius:1 }; var settings = addDefaultPlotSettings(inSettings, extraDefaults); var width = settings.dimension.w - settings.padding.l - settings.padding.r; var height = settings.dimension.h - settings.padding.t - settings.padding.b; var data = []; var c = 0; var yMin = 20; var yMax = -20; for (var i in d_in) { if (!d_in.hasOwnProperty(i)) { continue; } data[c] = []; data[c][1] = d_in[i].data; data[c][0] = d_in[i]; data[c][0].name = i; data[c][0].url = (d_in[i].url === undefined) ? '' : d_in[i].url; data[c][0].name_label = (d_in[i].name_label === undefined) ? i : d_in[i].name_label; c++; } var chart = d3.box() .whiskers(iqr(1.5)) .height(height) .showLabels(settings.boxLabels) .showOutliers(settings.showOutliers) .tickFormat(d3.format(settings.outlierFormat)) ; if (settings.yMin !== false) { chart.minVal(settings.yMin); } else { chart.minVal(yMin); } if (settings.yMax !== false) { chart.maxVal(settings.yMax); } else { chart.maxVal(yMax); } var svg = d3.select($(this).get(0)).append("svg") .attr("width", settings.dimension.w) .attr("height", settings.dimension.h) .attr("class", "boxplot") .append("g") .attr("transform", "translate(" + settings.padding.l + "," + settings.padding.t + ")"); // the x-axis var xScale = d3.scaleBand() .domain(data.map(function(d) { return d[0].name_label; })) .range([0, width]) .padding(settings.xPadding); var xAxis = d3.axisBottom(xScale) .tickSize(0); // draw the boxplots var tooltips = []; svg.selectAll(".boxplot") .data(data) .enter().append("g") .attr("class", "group") .attr("transform", function(d) { return "translate(" + xScale(d[0].name_label) + ")"; } ) .append('a') .call(chart.width(xScale.bandwidth())) .each(function(d, i) { var tooltip = d[0].tooltip? d[0].tooltip : d[0].name; if (settings.medianTooltip) tooltip += '<br>Median: '+Math.round(d['quartileData'][1]*10)/10; if (settings.medianUnitTooltip) tooltip += ' '+settings.yAxisLabel; if (settings.samplesTooltip) tooltip += '<br>Samples: '+d[1].length; d3.select(this) .attr("title", tooltip) //.attr("xlink:href", function(d) { return d[0].url; }) .attr("class", function(d) { return d[0].clss; }); if (d[0].color) { d3.select(this).selectAll('rect').style('fill', d[0].color); } tooltips[i] = tooltip; }); var name = ""; // the y-axis var y = d3.scaleLinear() .domain([ chart.minVal(), chart.maxVal() ]) .range([height, 0]); var yAxis; if (settings.yAxisFormat === 'auto') { yAxis = d3.axisLeft(y) .scale(y); } else { yAxis = d3.axisLeft(y) .tickFormat(d3.format(settings.yAxisFormat)); } svg.selectAll("indPoints") .data(data) .enter() .each(function(d,i) { name = d[0].name_label; d3.select(this).selectAll("indPoints") .data(d[0].data) .enter() .append("circle") .attr("cx", function(d) {return xScale(name)+ xScale.bandwidth()/4+Math.random()*xScale.bandwidth()/2}) .attr("cy", function(d,i) { return y(d)}) .attr("r", settings.pointRadius) .attr("fill", d[0].color) .style("opacity",0.5) .attr("stroke", "black") .attr("stroke-width",.4) }) // Plot area background svg.insert("rect", ":first-child") .attr("class", "plotBG") .attr("width", xScale.step() * (data.length + xScale.paddingOuter())) .attr("height", y(settings.yMin!==false? settings.yMin : chart.minVal())); // draw y axis var yAxisDrawn = svg.append("g") .attr("class", "y axis") .call(yAxis); // draw y axis Label var textTransform = "rotate(-90)"; var textAnchor = "end"; var textPosX = 0; var textPosY = 10; if (settings.yAxisLabelPos === "outsideMiddle") { // Get the middle point of the axis from the range of the scale var yAxisRange = yAxis.scale().range(); textPosX = Math.abs(yAxisRange[0]-yAxisRange[1])/2 + Math.min(yAxisRange[0], yAxisRange[1]); textTransform = "translate("+yAxisRange[1]+","+yAxisRange[0]+") " + textTransform; textAnchor = "middle"; // Align the label outside the tick labels based on the tick labels bounding boxes and add some extra padding. textPosY = -10 - Math.ceil(d3.max(yAxisDrawn.selectAll("g.tick").nodes(), function(item) {return item.getBBox().width;})); } var yAxisLabel = yAxisDrawn.append("text") .attr("transform", textTransform) .attr("y", textPosY) .attr("x", textPosX) .style("text-anchor", textAnchor); if (settings.yAxisLabel.indexOf("\n") !== -1) { var yLabels = settings.yAxisLabel.split("\n"); var dy = -1 * (yLabels.length-1); for (var idx in yLabels) { yAxisLabel.append("tspan") .attr("x", textPosX) .attr("dy", dy+"em") .text(yLabels[idx]); dy = 1.2; } } else { yAxisLabel.text(settings.yAxisLabel); } // draw x axis svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("x", width) .attr("y", -2) .style("text-anchor", "end") .text(settings.xAxisLabel); svg.selectAll('.x.axis .tick text') .attr("transform", settings.xLabelTransform) .attr("text-anchor", settings.xLabelTransform.indexOf('rotate')!==-1? "end":"middle") .each(function (name, i) { var node = d3.select(this); if (tooltips[i]) { node.attr('title', tooltips[i]); } if (data[i][0].url) { var txt = node.text(); node.text(''); node.append('a').attr("xlink:href", data[i][0]['url']).text(txt); } }); $(this).on('sort', function(e, order_func) { var x0 = xScale.domain( data.sort(order_func) .map(function(d) {return d[0].name_label;}) ) .copy(); var comp = function(a, b) { return x0(a[0].name) - x0(b[0].name); }; svg.selectAll("g.group").sort(comp); svg.selectAll(".x.axis.tick").sort(comp); var transition = svg.transition().duration(100); transition.selectAll("g.group") .attr("transform", function(d) { return 'translate('+x0(d[0].name_label)+', 0)'; }); transition.select(".x.axis") .call(xAxis); // .selectAll(".tick text") }); return { vis: svg, settings: settings, h: height, w: width, x: xScale, xMin: xScale.domain()[0], xMax: xScale.domain()[1], y: y, yMin: y.domain()[0], yMax: y.domain()[1], color: settings.color, xAxis: xAxis, yAxis: yAxis, data: data }; }; // Returns a function to compute the interquartile range. function iqr(k) { return function(d, i) { var q1 = d.quartiles[0], q3 = d.quartiles[2], iqr = (q3 - q1) * k, idx = -1, j = d.length; while (d[++idx] < q1 - iqr); while (d[--j] > q3 + iqr); return [idx, j]; }; } $.fn.boxPlotDotsLog2Scale = function(d_in, inSettings) { var extraDefaults = { xLabelTransform: 'translate(0,5)', yAxisFormat: '.2s', boxLabels: true, showOutliers: true, xPadding: 0.4, yMin: false, yMax: false, yAxisLabelPos: "insideTop", medianTooltip: true, medianUnitTooltip: true, samplesTooltip: true, outlierFormat: '.1f', pointRadius: 1 }; var settings = addDefaultPlotSettings(inSettings, extraDefaults); var width = settings.dimension.w - settings.padding.l - settings.padding.r; var height = settings.dimension.h - settings.padding.t - settings.padding.b; var data = []; var c = 0; for (var i in d_in) { if (!d_in.hasOwnProperty(i)) { continue; } data[c] = []; data[c][1] = d_in[i].data; data[c][0] = d_in[i]; data[c][0].name = i; data[c][0].url = (d_in[i].url === undefined) ? '' : d_in[i].url; data[c][0].name_label = (d_in[i].name_label === undefined) ? i : d_in[i].name_label; c++; } var chart = d3.box() .whiskers(iqr(1.5)) .height(height) .showLabels(settings.boxLabels) .showOutliers(settings.showOutliers) .tickFormat(d3.format(settings.outlierFormat)); var svg = d3.select($(this).get(0)).append("svg") .attr("width", settings.dimension.w) .attr("height", settings.dimension.h) .attr("class", "boxplot") .append("g") .attr("transform", "translate(" + settings.padding.l + "," + settings.padding.t + ")"); // the x-axis var xScale = d3.scaleBand() .domain(data.map(function(d) { return d[0].name_label; })) .range([0, width]) .padding(settings.xPadding); var xAxis = d3.axisBottom(xScale) .tickSize(0); // Logarithmic y-axis with base 2 var y = d3.scaleLog() .base(2) .domain([d3.min(data, d => d3.min(d[0].data)), d3.max(data, d => d3.max(d[0].data))]) .range([height, 0]); // draw the boxplots var tooltips = []; svg.selectAll(".boxplot") .data(data) .enter().append("g") .attr("class", "group") .attr("transform", function(d) { return "translate(" + xScale(d[0].name_label) + ")"; } ) .append('a') .call(chart.width(xScale.bandwidth())) .each(function(d, i) { var tooltip = d[0].tooltip? d[0].tooltip : d[0].name; if (settings.medianTooltip) tooltip += '<br>Median: '+Math.round(d['quartileData'][1]*10)/10; if (settings.medianUnitTooltip) tooltip += ' '+settings.yAxisLabel; if (settings.samplesTooltip) tooltip += '<br>Samples: '+d[1].length; d3.select(this) .attr("title", tooltip) .attr("class", function(d) { return d[0].clss; }); if (d[0].color) { d3.select(this).selectAll('rect').style('fill', d[0].color); } tooltips[i] = tooltip; }); var name = ""; var yAxis; if (settings.yAxisFormat === 'auto') { yAxis = d3.axisLeft(y); } else { yAxis = d3.axisLeft(y) .tickFormat(d3.format(settings.yAxisFormat)); } svg.selectAll("indPoints") .data(data) .enter() .each(function(d,i) { name = d[0].name_label; d3.select(this).selectAll("indPoints") .data(d[0].data) .enter() .append("circle") .attr("cx", function(d) {return xScale(name)+ xScale.bandwidth()/4+Math.random()*xScale.bandwidth()/2}) .attr("cy", function(d,i) { return y(d)}) .attr("r", settings.pointRadius) .attr("fill", d[0].color) .style("opacity",0.5) .attr("stroke", "black") .attr("stroke-width",.4) }); // Plot area background svg.insert("rect", ":first-child") .attr("class", "plotBG") .attr("width", xScale.step() * (data.length + xScale.paddingOuter())) .attr("height", y(settings.yMin!==false? settings.yMin : chart.minVal())); // draw y axis var yAxisDrawn = svg.append("g") .attr("class", "y axis") .call(yAxis); // draw y axis Label var textTransform = "rotate(-90)"; var textAnchor = "end"; var textPosX = 0; var textPosY = 10; if (settings.yAxisLabelPos === "outsideMiddle") { var yAxisRange = yAxis.scale().range(); textPosX = Math.abs(yAxisRange[0]-yAxisRange[1])/2 + Math.min(yAxisRange[0], yAxisRange[1]); textTransform = "translate("+yAxisRange[1]+","+yAxisRange[0]+") " + textTransform; textAnchor = "middle"; textPosY = -10 - Math.ceil(d3.max(yAxisDrawn.selectAll("g.tick").nodes(), function(item) {return item.getBBox().width;})); } var yAxisLabel = yAxisDrawn.append("text") .attr("transform", textTransform) .attr("y", textPosY) .attr("x", textPosX) .style("text-anchor", textAnchor) .text(settings.yAxisLabel); // draw x axis svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("x", width) .attr("y", -2) .style("text-anchor", "end") .text(settings.xAxisLabel); svg.selectAll('.x.axis .tick text') .attr("transform", settings.xLabelTransform) .attr("text-anchor", settings.xLabelTransform.indexOf('rotate')!==-1? "end":"middle") .each(function (name, i) { var node = d3.select(this); if (tooltips[i]) { node.attr('title', tooltips[i]); } if (data[i][0].url) { var txt = node.text(); node.text(''); node.append('a').attr("xlink:href", data[i][0]['url']).text(txt); } }); $(this).on('sort', function(e, order_func) { var x0 = xScale.domain( data.sort(order_func) .map(function(d) {return d[0].name_label;}) ) .copy(); var comp = function(a, b) { return x0(a[0].name) - x0(b[0].name); }; svg.selectAll("g.group").sort(comp); svg.selectAll(".x.axis.tick").sort(comp); var transition = svg.transition().duration(100); transition.selectAll("g.group") .attr("transform", function(d) { return 'translate('+x0(d[0].name_label)+', 0)'; }); transition.select(".x.axis") .call(xAxis); // .selectAll(".tick text") }); return { vis: svg, settings: settings, h: height, w: width, x: xScale, xMin: xScale.domain()[0], xMax: xScale.domain()[1], y: y, yMin: y.domain()[0], yMax: y.domain()[1], color: settings.color, xAxis: xAxis, yAxis: yAxis, data: data }; };