UNPKG

biojs-vis-violin-plot

Version:

BioJS component to provide a box plot graph or bar graph hosted in Stemformatics

1,127 lines (1,037 loc) 59.8 kB
/* Copyright 2015 Ariane Mora Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This is a standalone unit to call when you want to create a box plot graph. */ /* global d3 */ var biojsvislinegraph; module.exports = biojsvislinegraph = function (init_options) { /* this is just to define the options as defaults: added numberFormat*/ default_options = function () { var options = { target: "#graph", unique_id: "Sample_ID", margin: {top: 80, right: 0, bottom: 30, left: 0}, height: 1500, width: 1060, x_axis_title: "Samples", y_axis_title: "Log2 Expression" }; return options; }; // end defaultOptions // Derived from http://bl.ocks.org/mbostock/7555321 d3_wrap = function (text, width) { text.each(function () { var text = d3.select(this), words = text.text().split(/\s+/).reverse(), word, line = [], lineNumber = 0, lineHeight = 1.1, // ems y = text.attr("y"), x = text.attr("x"), // set x to be x, not 0 as in the example dy = parseFloat(text.attr("dy")); // no dy // added this in as sometimes dy is not used if (isNaN(dy)) { dy = 0; } tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em"); while (word === words.pop()) { line.push(word); tspan.text(line.join(" ")); if (tspan.node().getComputedTextLength() > width) { line.pop(); tspan.text(line.join(" ")); line = [word]; new_dy = ++lineNumber * lineHeight + dy; // added this in as well tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", new_dy + "em").text(word).attr('text-anchor', 'middle'); } } }); }; // end d3_wrap // setup margins in a different function (sets up the page options (i.e. margins height etc) setup_margins = function (graph) { options = graph.options; page_options.margin = options.margin; page_options.margin_left = options.margin.left; page_options.margin_top = options.margin.top; page_options.margin_bottom = options.margin.bottom; page_options.height = options.height; page_options.horizontal_grid_lines = options.horizontal_grid_lines; page_options.full_width = options.width + options.margin.left + options.margin.right; page_options.full_height = options.height + options.margin.top + options.margin.bottom; width_to_support_many_samples = 0; if (options.num_line_groups * options.box_width * 2 > options.width) { //Here we are compensating for any overflow that may occur due to many samples width_to_support_many_samples = options.box_width * 3; } page_options.width_to_support_many_samples = width_to_support_many_samples / 2; page_options.width = (width_to_support_many_samples * options.probe_count) + options.width; graph.page_options = page_options; return graph; }; ///end setup margins //Setting up the max and minimum values for the graph //we are trying to take into account not just the data but the lines as well // and we are taking into account that we want to be able to see 0 too return_y_min_max_values = function (graph) { options = graph.options; max_val = 1; min_val = 0; lwr_min_max_values_from_data = d3.extent(options.data, function (d) { // this will go through each row of the options.data // and provide a way to access the values // you want to check that we use the highest and lowest values of the lines and at least stop at 0 lwr = (d.Expression_Value - d.Standard_Deviation); temp = lwr; // this will get the y_column (usually prediction) from the row // have to take into account lwr and upr if (lwr < min_val) { min_val = lwr; } return temp; } ); // do the same for upr upr_min_max_values_from_data = d3.extent(options.data, function (d) { upr = (d.Standard_Deviation + d.Expression_Value); temp = upr; if (upr > max_val) { max_val = upr; } return temp; } ); min = lwr_min_max_values_from_data[0]; max = upr_min_max_values_from_data[1]; // set minimum to 0 if the minimum is a positive number // this means that the minimum number is at least 0 // a negative number will be the only way to drop below 0 if (min > 0) { min = 0; } // similarly, if the max number from the data is -ve // at least show 0 if (max < 1) { max = 1; } for (key in options.horizontal_lines){ value = options.horizontal_lines[key]; if (value > max){ max = value; } if (value < min){ min = value; } } graph.max_val = max_val; graph.min_val = min_val; graph.force_domain = [min, max]; return graph; }; setup_y_axis = function (graph) { svg = graph.svg; max = graph.max_val; // ########################################## Setup Y axis labels ###################################3 /* For the y axis, the scale is linear, so we create a variable called y that we can use later to scale and do other things. in some people call it yScale https://github.com/mbostock/d3/wiki/Quantitative-Scales The range is the range of the graph from the height to 0. This is true for all y axes */ var scaleY = d3.scale.linear() .range([page_options.height, 0]); // setup the yaxis. this is later called when appending as a group .append("g") // Note that it uses the y to work out what it should output //trying to have the grid lines as an option var yAxis = d3.svg.axis() .scale(scaleY) .orient("left") //sets the number of points to increment by 1 whole number. To change see options.increment .ticks(options.increment) .innerTickSize(-page_options.width) .outerTickSize(0); y_column = options.y_column; // d3.extent returns the max and min values of the array using natural order // we are trying to take into account not just the data but the lines as well graph = return_y_min_max_values(graph); scaleY.domain(graph.force_domain); y_axis_legend_y = (graph.full_height - options.margin.top - options.margin.bottom) / 2; /*Adding the title to the Y-axis: stored in options.y_axis_title: information from ** http://bl.ocks.org/dougdowson/8a43c7a7e5407e47afed*/ // only display the title if the user has indicated they would like the title displayed if (options.display.y_axis_title === "yes") { svg.append("text") .text(options.y_axis_title) .attr("text-anchor", "middle") .style("font-family", options.font_style) .style("font-size", options.y_label_text_size) .attr("transform", "rotate(-90)") .style("text-anchor", "middle") .attr("stroke", "black") .attr("x", -y_axis_legend_y) .attr("y", -options.y_label_x_val); //specifies how far away it is from the axis } // Only display the grid lines accross the page if the user has specified they want a grid if (options.display.horizontal_grid_lines === "yes") { svg.append("g") .attr("class", "grid") //creates the horizontal lines accross the page .attr("opacity", options.grid_opacity) .attr("stroke", options.grid_colour) .attr("stroke-width", options.background_stroke_width) .call(yAxis); //implementing the y axis as an axis } else { svg.append("g") .call(yAxis); //implementing the y axis as an axis } graph.svg = svg; graph.scaleY = scaleY; graph.yAxis = yAxis; return graph; }; // end setup_y_axis // Sorts the sorted probe types by sample_type state if necesary so that they can // be grouped by both sample_type state and probe on the x axis // http://bl.ocks.org/phoebebright/raw/3176159/ for sorting sort_x_by_probe_and_sample_type = function (graph) { options = graph.options; //Check if there is an order given for the sample_type states, if none given order by dataset if (options.probe_order !== 'none') { line_group_order = options.line_group_order; probe_order = options.probe_order; nested_values = d3.nest() .key(function (d) { return d.Probe; }) .sortKeys(function (a, b) { return probe_order.indexOf(a.Probe) - probe_order.indexOf(b.Probe); }) .key(function (d) { return d.Sample_Type; }) .entries(options.data); } else { nested_values = d3.nest() .key(function (d) { return d.Probe; }) .key(function (d) { return d.Sample_Type; }) .entries(options.data); } graph.nested_values = nested_values; return graph; }; sort_by_sample_type = function (graph) { if (options.line_group_order !== 'none') { line_order = options.line_group_order; nested_values = d3.nest() .key(function (d) { return d.LineGraphGroup; }) .sortKeys(function (a, b) { return line_order.indexOf(a.LineGraphGroup) - line_order.indexOf(b.LineGraphGroup); }) .entries(options.data); } else { nested_values = d3.nest() .key(function (d) { return d.LineGraphGroup; }) .entries(options.data); } graph.nested_values = nested_values; return graph; }; setup_x_axis = function (graph) { page_options = graph.page_options; svg = graph.svg; options = graph.options; probe_list = options.probe_list; /* http://bost.ocks.org/mike/bar/3/ - Probes along the bottom we use ordinal instead of linear - See here for more: https://github.com/mbostock/d3/wiki/Ordinal-Scales - rangePoints gives greatest accuracy (first to the last point) - Padding is set as a factor of the interval size (i.e. outer padidng = 1/2 dist between two samples) 1 = 1/2 interval distance on the outside 2 = 1 interval dist on the outside. Have set the default to 2 */ var scaleX = d3.scale.linear() .range([0, page_options.width]); /* http://stackoverflow.com/questions/15713955/d3-ordinal-x-axis-change-label-order-and-shift-data-position The order of values for ordinal scales is the order in which you give them to .domain(). That is, simply pass the order you want to .domain() and it should just work. */ scaleX.domain(options.x_axis_list); // setup the xaxis. this is later called when appending as a group .append("g") // Note that it uses the x to work out what it should output var xAxis = d3.svg.axis() .scale(scaleX) .tickSize(0) .orient("bottom"); font_size = "0px"; // set this to 0 if you don't want sample_id as the labels on the x axis svg.append("g") .attr("class", "x_axis") .attr("transform", "translate(0," + page_options.height + ")") .call(xAxis)// this is actually implementing the xAxis as an axis itself .selectAll("text") // text for the xaxes - remember they are on a slant .attr("dx", "-2em") // when rotating the text and the size .style("font-size", font_size) .style("text-anchor", "end") .attr("dy", "-0.1em") .attr("transform", function (d) { return "rotate(-65)"; // this is rotating the text }) .append("text") // main x axis title .attr("class", "label") .attr("x", page_options.width) .attr("y", +24) .style("text-anchor", "end") .text(options.x_axis_title); //graph.probe_list = probe_list; graph.svg = svg; graph.scaleX = scaleX; return graph; }; //end setup_x_axis /* Calculates interval between the probes * also used for calculating the distance bwteen the sample_type states */ calculate_x_value_of_probes = function (graph) { options = graph.options; width = options.width; scaleX = graph.scaleX; probe_count = options.probe_count; section_size = (width / probe_count); graph.size_of_sample_type_collumn = section_size; graph.size_of_probe_collumn = section_size; return graph; }; // calculate_x_value_of_probes /* Calculates interval between the probes * also used for calculating the distance bwteen the sample_type states */ calculate_x_value_of_sample_type = function (graph) { options = graph.options; width = options.width; probe_count = options.probe_count; scaleX = graph.scaleX; sample_type_count = options.sample_type_count; section_size = (width / probe_count) / sample_type_count; graph.size_of_sample_type_collumn = section_size; return graph; }; // calculate_x_value_of_probes /*------------------------------Box plot Calculations--------------------------------------*/ /* Sets up the box plot */ setup_line_graph = function (graph) { options = graph.options; probe_size = graph.size_of_probe_collumn; sample_type_size = graph.size_of_sample_type_collumn; nested_values = graph.nested_values; line_group_list = []; sample_type_list = options.sample_types; probe = null; line_groups = null; for (probe in nested_values) { row = nested_values[probe]; values = row.values; probe_name = row.key; probe_num = parseInt(probe); scale_x = d3.scale.ordinal() .rangePoints([(probe_size * probe_num) + (0.5 * sample_type_size), (probe_size * (probe_num + 1)) - (0.5 * sample_type_size)]); //No padding for now scale_x.domain(sample_type_list); for (line_groups in values) { // These are the expression values for a specific sample grouped by the probe // then the sample_type type so now we need to append all the expression values for this // group then calculate the box plot and draw the values srow = values[line_groups]; samples = srow.values; line_group = srow.key; // Actually draw the box plot ons the graph //Setup the scatter line once all the points have been placed graph = setup_scatter_line(graph, probe_num, parseInt(line_groups), scale_x, samples, probe_name, line_group); } if (options.include_sample_type_x_axis === "yes" && options.display.x_axis_labels === "yes") { graph = setup_extra_labels(graph, scale_x, probe_num); } } graph.line_group_list = line_group_list; return graph; }; add_scatter_for_sample_ids = function (scatter_values, graph, colour, scale_x) { options = graph.options; radius = options.radius; svg = graph.svg; probe_colours = options.probe_colours; scaleY = graph.scaleY; tooltip = options.tooltip; svg.call(tooltip); probe_count = 0; svg.selectAll(".dot") // class of .dot .data(scatter_values) // use the options.data and connect it to the elements that have .dot css .enter() // this will create any new data points for anything that is missing. .append("circle") // append an object circle .attr("class", function (d) { //adds the sample type as the class so that when the sample type is overered over //on the x label, the dots become highlighted return "sample-type-" + d.LineGraphGroup; }) .attr("r", radius) //radius 3.5 .attr("cx", function (d) { var cx = scale_x(d.Sample_ID); return cx; }) .attr("cy", function (d) { // set the y position as based off y_column // ensure that you put these on separate lines to make it easier to troubleshoot var cy = 0; cy = scaleY(d.Expression_Value); return cy; }) .style("stroke", options.background_stroke_colour) .style("stroke-width", "1px") .style("fill", function (d) { return colour[probe_count]; }) .attr("opacity", 0.8) .on('mouseover', tooltip.show) .on('mouseout', tooltip.hide); graph.svg = svg; return graph; }; add_scatter = function (scatter_values, graph, colour, scale_x, y_value_if_error, scatter_tooltip, sample_count) { options = graph.options; radius = options.radius; svg = graph.svg; scaleY = graph.scaleY; svg.call(scatter_tooltip); centreX = scale_x(scatter_values[0].Sample_Type); toggle_count_odd = 1; toggle_count_even = 0; min = 0; max = 0; svg.selectAll(".dot") // class of .dot .data(scatter_values) // use the options.data and connect it to the elements that have .dot css .enter() // this will create any new data points for anything that is missing. .append("circle") // append an object circle .attr("id", scatter_values[0].Sample_Type) .attr("class", function (d) { //adds the sample type as the class so that when the sample type is overered over //on the x label, the dots become highlighted return "sample-type-" + d.LineGraphGroup; }) .attr("r", radius) //radius 3.5 .attr("cx", function (d, i) { if (i % 2 === 0) { cx = centreX + (radius * toggle_count_even); max = cx; toggle_count_even++; //temp = {x: cx, y: scaleY(d.Expression_Value)}; //graph.interpolate_values.push(temp); } else { cx = centreX - (radius * toggle_count_odd); min = cx; toggle_count_odd++; // temp = {x: cx, y: scaleY(d.Expression_Value)}; // graph.interpolate_values.push(temp); } return cx; }) .attr("cy", function (d) { // set the y position as based off y_column // ensure that you put these on separate lines to make it easier to troubleshoot var cy = 0; if (y_value_if_error === 0) { cy = scaleY(d.Expression_Value); } else { cy = scaleY(y_value_if_error); } return cy; }) .style("stroke", options.background_stroke_colour) .style("stroke-width", "1px") .style("fill", colour) .attr("opacity", 0.8) .on('mouseover', scatter_tooltip.show) .on('mouseout', scatter_tooltip.hide); if (scatter_values.length > 1) { len = scatter_values.length - 1; temp1 = {x: centreX - (radius * toggle_count_odd) - (radius * 2), y: scaleY(scatter_values[len].Expression_Value)}; graph.interpolate_min_values.push(temp1); temp2 = {x: centreX + (radius * toggle_count_odd) + (radius * 2), y: scaleY(scatter_values[len].Expression_Value)}; graph.interpolate_max_values.push(temp2); } return svg; }; setup_scatter_line = function (graph, probe_num, sample_num, scale_x, sample_values, probe_name, sample_name) { options = graph.options; samples = sample_values; colour = options.colour; scaleY = graph.scaleY; radius = options.radius; graph.interpolate_min_values = []; graph.interpolate_max_values = []; centreX = scale_x(sample_values[0].Sample_Type); probe_size = graph.size_of_probe_collumn; sample_type_size = graph.size_of_sample_type_collumn; rad = options.level_of_overlap; current_samples = []; sample_count = 0; sample_values.sort(function (a, b) { return a.Expression_Value - b.Expression_Value; }); lwr_l = {x: centreX, y: scaleY(sample_values[0].Expression_Value) + (2 * radius)}; graph.interpolate_min_values.push(lwr_l); graph.interpolate_max_values.push(lwr_l); final_diff = 0; diff = 0; //Difference between the sample types expression values for each ID for (i = 0; i < sample_values.length; i++) { current_samples.push(sample_values[i]); if (i === sample_values.length - 1) { diff += Math.abs(sample_values[i].Expression_Value - sample_values[i - 1].Expression_Value); } else { diff += Math.abs(sample_values[i].Expression_Value - sample_values[i + 1].Expression_Value); } if (diff < rad) { sample_count++; } else { svg = add_scatter(current_samples, graph, colour[sample_num], scale_x, 0, options.tooltip, sample_count); console.log(current_samples); current_samples = []; sample_count = 0; if (diff > (2 * rad)) { if (i === sample_values.length - 1) { yval = sample_values[i].Expression_Value; final_diff = diff; } else { yval = sample_values[i + 1].Expression_Value; } tmp = {x: centreX, y: scaleY(yval) - (diff / 2)}; graph.interpolate_min_values.push(tmp); graph.interpolate_max_values.push(tmp); } diff = 0; } } //----------------------NEED TO FIX THIS CURRENTLY ADDING LAST SCATTER SEPARATELY ----------------------- len = sample_values.length - 1; //current_samples = []; //current_samples.push(sample_values[len]); //svg = add_scatter(current_samples, graph, colour[sample_num], scale_x, 0, options.tooltip, sample_count); line = d3.svg.line() .x(function (d) { return d.x; }) .y(function (d) { return d.y; }) .interpolate("basis"); upr_l = {x: centreX, y: scaleY(sample_values[len].Expression_Value - (final_diff)) - (2 * rad)}; graph.interpolate_min_values.push(upr_l); graph.interpolate_max_values.push(upr_l); graph.interpolate_min_values.sort(function (a, b) { return a.y - b.y; }); graph.interpolate_max_values.sort(function (a, b) { return b.y - a.y; }); for (i in graph.interpolate_max_values) { graph.interpolate_min_values.push(graph.interpolate_max_values[i]); } /* svg.append("path") .attr("d", line(graph.interpolate_max_values)) .attr("stroke", "black") .style("stroke-width","2") .style("fill", "none"); */ svg.append("path") .attr("d", line(graph.interpolate_min_values)) .attr("stroke", "black") .style("stroke-width", "2") .style("fill", "none");/* .on("mouseover", function(data, i) { if (options.legend_toggle_opacity !=="no") { var leg = document.getElementById(data[i].Sample_Type); console.log(leg); console.log("LEG"); if (leg.style.opacity !==0) { d3.select(leg).style("opacity", 0); } else { d3.select(leg).style("opacity", 1); } } });*/ graph.svg = svg; return graph; }; make_scatter_tooltip = function (probe, line_group, sample_type, sample_ids) { var tooltip_scatter = d3.tip() .attr('class', 'd3-tip') .offset([0, +110]) .html(function (d) { temp = "Probe: " + probe + "<br/>" + "Line Group: " + line_group + "<br/>" + "Sample_Type: " + sample_type + "<br/>" + "Samples: " + sample_ids + "<br/>"; return temp; }); return tooltip_scatter; }; sort_by_sample_type = function (data, sample_type_order) { if (sample_type_order !== "none") { data.sort(function (a, b) { return sample_type_order.indexOf(a.Sample_Type) - sample_type_order.indexOf(b.Sample_Type); }); } else { //SORTING FOR LINEGRAPH data.sort(function (a, b) { return a.Sample_Type.localeCompare(b.Sample_Type); }); } return data; }; add_scatter_for_multiple_reps = function (graph, sample_types_with_replicates, colour, scale_x, scatter_tooltip) { svg = graph.svg; options = graph.options; box_width = options.box_width; box_width_wiskers = box_width / 2; scaleY = graph.scaleY; x_buffer = scale_x(sample_types_with_replicates[0].Sample_Type); bar_vals = calculate_error_bars(sample_types_with_replicates); svg = add_line_to_box(options.stroke_width, x_buffer, box_width / 2, bar_vals[0], svg, scaleY, colour, box_width_wiskers / 2); //Add max line svg = add_line_to_box(options.stroke_width, x_buffer, box_width / 2, bar_vals[2], svg, scaleY, colour, box_width_wiskers / 2); //Add median lines svg = add_vertical_line_to_box(options.stroke_width, x_buffer, bar_vals[0], bar_vals[2], svg, scaleY, colour); svg = add_scatter(sample_types_with_replicates, graph, colour, scale_x, bar_vals[1], scatter_tooltip); //(options.stroke_width, x_buffer, box_width, box_plot_vals[1], svg, scaleY, colour_wiskers, box_width_wiskers); graph.svg = svg; graph.temp_y_val = bar_vals[1]; return graph; }; /* Takes the array of samples for a specific sample type * already ordered */ calculate_error_bars = function (values_raw) { var values = []; x = 0; for (i in values_raw) { values.push(values_raw[i].Expression_Value); } var mean = get_mean_value(values); sum = 0; numbers_meaned = []; for (x in values) { numbers_meaned.push(Math.abs(values[x] - mean)); } standard_deviation = get_mean_value(numbers_meaned); return [mean - standard_deviation, mean, mean + standard_deviation]; }; get_mean_value = function (values) { sum = 0; for (i in values) { sum += values[i]; } mean = sum / values.length; return mean; }; add_line_to_box = function (stroke_width, x_buffer, box_width, y_value, svg, scaleY, colour, box_width_wiskers) { svg.append("line") .attr("x1", (x_buffer - box_width) + box_width_wiskers) .attr("x2", (x_buffer + box_width) - box_width_wiskers) .attr("y1", scaleY(y_value)) .attr("y2", scaleY(y_value)) .attr("shape-rendering", "crispEdges") .attr("stroke-width", stroke_width) .attr("stroke", colour); return svg; }; add_vertical_line_to_box = function (stroke_width, x_position, y_lower, y_upper, svg, scaleY, colour_wiskers) { svg.append("line") .attr("x1", x_position) .attr("x2", x_position) .attr("y1", scaleY(y_lower)) .attr("y2", scaleY(y_upper)) .attr("shape-rendering", "crispEdges") .attr("stroke-width", stroke_width) .attr("stroke", colour_wiskers); return svg; }; //setting up the line to append for each of the values (i.e. line between scatter points) //http://bl.ocks.org/d3noob/e99a762017060ce81c76 helpful for nesting the probes add_scatter_line = function (svg, colour, scale_x, scaleY, line_stroke_width, line_vals) { for (i = 0; i < line_vals.length - 1; i++) { svg.append("line") .attr("x1", scale_x(line_vals[i].Sample_Type)) .attr("x2", scale_x(line_vals[i + 1].Sample_Type)) .attr("y1", scaleY(line_vals[i].Expression_Value)) .attr("y2", scaleY(line_vals[i + 1].Expression_Value)) .attr("shape-rendering", "crispEdges") .attr("stroke-width", line_stroke_width) .attr("stroke", colour); } return svg; };//end setup_scatter_line /* A small function to test the values from the computed values * Checks values from graphs downloaded from stemformatics */ test_values = function (name, box_plot_vals, graph, options) { //var fs = require('fs'); //name in format as saved by stemformatics: name, average. standard deviation, min, max, median, Q1, Q3 row = name + "," + 0 + "," + 0 + "," + box_plot_vals[0] + "," + box_plot_vals[4] + "," + box_plot_vals[2] + "," + box_plot_vals[1] + "," + box_plot_vals[3]; if (options.bar_graph === "yes") { row = name + "," + box_plot_vals[1] + "," + 0 + "," + box_plot_vals[0] + "," + box_plot_vals[2] + "," + 0 + "," + 0 + "," + 0; } //console.log(row); /*fs.writeFile(options.test_path, row, function(err) { if(err) { return console.log(err); } console.log("The file was saved!"); }); */ }; /*------------------------End of box plot calculations -----------------------------------*/ /* Adds probe labels to the bottom of the graph */ setup_probe_labels = function (graph) { svg = graph.svg; scaleX = graph.scaleX; vertical_lines = options.x_axis_list; page_options = graph.page_options; options = graph.options; correction_for_single = 1; if (vertical_lines.length === 1) { correction_for_single = 0.75; } //Below are used for calculating the positioning of the labels size_of_probe_collumn = graph.size_of_probe_collumn; padding = 2 * page_options.width_to_support_many_samples; if (vertical_lines.length === 1 && options.include_sample_type_x_axis === "yes") { size_of_probe_collumn = 0.75 * size_of_probe_collumn; } svg.selectAll(".probe_text") .data(vertical_lines).enter() .append("text") // when rotating the text and the size .text( function (d) { // If the user does't want to have labels on the x axis we don't append the probe return d; } ) .attr("class", "x_axis_diagonal_labels") .style("text-anchor", "end") // Even though we are rotating the text and using the cx and the cy, we need to // specify the original y and x .attr("y", page_options.height + options.x_axis_label_padding) .attr("x", function (d, i) { x_value = (size_of_probe_collumn * (i + correction_for_single)); if (options.include_sample_type_x_axis !== "yes") { x_value = x_value - (0.5 * size_of_probe_collumn); } return x_value; } ) // when rotating the text and the size .style("font-family", options.font_style) .style("font-size", options.text_size) .attr("transform", function (d, i) { // actual x value if there was no rotation x_value = (size_of_probe_collumn * (i + correction_for_single)); // actual y value if there was no rotation if (options.include_sample_type_x_axis === "yes") { y_value = page_options.height + options.size_of_sample_type_labels; } else { //x_value = //x_value - (0.5 * size_of_probe_collumn) + (padding * (i + 1)); y_value = page_options.height + 10; } return "rotate(" + options.x_axis_text_angle + "," + x_value + "," + y_value + ")"; } ); graph.svg = svg; return graph; }; /* // combination of this: http://stackoverflow.com/questions/11252753/rotate-x-axis-text-in-d3 // and this: http://www.w3.org/TR/SVG/coords.html#TransformAttribute // basically, you just have to specify the angle of the rotation and you have // additional cx and cy points that you can use as the origin. // therefore you make cx and cy your actual points on the graph as if it was 0 angle change // you still need to make the y and x set as above*/ setup_extra_labels = function (graph, scale_x, probe_num) { svg = graph.svg; scaleX = graph.scaleX; page_options = graph.page_options; options = graph.options; y_val = options.height + 10; //Below are used for calculating the positioning of the labels size_of_probe_collumn = graph.size_of_probe_collumn / options.data.length; padding = 2 * page_options.width_to_support_many_samples; sort_by_sample_id = options.sort_by_sample_id; svg.selectAll(".sample_type_text") .data(options.data).enter() .append("text") // when rotating the text and the size .text(function (d) { // If the user does't want to have labels on the x axis we don't append the probe if (sort_by_sample_id === "no") { return d.Sample_Type; } else { return d.Sample_ID; } }) /*combination of this: http://stackoverflow.com/questions/11252753/rotate-x-axis-text-in-d3 and this: http://www.w3.org/TR/SVG/coords.html#TransformAttribute basically, you just have to specify the angle of the rotation and you have additional cx and cy points that you can use as the origin. therefore you make cx and cy your actual points on the graph as if it was 0 angle change you still need to make the y and x set as above */ .attr("class", "x_axis_diagonal_labels") .style("text-anchor", "end") // Even though we are rotating the text and using the cx and the cy, we need to // specify the original y and x .attr("y", page_options.height + options.x_axis_label_padding) .attr("x", function (d) { if (sort_by_sample_id === "no") { x_value = scale_x(d.Sample_Type) + (size_of_probe_collumn); } else { x_value = scale_x(d.Sample_ID); } return x_value; } ) // when rotating the text and the size .style("font-family", options.font_style) .style("font-size", options.text_size) .attr("transform", function (d, i) { // actual x value if there was no rotation if (sort_by_sample_id === "no") { x_value = scale_x(d.Sample_Type) + (size_of_probe_collumn); } else { x_value = scale_x(d.Sample_ID); } y_value = y_val; return "rotate(" + options.x_axis_text_angle + "," + x_value + "," + y_value + ")"; } ); graph.svg = svg; return graph; }; /* sets up the vertical lines between the sample points */ setup_vertical_lines = function (graph) { svg = graph.svg; sample_id_list = options.sample_id_list; page_options = graph.page_options; padding = (2 * page_options.width_to_support_many_samples); size_of_probe_collumn = graph.size_of_probe_collumn; sample_size = graph.id_size / 2; scale_x = graph.scale_x; sort_by_sample_id = options.sort_by_sample_id; if (sort_by_sample_id === "no") { line_count = options.probe_count; } else { line_count = options.sample_id_count; } for (i = 0; i < line_count; i++) { data = options.data[i]; svg.append("line") .attr("x1", function (d) { if (sort_by_sample_id === "no") { avg = (padding + size_of_probe_collumn) * (i + 1); //returns the position for the line } else { avg = scale_x(data.Sample_ID) - (sample_size); } return avg; } ) .attr("x2", function (d) { if (sort_by_sample_id === "no") { avg = (padding + size_of_probe_collumn) * (i + 1); //returns the position for the line } else { avg = scale_x(data.Sample_ID) - (sample_size); } return avg; } ) .attr("y1", function (d) { temp = 0; return temp; } ) .attr("y2", function (d) { // this is to keep it within the graph temp = page_options.height; return temp; } ) .attr("shape-rendering", "crispEdges") .attr("stroke-width", options.line_stroke_width) .attr("opacity", "0.2") .attr("stroke", "black"); } graph.svg = svg; return graph; }; // //setup_vertical_lines //This is to setup multiple horizontal lines with a label //colours can be chosen (options) otherwise a random colour is chosen setup_horizontal_lines = function (graph) { svg = graph.svg; scaleX = graph.scaleX; scaleY = graph.scaleY; options = graph.options; width = page_options.width; lines = options.lines; horizontal_lines = options.horizontal_lines; font_size = options.text_size; margin_y_value = 20; colour_random = d3.scale.category20(); //adds the horizontal lines to the graph. Colours are given, if no colour is given //a coloour is chosen at random. for (var i = 0; i < horizontal_lines.length; i++) { var name = horizontal_lines[i][0]; //if no colours are defined pick one at random if (horizontal_lines[i][1] === undefined) { var colour = colour_random; } else { var colour = horizontal_lines[i][1]; } var y_value = horizontal_lines[i][2]; svg.append("line") // append an object line .attr("class", "lines") .attr("data-legend", function (d) { return name; }) .attr("x1", 0) .attr("x2", width) .attr("y1", scaleY(y_value)) .attr("y2", scaleY(y_value)) .attr("shape-rendering", "crispEdges") .attr("stroke-width", options.line_stroke_width) .attr("opacity", "0.6") .style("stroke", colour); svg.append("text") .attr("x", margin_y_value + (name.length * 10) + 15) .attr("y", scaleY(y_value) - 10) .text(name) .attr("text-anchor", "middle") .style("font-family", options.font_style) .style("font-size", font_size) .style("fill", colour) .attr("class", options.title_class); } graph.svg = svg; return graph; }; // end setup_horizontal_lines preprocess_lines = function (graph) { horizontal_lines = graph.options.horizontal_lines; lines = Array(); key = null; for (key in horizontal_lines) { name = key; value = horizontal_lines[key]; data_line = {'value': value, 'name': name}; lines.push(data_line); } graph.options.lines = lines; return graph; }; // end preprocess_lines //sets up bars under the graph so that when the user hovers the mouse above it //Essentially sets a bar graph up under the box plot setup_hover_bars = function (graph) { svg = graph.svg; options = graph.options; //sets up the tooltip which displys the sample type when the bar is hovered //over. tip = options.tip; svg.call(tip); opacity = 0.4; // start with the colour being white scaleX = graph.scaleX; scaleY = graph.scaleY; vertical_lines = options.legend_list; page_options = graph.page_options; //This is required so taht the bars stop midway between the two sample types (i.e. on the line) padding = options.padding; //Appending the bar to the graph svg.selectAll(".bar") .data(options.data) // use the options.data and connect it to the elements that have .dot css .enter() // this will create any new data points for anything that is missing. .append("rect") .attr("id", function (d) { return d; } ) .style("opacity", opacity) .style("fill", "#FFA62F") .attr("x", function (d) { if (sort_by_sample_id === "no") { avg = scaleX(d.Probe); //returns the position for the line } else { avg = scaleX(d.Sample_ID) - (sample_size); } return avg; }) .attr("width", function (d, i) { if (sort_by_sample_id === "no") { avg = scaleX(d[i + 1].Probe) - scaleX(d[i].Probe); //returns the position for the line } else { avg = scaleX(d[i + 1].Sample_ID) - scaleX(d[i].SampleID); } return avg; }) .attr("y", 0) .attr("height", page_options.height - 2) .on("mouseover", function (d) { //on the mouse over of the graph the tooltip is displayed (tranisition fades it in) barOver = document.getElementById(d.line_group); barOver.style.opacity = "0.5"; tooltip_sample = d3.select("body").append("div") .attr('class', 'tooltip') .style("opacity", 1e-6) .html(function () { temp = "Sample Type: " + d.line_group + "<br/>"; return temp; }); tooltip_sample.style("opacity", 1); }) .on("mousemove", function (d) { //on mousemove it follows the cursor around and displayed the current sample type it is hovering over tooltip_sample.html = "Sample Type: " + d.line_group + "<br/>"; tooltip_sample.style('left', Math.max(0, d3.event.pageX - 150) + "px"); tooltip_sample.style('top', (d3.event.pageY + 20) + "px"); tooltip_sample.show; }) .on("mouseout", function (d) { tooltip_sample.remove(); barOver = document.getElementById(d.line_group); barOver.style.opacity = "0"; }); graph.svg = svg; return graph; }; //Sets up the SVG element setup_svg = function (graph) { options = graph.options; page_options = graph.page_options; full_width = page_options.full_width; full_height = page_options.full_height; graph.full_width = full_width; graph.full_height = full_height; background_stroke_width = options.background_stroke_width; background_stroke_colour = options.background_stroke_colour; // clear out html $(options.target) .html('') .css('width', full_width + 'px') .css('height', full_height + 'px'); // setup the SVG. We do this inside the d3.tsv as we want to keep everything in the same place // and inside the d3.tsv we get the data ready to go (called options.data in here) var svg = d3.select(options.target).append("svg") .attr("width", full_width) .attr("height", full_height) .append("g") // this is just to move the picture down to the right margin length .attr("transform", "translate(" + page_options.margin.left + "," + page_options.margin.top + ")"); // this is to add a background color // from: http://stackoverflow.com/questions/20142951/how-to-set-the-background-color-of-a-d3-js-svg svg.append("rect") .attr("width", page_options.width) .attr("hei