for a point
this.getTooltip = function (xd, yd, xp, yp) {
var that = this;
var resultMarkers = that.findToolTipMarkers(xd, yd, xp, yp);
var buildTooltip = function (markerInfo) {
var $content = $("
").addClass('idd-tooltip-compositevalue');
for (var prop in markerInfo) {
if (markerInfo.hasOwnProperty(prop)) {
var propTitle = that.getTitle(prop);
var markerContent = undefined;
if (typeof markerInfo[prop] == 'object') {
markerContent = buildTooltip(markerInfo[prop]);
}
if (markerContent)
$content.append($("
" + propTitle + ":
")).append(markerContent);
else {
$content.append($("
" + propTitle + ": " + markerInfo[prop] + "
"));
}
}
}
return $content;
};
if (resultMarkers.length > 0) {
var $toolTip = $("
")
$("
").addClass('idd-tooltip-name').text((that.name || "markers")).appendTo($toolTip);
resultMarkers.forEach(function (markerInfo) {
buildTooltip(markerInfo).addClass('idd-tooltip-itemvalues').appendTo($toolTip);
});
return $toolTip;
}
};
this.getLegend = function () {
//var div = $("
");
var nameDiv = $("
");
var legendDiv = { thumbnail: $("
"), content: $("
") };
var buildLegend = function () {
nameDiv.empty();
//nameDiv = $("
").appendTo(div);
if (_shape && typeof _shape.getLegend != "undefined") {
legendDiv.content.empty();
_shape.getLegend(_data, that.getTitle, legendDiv);
}
nameDiv.text(that.name);
}
this.host.bind("appearanceChanged", buildLegend);
buildLegend();
var onLegendRemove = function () {
that.host.unbind("appearanceChanged", buildLegend);
nameDiv.empty();
//div.empty();
//div.removeClass("idd-legend-item");
};
return { name: nameDiv, legend: legendDiv, onLegendRemove: onLegendRemove };
};
this.buildSvgLegend = function (legendSettings, svg) {
var that = this;
var legendElements = {thumbnail: svg.group(), content: svg.group() };
legendSettings.height = 30;
if (_shape && typeof _shape.buildSvgLegendElements != "undefined")
legendElements = _shape.buildSvgLegendElements(legendSettings, svg, _data, that.getTitle);
svg.rect(legendSettings.width, legendSettings.height).fill({ color: "white", opacity: 0 });
svg.add(legendElements.thumbnail.translate(5, 5));
var style = window.getComputedStyle(legendSettings.legendDiv.children[0].children[1], null);
var fontSize = parseFloat(style.getPropertyValue('font-size'));
var fontFamily = style.getPropertyValue('font-family');
svg.add(svg.text(that.name).font({ family: fontFamily, size: fontSize }).translate(40, 0));
svg.add(legendElements.content.translate(5, 30));
}
// Others
this.onDataTransformChanged = function (arg) {
this.invalidateLocalBounds();
InteractiveDataDisplay.Markers.prototype.onDataTransformChanged.call(this, arg);
};
// Initialization
var initializer = InteractiveDataDisplay.Utils.getDataSourceFunction(div, InteractiveDataDisplay.readCsv);
var initialData = initializer(div);
if (initialData && typeof initialData.y != 'undefined')
this.draw(initialData);
};
InteractiveDataDisplay.Markers.prototype = new InteractiveDataDisplay.CanvasPlot;
InteractiveDataDisplay.Markers.defaults = {
color : "#4169ed",
colorPalette : InteractiveDataDisplay.palettes.grayscale,
border : "#000000",
size : 10
}
InteractiveDataDisplay.Markers.shapes = InteractiveDataDisplay.Markers.shapes || {};
(function() {
var primitiveShape =
{
// Checks validity of the data and modifies it by replacing missing values with defaults
// and applying palettes, if required. Filters out missing values, original indices are in `indices`.
// Output data:
// `shape`: shape is transformed to an integer value to enable fast switch.
// `y`: must be an array and its length must be length of other data series.
// `x`:
// input: either array of proper length or undefined; if undefined, [0,1,...] is taken;
// output: an array of numbers
// 'border'
// input: a color string, null, undefined or "none"
// output: becomes a string color or null, if no border
// `color`: becomes either a string color or an array of string colors
// - if undefined, a default color is used
// - if an array of numbers, the color palette is applied, so 'color' is an array of colors.
// `individualColors`: If data.color is a scalar string, the `data.individualColors` is true, otherwise false.
// `colorPalette`: if undefined, uses default palette.
// `size`: always becomes an array of numbers, those are sizes in pixels.
// `sizeMax`: a number which is a maximum of marker size in pixels.
// `inidices`: array of original marker index (may be required if there're missing values filtered out).
prepare : function(data) {
// shape
var invShape = data.shape ? data.shape.toLowerCase() : "box";
if (invShape == "box") data.shape = 1;
else if (invShape == "circle") data.shape = 2;
else if (invShape == "diamond") data.shape = 3;
else if (invShape == "cross") data.shape = 4;
else if (invShape == "triangle") data.shape = 5;
else throw "Unexpected value of property 'shape'";
// y
if(data.y == undefined || data.y == null) throw "The mandatory property 'y' is undefined or null";
if(!InteractiveDataDisplay.Utils.isArray(data.y)) throw "The property 'y' must be an array of numbers";
var n = data.y.length;
var mask = new Int8Array(n);
InteractiveDataDisplay.Utils.maskNaN(mask, data.y);
// x
if(data.x == undefined)
data.x = InteractiveDataDisplay.Utils.range(0, n - 1);
else if (!InteractiveDataDisplay.Utils.isArray(data.x)) throw "The property 'x' must be an array of numbers";
else if (data.x.length != n) throw "Length of the array which is a value of the property 'x' differs from lenght of 'y'"
else InteractiveDataDisplay.Utils.maskNaN(mask, data.x);
// border
if(data.border == undefined || data.border == "none")
data.border = null; // no border
// colors
if(data.color == undefined) data.color = InteractiveDataDisplay.Markers.defaults.color;
if(InteractiveDataDisplay.Utils.isArray(data.color)) {
if(data.color.length != n) throw "Length of the array 'color' is different than length of the array 'y'"
if(n > 0 && typeof(data.color[0]) !== "string"){ // color is a data series (otherwise, it is an array of string colors)
var palette = data.colorPalette;
if (palette == undefined) palette = InteractiveDataDisplay.Markers.defaults.colorPalette;
if (typeof palette == 'string') palette = new InteractiveDataDisplay.ColorPalette.parse(palette);
if (palette != undefined && palette.isNormalized) {
var r = InteractiveDataDisplay.Utils.getMinMax(data.color);
r = InteractiveDataDisplay.Utils.makeNonEqual(r);
palette = palette.absolute(r.min, r.max);
}
data.colorPalette = palette;
var colors = new Array(n);
for (var i = 0; i < n; i++){
var color = data.color[i];
if(color != color) // NaN
mask[i] = 1;
else {
var rgba = palette.getRgba(color);
colors[i] = "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
}
}
data.color = colors;
}
data.individualColors = true;
}else{
data.individualColors = false;
}
//sizes
var sizes = new Array(n);
if (data.size == undefined) data.size = InteractiveDataDisplay.Markers.defaults.size;
if (InteractiveDataDisplay.Utils.isArray(data.size)) {
if (data.size.length != n) throw "Length of the array 'size' is different than length of the array 'y'"
if (data.sizePalette != undefined) { // 'size' is a data series
var palette = InteractiveDataDisplay.SizePalette.Create(data.sizePalette);
if (palette.isNormalized) {
var r = InteractiveDataDisplay.Utils.getMinMax(data.size);
r = InteractiveDataDisplay.Utils.makeNonEqual(r);
palette = new InteractiveDataDisplay.SizePalette(false, palette.sizeRange, r);
}
data.sizePalette = palette;
for (var i = 0; i < n; i++) {
var size = data.size[i];
if (size != size) // NaN
mask[i] = 1;
else
sizes[i] = palette.getSize(size)
}
} else { // 'size' contains values in pixels
sizes = data.size;
data.sizeMax = InteractiveDataDisplay.Utils.getMax(data.size);
}
} else { // sizes is a constant
for (var i = 0; i < n; i++) sizes[i] = data.size;
data.sizeMax = data.size;
}
data.size = sizes;
// Filtering out missing values
var m = 0;
for(var i = 0; i < n; i++) if(mask[i] === 1) m++;
if(m > 0){ // there are missing values
m = n - m;
data.x = InteractiveDataDisplay.Utils.applyMask(mask, data.x, m);
data.y = InteractiveDataDisplay.Utils.applyMask(mask, data.y, m);
data.size = InteractiveDataDisplay.Utils.applyMask(mask, data.size, m);
if(data.individualColors)
data.color = InteractiveDataDisplay.Utils.applyMask(mask, data.color, m);
var indices = Array(m);
for(var i = 0, j = 0; i < n; i++) if(mask[i] === 0) indices[j++] = i;
data.indices = indices;
}else{
data.indices = InteractiveDataDisplay.Utils.range(0, n-1);
}
},
preRender : function (data, plotRect, screenSize, dt, context){
if(!data.individualColors)
context.fillStyle = data.color;
if(data.border != null)
context.strokeStyle = data.border;
return data;
},
draw : function (d, plotRect, screenSize, t, context, index){
if(d.individualColors) context.fillStyle = d.color;
var drawBorder = d.border != null;
var x1 = t.dataToScreenX(d.x);
var y1 = t.dataToScreenY(d.y);
var w_s = screenSize.width;
var h_s = screenSize.height;
var localSize = d.size;
var halfSize = localSize / 2;
if ((x1 - halfSize) > w_s || (x1 + halfSize) < 0 || (y1 - halfSize) > h_s || (y1 + halfSize) < 0) return;
switch (d.shape) {
case 1: // box
context.fillRect(x1 - halfSize, y1 - halfSize, localSize, localSize);
if (drawBorder)
context.strokeRect(x1 - halfSize, y1 - halfSize, localSize, localSize);
break;
case 2: // circle
context.beginPath();
context.arc(x1, y1, halfSize, 0, 2 * Math.PI);
context.fill();
if (drawBorder)
context.stroke();
break;
case 3: // diamond
context.beginPath();
context.moveTo(x1 - halfSize, y1);
context.lineTo(x1, y1 - halfSize);
context.lineTo(x1 + halfSize, y1);
context.lineTo(x1, y1 + halfSize);
context.closePath();
context.fill();
if (drawBorder)
context.stroke();
break;
case 4: // cross
var thirdSize = localSize / 3;
var halfThirdSize = thirdSize / 2;
if (drawBorder) {
context.beginPath();
context.moveTo(x1 - halfSize, y1 - halfThirdSize);
context.lineTo(x1 - halfThirdSize, y1 - halfThirdSize);
context.lineTo(x1 - halfThirdSize, y1 - halfSize);
context.lineTo(x1 + halfThirdSize, y1 - halfSize);
context.lineTo(x1 + halfThirdSize, y1 - halfThirdSize);
context.lineTo(x1 + halfSize, y1 - halfThirdSize);
context.lineTo(x1 + halfSize, y1 + halfThirdSize);
context.lineTo(x1 + halfThirdSize, y1 + halfThirdSize);
context.lineTo(x1 + halfThirdSize, y1 + halfSize);
context.lineTo(x1 - halfThirdSize, y1 + halfSize);
context.lineTo(x1 - halfThirdSize, y1 + halfThirdSize);
context.lineTo(x1 - halfSize, y1 + halfThirdSize);
context.closePath();
context.fill();
context.stroke();
} else {
context.fillRect(x1 - halfThirdSize, y1 - halfSize, thirdSize, localSize);
context.fillRect(x1 - halfSize, y1 - halfThirdSize, localSize, thirdSize);
}
break;
case 5: // triangle
context.beginPath();
context.moveTo(x1 - halfSize, y1 + halfSize);
context.lineTo(x1, y1 - halfSize);
context.lineTo(x1 + halfSize, y1 + halfSize);
context.closePath();
context.fill();
if (drawBorder)
context.stroke();
break;
}
},
getPadding : function(data) {
var p = data.sizeMax / 2;
return { left: p, right: p, top: p, bottom: p };
},
hitTest : function(d, t, ps, pd){
var isInside = function (p, points) {
var classify = function (p, p0, p1) {
var a = { x: p1.x - p0.x, y: p1.y - p0.y };
var b = { x: p.x - p0.x, y: p.y - p0.y };
var s = a.x * b.y - a.y * b.x;
if (s > 0) return 1; // left
if (s < 0) return 2; // right
return 0;
}
var n = points.length;
for (var i = 0; i < n; i++) {
if (classify(p, points[i], points[(i + 1) % n]) != 1) return false;
}
return true;
};
var x1 = t.dataToScreenX(d.x);
var y1 = t.dataToScreenY(d.y);
var xs = ps.x;
var ys = ps.y;
var localSize = d.size;
var halfSize = localSize / 2; // Checks bounding box hit:
if (ps.x >= x1 - halfSize && ps.x <= x1 + halfSize && ps.y >= y1 - halfSize && ps.y <= y1 + halfSize) {
switch (d.shape) {
case 1: // box
return true;
case 2: // circle
return ((x1 - xs) * (x1 - xs) + (y1 - ys) * (y1 - ys) <= halfSize * halfSize);
case 3: // diamond
return (isInside({ x: xs, y: ys }, [{ x: x1 - halfSize, y: y1 }, { x: x1, y: y1 - halfSize },
{ x: x1 + halfSize, y: y1 }, { x: x1, y: y1 + halfSize }, ]));
case 4: // cross
var thirdSize = localSize / 3;
var halfThirdSize = thirdSize / 2;
return (isInside({ x: xs, y: ys }, [{ x: x1 - halfThirdSize, y: y1 + halfSize }, { x: x1 - halfThirdSize, y: y1 - halfSize },
{ x: x1 + halfThirdSize, y: y1 - halfSize }, { x: x1 + halfThirdSize, y: y1 + halfSize }]) ||
isInside({ x: xs, y: ys }, [{ x: x1 - halfSize, y: y1 + halfThirdSize }, { x: x1 - halfSize, y: y1 - halfThirdSize },
{ x: x1 + halfSize, y: y1 - halfThirdSize }, { x: x1 + halfSize, y: y1 + halfThirdSize }]));
case 5: // triangle
return (isInside({ x: xs, y: ys }, [{ x: x1 - halfSize, y: y1 + halfSize }, { x: x1, y: y1 - halfSize },
{ x: x1 + halfSize, y: y1 + halfSize }]));
}
}
},
getLegend: function(data, getTitle, legendDiv) { // todo: should be refactored
var itemDiv = legendDiv.content;
var fontSize = 14;
if (document.defaultView && document.defaultView.getComputedStyle) {
fontSize = parseFloat(document.defaultView.getComputedStyle(itemDiv[0], null).getPropertyValue("font-size"));
}
if (isNaN(fontSize) || fontSize == 0) fontSize = 14;
//var thumbDiv = $("
");
var canvas = legendDiv.thumbnail;
var canvasIsVisible = true;
var maxSize = fontSize * 1.5;
var x1 = maxSize / 2 + 1;
var y1 = x1;
canvas[0].width = canvas[0].height = maxSize + 2;
var canvasStyle = canvas[0].style;
var context = canvas.get(0).getContext("2d");
context.clearRect(0, 0, canvas[0].width, canvas[0].height);
var item, itemDivStyle;
var itemIsVisible = 0;
var colorIsArray, color, border, drawBorder;
var colorDiv, colorDivStyle, colorControl;
var colorIsVisible = 0;
var sizeIsArray, size, halfSize;
var sizeDiv, sizeDivStyle, sizeControl;
var sizeIsVisible = 0;
var sizeTitle;
var refreshSize = function () {
size = maxSize;
if (data.sizePalette) {
var szTitleText = getTitle("size");
if (sizeIsVisible == 0) {
sizeDiv = $("
").appendTo(itemDiv);
sizeTitle = $("
").text(szTitleText).appendTo(sizeDiv);
sizeDivStyle = sizeDiv[0].style;
var paletteDiv = $("
").appendTo(sizeDiv);
sizeControl = new InteractiveDataDisplay.SizePaletteViewer(paletteDiv);
sizeIsVisible = 2;
} else {
sizeTitle.text(szTitleText);
}
sizeControl.palette = InteractiveDataDisplay.SizePalette.Create(data.sizePalette);
}
halfSize = size / 2;
};
var colorTitle;
var refreshColor = function () {
drawBorder = false;
if (data.individualColors && data.colorPalette) {
var clrTitleText = getTitle("color");
if (colorIsVisible == 0) {
colorDiv = $("
").appendTo(itemDiv);
colorTitle = $("
").text(clrTitleText).appendTo(colorDiv);
colorDivStyle = colorDiv[0].style;
var paletteDiv = $("
").appendTo(colorDiv);
colorControl = new InteractiveDataDisplay.ColorPaletteViewer(paletteDiv);
colorIsVisible = 2;
} else {
colorTitle.text(clrTitleText);
}
colorControl.palette = data.colorPalette;
if (colorIsVisible == 1) {
colorDivStyle.display = "block";
colorIsVisible = 2;
}
}
else {
if (colorIsVisible == 2) {
colorDivStyle.display = "none";
colorIsVisible = 1;
}
}
if (data.individualColors) {
border = "#000000";
color = "#ffffff";
drawBorder = true;
}
else {
color = data.color;
border = color;
if (data.border != null) {
drawBorder = true;
border = data.border;
}
}
};
var renderShape = function () {
if (itemIsVisible == 2) {
itemDivStyle.display = "none";
itemIsVisible = 1;
}
context.clearRect(0, 0, maxSize + 2, maxSize + 2);
context.strokeStyle = border;
context.fillStyle = color;
switch (data.shape) {
case 1: // box
context.fillRect(x1 - halfSize, y1 - halfSize, size, size);
if (drawBorder)
context.strokeRect(x1 - halfSize, y1 - halfSize, size, size);
break;
case 2: // circle
context.beginPath();
context.arc(x1, y1, halfSize, 0, 2 * Math.PI);
context.fill();
if (drawBorder)
context.stroke();
break;
case 3: // diamond
context.beginPath();
context.moveTo(x1 - halfSize, y1);
context.lineTo(x1, y1 - halfSize);
context.lineTo(x1 + halfSize, y1);
context.lineTo(x1, y1 + halfSize);
context.closePath();
context.fill();
if (drawBorder)
context.stroke();
break;
case 4: // cross
var thirdSize = size / 3;
var halfThirdSize = thirdSize / 2;
if (drawBorder) {
context.beginPath();
context.moveTo(x1 - halfSize, y1 - halfThirdSize);
context.lineTo(x1 - halfThirdSize, y1 - halfThirdSize);
context.lineTo(x1 - halfThirdSize, y1 - halfSize);
context.lineTo(x1 + halfThirdSize, y1 - halfSize);
context.lineTo(x1 + halfThirdSize, y1 - halfThirdSize);
context.lineTo(x1 + halfSize, y1 - halfThirdSize);
context.lineTo(x1 + halfSize, y1 + halfThirdSize);
context.lineTo(x1 + halfThirdSize, y1 + halfThirdSize);
context.lineTo(x1 + halfThirdSize, y1 + halfSize);
context.lineTo(x1 - halfThirdSize, y1 + halfSize);
context.lineTo(x1 - halfThirdSize, y1 + halfThirdSize);
context.lineTo(x1 - halfSize, y1 + halfThirdSize);
context.closePath();
context.fill();
context.stroke();
} else {
context.fillRect(x1 - halfThirdSize, y1 - halfSize, thirdSize, size);
context.fillRect(x1 - halfSize, y1 - halfThirdSize, size, thirdSize);
}
break;
case 5: // triangle
context.beginPath();
context.moveTo(x1 - halfSize, y1 + halfSize);
context.lineTo(x1, y1 - halfSize);
context.lineTo(x1 + halfSize, y1 + halfSize);
context.closePath();
context.fill();
if (drawBorder)
context.stroke();
break;
}
if (!canvasIsVisible) {
canvasStyle.display = "inline-block";
canvasIsVisible = true;
}
};
refreshColor();
refreshSize();
renderShape();
},
renderSvg: function (plotRect, screenSize, svg, data, t) {
var n = data.y.length;
if (n == 0) return;
var marker_g = svg.group();
var dataToScreenX = t.dataToScreenX;
var dataToScreenY = t.dataToScreenY;
// size of the canvas
var w_s = screenSize.width;
var h_s = screenSize.height;
var xmin = 0, xmax = w_s;
var ymin = 0, ymax = h_s;
var x1, y1;
var i = 0;
var nextValuePoint = function () {
var border = data.border == null? 'none': data.border;
for (; i < n; i++) {
x1 = dataToScreenX(data.x[i]);
y1 = dataToScreenY(data.y[i]);
var size = data.size[i];
var halfSize = size / 2;
c1 = ((x1 - halfSize) > w_s || (x1 + halfSize) < 0 || (y1 - halfSize) > h_s || (y1 + halfSize) < 0);
var color = data.individualColors ? data.color[i] : data.color;
if (!c1) {// point is inside visible rect
if (data.shape == 1) marker_g.rect(data.size[i], data.size[i]).translate(x1 - halfSize, y1 - halfSize).fill(color).style({ stroke: border });
else if (data.shape == 2) marker_g.circle(data.size[i]).translate(x1 - halfSize, y1 - halfSize).style({fill: color, stroke:border});
else if (data.shape == 3) marker_g.rect(data.size[i] / Math.sqrt(2), data.size[i] / Math.sqrt(2)).translate(x1, y1 - halfSize).style({fill:color, stroke:border}).rotate(45); //diamond
else if (data.shape == 4) {
var halfThirdSize = size / 6;
marker_g.polyline([[-halfSize, -halfThirdSize], [-halfThirdSize, -halfThirdSize], [-halfThirdSize, -halfSize], [halfThirdSize, -halfSize],
[halfThirdSize, -halfThirdSize], [halfSize, -halfThirdSize], [halfSize, halfThirdSize], [halfThirdSize, halfThirdSize], [halfThirdSize, halfSize],
[-halfThirdSize, halfSize], [-halfThirdSize, halfThirdSize], [-halfSize, halfThirdSize], [-halfSize, -halfThirdSize]]).translate(x1, y1).style({ fill: color, stroke: border });//cross
}
else if (data.shape == 5) {
var r = Math.sqrt(3) / 6 * size;
marker_g.polyline([[x1 - halfSize, y1 + r], [x1, y1 - r * 2], [x1 + halfSize, y1 + r], [x1 - halfSize, y1 + r]]).style({ fill: color, stroke: border });//triangle
}
}
}
marker_g.clipWith(marker_g.rect(w_s, h_s));
};
nextValuePoint();
},
buildSvgLegendElements: function (legendSettings, svg, data, getTitle) {
var thumbnail = svg.group();
var content = svg.group();
var fontSize = 12;
var size = fontSize * 1.5;
var x1 = size / 2 + 1;
var y1 = x1;
var halfSize = size / 2;
//thumbnail
if (data.individualColors) {
border = "#000000";
color = "#ffffff";
}
else {
color = data.color;
border = "none";
if (data.border != null) border = data.border;
}
switch (data.shape) {
case 1: // box
thumbnail.rect(size, size).translate(x1 - halfSize, y1 - halfSize).fill({color: color, opacity: 1}).stroke(border);
break;
case 2: // circle
thumbnail.circle(size).translate(x1 - halfSize, y1 - halfSize).fill(color).stroke(border);
break;
case 3: // diamond
thumbnail.rect(size / Math.sqrt(2), size / Math.sqrt(2)).translate(x1, y1 - halfSize).fill(color).stroke(border).rotate(45);
break;
case 4: // cross
var halfThirdSize = size / 6;
thumbnail.polyline([[-halfSize, -halfThirdSize], [-halfThirdSize, -halfThirdSize], [-halfThirdSize, -halfSize], [halfThirdSize, -halfSize],
[halfThirdSize, -halfThirdSize], [halfSize, -halfThirdSize], [halfSize, halfThirdSize], [halfThirdSize, halfThirdSize], [halfThirdSize, halfSize],
[-halfThirdSize, halfSize], [-halfThirdSize, halfThirdSize], [-halfSize, halfThirdSize], [-halfSize, -halfThirdSize]]).translate(x1, y1).fill(color).stroke(border);//cross
break;
case 5: // triangle
var r = Math.sqrt(3) / 6 * size;
thumbnail.polyline([[x1 - halfSize, y1 + r], [x1, y1 - r * 2], [x1 + halfSize, y1 + r], [x1 - halfSize, y1 + r]]).fill(color).stroke(border);//triangle
break;
}
//content
var shiftsizePalette = 0;
var isContent = legendSettings.legendDiv.children[1];
var isColor = data.individualColors && data.colorPalette;
var isSize = data.sizePalette;
var style = (isContent && legendSettings.legendDiv.children[1].children[0] && legendSettings.legendDiv.children[1].children[0].children[0]) ? window.getComputedStyle(legendSettings.legendDiv.children[1].children[0].children[0], null) : undefined;
fontSize = style ? parseFloat(style.getPropertyValue('font-size')) : undefined;
fontFamily = style ? style.getPropertyValue('font-family') : undefined;
var fontWeight = style ? style.getPropertyValue('font-weight') : undefined;
if (isColor) {
var colorText = getTitle("color");
content.text(colorText).font({ family: fontFamily, size: fontSize, weight: fontWeight });
var colorPalette_g = svg.group();
var width = legendSettings.width;
var height = 20;
InteractiveDataDisplay.SvgColorPaletteViewer(colorPalette_g, data.colorPalette, legendSettings.legendDiv.children[1].children[0].children[1], { width: width, height: height });
colorPalette_g.translate(5, 50);
shiftsizePalette = 50 + height;
legendSettings.height += (50 + height);
};
if (data.sizePalette) {
var sizeText = getTitle("size");
content.add(svg.text(sizeText).font({ family: fontFamily, size: fontSize, weight: fontWeight }).translate(0, shiftsizePalette));
var sizePalette_g = svg.group();
var width = legendSettings.width;
var height = 35;
var sizeElement = isColor ? legendSettings.legendDiv.children[1].children[1].children[1] : legendSettings.legendDiv.children[1].children[0].children[1];
InteractiveDataDisplay.SvgSizePaletteViewer(sizePalette_g, data.sizePalette, sizeElement, { width: width, height: height });
sizePalette_g.translate(5, 50 + shiftsizePalette);
legendSettings.height += (50 + height);
};
svg.front();
return { thumbnail: thumbnail, content: content };
}
}
InteractiveDataDisplay.Markers.shapes["box"] = primitiveShape;
InteractiveDataDisplay.Markers.shapes["circle"] = primitiveShape;
InteractiveDataDisplay.Markers.shapes["diamond"] = primitiveShape;
InteractiveDataDisplay.Markers.shapes["cross"] = primitiveShape;
InteractiveDataDisplay.Markers.shapes["triangle"] = primitiveShape;
})();
InteractiveDataDisplay.Petal = {
prepare: function (data) {
if (!data.maxDelta) {
var i = 0;
while (isNaN(data.size.upper95[i]) || isNaN(data.size.lower95[i])) i++;
var maxDelta = data.size.upper95[i] - data.size.lower95[i];
i++;
for (; i < data.size.upper95.length; i++)
if (!isNaN(data.size.upper95[i]) && !isNaN(data.size.lower95[i]))
maxDelta = Math.max(maxDelta, data.size.upper95[i] - data.size.lower95[i]);
data.maxDelta = maxDelta;
}
// y
if (data.y == undefined || data.y == null) throw "The mandatory property 'y' is undefined or null";
if (!InteractiveDataDisplay.Utils.isArray(data.y)) throw "The property 'y' must be an array of numbers";
var n = data.y.length;
var mask = new Int8Array(n);
InteractiveDataDisplay.Utils.maskNaN(mask, data.y);
// x
if (data.x == undefined)
data.x = InteractiveDataDisplay.Utils.range(0, n - 1);
else if (!InteractiveDataDisplay.Utils.isArray(data.x)) throw "The property 'x' must be an array of numbers";
else if (data.x.length != n) throw "Length of the array which is a value of the property 'x' differs from lenght of 'y'"
else InteractiveDataDisplay.Utils.maskNaN(mask, data.x);
// border
if (data.border == undefined || data.border == "none")
data.border = null; // no border
// colors
if (data.color == undefined) data.color = InteractiveDataDisplay.Markers.defaults.color;
if (InteractiveDataDisplay.Utils.isArray(data.color)) {
if (data.color.length != n) throw "Length of the array 'color' is different than length of the array 'y'"
if (n > 0 && typeof (data.color[0]) !== "string") { // color is a data series
var palette = data.colorPalette;
if (palette == undefined) palette = InteractiveDataDisplay.Markers.defaults.colorPalette;
if (typeof palette == 'string') palette = new InteractiveDataDisplay.ColorPalette.parse(palette);
if (palette != undefined && palette.isNormalized) {
var r = InteractiveDataDisplay.Utils.getMinMax(data.color);
r = InteractiveDataDisplay.Utils.makeNonEqual(r);
palette = palette.absolute(r.min, r.max);
}
data.colorPalette = palette;
var colors = new Array(n);
for (var i = 0; i < n; i++) {
var color = data.color[i];
if (color != color) // NaN
mask[i] = 1;
else {
var rgba = palette.getRgba(color);
colors[i] = "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
}
}
data.color = colors;
}
data.individualColors = true;
} else {
data.individualColors = false;
}
// sizes
var sizes = new Array(n);
if (InteractiveDataDisplay.Utils.isArray(data.size.lower95) && InteractiveDataDisplay.Utils.isArray(data.size.upper95)) {
if (data.size.lower95.length != n && data.size.upper95.length != n) throw "Length of the array 'size' is different than length of the array 'y'";
if (n > 0 && typeof (data.size.lower95[0]) === "number" && typeof (data.size.upper95[0]) === "number") { // color is a data series
var sizes_u95 = [];
var sizes_l95 = [];
for (var i = 0; i < n; i++) {
var size_u95 = data.size.upper95[i];
var size_l95 = data.size.lower95[i];
if (size_u95 != size_u95 || size_l95 != size_l95)
mask[i] = 1;
else {
sizes_u95[i] = data.size.upper95[i];
sizes_l95[i] = data.size.lower95[i];
}
}
data.upper95 = sizes_u95;
data.lower95 = sizes_l95;
}
}
data.size = '15.0';
for (var i = 0; i < n; i++) sizes[i] = data.size;
data.sizeMax = data.size;
data.size = sizes;
// Filtering out missing values
var m = 0;
for (var i = 0; i < n; i++) if (mask[i] === 1) m++;
if (m > 0) { // there are missing values
m = n - m;
data.x = InteractiveDataDisplay.Utils.applyMask(mask, data.x, m);
data.y = InteractiveDataDisplay.Utils.applyMask(mask, data.y, m);
data.size = InteractiveDataDisplay.Utils.applyMask(mask, data.size, m);
data.upper95 = InteractiveDataDisplay.Utils.applyMask(mask, data.upper95, m);
data.lower95 = InteractiveDataDisplay.Utils.applyMask(mask, data.lower95, m);
if (data.individualColors)
data.color = InteractiveDataDisplay.Utils.applyMask(mask, data.color, m);
var indices = Array(m);
for (var i = 0, j = 0; i < n; i++) if (mask[i] === 0) indices[j++] = i;
data.indices = indices;
} else {
data.indices = InteractiveDataDisplay.Utils.range(0, n - 1);
}
},
preRender: function (data, plotRect, screenSize, dt, context) {
if (!data.individualColors)
context.fillStyle = data.color;
if (data.border != null)
context.strokeStyle = data.border;
return data;
},
draw: function (marker, plotRect, screenSize, transform, context) {
var x0 = transform.dataToScreenX(marker.x);
var y0 = transform.dataToScreenY(marker.y);
if (x0 > screenSize.width || x0 < 0) return;
if (y0 > screenSize.height || y0 < 0) return;
var maxSize = marker.size / 2;
var minSize = maxSize * (1 - (marker.upper95 - marker.lower95) / marker.maxDelta);
if (marker.maxDelta <= 0) minSize = NaN;
InteractiveDataDisplay.Petal.drawSample(context, x0, y0, minSize, maxSize, marker.color);
},
drawSample: function (context, x, y, minSize, maxSize, color) {
var A, D;
var C = Math.random() * Math.PI * 2;
if (isNaN(minSize)) {
A = 0;
D = maxSize;
context.fillStyle = "rgba(0, 0, 0, 0.2)";
}
else {
A = (maxSize - minSize) / 2;
D = (maxSize + minSize) / 2;
context.fillStyle = color;
}
context.strokeStyle = "black";
context.beginPath();
var n = 200;
var alpha = Math.PI * 2 / n;
for (var i = 0; i < n; i++) {
var phi = alpha * i;
var r = A * Math.sin(6 * phi + C) + D;
if (i == 0)
context.moveTo(x + r * Math.cos(phi), y + r * Math.sin(phi));
else
context.lineTo(x + r * Math.cos(phi), y + r * Math.sin(phi));
}
context.stroke();
context.closePath();
context.fill();
context.strokeStyle = "gray";
context.beginPath();
context.arc(x, y, 1, 0, Math.PI * 2);
context.stroke();
context.closePath();
},
hitTest: function (marker, transform, ps, pd) {
var x = transform.dataToScreenX(marker.x);
var y = transform.dataToScreenY(marker.y);
var r = marker.size / 2;
if (ps.x < x - r || ps.x > x + r) return false;
if (ps.y < y - r || ps.y > y + r) return false;
return true;
},
getLegend: function (data, getTitle, legendDiv) { // todo: should be refactored
var itemDiv = legendDiv.content;
var fontSize = 14;
if (document.defaultView && document.defaultView.getComputedStyle) {
fontSize = parseFloat(document.defaultView.getComputedStyle(itemDiv[0], null).getPropertyValue("font-size"));
}
if (isNaN(fontSize) || fontSize == 0) fontSize = 14;
var canvas = legendDiv.thumbnail;
var canvasIsVisible = true;
var maxSize = fontSize * 1.5;
var x1 = maxSize / 2 + 1;
var y1 = maxSize / 2 + 1;
canvas[0].width = canvas[0].height = maxSize + 2;
var canvasStyle = canvas[0].style;
var context = canvas.get(0).getContext("2d");
context.clearRect(0, 0, canvas[0].width, canvas[0].height);
var color, border, drawBorder;
var colorDiv, colorDivStyle, colorControl;
var colorIsVisible = 0;
var size, halfSize;
var sizeDiv, sizeDivStyle, sizeControl;
var sizeIsVisible = 0;
var sizeTitle;
var refreshSize = function () {
size = maxSize;
var szTitleText = getTitle("size");
if (sizeIsVisible == 0) {
sizeDiv = $("
").appendTo(itemDiv);
sizeTitle = $("
").text(szTitleText).appendTo(sizeDiv);
sizeDivStyle = sizeDiv[0].style;
var paletteDiv = $("
").appendTo(sizeDiv);
sizeControl = new InteractiveDataDisplay.UncertaintySizePaletteViewer(paletteDiv);
sizeIsVisible = 2;
} else {
sizeTitle.text(szTitleText);
}
halfSize = size / 2;
};
var colorTitle;
var refreshColor = function () {
drawBorder = false;
if (data.individualColors && data.colorPalette) {
var clrTitleText = getTitle("color");
if (colorIsVisible == 0) {
colorDiv = $("
").appendTo(itemDiv);
colorTitle = $("
").text(clrTitleText).appendTo(colorDiv);
colorDivStyle = colorDiv[0].style;
var paletteDiv = $("
").appendTo(colorDiv);
colorControl = new InteractiveDataDisplay.ColorPaletteViewer(paletteDiv);
colorIsVisible = 2;
} else {
colorTitle.text(clrTitleText);
}
colorControl.palette = data.colorPalette;
if (colorIsVisible == 1) {
colorDivStyle.display = "block";
colorIsVisible = 2;
}
}
else {
if (colorIsVisible == 2) {
colorDivStyle.display = "none";
colorIsVisible = 1;
}
}
if (data.individualColors) {
border = "#000000";
color = "#ffffff";
drawBorder = true;
}
else {
color = data.color;
border = color;
if (data.border != null) {
drawBorder = true;
border = data.border;
}
}
};
var renderShape = function () {
var sampleColor = "gray";
var sampleBorderColor = "gray";
InteractiveDataDisplay.Petal.drawSample(context, x1, y1, halfSize / 2, halfSize, sampleColor);
};
refreshColor();
refreshSize();
renderShape();
},
getTooltipData: function (originalData, index) {
var dataRow = {};
var formatter = {};
if (InteractiveDataDisplay.Utils.isArray(originalData.x) && index < originalData.x.length) {
formatter["x"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.x);
dataRow['x'] = formatter["x"].toString(originalData.x[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.y) && index < originalData.y.length) {
formatter["y"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.y);
dataRow['y'] = formatter["y"].toString(originalData.y[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.color) && index < originalData.color.length) {
formatter["color"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.color);
dataRow['color'] = formatter["color"].toString(originalData.color[index]);
}
if (originalData.size) {
dataRow['size'] = {};
if (InteractiveDataDisplay.Utils.isArray(originalData.size.lower95) && index < originalData.size.lower95.length) {
formatter["lower95"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.size.lower95);
dataRow['size']["lower 95%"] = formatter["lower95"].toString(originalData.size.lower95[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.size.upper95) && index < originalData.size.upper95.length) {
formatter["upper95"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.size.upper95);
dataRow['size']["upper 95%"] = formatter["upper95"].toString(originalData.size.upper95[index]);
}
}
dataRow["index"] = index;
return dataRow;
},
renderSvg: function (plotRect, screenSize, svg, data, t) {
var n = data.y.length;
if (n == 0) return;
var petal_g = svg.group();
var dataToScreenX = t.dataToScreenX;
var dataToScreenY = t.dataToScreenY;
// size of the canvas
var w_s = screenSize.width;
var h_s = screenSize.height;
var xmin = 0, xmax = w_s;
var ymin = 0, ymax = h_s;
var x1, y1;
var i = 0;
var size = data.size[0];
var border = data.border == null ? 'none' : data.border;
for (; i < n; i++) {
x1 = dataToScreenX(data.x[i]);
y1 = dataToScreenY(data.y[i]);
var maxSize = size / 2;
var minSize = maxSize * (1 - (data.upper95[i] - data.lower95[i]) / data.maxDelta);
var color = data.color[i];
c1 = (x1 > w_s || x1 < 0 || y1 > h_s || y1 < 0);
if (!c1) {
var A, D;
var C = Math.random() * Math.PI * 2;
A = (maxSize - minSize) / 2;
D = (maxSize + minSize) / 2;
var m = 200;
var alpha = Math.PI * 2 / m;
var segment = [];
for (var j = 0; j < m; j++) {
var phi = alpha * j;
var r = A * Math.sin(6 * phi + C) + D;
segment.push([x1 + r * Math.cos(phi), y1 + r * Math.sin(phi)]);
}
petal_g.polyline(segment).fill(color).stroke("black");
petal_g.circle(2).translate(x1 - 1, y1 - 1).fill("gray").stroke({ width: 2, color: "gray" });
}
}
petal_g.clipWith(petal_g.rect(w_s, h_s));
}
};
InteractiveDataDisplay.BullEye = {
prepare: function (data) {
data.bullEyeShape = data.bullEyeShape ? data.bullEyeShape.toLowerCase() : "circle";
// y
if (data.y == undefined || data.y == null) throw "The mandatory property 'y' is undefined or null";
if (!InteractiveDataDisplay.Utils.isArray(data.y)) throw "The property 'y' must be an array of numbers";
var n = data.y.length;
var mask = new Int8Array(n);
InteractiveDataDisplay.Utils.maskNaN(mask, data.y);
// x
if (data.x == undefined)
data.x = InteractiveDataDisplay.Utils.range(0, n - 1);
else if (!InteractiveDataDisplay.Utils.isArray(data.x)) throw "The property 'x' must be an array of numbers";
else if (data.x.length != n) throw "Length of the array which is a value of the property 'x' differs from lenght of 'y'"
else InteractiveDataDisplay.Utils.maskNaN(mask, data.x);
// border
if (data.border == undefined || data.border == "none")
data.border = null; // no border
// colors
if (InteractiveDataDisplay.Utils.isArray(data.color.lower95) && InteractiveDataDisplay.Utils.isArray(data.color.upper95)) {
if (data.color.lower95.length != n && data.color.upper95.length != n) throw "Length of the array 'color' is different than length of the array 'y'"
if (n > 0 && typeof (data.color.lower95[0]) !== "undefined" && typeof (data.color.upper95[0]) !== "string") { // color is a data series
var palette = data.colorPalette;
if (palette == undefined) palette = InteractiveDataDisplay.Markers.defaults.colorPalette;
if (typeof palette == 'string') palette = new InteractiveDataDisplay.ColorPalette.parse(palette);
if (palette != undefined && palette.isNormalized) {
var r = { min: InteractiveDataDisplay.Utils.getMin(data.color.lower95), max: InteractiveDataDisplay.Utils.getMax(data.color.upper95) };
r = InteractiveDataDisplay.Utils.makeNonEqual(r);
palette = palette.absolute(r.min, r.max);
}
data.colorPalette = palette;
var colors_u95 = [];
var colors_l95 = [];
for (var i = 0; i < n; i++){
var color_u95 = data.color.upper95[i];
var color_l95 = data.color.lower95[i];
if (color_u95 != color_u95 || color_l95 != color_l95)
mask[i] = 1;
else {
var u95rgba = palette.getRgba(color_u95);
var l95rgba = palette.getRgba(color_l95);
colors_u95[i] = "rgba(" + u95rgba.r + "," + u95rgba.g + "," + u95rgba.b + "," + u95rgba.a + ")";
colors_l95[i] = "rgba(" + l95rgba.r + "," + l95rgba.g + "," + l95rgba.b + "," + l95rgba.a + ")";
}
}
data.upper95 = colors_u95;
data.lower95 = colors_l95;
}
data.individualColors = true;
} else {
data.individualColors = false;
}
// sizes
var sizes = new Array(n);
if (data.size == undefined) data.size = InteractiveDataDisplay.Markers.defaults.size;
if (InteractiveDataDisplay.Utils.isArray(data.size)) {
if (data.size.length != n) throw "Length of the array 'size' is different than length of the array 'y'"
if (data.sizePalette != undefined) { // 'size' is a data series
var palette = InteractiveDataDisplay.SizePalette.Create(data.sizePalette);
if (palette.isNormalized) {
var r = InteractiveDataDisplay.Utils.getMinMax(data.size);
r = InteractiveDataDisplay.Utils.makeNonEqual(r);
palette = new InteractiveDataDisplay.SizePalette(false, palette.sizeRange, r);
}
data.sizePalette = palette;
for (var i = 0; i < n; i++) {
var size = data.size[i];
if (size != size) // NaN
mask[i] = 1;
else
sizes[i] = palette.getSize(size)
}
} else { // 'size' contains values in pixels
data.sizeMax = InteractiveDataDisplay.Utils.getMax(data.size);
}
} else { // sizes is a constant
for (var i = 0; i < n; i++) sizes[i] = data.size;
data.sizeMax = data.size;
}
data.size = sizes;
// Filtering out missing values
var m = 0;
for (var i = 0; i < n; i++) if (mask[i] === 1) m++;
if (m > 0) { // there are missing values
m = n - m;
data.x = InteractiveDataDisplay.Utils.applyMask(mask, data.x, m);
data.y = InteractiveDataDisplay.Utils.applyMask(mask, data.y, m);
data.size = InteractiveDataDisplay.Utils.applyMask(mask, data.size, m);
if (data.individualColors) {
data.upper95 = InteractiveDataDisplay.Utils.applyMask(mask, data.upper95, m);
data.lower95 = InteractiveDataDisplay.Utils.applyMask(mask, data.lower95, m);
}
var indices = Array(m);
for (var i = 0, j = 0; i < n; i++) if (mask[i] === 0) indices[j++] = i;
data.indices = indices;
} else {
data.indices = InteractiveDataDisplay.Utils.range(0, n - 1);
}
},
preRender: function (data, plotRect, screenSize, dt, context) {
if(data.border != null)
context.strokeStyle = data.border;
return data;
},
draw: function (marker, plotRect, screenSize, transform, context) {
var mean = marker.y_mean;
var u95 = marker.upper95;
var l95 = marker.lower95;
var msize = marker.size;
var shift = msize / 2;
var x = transform.dataToScreenX(marker.x);
var y = transform.dataToScreenY(marker.y);
if (x + shift < 0 || x - shift > screenSize.width) return;
if (y + shift < 0 || y - shift > screenSize.height) return;
InteractiveDataDisplay.BullEye.drawBullEye(context, marker.bullEyeShape, x, y, msize, msize, u95);
InteractiveDataDisplay.BullEye.drawBullEye(context, marker.bullEyeShape, x, y, shift, shift, l95);
},
drawBullEye: function(context, shape, x, y, width, height, fill, stroke) {
var w = width;
var h = height;
var useStroke = stroke !== "none";
context.strokeStyle = stroke !== undefined ? stroke : "black";
context.fillStyle = fill !== undefined ? fill : "black";
var x1 = x;
var y1 = y;
var size = Math.min(w, h);
var halfSize = 0.5 * size;
var quarterSize = 0.5 * halfSize;
context.clearRect(0, 0, w, h);
switch (shape) {
case "box": // box
if (useStroke) context.strokeRect(x1 - halfSize, y1 - halfSize, size, size);
context.fillRect(x1 - halfSize, y1 - halfSize, size, size);
break;
case "circle": // circle
context.beginPath();
context.arc(x1, y1, halfSize, 0, 2 * Math.PI);
if (useStroke) context.stroke();
context.fill();
break;
case "diamond": // diamond
context.beginPath();
context.moveTo(x1 - halfSize, y1);
context.lineTo(x1, y1 - halfSize);
context.lineTo(x1 + halfSize, y1);
context.lineTo(x1, y1 + halfSize);
context.closePath();
if (useStroke) context.stroke();
context.fill();
break;
case "cross": // cross
var thirdSize = size / 3;
var halfThirdSize = thirdSize / 2;
context.beginPath();
context.moveTo(x1 - halfSize, y1 - halfThirdSize);
context.lineTo(x1 - halfThirdSize, y1 - halfThirdSize);
context.lineTo(x1 - halfThirdSize, y1 - halfSize);
context.lineTo(x1 + halfThirdSize, y1 - halfSize);
context.lineTo(x1 + halfThirdSize, y1 - halfThirdSize);
context.lineTo(x1 + halfSize, y1 - halfThirdSize);
context.lineTo(x1 + halfSize, y1 + halfThirdSize);
context.lineTo(x1 + halfThirdSize, y1 + halfThirdSize);
context.lineTo(x1 + halfThirdSize, y1 + halfSize);
context.lineTo(x1 - halfThirdSize, y1 + halfSize);
context.lineTo(x1 - halfThirdSize, y1 + halfThirdSize);
context.lineTo(x1 - halfSize, y1 + halfThirdSize);
context.closePath();
if (useStroke) context.stroke();
context.fill();
break;
case "triangle": // triangle
var r = Math.sqrt(3) / 6 * size;
context.beginPath();
context.moveTo(x1 - halfSize, y1 + r);
context.lineTo(x1, y1 - r * 2);
context.lineTo(x1 + halfSize, y1 + r);
context.closePath();
if (useStroke) context.stroke();
context.fill();
break;
}
},
hitTest: function (marker, transform, ps, pd) {
var xScreen = transform.dataToScreenX(marker.x);
var yScreen = transform.dataToScreenY(marker.y);
var isIntersecting =
ps.x > xScreen - marker.size / 2 &&
ps.x < xScreen + marker.size / 2 &&
ps.y > yScreen - marker.size / 2 &&
ps.y < yScreen + marker.size / 2;
return isIntersecting;
},
getPadding: function (data) {
var padding = 0;
return { left: padding, right: padding, top: padding, bottom: padding };
},
getLegend: function (data, getTitle, legendDiv) { // todo: should be refactored
var itemDiv = legendDiv.content;
var fontSize = 14;
if (document.defaultView && document.defaultView.getComputedStyle) {
fontSize = parseFloat(document.defaultView.getComputedStyle(itemDiv[0], null).getPropertyValue("font-size"));
}
if (isNaN(fontSize) || fontSize == 0) fontSize = 14;
var canvas = legendDiv.thumbnail;
var canvasIsVisible = true;
var maxSize = fontSize * 1.5;
var x1 = maxSize / 2 + 1;
var y1 = maxSize / 2 + 1;
canvas[0].width = canvas[0].height = maxSize + 2;
var canvasStyle = canvas[0].style;
var context = canvas.get(0).getContext("2d");
context.clearRect(0, 0, canvas[0].width, canvas[0].height);
var color, border, drawBorder;
var colorDiv, colorDivStyle, colorControl;
var colorIsVisible = 0;
var size, halfSize;
var sizeDiv, sizeDivStyle, sizeControl;
var sizeIsVisible = 0;
var sizeTitle;
var refreshSize = function () {
size = maxSize;
if (data.sizePalette) {
var szTitleText = getTitle("size");
if (sizeIsVisible == 0) {
sizeDiv = $("
").appendTo(itemDiv);
sizeTitle = $("
").text(szTitleText).appendTo(sizeDiv);
sizeDivStyle = sizeDiv[0].style;
var paletteDiv = $("
").appendTo(sizeDiv);
sizeControl = new InteractiveDataDisplay.SizePaletteViewer(paletteDiv);
sizeIsVisible = 2;
} else {
sizeTitle.text(szTitleText);
}
sizeControl.palette = InteractiveDataDisplay.SizePalette.Create(data.sizePalette);
}
halfSize = size / 2;
};
var colorTitle;
var refreshColor = function () {
drawBorder = false;
if (data.individualColors && data.colorPalette) {
var clrTitleText = getTitle("color");
if (colorIsVisible == 0) {
colorDiv = $("
").appendTo(itemDiv);
colorTitle = $("
").text(clrTitleText).appendTo(colorDiv);
colorDivStyle = colorDiv[0].style;
var paletteDiv = $("
").appendTo(colorDiv);
colorControl = new InteractiveDataDisplay.ColorPaletteViewer(paletteDiv);
colorIsVisible = 2;
} else {
colorTitle.text(clrTitleText);
}
colorControl.palette = data.colorPalette;
if (colorIsVisible == 1) {
colorDivStyle.display = "block";
colorIsVisible = 2;
}
}
else {
if (colorIsVisible == 2) {
colorDivStyle.display = "none";
colorIsVisible = 1;
}
}
if (data.individualColors) {
border = "#000000";
color = "#ffffff";
drawBorder = true;
}
else {
color = data.color;
border = color;
if (data.border != null) {
drawBorder = true;
border = data.border;
}
}
};
var renderShape = function () {
var sampleColor = "gray";
var sampleBorderColor = "gray";
InteractiveDataDisplay.BullEye.drawBullEye(context, data.bullEyeShape, x1, y1, size, size, sampleColor, sampleBorderColor);
};
refreshColor();
refreshSize();
renderShape();
},
getTooltipData: function (originalData, index) {
var dataRow = {};
var formatter = {};
if (InteractiveDataDisplay.Utils.isArray(originalData.x) && index < originalData.x.length) {
formatter["x"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.x);
dataRow['x'] = formatter["x"].toString(originalData.x[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.y) && index < originalData.y.length) {
formatter["y"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.y);
dataRow['y'] = formatter["y"].toString(originalData.y[index]);
}
if (originalData.color) {
dataRow['color'] = {};
if (InteractiveDataDisplay.Utils.isArray(originalData.color.lower95) && index < originalData.color.lower95.length) {
formatter["lower95"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.color.lower95);
dataRow['color']["lower 95%"] = formatter["lower95"].toString(originalData.color.lower95[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.color.upper95) && index < originalData.color.upper95.length) {
formatter["upper95"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.color.upper95);
dataRow['color']["upper 95%"] = formatter["upper95"].toString(originalData.color.upper95[index]);
}
}
if (InteractiveDataDisplay.Utils.isArray(originalData.size) && index < originalData.size.length) {
formatter["size"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.size);
dataRow['size'] = formatter["size"].toString(originalData.size[index]);
}
dataRow["index"] = index;
return dataRow;
},
renderSvg: function (plotRect, screenSize, svg, data, t) {
var n = data.y.length;
if (n == 0) return;
var bulleye_g = svg.group();
var dataToScreenX = t.dataToScreenX;
var dataToScreenY = t.dataToScreenY;
// size of the canvas
var w_s = screenSize.width;
var h_s = screenSize.height;
var xmin = 0, xmax = w_s;
var ymin = 0, ymax = h_s;
var x1, y1;
var i = 0;
var size = data.size[0];
var halfSize = 0.5 * size;
var quarterSize = 0.5 * halfSize;
for (; i < n; i++) {
x1 = dataToScreenX(data.x[i]);
y1 = dataToScreenY(data.y[i]);
c1 = (x1 + halfSize < 0 || x1 - halfSize > w_s || y1 + halfSize < 0 || y1 - halfSize > h_s);
var color_u95 = data.individualColors ? data.upper95[i] : data.upper95;
var color_l95 = data.individualColors ? data.lower95[i] : data.lower95;
if (!c1) {
switch (data.bullEyeShape) {
case "box":
bulleye_g.rect(size, size).translate(x1 - halfSize, y1 - halfSize).fill(color_u95).stroke({ width: 0.5, color: "black" });
bulleye_g.rect(halfSize, halfSize).translate(x1 - quarterSize, y1 - quarterSize).fill(color_l95).stroke({ width: 0.5, color: "black" });
break;
case "circle":
bulleye_g.circle(size).translate(x1 - halfSize, y1 - halfSize).fill(color_u95).stroke({ width: 0.5, color: "black" });
bulleye_g.circle(halfSize).translate(x1 - quarterSize, y1 - quarterSize).fill(color_l95).stroke({ width: 0.5, color: "black" });
break;
case "diamond":
bulleye_g.rect(size / Math.sqrt(2), size / Math.sqrt(2)).translate(x1, y1 - halfSize).fill(color_u95).stroke({ width: 0.5, color: "black" }).rotate(45);
bulleye_g.rect(halfSize / Math.sqrt(2), halfSize / Math.sqrt(2)).translate(x1, y1 - quarterSize).fill(color_l95).stroke({ width: 0.5, color: "black" }).rotate(45);
break;
case "cross":
var halfThirdSize = size / 6;
var quarterThirdSize = halfSize / 6;
bulleye_g.polyline([[-halfSize, -halfThirdSize], [-halfThirdSize, -halfThirdSize], [-halfThirdSize, -halfSize], [halfThirdSize, -halfSize],
[halfThirdSize, -halfThirdSize], [halfSize, -halfThirdSize], [halfSize, halfThirdSize], [halfThirdSize, halfThirdSize], [halfThirdSize, halfSize],
[-halfThirdSize, halfSize], [-halfThirdSize, halfThirdSize], [-halfSize, halfThirdSize], [-halfSize, -halfThirdSize]]).translate(x1, y1).fill(color_u95).stroke({ width: 0.5, color: "black" });
bulleye_g.polyline([[-quarterSize, -quarterThirdSize], [-quarterThirdSize, -quarterThirdSize], [-quarterThirdSize, -quarterSize], [quarterThirdSize, -quarterSize],
[quarterThirdSize, -quarterThirdSize], [quarterSize, -quarterThirdSize], [quarterSize, quarterThirdSize], [quarterThirdSize, quarterThirdSize], [quarterThirdSize, quarterSize],
[-quarterThirdSize, quarterSize], [-quarterThirdSize, quarterThirdSize], [-quarterSize, quarterThirdSize], [-quarterSize, -quarterThirdSize]]).translate(x1, y1).fill(color_l95).stroke({ width: 0.5, color: "black" });
break;
case "triangle":
var r = Math.sqrt(3) / 6 * size;
var hr = Math.sqrt(3) / 6 * halfSize;
bulleye_g.polyline([[-halfSize, r], [0, -r * 2], [halfSize, r], [-halfSize, r]]).translate(x1, y1).fill(color_u95).stroke({ width: 0.5, color: "black" });
bulleye_g.polyline([[-quarterSize, hr], [0, -hr * 2], [quarterSize, hr], [-quarterSize, hr]]).translate(x1, y1).fill(color_l95).stroke({ width: 0.5, color: "black" });
break;
}
}
}
bulleye_g.clipWith(bulleye_g.rect(w_s, h_s));
}
};
InteractiveDataDisplay.BoxWhisker = {
prepare: function (data) {
// y
if (data.y.median == undefined || data.y.median == null) throw "The mandatory property 'y' is undefined or null";
if (!InteractiveDataDisplay.Utils.isArray(data.y.median)) throw "The property 'y' must be an array of numbers";
var n = data.y.median.length;
var mask = new Int8Array(n);
InteractiveDataDisplay.Utils.maskNaN(mask, data.y.median);
// x
if (data.x == undefined)
data.x = InteractiveDataDisplay.Utils.range(0, n - 1);
else if (!InteractiveDataDisplay.Utils.isArray(data.x)) throw "The property 'x' must be an array of numbers";
else if (data.x.length != n) throw "Length of the array which is a value of the property 'x' differs from lenght of 'y'"
else InteractiveDataDisplay.Utils.maskNaN(mask, data.x);
// border
if (data.border == undefined || data.border == "none")
data.border = null; // no border
if (data.thickness == undefined || data.thickness == "none")
data.thickness = 2;
// colors
if (data.color == undefined) data.color = InteractiveDataDisplay.Markers.defaults.color;
// sizes
var sizes = new Array(n);
if (data.size == undefined) data.size = InteractiveDataDisplay.Markers.defaults.size;
if (InteractiveDataDisplay.Utils.isArray(data.y.lower95) && InteractiveDataDisplay.Utils.isArray(data.y.upper95)) {
if (data.y.lower95.length != n && data.y.upper95.length != n)
throw "Length of the array 'y' is different than length of the array 'y'";
if (n > 0 && typeof (data.y.lower95[0]) === "number" && typeof (data.y.upper95[0]) === "number") { // color is a data series
var ys_u95 = [];
var ys_l95 = [];
for (var i = 0; i < n; i++) {
var y_u95 = data.y.upper95[i];
var y_l95 = data.y.lower95[i];
if (y_u95 != y_u95 || y_l95 != y_l95)
mask[i] = 1;
else {
ys_u95[i] = data.y.upper95[i];
ys_l95[i] = data.y.lower95[i];
}
}
data.upper95 = ys_u95;
data.lower95 = ys_l95;
}
}
if (InteractiveDataDisplay.Utils.isArray(data.y.lower68) && InteractiveDataDisplay.Utils.isArray(data.y.upper68)) {
if (data.y.lower68.length != n && data.y.upper68.length != n)
throw "Length of the array 'y' is different than length of the array 'y'";
if (n > 0 && typeof (data.y.lower68[0]) === "number" && typeof (data.y.upper68[0]) === "number") { // color is a data series
var ys_u68 = [];
var ys_l68 = [];
for (var i = 0; i < n; i++) {
var y_u68 = data.y.upper68[i];
var y_l68 = data.y.lower68[i];
if (y_u68 != y_u68 || y_l68 != y_l68)
mask[i] = 1;
else {
ys_u68[i] = data.y.upper68[i];
ys_l68[i] = data.y.lower68[i];
}
}
data.upper68 = ys_u68;
data.lower68 = ys_l68;
}
}
for (var i = 0; i < n; i++) sizes[i] = data.size;
data.sizeMax = data.size;
data.size = sizes;
// Filtering out missing values
var m = 0;
for (var i = 0; i < n; i++) if (mask[i] === 1) m++;
if (m > 0) { // there are missing values
m = n - m;
data.x = InteractiveDataDisplay.Utils.applyMask(mask, data.x, m);
data.y = InteractiveDataDisplay.Utils.applyMask(mask, data.y.median, m);
data.size = InteractiveDataDisplay.Utils.applyMask(mask, data.size, m);
data.upper95 = InteractiveDataDisplay.Utils.applyMask(mask, data.upper95, m);
data.lower95 = InteractiveDataDisplay.Utils.applyMask(mask, data.lower95, m);
data.upper68 = InteractiveDataDisplay.Utils.applyMask(mask, data.upper68, m);
data.lower68 = InteractiveDataDisplay.Utils.applyMask(mask, data.lower68, m);
var indices = Array(m);
for (var i = 0, j = 0; i < n; i++) if (mask[i] === 0) indices[j++] = i;
data.indices = indices;
} else {
data.y = data.y.median;
data.indices = InteractiveDataDisplay.Utils.range(0, n - 1);
}
},
preRender: function (data, plotRect, screenSize, dt, context) {
context.fillStyle = data.color;
if (data.border != null)
context.strokeStyle = data.border;
return data;
},
draw: function (marker, plotRect, screenSize, transform, context) {
var msize = marker.size;
var shift = msize / 2;
var x = transform.dataToScreenX(marker.x);
var u68 = transform.dataToScreenY(marker.upper68);
var l68 = transform.dataToScreenY(marker.lower68);
var u95 = transform.dataToScreenY(marker.upper95);
var l95 = transform.dataToScreenY(marker.lower95);
var mean = transform.dataToScreenY(marker.y);
context.beginPath();
context.strokeStyle = marker.border === undefined ? "black" : marker.border;
context.lineWidth = marker.thickness;
if (marker.color) context.fillRect(x - shift, l68, msize, u68 - l68);
context.strokeRect(x - shift, l68, msize, u68 - l68);
context.moveTo(x - shift, u95);
context.lineTo(x + shift, u95);
context.moveTo(x, u95);
context.lineTo(x, u68);
context.moveTo(x, l68);
context.lineTo(x, l95);
context.moveTo(x - shift, l95);
context.lineTo(x + shift, l95);
context.moveTo(x - shift, mean);
context.lineTo(x + shift, mean);
context.stroke();
if (marker.y_min !== undefined) {
context.beginPath();
context.arc(x, transform.dataToScreenY(marker.y_min), shift / 2, 0, 2 * Math.PI);
context.stroke();
}
if (marker.y_max !== undefined) {
context.beginPath();
context.arc(x, transform.dataToScreenY(marker.y_max), shift / 2, 0, 2 * Math.PI);
context.stroke();
}
},
hitTest: function (marker, transform, ps, pd) {
var xScreen = transform.dataToScreenX(marker.x);
var ymax = transform.dataToScreenY(marker.y_min === undefined ? marker.lower95 : marker.y_min);
var ymin = transform.dataToScreenY(marker.y_max === undefined ? marker.upper95 : marker.y_max);
var isIntersecting =
ps.x > xScreen - marker.size / 2 &&
ps.x < xScreen + marker.size / 2 &&
ps.y > ymin &&
ps.y < ymax;
return isIntersecting;
},
getBoundingBox: function (marker) {
var size = marker.size;
var xLeft = marker.x;
var yBottom = marker.lower95 ? marker.lower95 : (marker.lower68 ? marker.lower68 : marker.y);
var yTop = marker.upper95 ? marker.upper95 : (marker.upper68 ? marker.upper68 : marker.y);
return { x: xLeft, y: yBottom, width: 0, height: Math.abs(yTop - yBottom) };
},
getPadding: function (data) {
var padding = 0;
return { left: padding, right: padding, top: padding, bottom: padding };
},
getLegend: function (data, getTitle, legendDiv) { // todo: should be refactored
var itemDiv = legendDiv.content;
var fontSize = 14;
if (document.defaultView && document.defaultView.getComputedStyle) {
fontSize = parseFloat(document.defaultView.getComputedStyle(itemDiv[0], null).getPropertyValue("font-size"));
}
if (isNaN(fontSize) || fontSize == 0) fontSize = 14;
var canvas = legendDiv.thumbnail;
var canvasIsVisible = true;
var maxSize = fontSize * 1.5;
var x1 = maxSize / 2 + 1;
var y1 = maxSize / 2 + 1;
canvas[0].width = canvas[0].height = maxSize + 2;
var canvasStyle = canvas[0].style;
var context = canvas.get(0).getContext("2d");
var itemIsVisible = 0;
var color, border, drawBorder;
var colorDiv, colorDivStyle, colorControl;
var colorIsVisible = 0;
var size, halfSize;
var sizeDiv, sizeDivStyle, sizeControl;
var sizeIsVisible = 0;
var sizeTitle;
var refreshSize = function () {
size = maxSize;
if (data.sizePalette) {
var szTitleText = getTitle("size");
if (sizeIsVisible == 0) {
sizeDiv = $("
").appendTo(itemDiv);
sizeTitle = $("
").text(szTitleText).appendTo(sizeDiv);
sizeDivStyle = sizeDiv[0].style;
var paletteDiv = $("
").appendTo(sizeDiv);
sizeControl = new InteractiveDataDisplay.SizePaletteViewer(paletteDiv);
sizeIsVisible = 2;
} else {
sizeTitle.text(szTitleText);
}
sizeControl.palette = InteractiveDataDisplay.SizePalette.Create(data.sizePalette);
}
halfSize = size / 2;
};
var colorTitle;
var refreshColor = function () {
drawBorder = false;
if (data.individualColors && data.colorPalette) {
var clrTitleText = getTitle("color");
if (colorIsVisible == 0) {
colorDiv = $("
").appendTo(itemDiv);
colorTitle = $("
").text(clrTitleText).appendTo(colorDiv);
colorDivStyle = colorDiv[0].style;
var paletteDiv = $("
").appendTo(colorDiv);
colorControl = new InteractiveDataDisplay.ColorPaletteViewer(paletteDiv);
colorIsVisible = 2;
} else {
colorTitle.text(clrTitleText);
}
colorControl.palette = data.colorPalette;
if (colorIsVisible == 1) {
colorDivStyle.display = "block";
colorIsVisible = 2;
}
}
else {
if (colorIsVisible == 2) {
colorDivStyle.display = "none";
colorIsVisible = 1;
}
}
if (data.individualColors) {
border = "#000000";
color = "#ffffff";
drawBorder = true;
}
else {
color = data.color;
border = color;
if (data.border != null) {
drawBorder = true;
border = data.border;
}
}
};
var renderShape = function () {
var sampleColor = typeof data.color == "string" ? data.color : "gray";
var sampleBorderColor = typeof data.border == "string" ? data.border : "gray";
var useStroke = sampleBorderColor !== "none";
context.strokeStyle = sampleBorderColor !== undefined ? sampleBorderColor : "black";
context.fillStyle = sampleColor !== undefined ? sampleColor : "black";
var halfSize = 0.5 * size;
var quarterSize = 0.5 * halfSize;
context.clearRect(0, 0, size, size);
context.fillRect(x1 - halfSize, y1 - quarterSize, size, halfSize);
if (useStroke) context.strokeRect(x1 - halfSize, y1 - quarterSize, size, halfSize);
context.beginPath();
context.moveTo(x1 - halfSize, y1 + halfSize);
context.lineTo(x1 + halfSize, y1 + halfSize);
context.moveTo(x1 - halfSize, y1 - halfSize);
context.lineTo(x1 + halfSize, y1 - halfSize);
context.moveTo(x1, y1 + halfSize);
context.lineTo(x1, y1 + quarterSize);
context.moveTo(x1, y1 - halfSize);
context.lineTo(x1, y1 - quarterSize);
context.closePath();
if (useStroke) context.stroke();
if (useStroke) {
context.beginPath();
context.moveTo(x1 - halfSize, y1);
context.lineTo(x1 + halfSize, y1);
context.stroke();
}
};
refreshColor();
refreshSize();
renderShape();
},
getTooltipData: function (originalData, index) {
var dataRow = {};
var formatter = {};
if (InteractiveDataDisplay.Utils.isArray(originalData.x) && index < originalData.x.length) {
formatter["x"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.x);
dataRow['x'] = formatter["x"].toString(originalData.x[index]);
}
if (originalData.y) {
dataRow['y'] = {};
if (InteractiveDataDisplay.Utils.isArray(originalData.y.median) && index < originalData.y.median.length) {
formatter["median"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.y.median);
dataRow['y']["median"] = formatter["median"].toString(originalData.y.median[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.y.lower95) && index < originalData.y.lower95.length) {
formatter["lower95"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.y.lower95);
dataRow['y']["lower 95%"] = formatter["lower95"].toString(originalData.y.lower95[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.y.upper95) && index < originalData.y.upper95.length) {
formatter["upper95"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.y.upper95);
dataRow['y']["upper 95%"] = formatter["upper95"].toString(originalData.y.upper95[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.y.lower68) && index < originalData.y.lower68.length) {
formatter["lower68"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.y.lower68);
dataRow['y']["lower 68%"] = formatter["lower68"].toString(originalData.y.lower68[index]);
}
if (InteractiveDataDisplay.Utils.isArray(originalData.y.upper68) && index < originalData.y.upper68.length) {
formatter["upper68"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.y.upper68);
dataRow['y']["upper 68%"] = formatter["upper68"].toString(originalData.y.upper68[index]);
}
}
if (InteractiveDataDisplay.Utils.isArray(originalData.size) && index < originalData.size.length) {
formatter["size"] = new InteractiveDataDisplay.AdaptiveFormatter(originalData.size);
dataRow['size'] = formatter["size"].toString(originalData.size[index]);
}
dataRow["index"] = index;
return dataRow;
},
renderSvg: function (plotRect, screenSize, svg, data, t) {
var n = data.y.length;
if (n == 0) return;
var bx_g = svg.group();
var dataToScreenX = t.dataToScreenX;
var dataToScreenY = t.dataToScreenY;
// size of the canvas
var w_s = screenSize.width;
var h_s = screenSize.height;
var xmin = 0, xmax = w_s;
var ymin = 0, ymax = h_s;
var x1, y1, u68, l68, u95, l95, mean;
var i = 0;
var size = data.size[0];
var shift = size / 2;
var color = data.color;
var border = data.border == null ? 'none' : data.border;
var thickness = data.thickness;
for (; i < n; i++) {
x1 = dataToScreenX(data.x[i]);
y1 = dataToScreenY(data.y[i]);
u68 = data.upper68 ? dataToScreenY(data.upper68[i]) : undefined;
l68 = data.lower68 ? dataToScreenY(data.lower68[i]) : undefined;
u95 = data.upper95 ? dataToScreenY(data.upper95[i]) : undefined;
l95 = data.lower95 ? dataToScreenY(data.lower95[i]) : undefined;
mean = dataToScreenY(data.y[i]);
c1 = (x1 < 0 || x1 > w_s || y1 < 0 || y1 > h_s);
if (!c1) {
if (u95 != undefined && l95 != undefined) {
bx_g.polyline([[x1 - shift, u95], [x1 + shift, u95]]).style({ fill: "none", stroke: border, "stroke-width": thickness });
bx_g.polyline([[x1, u95], [x1, l95]]).style({ fill: "none", stroke: border, "stroke-width": thickness });
//bx_g.polyline([[x1, l68], [x1, l95]]).style({ fill: "none", stroke: border, "stroke-width": thickness });
bx_g.polyline([[x1 - shift, l95], [x1 + shift, l95]]).style({ fill: "none", stroke: border, "stroke-width": thickness });
}
if (u68 != undefined && l68 != undefined) bx_g.rect(size, Math.abs(u68 - l68)).translate(x1 - shift, u68).fill(color).style({ fill: color, stroke: border, "stroke-width": thickness });
bx_g.polyline([[x1 - shift, mean], [x1 + shift, mean]]).style({ fill: "none", stroke: border, "stroke-width": thickness });
}
}
bx_g.clipWith(bx_g.rect(w_s, h_s));
}
};
InteractiveDataDisplay.Bars = {
prepare: function (data) {
var n, mask;
// x
if (data.orientation == "h") {
if (data.x == undefined || data.x == null) throw "The mandatory property 'x' is undefined or null";
if (!InteractiveDataDisplay.Utils.isArray(data.x)) throw "The property 'x' must be an array of numbers";
n = data.x.length;
mask = new Int8Array(n);
InteractiveDataDisplay.Utils.maskNaN(mask, data.x);
//x
if (data.y == undefined)
data.y = InteractiveDataDisplay.Utils.range(0, n - 1);
else if (!InteractiveDataDisplay.Utils.isArray(data.y)) throw "The property 'x' must be an array of numbers";
else if (data.y.length != n) throw "Length of the array which is a value of the property 'x' differs from length of 'y'"
else InteractiveDataDisplay.Utils.maskNaN(mask, data.y);
}
else {
if (data.y == undefined || data.y == null) throw "The mandatory property 'y' is undefined or null";
if (!InteractiveDataDisplay.Utils.isArray(data.y)) throw "The property 'y' must be an array of numbers";
n = data.y.length;
mask = new Int8Array(n);
InteractiveDataDisplay.Utils.maskNaN(mask, data.y);
//x
if (data.x == undefined)
data.x = InteractiveDataDisplay.Utils.range(0, n - 1);
else if (!InteractiveDataDisplay.Utils.isArray(data.x)) throw "The property 'x' must be an array of numbers";
else if (data.x.length != n) throw "Length of the array which is a value of the property 'x' differs from length of 'y'"
else InteractiveDataDisplay.Utils.maskNaN(mask, data.x);
}
//var mask = new Int8Array(n);
//InteractiveDataDisplay.Utils.maskNaN(mask, data.y);
////x
//if (data.x == undefined)
// data.x = InteractiveDataDisplay.Utils.range(0, n - 1);
//else if (!InteractiveDataDisplay.Utils.isArray(data.x)) throw "The property 'x' must be an array of numbers";
//else if (data.x.length != n) throw "Length of the array which is a value of the property 'x' differs from length of 'y'"
//else InteractiveDataDisplay.Utils.maskNaN(mask, data.x);
// border
if (data.border == undefined || data.border == "none")
data.border = null; // no border
// shadow
if (data.shadow == undefined || data.shadow == "none")
data.shadow = null; // no shadow
if (data.color == undefined) data.color = InteractiveDataDisplay.Markers.defaults.color;
if (InteractiveDataDisplay.Utils.isArray(data.color)) {
if (data.color.length != n) throw "Length of the array 'color' is different than length of the array 'y'"
if (n > 0 && typeof (data.color[0]) !== "string") { // color is a data series
var palette = data.colorPalette;
if (palette == undefined) palette = InteractiveDataDisplay.Markers.defaults.colorPalette;
if (typeof palette == 'string') palette = new InteractiveDataDisplay.ColorPalette.parse(palette);
if (palette != undefined && palette.isNormalized) {
var r = InteractiveDataDisplay.Utils.getMinMax(data.color);
r = InteractiveDataDisplay.Utils.makeNonEqual(r);
palette = palette.absolute(r.min, r.max);
}
data.colorPalette = palette;
var colors = new Array(n);
for (var i = 0; i < n; i++) {
var color = data.color[i];
if (color != color) // NaN
mask[i] = 1;
else {
var rgba = palette.getRgba(color);
colors[i] = "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
}
}
data.color = colors;
}
data.individualColors = true;
} else data.individualColors = false;
// Filtering out missing values
var m = 0;
for (var i = 0; i < n; i++) if (mask[i] === 1) m++;
if (m > 0) { // there are missing values
m = n - m;
data.x = InteractiveDataDisplay.Utils.applyMask(mask, data.x, m);
data.y = InteractiveDataDisplay.Utils.applyMask(mask, data.y, m);
if (data.individualColors)
data.color = InteractiveDataDisplay.Utils.applyMask(mask, data.color, m);
var indices = Array(m);
for (var i = 0, j = 0; i < n; i++) if (mask[i] === 0) indices[j++] = i;
data.indices = indices;
} else {
data.indices = InteractiveDataDisplay.Utils.range(0, n - 1);
}
},
draw: function (marker, plotRect, screenSize, transform, context) {
var barWidth = 0.5 * marker.barWidth;
var xLeft, xRight, yTop, yBottom;
if (marker.orientation == "h") {
xLeft = transform.dataToScreenX(0);
xRight = transform.dataToScreenX(marker.x);
if (xLeft > xRight) {
var k = xRight;
xRight = xLeft;
xLeft = k;
}
if (xLeft > screenSize.width || xRight < 0) return;
yTop = transform.dataToScreenY(marker.y + barWidth);
yBottom = transform.dataToScreenY(marker.y - barWidth);
if (yTop > screenSize.height || yBottom < 0) return;
} else {
xLeft = transform.dataToScreenX(marker.x - barWidth);
xRight = transform.dataToScreenX(marker.x + barWidth);
if (xLeft > screenSize.width || xRight < 0) return;
yTop = transform.dataToScreenY(marker.y);
yBottom = transform.dataToScreenY(0);
if (yTop > yBottom) {
var k = yBottom;
yBottom = yTop;
yTop = k;
}
if (yTop > screenSize.height || yBottom < 0) return;
}
if (marker.shadow) {
context.fillStyle = marker.shadow;
context.fillRect(xLeft + 2, yTop + 2, xRight - xLeft, yBottom - yTop);
}
context.fillStyle = marker.color;
context.fillRect(xLeft, yTop, xRight - xLeft, yBottom - yTop);
if (marker.border) {
context.strokeStyle = marker.border;
context.strokeRect(xLeft, yTop, xRight - xLeft, yBottom - yTop);
}
},
getBoundingBox: function (marker) {
var barWidth = marker.barWidth;
var xLeft = marker.orientation == "h"? Math.min(0, marker.x) : marker.x - barWidth / 2;
var yBottom = marker.orientation == "h" ? marker.y - barWidth / 2 : Math.min(0, marker.y);
return marker.orientation == "h" ? {x: xLeft, y: yBottom, width: Math.abs(marker.x), height: barWidth} : { x: xLeft, y: yBottom, width: barWidth, height: Math.abs(marker.y) };
},
hitTest: function (marker, transform, ps, pd) {
var barWidth = marker.barWidth;
var xLeft = marker.orientation == "h"? Math.min(0, marker.x) : marker.x - barWidth / 2;
var yBottom = marker.orientation == "h" ? marker.y - barWidth / 2 : Math.min(0, marker.y);
if (marker.orientation == "h") {
if (pd.x < xLeft || pd.x > xLeft + Math.abs(marker.x)) return false;
if (pd.y < yBottom || pd.y > yBottom + barWidth) return false;
return true;
}
if (pd.x < xLeft || pd.x > xLeft + barWidth) return false;
if (pd.y < yBottom || pd.y > yBottom + Math.abs(marker.y)) return false;
return true;
},
getLegend: function (data, getTitle, legendDiv) {
var itemDiv = legendDiv.content;
var fontSize = 14;
if (document.defaultView && document.defaultView.getComputedStyle) {
fontSize = parseFloat(document.defaultView.getComputedStyle(itemDiv[0], null).getPropertyValue("font-size"));
}
if (isNaN(fontSize) || fontSize == 0) fontSize = 14;
var canvas = legendDiv.thumbnail;
var canvasIsVisible = true;
var size = fontSize * 1.5;
var x1 = size / 3 - 0.5;
var x2 = size / 3 * 2;
var x3 = size;
var y1 = size / 2;
var y2 = 0;
var y3 = size / 3;
var barWidth = size / 3;
var shadowSize = 1;
canvas[0].width = canvas[0].height = size + 2;
var canvasStyle = canvas[0].style;
var context = canvas.get(0).getContext("2d");
var itemIsVisible = 0;
var color, border, drawBorder, shadow, drawShadow;
var colorDiv, colorDivStyle, colorControl;
var colorIsVisible = 0;
var colorTitle;
var refreshColor = function () {
drawBorder = false;
drawShadow = false;
if (data.individualColors && data.colorPalette) {
var clrTitleText = getTitle("color");
if (colorIsVisible == 0) {
colorDiv = $("
").appendTo(itemDiv);
colorTitle = $("
").text(clrTitleText).appendTo(colorDiv);
colorDivStyle = colorDiv[0].style;
var paletteDiv = $("
").appendTo(colorDiv);
colorControl = new InteractiveDataDisplay.ColorPaletteViewer(paletteDiv);
colorIsVisible = 2;
} else {
colorTitle.text(clrTitleText);
}
colorControl.palette = data.colorPalette;
if (colorIsVisible == 1) {
colorDivStyle.display = "block";
colorIsVisible = 2;
}
}
else {
if (colorIsVisible == 2) {
colorDivStyle.display = "none";
colorIsVisible = 1;
}
}
if (data.individualColors) {
border = "#000000";
color = "#ffffff";
drawBorder = true;
shadow = "grey";
drawShadow = true;
}
else {
color = data.color;
border = color;
shadow = "none";
if (data.border != null) {
drawBorder = true;
border = data.border;
}
if (data.shadow != null) {
drawShadow = true;
shadow = data.shadow;
}
}
};
var renderShape = function () {
var sampleColor = typeof data.color == "string" ? data.color : "lightgray";
var sampleBorderColor = typeof data.border == "string" ? data.border : "gray";
var sampleShadowColor = typeof data.shadow == "string" ? data.shadow : "gray";
var useStroke = sampleBorderColor !== "none";
context.clearRect(0, 0, size, size);
if (sampleShadowColor) {
context.fillStyle = sampleShadowColor;
context.fillRect(0 + shadowSize, y1 + shadowSize, x1, size - y1);
x1 += 1;
context.fillRect(x1 + shadowSize, y2 + shadowSize, x2 - x1, size - y2);
x2 += 1;
context.fillRect(x2 + shadowSize, y3 + shadowSize, x3 - x2, size - y3);
}
context.strokeStyle = sampleBorderColor !== undefined ? sampleBorderColor : "black";
context.fillStyle = sampleColor !== undefined ? sampleColor : "black";
context.fillStyle = sampleColor;
context.fillRect(0, y1, x1, size - y1);
context.fillRect(x1, y2, x2 - x1, size - y2);
context.fillRect(x2, y3, x3 - x2, size - y3);
if (useStroke) {
context.strokeStyle = sampleBorderColor;
context.strokeRect(0, y1, x1, size - y1);
context.strokeRect(x1, y2, x2 - x1, size - y2);
context.strokeRect(x2, y3, x3 - x2, size - y3);
}
};
refreshColor();
renderShape();
},
renderSvg: function (plotRect, screenSize, svg, data, t) {
var n = data.y.length;
if (n == 0) return;
var bars_g = svg.group();
var dataToScreenX = t.dataToScreenX;
var dataToScreenY = t.dataToScreenY;
// size of the canvas
var w_s = screenSize.width;
var h_s = screenSize.height;
var xmin = 0, xmax = w_s;
var ymin = 0, ymax = h_s;
var xLeft, xRight, yTop, yBottom, color;
var i = 0;
var barWidth = data.barWidth;
var shift = barWidth / 2;
var border = data.border == null ? 'none' : data.border;
var shadow = data.shadow == null ? 'none' : data.shadow;
for (; i < n; i++) {
if (data.orientation == "h") {
xLeft = dataToScreenX(0);
xRight = dataToScreenX(data.x[i]);
yTop = dataToScreenY(data.y[i] + shift);
yBottom = dataToScreenY(data.y[i] - shift);
if (xLeft > xRight) {
var k = xRight;
xRight = xLeft;
xLeft = k;
}
} else {
xLeft = dataToScreenX(data.x[i] - shift);
xRight = dataToScreenX(data.x[i] + shift);
yTop = dataToScreenY(data.y[i]);
yBottom = dataToScreenY(0);
if (yTop > yBottom) {
var k = yBottom;
yBottom = yTop;
yTop = k;
}
}
color = data.individualColors ? data.color[i] : data.color;
c1 = (xRight < 0 || xLeft > w_s || yBottom < 0 || yTop > h_s);
if (!c1) {
bars_g.polyline([[xLeft + 2, yBottom + 2], [xLeft + 2, yTop + 2], [xRight + 2, yTop + 2], [xRight + 2, yBottom + 2], [xLeft + 2, yBottom + 2]]).fill(shadow);
bars_g.polyline([[xLeft, yBottom], [xLeft, yTop], [xRight, yTop], [xRight, yBottom], [xLeft, yBottom]]).fill(color).stroke({ width: 1, color: border });
}
}
bars_g.clipWith(bars_g.rect(w_s, h_s));
},
buildSvgLegendElements: function (legendSettings, svg, data, getTitle) {
var thumbnail = svg.group();
var content = svg.group();
var fontSize = 12;
var size = fontSize * 1.5;
var x1 = size / 3 - 0.5;
var x2 = size / 3 * 2;
var x3 = size;
var y1 = size / 2;
var y2 = 0;
var y3 = size / 3;
var barWidth = size / 3;
var shadowSize = 1;
var shadow = 'none';
//thumbnail
if (data.individualColors) {
border = "#000000";
color = "lightgrey";
shadow = "grey";
}
else {
color = data.color;
border = "none";
if (data.border != null) border = data.border;
if (data.shadow != null) shadow = data.shadow;
}
thumbnail.polyline([[shadowSize, size + shadowSize], [shadowSize, y1 + shadowSize], [x1 + shadowSize, y1 + shadowSize], [x1 + shadowSize, size + shadowSize], [shadowSize, size + shadowSize]]).fill(shadow);
thumbnail.polyline([[0, size], [0, y1], [x1, y1], [x1, size], [0, size]]).fill(color).stroke({ width: 0.2, color: border });
x1 += 1;
thumbnail.polyline([[x1 + shadowSize, size + shadowSize], [x1 + shadowSize, y2 + shadowSize], [x2 + shadowSize, y2 + shadowSize], [x2 + shadowSize, size + shadowSize], [x1 + shadowSize, size + shadowSize]]).fill(shadow);
thumbnail.polyline([[x1, size], [x1, y2], [x2, y2], [x2, size], [x1, size]]).fill(color).stroke({ width: 0.2, color: border });
x2 += 1;
thumbnail.polyline([[x2 + shadowSize, size + shadowSize], [x2 + shadowSize, y3 + shadowSize], [x3 + shadowSize, y3 + shadowSize], [x3 + shadowSize, size + shadowSize], [x2 + shadowSize, size + shadowSize]]).fill(shadow);
thumbnail.polyline([[x2, size], [x2, y3], [x3, y3], [x3, size], [x2, size]]).fill(color).stroke({ width: 0.2, color: border });
//content
var isContent = legendSettings.legendDiv.children[1];
var isColor = data.individualColors && data.colorPalette;
var style = (isContent && legendSettings.legendDiv.children[1].children[0] && legendSettings.legendDiv.children[1].children[0].children[0]) ? window.getComputedStyle(legendSettings.legendDiv.children[1].children[0].children[0], null) : undefined;
var fontSize = style ? parseFloat(style.getPropertyValue('font-size')) : undefined;
var fontFamily = style ? style.getPropertyValue('font-family') : undefined;
var fontWeight = style ? style.getPropertyValue('font-weight') : undefined;
if (isColor) {
var colorText = getTitle("color");
content.text(colorText).font({ family: fontFamily, size: fontSize, weight: fontWeight });
var colorPalette_g = svg.group();
var width = legendSettings.width;
var height = 20;
InteractiveDataDisplay.SvgColorPaletteViewer(colorPalette_g, data.colorPalette, legendSettings.legendDiv.children[1].children[0].children[1], { width: width, height: height });
colorPalette_g.translate(5, 50);
shiftsizePalette = 50 + height;
legendSettings.height += (50 + height);
};
svg.front();
return { thumbnail: thumbnail, content: content };
}
};
InteractiveDataDisplay.Markers.shapes["boxwhisker"] = InteractiveDataDisplay.BoxWhisker;
InteractiveDataDisplay.Markers.shapes["petals"] = InteractiveDataDisplay.Petal;
InteractiveDataDisplay.Markers.shapes["bulleye"] = InteractiveDataDisplay.BullEye;
InteractiveDataDisplay.Markers.shapes["bars"] = InteractiveDataDisplay.Bars;
// Area plot takes data with coordinates named 'x', 'y1', 'y2' and a fill colour named 'fill'.
InteractiveDataDisplay.Area = function (div, master) {
var that = this;
var defaultFill = "rgba(0,0,0,0.2)";
// Initialization (#1)
var initializer = InteractiveDataDisplay.Utils.getDataSourceFunction(div, InteractiveDataDisplay.readCsv);
var initialData = initializer(div);
this.base = InteractiveDataDisplay.CanvasPlot;
this.base(div, master);
var _x = []; // an array of horizontal axis coordinates
var _y1 = [];
var _y2 = []; // arrays of lower and upper limits of the area
var _fill = defaultFill;
// default styles:
if (initialData) {
_fill = typeof initialData.fill != "undefined" ? initialData.fill : defaultFill;
}
this.draw = function (data) {
var y1 = data.y1;
if (!y1) throw "Data series y1 is undefined";
var n = y1.length;
var y2 = data.y2;
if (!y2) throw "Data series y2 is undefined";
if (y2.length !== n)
throw "Data series y1 and y2 have different lengths";
var x = data.x;
if (!x) {
x = InteractiveDataDisplay.Utils.range(0, n - 1);
}
if (x.length !== n)
throw "Data series x and y1, y2 have different lengths";
_y1 = y1;
_y2 = y2;
_x = x;
// styles:
_fill = typeof data.fill != "undefined" ? data.fill : defaultFill;
this.invalidateLocalBounds();
this.requestNextFrameOrUpdate();
this.fireAppearanceChanged();
};
// Returns a rectangle in the plot plane.
this.computeLocalBounds = function () {
var dataToPlotX = this.xDataTransform && this.xDataTransform.dataToPlot;
var dataToPlotY = this.yDataTransform && this.yDataTransform.dataToPlot;
var y1 = InteractiveDataDisplay.Utils.getBoundingBoxForArrays(_x, _y1, dataToPlotX, dataToPlotY);
var y2 = InteractiveDataDisplay.Utils.getBoundingBoxForArrays(_x, _y2, dataToPlotX, dataToPlotY);
return InteractiveDataDisplay.Utils.unionRects(y1, y2);
};
// Returns 4 margins in the screen coordinate system
this.getLocalPadding = function () {
return { left: 0, right: 0, top: 0, bottom: 0 };
};
this.renderCore = function (plotRect, screenSize) {
InteractiveDataDisplay.Area.prototype.renderCore.call(this, plotRect, screenSize);
var context = that.getContext(true);
InteractiveDataDisplay.Area.render.call(this, _x, _y1, _y2, _fill, plotRect, screenSize, context);
};
// Clipping algorithms
var code = function (x, y, xmin, xmax, ymin, ymax) {
return (x < xmin) << 3 | (x > xmax) << 2 | (y < ymin) << 1 | (y > ymax);
};
// Others
this.onDataTransformChanged = function (arg) {
this.invalidateLocalBounds();
InteractiveDataDisplay.Area.prototype.onDataTransformChanged.call(this, arg);
};
this.getLegend = function () {
var that = this;
var canvas = $("
");
canvas[0].width = 40;
canvas[0].height = 40;
var ctx = canvas.get(0).getContext("2d");
ctx.globalAlpha = 0.5;
ctx.strokeStyle = _fill;
ctx.fillStyle = _fill;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, 10);
ctx.lineTo(30, 40);
ctx.lineTo(40, 40);
ctx.lineTo(40, 30);
ctx.lineTo(10, 0);
ctx.lineTo(0, 0);
ctx.fill();
ctx.stroke();
ctx.closePath();
var nameDiv = $("
");
var setName = function () {
nameDiv.text(that.name);
}
setName();
this.host.bind("appearanceChanged",
function (event, propertyName) {
if (!propertyName || propertyName == "name")
setName();
ctx.clearRect(0, 0, canvas[0].width, canvas[0].height);
ctx.strokeStyle = _fill;
ctx.fillStyle = _fill;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, 10);
ctx.lineTo(30, 40);
ctx.lineTo(40, 40);
ctx.lineTo(40, 30);
ctx.lineTo(10, 0);
ctx.lineTo(0, 0);
ctx.fill();
ctx.stroke();
ctx.closePath();
});
var that = this;
var onLegendRemove = function () {
that.host.unbind("appearanceChanged");
//div[0].innerHTML = "";
//div.removeClass("idd-legend-item");
};
//return { div: div, onLegendRemove: onLegendRemove };
return { name: nameDiv, legend: { thumbnail: canvas, content: undefined }, onLegendRemove: onLegendRemove };
};
// Initialization
if (initialData && initialData.x && initialData.y1 && initialData.y2)
this.draw(initialData);
this.renderCoreSvg = function (plotRect, screenSize, svg) {
InteractiveDataDisplay.Area.renderSvg.call(this, plotRect, screenSize, svg, _x, _y1, _y2, _fill);
}
this.buildSvgLegend = function (legendSettings, svg) {
var that = this;
legendSettings.height = 30;
svg.add(svg.rect(legendSettings.width, legendSettings.height).fill("white").opacity(0.5));
svg.add(svg.polyline([[0, 0], [0, 4.5], [13.5, 18], [18, 18], [18, 13.5], [4.5, 0], [0, 0]]).fill(_fill).opacity(0.5).translate(5, 5));
var style = window.getComputedStyle(legendSettings.legendDiv.children[0].children[1], null);
var fontSize = parseFloat(style.getPropertyValue('font-size'));
var fontFamily = style.getPropertyValue('font-family');
var fontWeight = style ? style.getPropertyValue('font-weight') : undefined;
svg.add(svg.text(that.name).font({ family: fontFamily, size: fontSize, weight: fontWeight }).translate(40, 0));
svg.front();
}
}
InteractiveDataDisplay.Area.prototype = new InteractiveDataDisplay.CanvasPlot;
InteractiveDataDisplay.Area.render = function (_x, _y1, _y2, _fill, plotRect, screenSize, context, globalAlpha) {
if (_x === undefined || _y1 == undefined || _y2 == undefined)
return;
var n = _y1.length;
if (n == 0) return;
var t = this.getTransform();
var dataToScreenX = t.dataToScreenX;
var dataToScreenY = t.dataToScreenY;
// size of the canvas
var w_s = screenSize.width;
var h_s = screenSize.height;
var xmin = 0, xmax = w_s;
var ymin = 0, ymax = h_s;
context.globalAlpha = globalAlpha;
context.fillStyle = _fill;
//Drawing polygons
var polygons = [];
var curInd = undefined;
for (var i = 0; i < n; i++) {
if (isNaN(_x[i]) || isNaN(_y1[i]) || isNaN(_y2[i])) {
if (curInd === undefined) {
curInd = i;
}
else {
polygons.push([curInd, i]);
curInd = undefined;
}
} else {
if (curInd === undefined) {
curInd = i;
}
else {
if (i === n - 1) {
polygons.push([curInd, i]);
curInd = undefined;
}
}
}
}
var nPoly = polygons.length;
for (var i = 0; i < nPoly; i++) {
context.beginPath();
var curPoly = polygons[i];
context.moveTo(dataToScreenX(_x[curPoly[0]]), dataToScreenY(_y1[curPoly[0]]));
for (var j = curPoly[0] + 1; j <= curPoly[1]; j++) {
context.lineTo(dataToScreenX(_x[j]), dataToScreenY(_y1[j]));
}
for (var j = curPoly[1]; j >= curPoly[0]; j--) {
context.lineTo(dataToScreenX(_x[j]), dataToScreenY(_y2[j]));
}
context.fill();
}
};
InteractiveDataDisplay.Area.renderSvg = function (plotRect, screenSize, svg, _x, _y1, _y2, _fill, globalAlpha) {
if (_x === undefined || _y1 == undefined || _y2 == undefined) return;
var n = _y1.length;
if (n == 0) return;
var t = this.getTransform();
var dataToScreenX = t.dataToScreenX;
var dataToScreenY = t.dataToScreenY;
var area_g = svg.group();
// size of the canvas
var w_s = screenSize.width;
var h_s = screenSize.height;
var xmin = 0, xmax = w_s;
var ymin = 0, ymax = h_s;
var polygons = [];
var curInd = undefined;
for (var i = 0; i < n; i++) {
if (isNaN(_x[i]) || isNaN(_y1[i]) || isNaN(_y2[i])) {
if (curInd !== undefined) {
polygons.push([curInd, i - 1]);
curInd = undefined;
}
} else {
if (curInd === undefined) {
curInd = i;
}
else {
if (i === n - 1) {
polygons.push([curInd, i]);
curInd = undefined;
}
}
}
}
var segment = [];
var nPoly = polygons.length;
for (var i = 0; i < nPoly; i++) {
var curPoly = polygons[i];
segment = [];
segment.push([dataToScreenX(_x[curPoly[0]]), dataToScreenY(_y1[curPoly[0]])]);
for (var j = curPoly[0] + 1; j <= curPoly[1]; j++) {
segment.push([dataToScreenX(_x[j]), dataToScreenY(_y1[j])]);
}
for (var j = curPoly[1]; j >= curPoly[0]; j--) {
segment.push([dataToScreenX(_x[j]), dataToScreenY(_y2[j])]);
}
segment.push([dataToScreenX(_x[curPoly[0]]), dataToScreenY(_y1[curPoly[0]])]);
area_g.polyline(segment).fill(_fill).opacity(globalAlpha);
}
area_g.clipWith(area_g.rect(w_s, h_s));
}
// See http://jsperf.com/rendering-a-frame-in-image-data
InteractiveDataDisplay.heatmapBackgroundRenderer = undefined;
// Renders a fuction f(x,y) on a regular grid (x,y) as a heat map using color palette
InteractiveDataDisplay.Heatmap = function (div, master) {
// Initialization (#1)
var initializer = InteractiveDataDisplay.Utils.getDataSourceFunction(div, InteractiveDataDisplay.readCsv2d);
var initialData = initializer(div);
if (initialData && typeof initialData.y !== 'undefined' && typeof initialData.values !== 'undefined') {
var y = initialData.y;
var f = initialData.values;
var n = y.length;
var m = f.length;
if (n > 1 && m > 0 && y[0] > y[1]) {
y.reverse();
for (var i = 0; i < n; i++)
f[i].reverse();
}
}
this.base = InteractiveDataDisplay.CanvasPlot;
this.base(div, master);
if (!div) return;
//create heatmap background renderer
if (InteractiveDataDisplay.heatmapBackgroundRenderer == undefined) InteractiveDataDisplay.heatmapBackgroundRenderer = new InteractiveDataDisplay.SharedRenderWorker(
function () {
var workerCodeUri;
if (typeof InteractiveDataDisplay.heatmapBackgroundRendererCodeBase64 === 'undefined' || /PhantomJS/.test(window.navigator.userAgent) || InteractiveDataDisplay.Utils.getIEVersion() > 0) {
//Blob doesn't work in IE10 and IE11
// Build process usually initializes the heatmapBackgroundRendererCodeBase64 with base64 encoded
// concatenation of idd.heatmapworker.js and idd.transforms.js.
workerCodeUri = InteractiveDataDisplay.HeatmapworkerPath ? InteractiveDataDisplay.HeatmapworkerPath + "idd.heatmapworker.js" : "idd.heatmapworker.js";
}
else {
var workerBlob = new Blob([window.atob(InteractiveDataDisplay.heatmapBackgroundRendererCodeBase64)], { type: 'text/javascript' });
workerCodeUri = window.URL.createObjectURL(workerBlob);
}
return workerCodeUri
}(),
function (heatmapPlot, completedTask) {
heatmapPlot.onRenderTaskCompleted(completedTask);
});
// default styles:
var loadPalette = function (palette) {
if (palette) {
try {
if (typeof palette == 'string')
_palette = InteractiveDataDisplay.ColorPalette.parse(palette);
else
_palette = palette;
_paletteColors = InteractiveDataDisplay.ColorPalette.toArray(_palette, 512);
} catch (exc) {
if (window.console) console.error("Failed to initialize the palette");
}
}
};
var loadOpacity = function (opacity) {
_opacity = Math.min(1.0, Math.max(0.0, opacity));
};
var _innerCanvas = document.createElement("canvas");
var _imageData;
var _y;
var _x;
// _f contains values that are mapped to colors.
// _log_f is log10(_f) and it is used only if options.logColors is enabled instead of _f for color mapping.
// _f or _logf is passed to the heatmap render.
// _fmin/_fmax (and corresponding log-versions) are min/max for _f (_log_f) and are used EXCLUSIVELY for palette range.
// Log range are computed with respect to _logTolerance (taken from option.logTolerance).
// _log_f is passed to rendered only.
// _f is passed to renderer and to build tooltip value.
var _f, _f_u68, _f_l68, _f_median, _fmin, _fmax;
var _logColors, _logTolerance;
var _log_f, _log_fmin, _log_fmax;
var _opacity; // 1 is opaque, 0 is transparent
var _mode; // gradient or matrix
var _palette;
var _dataChanged;
var _paletteColors;
// Uncertainty interval
var _interval;
var _originalInterval;
var _heatmap_nav;
var _formatter_f, _formatter_f_median, _formatter_f_l68, _formatter_f_u68, _formatter_interval;
loadOpacity((initialData && typeof (initialData.opacity) != 'undefined') ? parseFloat(initialData.opacity) : 1.0);
loadPalette((initialData && typeof (initialData.colorPalette) != 'undefined') ? initialData.colorPalette : InteractiveDataDisplay.palettes.grayscale);
var findFminmax = function (f) {
var n = f.length;
if (n < 1) return;
var m = f[0].length;
if (m < 1) return;
var fmin, fmax;
fmin = fmax = f[0][0];
for (var i = 0; i < n; i++) {
var fi = f[i];
for (var j = 0; j < m; j++) {
var v = fi[j];
if (v == v) {
if (v > fmax || isNaN(fmax)) fmax = v;
if (v < fmin || isNaN(fmin)) fmin = v;
}
}
}
return { min: fmin, max: fmax };
};
var lastCompletedTask;
var that = this;
//from chart viewer
var makeHeatmapData = function(x, y, z, isDiscrete) {
if (!x || !y || !z) return {};
if (!x.length || !y.length || (z.v && !z.v.length) || (z.m && (!z.m.length || !z.lb68.length || !z.ub68.length))) return {};
// Convert to Array.
x = Array.prototype.slice.call(x);
y = Array.prototype.slice.call(y);
if (z.v) {
z.v = Array.prototype.slice.call(z.v);
} else if (z.m) {
z.m = Array.prototype.slice.call(z.m);
z.lb68 = Array.prototype.slice.call(z.lb68);
z.ub68 = Array.prototype.slice.call(z.ub68);
}
// All arrays must have the same length.
if (z.v && (x.length !== y.length || x.length !== z.v.length || y.length !== z.v.length)) {
x.length = y.length = z.v.length = Math.min(x.length, y.length, z.v.length);
} else if (z.m && (x.length !== y.length || x.length !== z.m.length || y.length !== z.m.length || z.m.length !== z.lb68.length || z.m.length !== z.ub68.length)) {
x.length = y.length = z.m.length = z.lb68.length = z.ub68.length = Math.min(x.length, y.length, z.m.length, z.lb68.length, z.ub68.length);
}
// Remember indices in unsorted arrays.
var ix = x.map(function (a, i) { return { v: a, i: i }; });
var iy = y.map(function (a, i) { return { v: a, i: i }; });
// Get sorted arrays.
var sx = ix.sort(function (a, b) { return a.v < b.v ? -1 : a.v > b.v ? 1 : 0; });
var sy = iy.sort(function (a, b) { return a.v < b.v ? -1 : a.v > b.v ? 1 : 0; });
// Get unique sorted arrays of grid dimensions.
var ux = sx.filter(function (a, i) { return !i || a.v != sx[i - 1].v; }).map(function (a) { return a.v; });
var uy = sy.filter(function (a, i) { return !i || a.v != sy[i - 1].v; }).map(function (a) { return a.v; });
// Using initial indices get arrays of grid indices for dimensions.
var i, j, ifx = [], ify = [];
i = 0; sx.forEach(function (a, k) { return ifx[a.i] = !k ? 0 : a.v != sx[k - 1].v ? ++i : i; });
i = 0; sy.forEach(function (a, k) { return ify[a.i] = !k ? 0 : a.v != sy[k - 1].v ? ++i : i; });
var f, m, lb68, ub68;
// Initializes 2d array with NaNs.
var initNaNs = function (d1, d2) {
var a = [];
for (var i = 0; i < d1; ++i) {
a[i] = [];
for (var j = 0; j < d2; ++j) {
a[i][j] = NaN;
}
}
return a;
}
if (z.v) {
f = initNaNs(ux.length, uy.length);
for (i = 0; i < z.v.length; ++i) {
f[ifx[i]][ify[i]] = z.v[i];
}
} else {
m = initNaNs(ux.length, uy.length);
lb68 = initNaNs(ux.length, uy.length);
ub68 = initNaNs(ux.length, uy.length);
for (i = 0; i < z.m.length; ++i) {
m[ifx[i]][ify[i]] = z.m[i];
lb68[ifx[i]][ify[i]] = z.lb68[i];
ub68[ifx[i]][ify[i]] = z.ub68[i];
}
}
if (isDiscrete) {
if (ux.length >= 2) {
var newx = [];
newx.push(ux[0] - (ux[1] - ux[0]) / 2);
var m;
for (m = 1; m < ux.length; m++) {
newx.push(ux[m] - (ux[m] - ux[m - 1]) / 2);
}
newx.push(ux[ux.length - 1] + (ux[ux.length - 1] - ux[ux.length - 2]) / 2);
ux = newx;
}
if (uy.length >= 2) {
var newy = [];
newy.push(uy[0] - (uy[1] - uy[0]) / 2);
var k;
for (k = 1; k < uy.length; k++) {
newy.push(uy[k] - (uy[k] - uy[k - 1]) / 2);
}
newy.push(uy[uy.length - 1] + (uy[uy.length - 1] - uy[uy.length - 2]) / 2);
uy = newy;
}
}
return {
x: ux,
y: uy,
values: f,
m: m,
l68: lb68,
u68: ub68
};
};
var computePaletteMinMax = function() {
var minmax = findFminmax(_f);
_fmin = minmax.min;
_fmax = minmax.max;
if(_logColors){
_fmin = Math.max(_fmin, _logTolerance);
_fmax = Math.max(_fmax, _logTolerance);
// Palette range is in "plot coordinates", i.e. log10 of range.
_log_fmin = InteractiveDataDisplay.Utils.log10(_fmin);
_log_fmax = InteractiveDataDisplay.Utils.log10(_fmax);
}
}
this.onRenderTaskCompleted = function (completedTask) {
lastCompletedTask = completedTask;
if (_innerCanvas.width !== lastCompletedTask.width || _innerCanvas.height !== lastCompletedTask.height) {
_innerCanvas.width = lastCompletedTask.width;
_innerCanvas.height = lastCompletedTask.height;
}
var context = _innerCanvas.getContext("2d");
context.putImageData(lastCompletedTask.image, 0, 0);
//console.log("Complete render " + this.name);
that.requestNextFrame();
};
this.draw = function (data, titles) {
var f = data.values;
if (!f) throw "Data series f is undefined";
var isOneDimensional =
f["median"] !== undefined && !InteractiveDataDisplay.Utils.isArray(f["median"][0])
|| !InteractiveDataDisplay.Utils.isArray(f[0]);
var x = data.x;
var y = data.y;
if (_originalInterval == undefined && _interval == undefined) _originalInterval = data.interval;
_interval = data.interval;
_logColors = data.logPalette !== undefined && data.logPalette;
_logTolerance = data.logTolerance ? data.logTolerance : 1e-12;
if (f["median"]) { //uncertain data
if (_heatmap_nav == undefined) {
var div = $("
")
.attr("data-idd-name", "heatmap__nav_")
.appendTo(this.host);
_heatmap_nav = new InteractiveDataDisplay.Heatmap(div, this.master);
_heatmap_nav.getLegend = function () {
return undefined;
};
this.addChild(_heatmap_nav);
_heatmap_nav.getTooltip = function (xd, yd, xp, yp) {
return undefined;
}
}
if (isOneDimensional) {
var r = makeHeatmapData(x, y, {
v: undefined,
m: f.median,
lb68: f.lower68,
ub68: f.upper68
}, data.treatAs === 'discrete');
_x = r.x;
_y = r.y;
_f = r.m;
_f_median = r.m;
_f_l68 = r.l68;
_f_u68 = r.u68;
_heatmap_nav.x = r.x;
_heatmap_nav.y = r.y;
_heatmap_nav.f_median = r.m;
_heatmap_nav.f_l68 = r.l68;
_heatmap_nav.f_u68 = r.u68;
} else {
_x = x;
_y = y;
_f = f.median;
_f_median = f.median;
_f_l68 = f.lower68;
_f_u68 = f.upper68;
_heatmap_nav.x = r.x;
_heatmap_nav.y = r.y;
_heatmap_nav.f_median = f.median68;
_heatmap_nav.f_l68 = f.lower68;
_heatmap_nav.f_u68 = f.upper68;
}
if (_interval) {
updateInterval();
}
} else {
if (_heatmap_nav) {
_heatmap_nav.remove();
}
_heatmap_nav = undefined;
if (isOneDimensional) {
var r = makeHeatmapData(x, y, {
v: f
}, data.treatAs === 'discrete');
_x = r.x;
_y = r.y;
_f = r.values;
} else {
_f = f;
_x = x;
_y = y;
}
}
// Logarithmic colors: builds log10(_f) to be rendered, so the color palette
// is logarithmic.
if(_logColors){
_log_f = new Array(_f.length);
for(var i = 0; i < _f.length; i++)
{
var row = _f[i];
var logRow = _log_f[i] = new Float32Array(row.length);
for(var j = 0; j < row.length; j++)
{
logRow[j] = InteractiveDataDisplay.Utils.log10(row[j]);
}
}
}
_formatter_f = undefined;
_formatter_f_median = undefined;
_formatter_f_l68 = undefined;
_formatter_f_u68 = undefined;
var n = _f_median ? _f_median.length : _f.length;
var m = _f_median ? _f_median[0].length : _f[0].length;
if (!_x) {
_x = InteractiveDataDisplay.Utils.range(0, n);
} else {
if (_x.length != n && _x.length != n + 1) throw "Data series x must have length equal or one more than length of data series f by first dimension";
}
if (!_y) {
_y = InteractiveDataDisplay.Utils.range(0, m);
} else {
if (_y.length != m && _y.length != m + 1) throw "Data series y must have length equal or one more than length of data series f by second dimension";
}
if (_x.length == n) {
if (_y.length != m) throw "Data series y must have length equal to length of data series f by second dimension";
_mode = 'gradient';
} else {
if (_y.length != m + 1) throw "Data series y must have length equal to one more than length of data series f by second dimension";
_mode = 'matrix';
}
if (_x.length < 2) throw "Data series x must have at least 2 elements by each dimension";
if (_y.length < 2) throw "Data series y must have at least 2 elements by each dimension";
// styles:
if (data && typeof (data.opacity) != 'undefined') {
loadOpacity(parseFloat(data.opacity));
}
if (data && typeof (data.colorPalette) != 'undefined')
loadPalette(data.colorPalette);
if (_palette.isNormalized) {
computePaletteMinMax();
}
_dataChanged = true;
var prevBB = this.invalidateLocalBounds();
var bb = this.getLocalBounds();
if (InteractiveDataDisplay.Utils.equalRect(prevBB, bb))
this.requestNextFrame();
else
this.requestNextFrameOrUpdate();
this.setTitles(titles, true);
this.fireAppearanceChanged();
};
// Returns a rectangle in the plot plane.
this.computeLocalBounds = function () {
var _bbox;
if (_x && _y) { // todo: fix for matrix mode
var xmin, xmax, ymin, ymax;
var n = _x.length;
var m = _y.length;
var i;
for (i = 0; i < n; i++) {
xmin = _x[i];
if (xmin == xmin) break;
}
for (i = n; --i >= 0;) {
xmax = _x[i];
if (xmax == xmax) break;
}
for (i = 0; i < m; i++) {
ymin = _y[i];
if (ymin == ymin) break;
}
for (i = m; --i >= 0;) {
ymax = _y[i];
if (ymax == ymax) break;
}
var dataToPlotX = this.xDataTransform && this.xDataTransform.dataToPlot;
var dataToPlotY = this.yDataTransform && this.yDataTransform.dataToPlot;
if (dataToPlotX) {
xmin = dataToPlotX(xmin);
xmax = dataToPlotX(xmax);
}
if (dataToPlotY) {
ymin = dataToPlotY(ymin);
ymax = dataToPlotY(ymax);
}
_bbox = { x: Math.min(xmin, xmax), y: Math.min(ymin, ymax), width: Math.abs(xmax - xmin), height: Math.abs(ymax - ymin) };
}
return _bbox;
};
if (typeof (Modernizr) != 'undefined') {
if (div && (!Modernizr.webworkers || !Modernizr.postmessage)) {
var parent = div[0].parentElement;
if (parent) {
var hasText = false;
for (var i = 0; i < parent.childNodes.length; i++) {
if ($(parent.childNodes[i]).hasClass("nowebworkers")) {
hasText = true;
break;
}
}
div[0].removeAttribute("data-idd-plot");
div[0].innerText = "";
if (!hasText) {
div[0].innerText = ' Heatmap cannot be rendered: browser does not support web workers.';
div.addClass("nowebworkers");
}
else div[0].innerText = "";
}
return;
}
}
//Theess objects are used for renderfing on the map
var polygon = undefined;
var polygon2 = undefined;
// Updates output of this plot using the current coordinate transform and screen size.
// plotRect {x,y,width,height} Rectangle in the plot plane which is visible, (x,y) is left/bottom of the rectangle
// screenSize {width,height} Size of the output region to render inside
// Returns true, if the plot actually has rendered something; otherwise, returns false.
this.renderCore = function (plotRect, screenSize) {
InteractiveDataDisplay.Heatmap.prototype.renderCore.call(this, plotRect, screenSize);
var context = this.getContext(true);
if (_x == undefined || _y == undefined || _f == undefined)
return;
var ct = this.coordinateTransform;
var plotToScreenX = ct.plotToScreenX;
var plotToScreenY = ct.plotToScreenY;
var bb = this.getLocalBounds();
// this is a rectangle which we should fill:
var visibleRect = InteractiveDataDisplay.Utils.intersect(bb, plotRect);
if (!visibleRect) return;
var drawBasic = true;
if (master.mapControl !== undefined) {
var left = bb.x;
var middle = bb.x + bb.width / 2;
var right = bb.x + bb.width;
if (polygon === undefined) {
var backColor = 120;
var options = {
fillColor: new Microsoft.Maps.Color(backColor, backColor, backColor, backColor),
strokeColor: new Microsoft.Maps.Color(backColor, backColor, backColor, backColor),
strokeThickness: 0
};
polygon = new Microsoft.Maps.Polygon([
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), left),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), middle),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), middle),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), left),
], options);
polygon2 = new Microsoft.Maps.Polygon([
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), middle),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), right),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), right),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), middle),
], options);
master.mapControl.entities.push(polygon);
master.mapControl.entities.push(polygon2);
}
if (_dataChanged) {
polygon.setLocations([
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), left),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), middle),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), middle),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), left),
]);
polygon2.setLocations([
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), middle),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y), right),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), right),
new Microsoft.Maps.Location(InteractiveDataDisplay.mercatorTransform.plotToData(bb.y + bb.height), middle),
]);
}
drawBasic = !master.isInAnimation;
polygon.setOptions({ visible: master.isInAnimation });
polygon2.setOptions({ visible: master.isInAnimation });
}
if (drawBasic) {
var visibleRect_s = {
left: Math.floor(plotToScreenX(visibleRect.x)),
width: Math.ceil(ct.plotToScreenWidth(visibleRect.width)),
top: Math.floor(plotToScreenY(visibleRect.y + visibleRect.height)),
height: Math.ceil(ct.plotToScreenHeight(visibleRect.height))
};
var scale = ct.getScale();
var offset = ct.getOffset();
// rendering a placeholder to indicate that here will be real heatmap
context.fillStyle = 'rgba(200,200,200,0.3)';
context.fillRect(visibleRect_s.left, visibleRect_s.top, visibleRect_s.width, visibleRect_s.height);
if (lastCompletedTask) {
var taskRect = InteractiveDataDisplay.Utils.intersect(lastCompletedTask.plotRect, plotRect);
// todo: draw bb here
if (taskRect) {
var left_s = plotToScreenX(lastCompletedTask.plotRect.x);
var top_s = plotToScreenY(lastCompletedTask.plotRect.y + lastCompletedTask.plotRect.height);
var alpha;
if (_opacity != 1) {
alpha = context.globalAlpha;
context.globalAlpha = _opacity;
}
if (scale.x != lastCompletedTask.scaleX || scale.y != lastCompletedTask.scaleY) {
var sx = scale.x / lastCompletedTask.scaleX;
var sy = scale.y / lastCompletedTask.scaleY;
context.drawImage(_innerCanvas, 0, 0, lastCompletedTask.image.width, lastCompletedTask.image.height,
left_s, top_s, sx * lastCompletedTask.image.width, sy * lastCompletedTask.image.height);
} else {
context.drawImage(_innerCanvas, left_s, top_s);
}
if (_opacity != 1) {
context.globalAlpha = alpha;
}
}
}
if (_dataChanged ||
!this.master.isInAnimation &&
(!lastCompletedTask || lastCompletedTask.scaleX != scale.x || lastCompletedTask.scaleY != scale.y || !InteractiveDataDisplay.Utils.includes(lastCompletedTask.plotRect, visibleRect))) {
if (!_imageData || _imageData.width !== visibleRect_s.width || _imageData.height !== visibleRect_s.height) {
// avoiding creating new image data,
// it is possible to reuse the image data since web worker marshalling makes a copy of it
_imageData = context.createImageData(visibleRect_s.width, visibleRect_s.height);
}
var task = {
image: _imageData,
width: _imageData.width,
height: _imageData.height,
x: _x,
y: _y,
f: _logColors ? _log_f : _f,
fmin: _logColors ? _log_fmin : _fmin,
fmax: _logColors ? _log_fmax : _fmax,
plotRect: visibleRect,
scaleX: scale.x,
scaleY: scale.y,
offsetX: offset.x - visibleRect_s.left,
offsetY: offset.y - visibleRect_s.top,
palette: {
isNormalized: _palette.isNormalized,
range: _palette.range,
points: _palette.points,
colors: _paletteColors
},
xDataTransform: this.xDataTransform && this.xDataTransform.type,
yDataTransform: this.yDataTransform && this.yDataTransform.type
};
//console.log("Heatmap " + this.name + " enqueues a task (isInAnimation: " + this.master.isInAnimation + ")");
InteractiveDataDisplay.heatmapBackgroundRenderer.enqueue(task, this);
_dataChanged = false;
}
//}
}
};
this.renderCoreSvg = function (plotRect, screenSize, svg) {
var imageData_g = svg.group();
if (_x == undefined || _y == undefined || _f == undefined)
return;
var w_s = screenSize.width;
var h_s = screenSize.height;
var ct = this.coordinateTransform;
var plotToScreenX = ct.plotToScreenX;
var plotToScreenY = ct.plotToScreenY;
var bb = this.getLocalBounds();
// this is a rectangle which we should fill:
var visibleRect = InteractiveDataDisplay.Utils.intersect(bb, plotRect);
if (!visibleRect) return;
var visibleRect_s = {
left: Math.floor(plotToScreenX(visibleRect.x)),
width: Math.ceil(ct.plotToScreenWidth(visibleRect.width)),
top: Math.floor(plotToScreenY(visibleRect.y + visibleRect.height)),
height: Math.ceil(ct.plotToScreenHeight(visibleRect.height))
};
var image = _innerCanvas.toDataURL("image/png");
var svgimage = imageData_g.image(image, _imageData.width, _imageData.height).opacity(_opacity);
svgimage.clipWith(imageData_g.rect(visibleRect_s.width, visibleRect_s.height));
imageData_g.translate(visibleRect_s.left, visibleRect_s.top);
}
this.onIsRenderedChanged = function () {
if (!this.isRendered) {
InteractiveDataDisplay.heatmapBackgroundRenderer.cancelPending(this);
}
};
// Others
this.onDataTransformChanged = function (arg) {
this.invalidateLocalBounds();
InteractiveDataDisplay.Heatmap.prototype.onDataTransformChanged.call(this, arg);
};
var getCellContaining = function (x_d, y_d) {
var n = _x.length;
var m = _y.length;
if (n == 0 || m == 0) return;
if (x_d < _x[0] || y_d < _y[0] ||
x_d > _x[n - 1] || y_d > _y[m - 1]) return;
var i;
for (i = 1; i < n; i++) {
if (x_d <= _x[i]) {
if (isNaN(_x[i - 1])) return NaN;
break;
}
}
var j;
for (j = 1; j < m; j++) {
if (y_d <= _y[j]) {
if (isNaN(_y[j - 1])) return NaN;
break;
}
}
if (i >= n || j >= m) return NaN;
return { iLeft: i - 1, jBottom: j - 1 };
};
/// Gets the value (probably, interpolated) for the heatmap
/// in the point (xd,yd) in data coordinates.
/// Depends on the heatmap mode.
/// Returns null, if the point is outside of the plot.
this.getValue = function (xd, yd, array) {
var n = _x.length;
var m = _y.length;
if (n == 0 || m == 0) return null;
var cell = getCellContaining(xd, yd);
if (cell == undefined) return null;
if (cell != cell) return "
" + (this.name || "heatmap") + ": (unknown value)
";
var value;
if (_mode === "gradient") {
var flb, flt, frt, frb;
flt = array[cell.iLeft][cell.jBottom + 1];
flb = array[cell.iLeft][cell.jBottom];
frt = array[cell.iLeft + 1][cell.jBottom + 1];
frb = array[cell.iLeft + 1][cell.jBottom];
if (isNaN(flt) || isNaN(flb) || isNaN(frt) || isNaN(frb)) {
value = NaN;
} else {
var y0 = _y[cell.jBottom];
var y1 = _y[cell.jBottom + 1];
var kyLeft = (flt - flb) / (y1 - y0);
var kyRight = (frt - frb) / (y1 - y0);
var fleft = kyLeft * (yd - y0) + flb;
var fright = kyRight * (yd - y0) + frb;
var x0 = _x[cell.iLeft];
var x1 = _x[cell.iLeft + 1];
var kx = (fright - fleft) / (x1 - x0);
value = kx * (xd - x0) + fleft;
}
} else {
value = array[cell.iLeft][cell.jBottom];
}
return value;
};
var updateInterval = function () {
var fmedian = _heatmap_nav.f_median;
var shadeData = new Array(fmedian.length);
for (var i = 0; i < fmedian.length; i++) {
var fmedian_i = fmedian[i];
shadeData[i] = new Array(fmedian_i.length);
for (var j = 0; j < fmedian_i.length; j++) {
shadeData[i][j] = (_heatmap_nav.f_l68[i][j] < _interval.max && _heatmap_nav.f_u68[i][j] > _interval.min) ? 0 : 1;
}
}
_heatmap_nav.draw({ x: _heatmap_nav.x, y: _heatmap_nav.y, values: shadeData, opacity: 0.5, colorPalette: InteractiveDataDisplay.ColorPalette.parse("0=#00000000=#00000080=1") });
_heatmap_nav.isVisible = true;
};
this.getTooltip = function (xd, yd, xp, yp, changeInterval) {
if (_f === undefined)
return;
var that = this;
var pinCoord = { x: xd, y: yd };
if (_f_u68 === undefined || _f_l68 === undefined || _f_median === undefined) {
var fminmax = findFminmax(_f);
_formatter_f = InteractiveDataDisplay.AdaptiveFormatter(fminmax.min, fminmax.max);
var $toolTip = $("
");
$("
").addClass('idd-tooltip-name').text((this.name || "heatmap")).appendTo($toolTip);
var value = this.getValue(pinCoord.x, pinCoord.y, _f, _mode);
if (value == null) return;
var propTitle = this.getTitle("values");
$("
" + propTitle + ": " + _formatter_f.toString(value) + "
").appendTo($toolTip);
return $toolTip;
} else {
var fminmax = findFminmax(_f_median);
_formatter_f_median = InteractiveDataDisplay.AdaptiveFormatter(fminmax.min, fminmax.max);
fminmax = findFminmax(_f_l68);
_formatter_f_l68 = InteractiveDataDisplay.AdaptiveFormatter(fminmax.min, fminmax.max);
fminmax = findFminmax(_f_u68);
_formatter_f_u68 = InteractiveDataDisplay.AdaptiveFormatter(fminmax.min, fminmax.max);
var $toolTip = $("
");
$("
").addClass('idd-tooltip-name').text((this.name || "heatmap")).appendTo($toolTip);
var lb = this.getValue(pinCoord.x, pinCoord.y, _f_l68);
var ub = this.getValue(pinCoord.x, pinCoord.y, _f_u68);
var median = this.getValue(pinCoord.x, pinCoord.y, _f_median);
if (lb == null || ub == null || median == null) return;
var propTitle = this.getTitle("values");
var uncertainContent = $("
").addClass('idd-tooltip-compositevalue');
uncertainContent.append($("
median: " + _formatter_f_median.toString(median) + "
"));
uncertainContent.append($("
lower 68%: " + _formatter_f_l68.toString(lb) + "
"));
uncertainContent.append($("
upper 68%: " + _formatter_f_u68.toString(ub) + "
"));
var $content = $("
");
$content.append($("
" + propTitle + ":
")).append(uncertainContent);
$content.appendTo($toolTip);
var checkBoxCnt = $("
").css("display", "inline-block").appendTo($toolTip);
var showSimilarBtn = $("
").addClass("checkButton").appendTo(checkBoxCnt);
if (_interval ) {
if (changeInterval) {
if (_interval != _originalInterval) {
_interval = { min: lb, max: ub };
$(".checkButton").removeClass("checkButton-checked");
showSimilarBtn.addClass("checkButton-checked");
this.fireAppearanceChanged("interval");
} else $(".checkButton").removeClass("checkButton-checked");
} else {
$(".checkButton").removeClass("checkButton-checked");
showSimilarBtn.addClass("checkButton-checked");
}
updateInterval();
}
showSimilarBtn.click(function () {
if (showSimilarBtn.hasClass("checkButton-checked")) {
showSimilarBtn.removeClass("checkButton-checked");
if (_originalInterval) {
_interval = _originalInterval;
updateInterval();
} else {
_interval = undefined;
_heatmap_nav.isVisible = false;
}
}
else {
$(".checkButton").removeClass("checkButton-checked");
showSimilarBtn.addClass("checkButton-checked");
_interval = { min: lb, max: ub };
updateInterval();
}
that.fireAppearanceChanged("interval");
});
$($("
highlight similar")).appendTo(checkBoxCnt);
return $toolTip;
}
};
Object.defineProperty(this, "palette", {
get: function () { return _palette; },
set: function (value) {
if (value == _palette) return;
if (!value) throw "Heatmap palette is undefined";
if (_palette && value.isNormalized && !_palette.isNormalized && _f) {
computePaletteMinMax();
}
loadPalette(value);
lastCompletedTask = undefined;
this.fireAppearanceChanged("palette");
this.requestNextFrame();
},
configurable: false
});
Object.defineProperty(this, "opacity", {
get: function () { return _opacity; },
set: function (value) {
if (!value) throw "Heatmap opacity is undefined";
if (value == _opacity) return;
loadOpacity(value);
this.fireAppearanceChanged("opacity");
this.requestNextFrame();
},
configurable: false
});
Object.defineProperty(this, "mode", {
get: function () { return _mode; },
configurable: false
});
this.getLegend = function () {
var canvas = $("
");
var infoDiv = $("
");
var that = this;
var nameDiv = $("
");
var setName = function () {
nameDiv.text(that.name);
}
setName();
var colorIsVisible = 0;
var paletteControl, colorDiv, paletteDiv;
colorDiv = $("
").appendTo(infoDiv);
var clrTitleText, colorTitle;
var refreshColor = function () {
clrTitleText = that.getTitle("values");
if (colorIsVisible == 0) {
colorDiv.empty();
colorTitle = $("
").text(clrTitleText).appendTo(colorDiv);
paletteDiv = $("
").appendTo(colorDiv);
paletteControl = new InteractiveDataDisplay.ColorPaletteViewer(paletteDiv, _palette, { logAxis: _logColors });
colorIsVisible = 2;
} else colorTitle.text(clrTitleText);
if (_palette && _palette.isNormalized) {
paletteControl.dataRange = { min: _fmin, max: _fmax };
}
}
refreshColor();
var intervalDiv;
var refreshInterval = function () {
if (_interval == undefined && intervalDiv) intervalDiv.empty();
else {
if (_interval) {
_formatter_interval = InteractiveDataDisplay.AdaptiveFormatter(_interval.min, _interval.max);
if (intervalDiv) intervalDiv.text("highlighted interval: " + _formatter_interval.toString(_interval.min) + ", " + _formatter_interval.toString(_interval.max));
else intervalDiv = $("
highlighted interval: " + _formatter_interval.toString(_interval.min) + ", " + _formatter_interval.toString(_interval.max) + "
").appendTo(infoDiv);
}
}
}
refreshInterval();
this.host.bind("appearanceChanged",
function (event, propertyName) {
if (!propertyName || propertyName == "name")
setName();
if (!propertyName || propertyName == "interval")
refreshInterval();
if (!propertyName || propertyName == "values" || propertyName == "colorPalette"){
colorIsVisible = 0;
refreshColor();
}
if (!propertyName || propertyName == "palette") paletteControl.palette = _palette;
var oldRange = paletteControl.dataRange;
if (_palette && _palette.isNormalized && (oldRange == undefined || oldRange.min != _fmin || oldRange.max != _fmax)) {
paletteControl.dataRange = { min: _fmin, max: _fmax };
}
});
var onLegendRemove = function () {
that.host.unbind("appearanceChanged");
};
return { name: nameDiv, legend: { thumbnail: canvas, content: infoDiv }, onLegendRemove: onLegendRemove };
};
this.buildSvgLegend = function (legendSettings, svg) {
var that = this;
legendSettings.height = 30;
svg.add(svg.rect(legendSettings.width, legendSettings.height).fill("white").opacity(0.5));
var style = window.getComputedStyle(legendSettings.legendDiv.children[0].children[1], null);
var fontSize = parseFloat(style.getPropertyValue('font-size'));
var fontFamily = style.getPropertyValue('font-family');
var fontWeight = style ? style.getPropertyValue('font-weight') : undefined;
svg.add(svg.text(that.name).font({ family: fontFamily, size: fontSize, weight: fontWeight }).translate(40, 0));
//content
var isContent = legendSettings.legendDiv.children[1];
style = (isContent && legendSettings.legendDiv.children[1].children[0] && legendSettings.legendDiv.children[1].children[0].children[0]) ? window.getComputedStyle(legendSettings.legendDiv.children[1].children[0].children[0], null) : undefined;
fontSize = style ? parseFloat(style.getPropertyValue('font-size')) : undefined;
fontFamily = style ? style.getPropertyValue('font-family') : undefined;
fontWeight = style ? style.getPropertyValue('font-weight') : undefined;
var content = svg.group();
var colorText = that.getTitle("values");
content.text(colorText).font({ family: fontFamily, size: fontSize, weight: fontWeight });
content.translate(5, 30);
var colorPalette_g = svg.group();
var width = legendSettings.width;
var height = 20;
InteractiveDataDisplay.SvgColorPaletteViewer(colorPalette_g, _palette, legendSettings.legendDiv.children[1].children[0].children[1], { width: width, height: height });
colorPalette_g.translate(5, 50);
legendSettings.height += (50 + height);
if (_interval) {
style = (isContent && legendSettings.legendDiv.children[1].children[1] && legendSettings.legendDiv.children[1].children[1]) ? window.getComputedStyle(legendSettings.legendDiv.children[1].children[1], null) : undefined;
fontSize = style ? parseFloat(style.getPropertyValue('font-size')) : undefined;
fontFamily = style ? style.getPropertyValue('font-family') : undefined;
fontWeight = style ? style.getPropertyValue('font-weight') : undefined;
var interval_g = svg.group();
var text = $(legendSettings.legendDiv.children[1].children[1]).text();
interval_g.add(interval_g.text(text).font({ family: fontFamily, size: fontSize, weight: fontWeight }));
var width = legendSettings.width;
var height = 25;
interval_g.translate(5, 100);
legendSettings.height += (50 + height);
};
svg.front();
}
// Initialization
if (initialData && typeof initialData.values != 'undefined')
this.draw(initialData);
};
InteractiveDataDisplay.Heatmap.prototype = new InteractiveDataDisplay.CanvasPlot();
InteractiveDataDisplay.register("heatmap", function (jqDiv, master) {
return new InteractiveDataDisplay.Heatmap(jqDiv, master);
});
InteractiveDataDisplay.NavigationPanel = function (plot, div, url) {
var that = this;
var leftKeyCode = 37;
var upKeyCode = 38;
var righKeyCode = 39;
var downKeyCode = 40;
var plusKeyCode = 107;
var minusKeyCode = 109;
var dashKeyCode = 189;
var equalKey = 187;
div.attr("tabindex", "0");
div.addClass('idd-navigation-container');
if (plot.legend) {
var hideShowLegend = $('
').appendTo(div);
if (plot.legend.isVisible) hideShowLegend.addClass("idd-onscreennavigation-showlegend");
else hideShowLegend.addClass("idd-onscreennavigation-hidelegend");
hideShowLegend.click(function () {
if (plot.legend.isVisible) {
plot.legend.isVisible = false;
hideShowLegend.removeClass("idd-onscreennavigation-showlegend").addClass("idd-onscreennavigation-hidelegend");
} else {
plot.legend.isVisible = true;
hideShowLegend.removeClass("idd-onscreennavigation-hidelegend").addClass("idd-onscreennavigation-showlegend");
}
});
};
var help;
if (url) {
help = $('
').addClass("idd-onscreennavigation-help").appendTo(div);
help.attr('href', url);
}
else help = $('