/**
* @fileoverview Matrix graphing library for javascript.
* @author birm@rbirm.us (Ryan Birmingham)
* @license Copyright 2017 Ryan Birmingham.
* Licensed under GPL-3.
*/
MiniMat = require("minimat");
d3 = require("d3");
/** canvas creation tool outside of class
* @param {str} tag - the html id to use for this canvas
* @param {int} x_len - the number of columns to draw
* @param {int} x_len - the number of rows to draw
* @param {int} drawsize - the height and width to draw each matrix element as, in pixels
*/
function make_canvas(tag, x_len, y_len, drawsize){
// if tag doesn't start with "#", add it
if (!(tag[0] === "#")){
tag = tag + "#";
}
// figure out how big to draw the whole plot
var drawx = parseInt(drawsize*x_len, 10);
var drawy = parseInt(drawsize*y_len, 10);
// add the canvas to the page
var canvas = d3.select("body")
.append("svg")
.attr("id", tag)
.attr("width", drawx)
.attr("height", drawy);
// return the canvas
return canvas;
}
/**
* MiniMat graph object creator/tracker.
* @constructor
* @param {MiniMat} Mat - The matrix to draw
* @param {str} tag - the tag to create the chart as (or update)
* @param {int} drawsize - the height and width to draw each matrix element as, in pixels
*/
class MMGraph{
constuctor(Mat, tag="#matgraph", drawsize=5){
this.Mat = Mat;
this.tag = tag;
// initializes a new canvas to draw on
this.canvas = make_canvas(tag, Mat.x_len, Mat.y_len, drawsize);
this.drawsize = drawsize;
}
/** linear scale function
* @param {float} val - a normalized on [0,1] value to normalize/scale
* @param {float} min - the minimum value of the data
* @param {float} max - the maximum value of the data
*/
static lin_scale(val, min, max){
// linear color scale function (from 0 to 1)
if (isNaN(val) || val === Infinity || val === -Infinity){
return NaN;
}
return (val - min)/(max - min);
}
// some plot color scheme engines
// all appended _gc for graph coloring
//HSLA style schemes
// expect values between 0 and 1
// return a hsla set in oder ['hsla', hue, sat, luminence, alpha]
// hue from 0 to 360, rest 0 to 1
/** change sauration and alpha value of blue-like, HSLA
* @param {float} value - a value on [0,1] to translate into a color for drawing
*/
static blue_gc(value){
return ["hsla", 249, (value*0.8+0.2), 255, (value/3+0.65)];
}
/** change sauration and alpha value of a greyscale range, HSLA
* @param {float} value - a value on [0,1] to translate into a color for drawing
*/
static grey_gc(value){
//keep in
return ["hsla", 197, 0.11, (0.28*value+11), 0.9];
}
/** change sauration and alpha value of a grayscale range, HSLA
* @param {float} value - a value on [0,1] to translate into a color for drawing
*/
static gray_gc(value){
// alias to grey
//keep in a greyscale range
MMGraph.grey_gc(value);
}
/** change sauration and alpha value of a rainbow, HSLA
* @param {float} value - a value on [0,1] to translate into a color for drawing
*/
static rainbow_gc(value){
// change hue for rainbow
return ["hsla", (242*value), 0.84, 0.48, 0.9];
}
/** Split data at the average; red less, green higher, HSLA
* @param {float} value - a value on [0,1] to translate into a color for drawing
*/
static redgreen_gc(value){
// split at 0.5, red less, green higher
var color;
if (value > 0.5){
color = ["hsla", 350, 1, 0.5+(0.5-value), 0.9];
} else {
color = ["hsla", 11, 1, 0.2+(0.4*value), 0.98];
}
return color;
}
/** Translate normalized data into a color output per a scheme, parsing nonnumerics.
* @param {float} value - a value on [0,1] to translate into a color for drawing
* @param {function} scheme - a color scaling scheme, which takes in a value [0,1] and returns an array of color information, starting with a string of the color type (e.g. HSLA)
*/
static scale_color(value, scheme=MMGraph.redgreen_gc){
// get a color from a normalized value (between 0 and 1)
// deal with bad values now so we don't mess up color
if (isNaN(value)){
// don't color in missing data
return [0,0,0,0];
} else if (value>1){
value=0.99;
} else if (value<0){
value=0;
}
// get the color
var color = scheme(value);
var color_str;
// parse the string
switch(color[0].toLowerCase()){
case "hsla":
// expect 4 other elements, 1 is ok, 2,3,4 to % form
color_str = "hsla("+String(parseInt(color[1],10))+"," +
String(parseInt(color[2]*100,10))+"%"+"," +
String(parseInt(color[3]*100,10))+"%"+"," +
String(parseInt(color[4]*100,10))+"%"+")";
break;
default:
// not an array, but a string of preformmated color, hopefully
color_str = color;
}
return color_str;
}
/** draw the Mat to this.tag given, already initalized */
draw() {
// a function to get the x,y position given coord
var Mat = this.Mat
var get_elem_pos = function(pos, x_len){
return [(Math.floor(x/x_len))*this.drawsize, (x%x_len)*this.drawsize];
}
var float_filter = function(val) { return (!(isNaN(val)||val === Infinity||val === -Infinity))};
// filter and get range for min and max of normal data
var filtered = Mat.data.filter(float_filter);
var data_range = [Math.min.apply(Math, filtered), Math.max.apply(Math, filtered)];
// draw each element in the matrix
for (var x=0; x< Mat.data.length; x++){
// for each one, get the element's position
var coords = get_elem_pos(x, Mat.x_len);
// get the color for the value
var color = scale_color(lin_scale(Mat.data[x], data_range[0], data_range[1]), "redgreen");
// add the node
this.canvas.append("rect")
.attr("width", this.drawsize)
.attr("height", this.drawsize)
.attr("x", coords[0])
.attr("y", coords[1])
.attr("id", this.tag + "- " + String(x))
.attr("style", "fill:" + color + ";");
}
}
}
// try to add make_canvas to the class
MMGraph.prototype.make_canvas = make_canvas;
module.exports = MMGraph;