function make_box_and_whiskers_chart(data) { var svg, chart_group, data_extent, canvas, margins, max_draw_space, doc_state var plot = "box_and_whiskers_chart" var button_keys = keys(data) var quantile_keys = ["min", "Q1", "Q2", "Q3", "max"] svg = make_chart_svg(plot) if (svg.select("g." + plot + "_group").empty()) { chart_group = svg.append("g").attr("class", plot + "_group") charts_config.document_state.box_and_whiskers = button_keys[0] } else { chart_group = svg.select("g." + plot + "_group") } chart_group.data(data) doc_state = charts_config.document_state.box_and_whiskers data = data[doc_state] data_extent = { "max": { "x": data.length + 1, "y": d3.max(data, function(d) {return d.max}) }, "min": { "x": 0, "y": d3.min(data, function(d) {return d.min}) } } canvas = extract_canvas_from_svg(svg) margins = make_margins(chart_group, canvas, data_extent, true) max_draw_space = calculate_maximum_drawing_space(canvas, margins) make_buttons(chart_group, margins, canvas, max_draw_space, button_keys) color_buttons(chart_group, doc_state) make_title(chart_group, ["Box and Whiskers Chart"], margins, canvas, max_draw_space) var x_scale = d3.scaleLinear().domain([data_extent.min.x, data_extent.max.x]).range([0, max_draw_space.x]) var y_scale = d3.scaleLinear().domain([data_extent.max.y, data_extent.min.y]).range([0, max_draw_space.y]) var x_axis_label = "Boxes" var y_axis_label = "Values" // use a scale band for the axes var custom_ticks = [""] data.map(function(d) { custom_ticks.push(d.data)}) custom_ticks.push("") make_axes(chart_group, x_scale, y_scale, canvas, margins, max_draw_space, x_axis_label, y_axis_label, custom_ticks) // box parameters box = {} box.spacing = 2 box.width = max_draw_space.x / (data.length + 1) - box.spacing var previous_boxes = chart_group.selectAll("g." + plot + "_box_group").size() var current_boxes = data.length var box_group = chart_group.selectAll("g." + plot + "_box_group").data(data) var whiskers = box_group.selectAll("g.whiskers") if (previous_boxes < current_boxes) { var box_group = box_group.enter().append("g").attr("class", plot + "_box_group") box_group.selectAll("rect").data(function(d) {return [d,d,d,d]}).enter().append("rect") .attr("class", function(d,i) { if (i == 0 || i == 3) { return plot + "_hidden_whisker_box_rect" } else { return plot + "_quantile_box_rect" }}) var whiskers = box_group.append("g").attr("class", "whiskers") whiskers.append("g").attr("class", "lower_whisker").append("path").attr("class", "whisker") whiskers.append("g").attr("class", "upper_whisker").append("path").attr("class", "whisker") } else if (previous_boxes > current_boxes) { box_group.exit().transition().delay(function(d, i) {return 100 + i * 10}) .attr("transform","translate(0,0)").remove() } else { var box_group = chart_group.selectAll("g." + plot + "_box_group") } box_group = chart_group.selectAll("g." + plot + "_box_group") whiskers = box_group.selectAll("g.whiskers") box_group.each(function(dat, ind) { var current_quantile_group = d3.select(this) var quantile_data = {"min": dat.min, "Q1": dat.Q1, "Q2": dat.Q2, "Q3": dat.Q3,"max": dat.max} var low_box, high_box current_quantile_group.selectAll('rect').each( function(d, i){ var current_rect = d3.select(this) var class_name = current_rect.node().className.baseVal var x_shift = box_x_shift(x_scale, ind, margins, box) var y_shift = box_y_shift(y_scale, quantile_data, i, margins, box) if (class_name.includes("_quantile_box")) { current_rect.attr("fill", charts_config.colors.palette[ind]) .attr("stroke", charts_config.plots.box_and_whiskers.stroke) .style("opacity", 1).transition().delay(function(d, i) {return 100 + ind * 10}) .attr("height", box_height(y_scale, quantile_data, i)) .attr("width", box.width) .attr("transform", "translate("+x_shift+","+y_shift+")") } else { current_rect.attr("fill", "white").style("opacity",0) .transition().delay(function(d, i) {return 100 + ind * 10}) .attr("height", box_height(y_scale, quantile_data, i)) .attr("width", box.width) .attr("transform", "translate("+x_shift+","+y_shift+")") if (i == 0) { low_box = {"x":x_shift,"y":y_shift,"h":box_height(y_scale, quantile_data, i),"w":box.width} lower_whisker = make_whisker_path(low_box, false) current_quantile_group.select("g.lower_whisker").select("path.whisker").transition().delay(function(d, i) {return 100 + ind * 10}).attr("d", lower_whisker).attr("fill", "none") .attr("stroke", charts_config.colors.palette[ind]) .attr("stroke-width", charts_config.plots.box_and_whiskers.whiskers.width) } else if (i == 3) { high_box = {"x":x_shift,"y":y_shift,"h":box_height(y_scale, quantile_data, i),"w":box.width} upper_whisker = make_whisker_path(high_box, true) current_quantile_group.select("g.upper_whisker").select("path.whisker").transition().delay(function(d, i) {return 100 + ind * 10}).attr("d", upper_whisker).attr("fill", "none") .attr("stroke", charts_config.colors.palette[ind]) .attr("stroke-width", charts_config.plots.box_and_whiskers.whiskers.width) } } } ) }) box_group.selectAll("rect").on("mouseover", mouseoverFunction) box_group.selectAll("rect").on("mouseout", mouseoutFunction) function mouseoverFunction(d, i) { var i = d3.select(this).node().className.baseVal[d3.select(this).node().className.baseVal.length - 1] d3.select(this).style("opacity", function () {if (i != 0 && i != 3) {return charts_config.plots.box_and_whiskers.opacity.hover} else {return 0}}) var quantile_data = {"min": d.min, "Q1": d.Q1, "Q2": d.Q2, "Q3": d.Q3,"max": d.max} var box = d3.select(this).node().getBBox() y = box.y + box.height / 2 x = x_scale(data.indexOf(d)) + margins.x.left + margins.axes.y + box.width / 2 var tooltip_text = [ "Data: " + d.data, "Max: " + quantile_data.max, "Q3: " + quantile_data.Q3, "Q2: " + quantile_data.Q2, "Q1: " + quantile_data.Q1, "Min: " + quantile_data.min ] // makeTooltip(d3.select(this.parentNode), tooltipText, chartGroup, canvas, margins) make_tooltip(d3.select(this), tooltip_text, chart_group, canvas, margins) // makeTooltip(x, y, tooltipText, chartGroup, canvas, margins) } function mouseoutFunction(d, i) { chart_group.select("g.tooltip_group").remove() var i = d3.select(this).node().className.baseVal[d3.select(this).node().className.baseVal.length - 1] // d3.select(this).style("opacity", charts_config.plots.box.opacity.nonhover) d3.select(this).style("opacity", function () {if (i != 0 && i != 3) {return charts_config.plots.box_and_whiskers.opacity.nonhover} else {return 0}}) } chart_group.select("g.axes").raise() } function box_height(y_scale, extracted_data, i) { var data_keys = keys(extracted_data) return y_scale(extracted_data[data_keys[i]]) - y_scale(extracted_data[data_keys[i+1]]) } function box_x_shift(x_scale, i, margins, box) { var x_shift = x_scale(i) + margins.x.left + margins.axes.y + box.width / 2 + box.spacing return x_shift } function box_y_shift(y_scale, extracted_data, i, margins, box) { var data_keys = keys(extracted_data) var sum = 0 for (var j = 3; j > i; j--) { sum += y_scale(extracted_data[data_keys[j]]) - y_scale(extracted_data[data_keys[j+1]]) } var y_shift = margins.y.top + margins.title + margins.buttons + sum + y_scale(extracted_data[data_keys[4]]) return y_shift } function make_whisker_path(box, which) { var x, y, w, h x = box.x y = box.y h = box.h w = box.w var p if (which) { // make upper whisker p = "M " + (x + w /2) + " " + (y + h) + " " p += "L " + (x + w /2) + " " + (y) + " " p += "L " + (x) + " " + (y) + " " p += "L " + (x + w) + " " + (y) + " " } else { // make lower whisker p = "M " + (x + w /2) + " " + (y) + " " p += "L " + (x + w /2) + " " + (y + h) + " " p += "L " + (x) + " " + (y + h) + " " p += "L " + (x + w) + " " + (y + h) + " " } return p }