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
JavaScript
/*
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