/**
* D3 for basic PCA plot
*
* @class
* @extends Biojs
* @author Rowland Mosbergen
* @version 0.0.1
* @category 3
*
* @requires d3 v3.4.8
* @dependency
* @requires Biojs
* @dependency
* @requires jQuery 1.8.2
* @dependency
* @requires D3 queue
* @dependency
*
* @param {Object} options An object with the options for PCA component
* @option {string} target
* Identifier of the DIV tag where the component should be displayed.
*
*
*
*
* @option {int} [height=800]
* Full height of svg
*
* @option {int} [width=1024]
* Full width of svg
*
* @option {string} [shape="circle"]
* To be passed into d3.svg.symbol eg. circle, square
*
* @option {string} [unique_id="sample_id"]
* Unique id column of the data file.
*
*
* @option {string} [x_column="component1"]
* column of the data file that will be used as the x axis.
*
* @option {string} [y_column="component2"]
* column of the data file that will be used as the y axis.
*
* @option {string} [x_axis_title="Component 1"]
* title of the x axis.
*
*
* @option {string} [y_axis_title="Component 2"]
* title of the y axis.
*
* @option {string} [title_class="title"]
* class to add to the title.
*
* @option {string} [tooltip_class="tooltip"]
* class to add to the tooltip.
*
* @option {string} [point_class="dot"]
* class to add to the points.
*
* @option {string} [legend_class="legend"]
* class to add to the legend.
*
* @option {function} [tooltip_name="function"]
* function to use for the tooltip. defaults to:
*
* function (d,this_object){
* name = d[this_object.unique_id] +':' + d[this_object.x_column] + ' ' + d[this_object.y_column];
* return name;
* },
*
* @option {object} [margin="{top:80,right:20,bottom:30,left:40}"]
* margin around the svg graph.
*
* @option {array} [domain="array"]
* specific types of values to be used for colours. eg. ['Apple','Orange','Pear','Grape']
*
* @option {array} [domain_colours="array"];
* colours that match the domain array eg. ['blue','green','orange','red'] where blue is Apple
*
*
* @example
* //data columns are state,type,component1,component2 tab separated
* d3.tsv('../biojs/data/usarrests.tsv',function (error,data){
*
* data.forEach(function(d){
* d.component1 = +d.component1;
* d.component2 = +d.component2;
* });
*
* target = "#YourOwnDivId";
* var options = {
* target: target,
* title: "US Arrests",
* unique_id: "state",
* domain : ['Republican','Democrat'],
* domain_colours:['blue','green'],
* margin:{top: 80, right: 20, bottom: 30, left: 40},
* height: 600,
* width:960,
* x_axis_title: "Principal Component #1",
* y_axis_title: "Principal Component #2",
* shape:'square',
* point_class: 'states',
* tooltip_name: function (d,this_object){
* name = d[this_object.unique_id] + '| |' + d[this_object.x_column] + ' ' + d[this_object.y_column];
* return name;
* },
*
* data: data
* }
* var new_instance = new Biojs.PCAD3(options);
* new_instance.pointClick(
* function( objEvent ) {
* alert('You just clicked ' + objEvent.data_object.state);
* }
* );
*
* });
*
* @css-example
* #CSS for tool tip with default class tooltip
* div.tooltip{
* @include default_large_text;
* padding: 8px;
* display: inline-block;
* max-width: 300px;
* z-index: 11000;
* position: absolute;
* color: #5a5a5a;
* background-color: #FFF;
* border: 1px solid #000;
* display:block;
* word-wrap: break-word;
* }
*
*
*
*
*
*
*
*/
// later on want to download the svg
// http://stackoverflow.com/questions/18492617/button-to-download-inpage-svg-code-as-an-svg-file
Biojs.PCAD3 = Biojs.extend ({
/** @lends Biojs.PCAD3# */
constructor: function (options) {
/* Your constructor code here
Note: options provided on instantiation time overrides the
default values in this.opt, automatically; i.e. ‘options’
argument refers to the provided values and ‘this.opt’
refers to the the overridden options. For more details,
go to section 6.3.2 in the spec. doc. */
var self = this;
this._container = jQuery(self.opt.target);
this._container.addClass('PCA');
this._init();
this._setup_graph();
this._set_points();
this._setup_legend();
},
opt: {
/* Target DIV
This mandatory parameter is the identifier of the DIV tag where the
component should be displayed. Use this value to draw your
component into. */
target: "YourOwnDivId",
title: "Title",
/* Component Options
These options defines the input data for your component.
Must have a default value for each one. Note that, either some or
all of values might be replaced by the constructor using the values
provided in instantiation time.
*/
height: 800,
width: 1024,
shape: "circle",
unique_id: "sample_id",
x_column: "component1",
y_column: "component2",
x_axis_title: "Component 1",
y_axis_title: "Component 2",
title_class: "title",
tooltip_class: "tooltip",
point_class: "dot",
legend_class: "legend",
tooltip_name: function (d,this_object){
name = d[this_object.unique_id] +':' + d[this_object.x_column] + ' ' + d[this_object.y_column];
return name;
},
margin: {top: 80, right: 20, bottom: 30, left: 40}
},
eventTypes: [
/* Event Names
The parent class Biojs build the event handlers automatically
with the names defined here. Use this.raiseEvent(,
) for triggering an event from this component. Where,
is a string (defined in eventTypes) and is
an object which should be passed to the registered listeners.
*/
/**
* @name Biojs.PCAD3#pointClick
* @event
* @param {function} actionPerformed A function which receives an {@link Biojs.Event} object as argument.
* @eventData {Object} source The component which triggered the event.
* @eventData {Object} data_object information of the point that has been clicked.
* @example
*/
"pointClick"
],
_init: function() {
this.target = this.opt.target;
this.page_options = new Object();
this.unique_id = this.opt.unique_id;
this.x_column = this.opt.x_column;
this.y_column = this.opt.y_column;
this.tooltip_name = this.opt.tooltip_name;
this.tooltip_class = this.opt.tooltip_class;
if (this.opt.domain != undefined){
this.page_options.domain = this.opt.domain;
this.page_options.color = d3.scale.ordinal().domain(this.page_options.domain).range(this.opt.domain_colours);
} else{
this.page_options.color = d3.scale.category10();
}
this.title = this.opt.title;
this.title_class = this.opt.title_class;
this.x_axis_title = this.opt.x_axis_title;
this.y_axis_title = this.opt.y_axis_title;
this.page_options.shape = this.opt.shape;
this.page_options.margin = this.opt.margin;
this.page_options.width = this.opt.width - this.page_options.margin.left - this.page_options.margin.right;
this.page_options.height = this.opt.height - this.page_options.margin.top - this.page_options.margin.bottom;
this.data = this.opt.data;
columns = new Array();
for (var name in this.data[0]) { if (this.data[0].hasOwnProperty(name)) { columns.push(name); }}
this.columns = columns;
this.point_class = this.opt.point_class;
this.legend_class = this.opt.legend_class;
},
/* Your own ‘PUBLIC’ methods */
_setup_graph: function (){
var x = d3.scale.linear()
.range([0, this.page_options.width]);
var y = d3.scale.linear()
.range([this.page_options.height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
$(this.target).html('');
var svg = d3.select(this.target).append("svg")
.attr("width", this.page_options.width + this.page_options.margin.left + this.page_options.margin.right)
.attr("height", this.page_options.height + this.page_options.margin.top + this.page_options.margin.bottom)
.append("g")
.attr("transform", "translate(" + this.page_options.margin.left + "," + this.page_options.margin.top + ")");
svg.append("text")
.attr("x", (this.page_options.width / 2))
.attr("y", 0 - (this.page_options.margin.top / 2))
.attr("text-anchor", "middle")
.text(this.title).attr("class",this.title_class);
x_column = this.x_column;
y_column = this.y_column;
x.domain(d3.extent(this.data, function(d) { return d[x_column]; })).nice();
y.domain(d3.extent(this.data, function(d) { return d[y_column]; })).nice();
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + this.page_options.height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", this.page_options.width)
.attr("y", -6)
.style("text-anchor", "end")
.text(this.x_axis_title);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(this.y_axis_title);
this.x = x;
this.y = y;
this.xAxis = xAxis;
this.yAxis = yAxis;
this.svg = svg;
},
_setup_legend: function (){
var legend = this.svg.selectAll("."+this.legend_class)
.data(this.page_options.color.domain())
.enter().append("g")
.attr("class", this.legend_class)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", this.page_options.width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", this.page_options.color);
legend.append("text")
.attr("x", this.page_options.width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
this.legend = legend;
},
_set_points: function (){
var self = this;
// https://github.com/mbostock/d3/wiki/SVG-Shapes
// For circle (part of the append), cx,cy and r are used
var tooltip = d3.select(self.target)
.append("div")
.attr("class", self.tooltip_class)
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
self.svg.selectAll("."+self.point_class)
.data(self.data)
.enter().append("path")
.attr("class", self.point_class)
.attr("transform", function(d) { return "translate(" + self.x(d[x_column]) + "," + self.y(d[y_column]) + ")"; })
.on("mouseover", function(d){tooltip.text(self.tooltip_name(d,self)); return tooltip.style("visibility", "visible");})
.on("mousemove", function(d){tooltip.text(self.tooltip_name(d,self)); return tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");})
.on("click", function(d){self.raiseEvent('pointClick', { data_object:d });})
.on("mouseout", function(d){return tooltip.style("visibility", "hidden");})
if (jQuery.inArray('size', self.columns) != -1 && jQuery.inArray('shape', self.columns) != -1){
self.svg.selectAll("."+self.point_class)
.attr("d", d3.svg.symbol().type(function (d) { return d.shape;}).size(function(d){ return d.size; }));
}else if (jQuery.inArray('size', self.columns)!=-1 ){
self.svg.selectAll("."+self.point_class)
.attr("d", d3.svg.symbol().type(function (d) { return self.page_options.shape;}).size(function(d){ return d.size; }));
} else {
self.svg.selectAll("."+self.point_class)
.attr("d", d3.svg.symbol().type(function (d) { return self.page_options.shape;}));
}
if ($.inArray('color_type', this.columns)!= -1){
this.svg.selectAll("."+self.point_class)
.style("fill", function(d) { return self.page_options.color(d.color_type); });
} else{
this.svg.selectAll("."+self.point_class)
.style("fill", function(d) { return self.page_options.color(d.type); });
}
}
});