|
|
<template>
|
|
|
<div>
|
|
|
<div id="violin" class="chart-wrapper" style="max-height: 200px !important"></div>
|
|
|
<div id="legendViolin" class="text-center"></div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import * as Plotly from 'plotly.js'
|
|
|
import { EventBus } from '../main.js'
|
|
|
|
|
|
export default {
|
|
|
name: "ValidationController",
|
|
|
data () {
|
|
|
return {
|
|
|
WH: [],
|
|
|
ResultsValid: '',
|
|
|
factorsValid: [1, 1, 1, 1, 0, 0, 0, 0],
|
|
|
activeCurr: 1,
|
|
|
Metrics: ['Accuracy', 'Precision', 'Recall', 'F1-score', 'G-mean', 'ROC AUC', 'Log loss', 'MCC'],
|
|
|
selectedSimple: [],
|
|
|
selectedEnsem: [],
|
|
|
storedEnsemble: [],
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
reset () {
|
|
|
var svg = d3.select("#violin");
|
|
|
svg.selectAll("*").remove();
|
|
|
var svgLeg = d3.select("#legendViolin");
|
|
|
svgLeg.selectAll("*").remove();
|
|
|
this.selectedSimple = []
|
|
|
this.selectedEnsem = []
|
|
|
this.storedEnsemble = []
|
|
|
this.activeCurr = 1
|
|
|
this.ResultsValid = ''
|
|
|
},
|
|
|
ViolinFun () {
|
|
|
var svg = d3.select("#violin");
|
|
|
svg.selectAll("*").remove();
|
|
|
var chart2
|
|
|
|
|
|
var data = []
|
|
|
var colorsGlobal = []
|
|
|
var colorsGlobalBins = []
|
|
|
var countFactors = 0
|
|
|
var activeLines = []
|
|
|
for (let j=0; j<this.factorsValid.length; j++) {
|
|
|
if (this.factorsValid[j] == 1) {
|
|
|
countFactors = countFactors + 1
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var meanGlobalSel = new Array(countFactors).fill(0)
|
|
|
var sumGlobalSel = new Array(countFactors).fill(0)
|
|
|
var countValuesSel = new Array(countFactors).fill(0)
|
|
|
var globalActive = this.activeCurr
|
|
|
var measure = 0
|
|
|
|
|
|
if (globalActive == 1) {
|
|
|
var IDs = JSON.parse(this.ResultsValid[0])
|
|
|
var valid = JSON.parse(this.ResultsValid[3])
|
|
|
|
|
|
for (let j=0; j<this.factorsValid.length; j++) {
|
|
|
if (this.factorsValid[j] == 0) {
|
|
|
measure = measure + 1
|
|
|
}
|
|
|
else {
|
|
|
for (let i=0; i<IDs.length; i++){
|
|
|
|
|
|
let tempValid = JSON.parse(valid[j])
|
|
|
|
|
|
let tempSplit = IDs[i].split(/([0-9]+)/)
|
|
|
|
|
|
if (tempSplit[0] == 'KNN' || tempSplit[0] == 'KNNC' || tempSplit[0] == 'KNNM' || tempSplit[0] == 'KNNCC' || tempSplit[0] == 'KNNCM' || tempSplit[0] == 'KNNMC' || tempSplit[0] == 'KNNMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#ff7f00"})
|
|
|
}
|
|
|
else if (tempSplit[0] == 'LR' || tempSplit[0] == 'LRC' || tempSplit[0] == 'LRM' || tempSplit[0] == 'LRCC' || tempSplit[0] == 'LRCM' || tempSplit[0] == 'LRMC' || tempSplit[0] == 'LRMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#fdbf6f"})
|
|
|
}
|
|
|
else if (tempSplit[0] == 'MLP' || tempSplit[0] == 'MLPC' || tempSplit[0] == 'MLPM' || tempSplit[0] == 'MLPCC' || tempSplit[0] == 'MLPCM' || tempSplit[0] == 'MLPMC' || tempSplit[0] == 'MLPMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#fb9a99"})
|
|
|
}
|
|
|
else if (tempSplit[0] == 'RF' || tempSplit[0] == 'RFC' || tempSplit[0] == 'RFM' || tempSplit[0] == 'RFCC' || tempSplit[0] == 'RFCM' || tempSplit[0] == 'RFMC' || tempSplit[0] == 'RFMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#b15928"})
|
|
|
}
|
|
|
else {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#a6cee3"})
|
|
|
}
|
|
|
if (this.selectedSimple.length != 0) {
|
|
|
|
|
|
if (this.selectedSimple.includes(IDs[i])) {
|
|
|
sumGlobalSel[j-measure] = sumGlobalSel[j-measure] + tempValid[i]
|
|
|
countValuesSel[j-measure] = countValuesSel[j-measure] + 1
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
colorsGlobal.push('#555')
|
|
|
colorsGlobalBins.push('#c0c0c0')
|
|
|
}
|
|
|
}
|
|
|
activeLines.push('mean')
|
|
|
if (this.selectedSimple.length != 0) {
|
|
|
activeLines.push('meanSelection')
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
var valid = JSON.parse(this.ResultsValid[3])
|
|
|
|
|
|
var mergedStoreEnsembleLoc = [].concat.apply([], this.storedEnsemble)
|
|
|
var mergedStoreEnsembleLocFormatted = []
|
|
|
for (let i = 0; i < mergedStoreEnsembleLoc.length; i++) {
|
|
|
mergedStoreEnsembleLocFormatted.push(parseInt(mergedStoreEnsembleLoc[i].replace(/\D/g,'')))
|
|
|
}
|
|
|
for (let j=0; j<this.factorsValid.length; j++) {
|
|
|
if (this.factorsValid[j] == 0) {
|
|
|
measure = measure + 1
|
|
|
}
|
|
|
else {
|
|
|
for (let i=0; i<mergedStoreEnsembleLoc.length; i++){
|
|
|
|
|
|
let tempValid = JSON.parse(valid[j])
|
|
|
tempValid = mergedStoreEnsembleLocFormatted.map((item) => tempValid[item])
|
|
|
|
|
|
let tempSplit = mergedStoreEnsembleLoc[i].split(/([0-9]+)/)
|
|
|
if (tempSplit[0] == 'KNN' || tempSplit[0] == 'KNNC' || tempSplit[0] == 'KNNM' || tempSplit[0] == 'KNNCC' || tempSplit[0] == 'KNNCM' || tempSplit[0] == 'KNNMC' || tempSplit[0] == 'KNNMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#ff7f00"})
|
|
|
}
|
|
|
else if (tempSplit[0] == 'LR' || tempSplit[0] == 'LRC' || tempSplit[0] == 'LRM' || tempSplit[0] == 'LRCC' || tempSplit[0] == 'LRCM' || tempSplit[0] == 'LRMC' || tempSplit[0] == 'LRMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#fdbf6f"})
|
|
|
}
|
|
|
else if (tempSplit[0] == 'MLP' || tempSplit[0] == 'MLPC' || tempSplit[0] == 'MLPM' || tempSplit[0] == 'MLPCC' || tempSplit[0] == 'MLPCM' || tempSplit[0] == 'MLPMC' || tempSplit[0] == 'MLPMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#fb9a99"})
|
|
|
}
|
|
|
else if (tempSplit[0] == 'RF' || tempSplit[0] == 'RFC' || tempSplit[0] == 'RFM' || tempSplit[0] == 'RFCC' || tempSplit[0] == 'RFCM' || tempSplit[0] == 'RFMC' || tempSplit[0] == 'RFMM') {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#b15928"})
|
|
|
}
|
|
|
else {
|
|
|
data.push({Algorithm: this.Metrics[j], value: tempValid[i], category: "#a6cee3"})
|
|
|
}
|
|
|
if (this.selectedEnsem.length != 0) {
|
|
|
if (this.selectedEnsem.includes(mergedStoreEnsembleLoc[i])) {
|
|
|
sumGlobalSel[j-measure] = sumGlobalSel[j-measure] + tempValid[i]
|
|
|
countValuesSel[j-measure] = countValuesSel[j-measure] + 1
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
colorsGlobal.push('#555')
|
|
|
colorsGlobalBins.push('#c0c0c0')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
activeLines.push('mean')
|
|
|
if (this.selectedEnsem.length != 0) {
|
|
|
activeLines.push('meanSelection')
|
|
|
}
|
|
|
}
|
|
|
colorsGlobalBins.push('#c0c0c0')
|
|
|
colorsGlobalBins.push('#000')
|
|
|
for (let j=0; j<sumGlobalSel.length; j++) {
|
|
|
meanGlobalSel[j] = sumGlobalSel[j] / countValuesSel[j]
|
|
|
}
|
|
|
|
|
|
chart2 = makeDistroChart({
|
|
|
data:data,
|
|
|
xName:'Algorithm',
|
|
|
yName:'value',
|
|
|
axisLabels: {xAxis: 'Algorithm', yAxis: 'Performance (%)'},
|
|
|
selector:"#violin",
|
|
|
constrainExtremes:false});
|
|
|
chart2.renderBoxPlot({showBox:false});
|
|
|
chart2.renderDataPlots({showBeanLines:true,beanWidth:15,showPlot:false,showLines:activeLines});
|
|
|
chart2.renderNotchBoxes({showNotchBox:false});
|
|
|
chart2.renderViolinPlot({reset:true, width:75, clamp:0, resolution:30, bandwidth:50});
|
|
|
|
|
|
/**
|
|
|
* Creates a box plot, violin plot, and or notched box plot
|
|
|
* @param settings Configuration options for the base plot
|
|
|
* @param settings.data The data for the plot
|
|
|
* @param settings.xName The name of the column that should be used for the x groups
|
|
|
* @param settings.yName The name of the column used for the y values
|
|
|
* @param {string} settings.selector The selector string for the main chart div
|
|
|
* @param [settings.axisLabels={}] Defaults to the xName and yName
|
|
|
* @param [settings.yTicks = 1] 1 = default ticks. 2 = double, 0.5 = half
|
|
|
* @param [settings.scale='linear'] 'linear' or 'log' - y scale of the chart
|
|
|
* @param [settings.chartSize={width:800, height:400}] The height and width of the chart itself (doesn't include the container)
|
|
|
* @param [settings.margin={top: 15, right: 60, bottom: 40, left: 50}] The margins around the chart (inside the main div)
|
|
|
* @param [settings.constrainExtremes=false] Should the y scale include outliers?
|
|
|
* @returns {object} chart A chart object
|
|
|
*/
|
|
|
function makeDistroChart(settings) {
|
|
|
|
|
|
var chart = {};
|
|
|
|
|
|
// Defaults
|
|
|
chart.settings = {
|
|
|
data: null,
|
|
|
xName: null,
|
|
|
yName: null,
|
|
|
selector: null,
|
|
|
axisLables: null,
|
|
|
yTicks: 0.5,
|
|
|
scale: 'linear',
|
|
|
chartSize: {width: 820, height: 200},
|
|
|
margin: {top: 15, right: 40, bottom: 60, left: 40},
|
|
|
constrainExtremes: false,
|
|
|
color: colorsGlobal
|
|
|
};
|
|
|
for (var setting in settings) {
|
|
|
chart.settings[setting] = settings[setting]
|
|
|
}
|
|
|
|
|
|
|
|
|
function formatAsFloat(d) {
|
|
|
if (d % 1 !== 0) {
|
|
|
return d3.format(".2f")(d);
|
|
|
} else {
|
|
|
return d3.format(".0f")(d);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function logFormatNumber(d) {
|
|
|
var x = Math.log(d) / Math.log(10) + 1e-6;
|
|
|
return Math.abs(x - Math.floor(x)) < 0.6 ? formatAsFloat(d) : "";
|
|
|
}
|
|
|
|
|
|
chart.yFormatter = formatAsFloat;
|
|
|
|
|
|
chart.data = chart.settings.data;
|
|
|
|
|
|
chart.groupObjs = {}; //The data organized by grouping and sorted as well as any metadata for the groups
|
|
|
chart.objs = {mainDiv: null, chartDiv: null, g: null, xAxis: null, yAxis: null};
|
|
|
chart.colorFunct = null;
|
|
|
|
|
|
/**
|
|
|
* Takes an array, function, or object mapping and created a color function from it
|
|
|
* @param {function|[]|object} colorOptions
|
|
|
* @returns {function} Function to be used to determine chart colors
|
|
|
*/
|
|
|
function getColorFunct(colorOptions) {
|
|
|
if (typeof colorOptions == 'function') {
|
|
|
return colorOptions
|
|
|
} else if (Array.isArray(colorOptions)) {
|
|
|
// If an array is provided, map it to the domain
|
|
|
var colorMap = {}, cColor = 0;
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
colorMap[cName] = colorOptions[cColor];
|
|
|
cColor = (cColor + 1) % colorOptions.length;
|
|
|
}
|
|
|
return function (group) {
|
|
|
return colorMap[group];
|
|
|
}
|
|
|
} else if (typeof colorOptions == 'object') {
|
|
|
// if an object is provided, assume it maps to the colors
|
|
|
return function (group) {
|
|
|
return colorOptions[group];
|
|
|
}
|
|
|
} else {
|
|
|
return d3.scale.ordinal().range(colorsGlobalBins)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Takes a percentage as returns the values that correspond to that percentage of the group range witdh
|
|
|
* @param objWidth Percentage of range band
|
|
|
* @param gName The bin name to use to get the x shift
|
|
|
* @returns {{left: null, right: null, middle: null}}
|
|
|
*/
|
|
|
function getObjWidth(objWidth, gName) {
|
|
|
var objSize = {left: null, right: null, middle: null};
|
|
|
var width = chart.xScale.rangeBand() * (objWidth / 100);
|
|
|
var padding = (chart.xScale.rangeBand() - width) / 2;
|
|
|
var gShift = chart.xScale(gName);
|
|
|
objSize.middle = chart.xScale.rangeBand() / 2 + gShift;
|
|
|
objSize.left = padding + gShift;
|
|
|
objSize.right = objSize.left + width;
|
|
|
return objSize;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Adds jitter to the scatter point plot
|
|
|
* @param doJitter true or false, add jitter to the point
|
|
|
* @param width percent of the range band to cover with the jitter
|
|
|
* @returns {number}
|
|
|
*/
|
|
|
function addJitter(doJitter, width) {
|
|
|
if (doJitter !== true || width == 0) {
|
|
|
return 0
|
|
|
}
|
|
|
return Math.floor(Math.random() * width) - width / 2;
|
|
|
}
|
|
|
|
|
|
function shallowCopy(oldObj) {
|
|
|
var newObj = {};
|
|
|
for (var i in oldObj) {
|
|
|
if (oldObj.hasOwnProperty(i)) {
|
|
|
newObj[i] = oldObj[i];
|
|
|
}
|
|
|
}
|
|
|
return newObj;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Closure that creates the tooltip hover function
|
|
|
* @param groupName Name of the x group
|
|
|
* @param metrics Object to use to get values for the group
|
|
|
* @returns {Function} A function that provides the values for the tooltip
|
|
|
*/
|
|
|
function tooltipHover(groupName, metrics) {
|
|
|
var tooltipString = "Group: " + groupName;
|
|
|
tooltipString += "<br\>Max: " + formatAsFloat(metrics.max, 0.1);
|
|
|
tooltipString += "<br\>Q3: " + formatAsFloat(metrics.quartile3);
|
|
|
tooltipString += "<br\>Median: " + formatAsFloat(metrics.median);
|
|
|
tooltipString += "<br\>Q1: " + formatAsFloat(metrics.quartile1);
|
|
|
tooltipString += "<br\>Min: " + formatAsFloat(metrics.min);
|
|
|
return function () {
|
|
|
chart.objs.tooltip.transition().duration(200).style("opacity", 0.9);
|
|
|
chart.objs.tooltip.html(tooltipString)
|
|
|
};
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Parse the data and calculates base values for the plots
|
|
|
*/
|
|
|
!function prepareData() {
|
|
|
function calcMetrics(values) {
|
|
|
|
|
|
var metrics = { //These are the original non<EFBFBD>scaled values
|
|
|
max: null,
|
|
|
upperOuterFence: null,
|
|
|
upperInnerFence: null,
|
|
|
quartile3: null,
|
|
|
median: null,
|
|
|
mean: null,
|
|
|
iqr: null,
|
|
|
quartile1: null,
|
|
|
lowerInnerFence: null,
|
|
|
lowerOuterFence: null,
|
|
|
min: null
|
|
|
};
|
|
|
|
|
|
metrics.min = d3.min(values);
|
|
|
metrics.quartile1 = d3.quantile(values, 0.25);
|
|
|
metrics.median = d3.median(values);
|
|
|
metrics.mean = d3.mean(values);
|
|
|
metrics.quartile3 = d3.quantile(values, 0.75);
|
|
|
metrics.max = d3.max(values);
|
|
|
metrics.iqr = metrics.quartile3 - metrics.quartile1;
|
|
|
|
|
|
//The inner fences are the closest value to the IQR without going past it (assumes sorted lists)
|
|
|
var LIF = metrics.quartile1 - (1.5 * metrics.iqr);
|
|
|
var UIF = metrics.quartile3 + (1.5 * metrics.iqr);
|
|
|
for (var i = 0; i <= values.length; i++) {
|
|
|
if (values[i] < LIF) {
|
|
|
continue;
|
|
|
}
|
|
|
if (!metrics.lowerInnerFence && values[i] >= LIF) {
|
|
|
metrics.lowerInnerFence = values[i];
|
|
|
continue;
|
|
|
}
|
|
|
if (values[i] > UIF) {
|
|
|
metrics.upperInnerFence = values[i - 1];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
metrics.lowerOuterFence = metrics.quartile1 - (3 * metrics.iqr);
|
|
|
metrics.upperOuterFence = metrics.quartile3 + (3 * metrics.iqr);
|
|
|
if (!metrics.lowerInnerFence) {
|
|
|
metrics.lowerInnerFence = metrics.min;
|
|
|
}
|
|
|
if (!metrics.upperInnerFence) {
|
|
|
metrics.upperInnerFence = metrics.max;
|
|
|
}
|
|
|
return metrics
|
|
|
}
|
|
|
|
|
|
var current_x = null;
|
|
|
var current_y = null;
|
|
|
var current_row;
|
|
|
|
|
|
// Group the values
|
|
|
for (current_row = 0; current_row < chart.data.length; current_row++) {
|
|
|
current_x = chart.data[current_row][chart.settings.xName];
|
|
|
current_y = chart.data[current_row][chart.settings.yName];
|
|
|
|
|
|
if (chart.groupObjs.hasOwnProperty(current_x)) {
|
|
|
chart.groupObjs[current_x].values.push(current_y);
|
|
|
} else {
|
|
|
chart.groupObjs[current_x] = {};
|
|
|
chart.groupObjs[current_x].values = [current_y];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].values.sort(d3.ascending);
|
|
|
chart.groupObjs[cName].metrics = {};
|
|
|
chart.groupObjs[cName].metrics = calcMetrics(chart.groupObjs[cName].values);
|
|
|
|
|
|
}
|
|
|
}();
|
|
|
|
|
|
/**
|
|
|
* Prepare the chart settings and chart div and svg
|
|
|
*/
|
|
|
!function prepareSettings() {
|
|
|
//Set base settings
|
|
|
chart.margin = chart.settings.margin;
|
|
|
chart.divWidth = chart.settings.chartSize.width;
|
|
|
chart.divHeight = chart.settings.chartSize.height;
|
|
|
chart.width = chart.divWidth - chart.margin.left - chart.margin.right;
|
|
|
chart.height = chart.divHeight - chart.margin.top - chart.margin.bottom;
|
|
|
|
|
|
if (chart.settings.axisLabels) {
|
|
|
chart.xAxisLable = chart.settings.axisLabels.xAxis;
|
|
|
chart.yAxisLable = chart.settings.axisLabels.yAxis;
|
|
|
} else {
|
|
|
chart.xAxisLable = chart.settings.xName;
|
|
|
chart.yAxisLable = chart.settings.yName;
|
|
|
}
|
|
|
|
|
|
if (chart.settings.scale === 'log') {
|
|
|
chart.yScale = d3.scale.log();
|
|
|
chart.yFormatter = logFormatNumber;
|
|
|
} else {
|
|
|
chart.yScale = d3.scale.linear();
|
|
|
}
|
|
|
|
|
|
if (chart.settings.constrainExtremes === true) {
|
|
|
var fences = [];
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
fences.push(chart.groupObjs[cName].metrics.lowerInnerFence);
|
|
|
fences.push(chart.groupObjs[cName].metrics.upperInnerFence);
|
|
|
}
|
|
|
chart.range = d3.extent(fences);
|
|
|
|
|
|
} else {
|
|
|
chart.range = d3.extent(chart.data, function (d) {return d[chart.settings.yName];});
|
|
|
}
|
|
|
|
|
|
chart.colorFunct = getColorFunct(chart.settings.colors);
|
|
|
|
|
|
// Build Scale functions
|
|
|
chart.yScale.range([chart.height, 0]).domain(chart.range).nice().clamp(true);
|
|
|
chart.xScale = d3.scale.ordinal().domain(Object.keys(chart.groupObjs)).rangeBands([0, chart.width]);
|
|
|
|
|
|
//Build Axes Functions
|
|
|
chart.objs.yAxis = d3.svg.axis()
|
|
|
.scale(chart.yScale)
|
|
|
.orient("left")
|
|
|
.tickFormat(chart.yFormatter)
|
|
|
.outerTickSize(0)
|
|
|
.innerTickSize(-chart.width + (chart.margin.right + chart.margin.left));
|
|
|
chart.objs.yAxis.ticks(chart.objs.yAxis.ticks()*chart.settings.yTicks);
|
|
|
chart.objs.xAxis = d3.svg.axis().scale(chart.xScale).orient("bottom").tickSize(5);
|
|
|
}();
|
|
|
|
|
|
/**
|
|
|
* Updates the chart based on the current settings and window size
|
|
|
* @returns {*}
|
|
|
*/
|
|
|
chart.update = function () {
|
|
|
// Update chart size based on view port size
|
|
|
chart.width = parseInt(chart.objs.chartDiv.style("width"), 10) - (chart.margin.left + chart.margin.right) + 30;
|
|
|
chart.height = parseInt(chart.objs.chartDiv.style("height"), 10) - (chart.margin.top + chart.margin.bottom) - 20;
|
|
|
|
|
|
// Update scale functions
|
|
|
chart.xScale.rangeBands([0, chart.width]);
|
|
|
chart.yScale.range([chart.height, 0]);
|
|
|
|
|
|
// Update the yDomain if the Violin plot clamp is set to -1 meaning it will extend the violins to make nice points
|
|
|
if (chart.violinPlots && chart.violinPlots.options.show == true && chart.violinPlots.options._yDomainVP != null) {
|
|
|
chart.yScale.domain(chart.violinPlots.options._yDomainVP).nice().clamp(true);
|
|
|
} else {
|
|
|
chart.yScale.domain(chart.range).nice().clamp(true);
|
|
|
}
|
|
|
|
|
|
//Update axes
|
|
|
chart.objs.g.select('.x.axis').attr("transform", "translate(0," + chart.height + ")").call(chart.objs.xAxis)
|
|
|
.selectAll("text")
|
|
|
.attr("y", 10)
|
|
|
.attr("x", 30)
|
|
|
.attr("transform", "rotate(0)")
|
|
|
.style("text-anchor", "end");
|
|
|
chart.objs.g.select('.x.axis .label').attr("x", chart.width / 2);
|
|
|
chart.objs.g.select('.y.axis').call(chart.objs.yAxis.innerTickSize(-chart.width));
|
|
|
chart.objs.g.select('.y.axis .label').attr("x", -chart.height / 2);
|
|
|
chart.objs.chartDiv.select('svg').attr("width", chart.width + (chart.margin.left + chart.margin.right)).attr("height", chart.height + (chart.margin.top + chart.margin.bottom));
|
|
|
|
|
|
return chart;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Prepare the chart html elements
|
|
|
*/
|
|
|
!function prepareChart() {
|
|
|
// Build main div and chart div
|
|
|
chart.objs.mainDiv = d3.select(chart.settings.selector)
|
|
|
.style("max-width", chart.divWidth + "px");
|
|
|
// Add all the divs to make it centered and responsive
|
|
|
chart.objs.mainDiv.append("div")
|
|
|
.attr("class", "inner-wrapper")
|
|
|
.append("div").attr("class", "outer-box")
|
|
|
.append("div").attr("class", "inner-box");
|
|
|
// Capture the inner div for the chart (where the chart actually is)
|
|
|
chart.selector = chart.settings.selector + " .inner-box";
|
|
|
chart.objs.chartDiv = d3.select(chart.selector);
|
|
|
d3.select(window).on('resize.' + chart.selector, chart.update);
|
|
|
|
|
|
// Create the svg
|
|
|
chart.objs.g = chart.objs.chartDiv.append("svg")
|
|
|
.attr("class", "chart-area")
|
|
|
.attr("width", chart.width + (chart.margin.left + chart.margin.right))
|
|
|
.attr("height", 200 + (chart.margin.top + chart.margin.bottom))
|
|
|
.append("g")
|
|
|
.attr("transform", "translate(" + chart.margin.left + "," + chart.margin.top + ")");
|
|
|
|
|
|
// Create axes
|
|
|
chart.objs.axes = chart.objs.g.append("g").attr("class", "axis");
|
|
|
chart.objs.axes.append("g")
|
|
|
.attr("class", "x axis")
|
|
|
.attr("transform", "translate(0," + chart.height + ")")
|
|
|
.call(chart.objs.xAxis);
|
|
|
chart.objs.axes.append("g")
|
|
|
.attr("class", "y axis")
|
|
|
.call(chart.objs.yAxis)
|
|
|
.append("text")
|
|
|
.attr("class", "label")
|
|
|
.attr("transform", "rotate(-90)")
|
|
|
.attr("y", -35)
|
|
|
.attr("x", -chart.height / 2)
|
|
|
.attr("dy", ".71em")
|
|
|
.style("text-anchor", "middle")
|
|
|
.text(chart.yAxisLable);
|
|
|
|
|
|
// Create tooltip div
|
|
|
chart.objs.tooltip = chart.objs.mainDiv.append('div').attr('class', 'tooltip');
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].g = chart.objs.g.append("g").attr("class", "group");
|
|
|
chart.groupObjs[cName].g.on("mouseover", function () {
|
|
|
chart.objs.tooltip
|
|
|
.style("display", null)
|
|
|
.style("left", (d3.event.pageX) + "px")
|
|
|
.style("top", (d3.event.pageY - 28) + "px");
|
|
|
}).on("mouseout", function () {
|
|
|
chart.objs.tooltip.style("display", "none");
|
|
|
}).on("mousemove", tooltipHover(cName, chart.groupObjs[cName].metrics))
|
|
|
}
|
|
|
chart.update();
|
|
|
}();
|
|
|
|
|
|
/**
|
|
|
* Render a violin plot on the current chart
|
|
|
* @param options
|
|
|
* @param [options.showViolinPlot=true] True or False, show the violin plot
|
|
|
* @param [options.resolution=100 default]
|
|
|
* @param [options.bandwidth=10 default] May need higher bandwidth for larger data sets
|
|
|
* @param [options.width=50] The max percent of the group rangeBand that the violin can be
|
|
|
* @param [options.interpolation=''] How to render the violin
|
|
|
* @param [options.clamp=0 default]
|
|
|
* 0 = keep data within chart min and max, clamp once data = 0. May extend beyond data set min and max
|
|
|
* 1 = clamp at min and max of data set. Possibly no tails
|
|
|
* -1 = extend chart axis to make room for data to interpolate to 0. May extend axis and data set min and max
|
|
|
* @param [options.colors=chart default] The color mapping for the violin plot
|
|
|
* @returns {*} The chart object
|
|
|
*/
|
|
|
chart.renderViolinPlot = function (options) {
|
|
|
chart.violinPlots = {};
|
|
|
|
|
|
var defaultOptions = {
|
|
|
show: true,
|
|
|
showViolinPlot: true,
|
|
|
resolution: 100,
|
|
|
bandwidth: 20,
|
|
|
width: 50,
|
|
|
interpolation: 'cardinal',
|
|
|
clamp: 1,
|
|
|
colors: chart.colorFunct,
|
|
|
_yDomainVP: null // If the Violin plot is set to close all violin plots, it may need to extend the domain, that extended domain is stored here
|
|
|
};
|
|
|
chart.violinPlots.options = shallowCopy(defaultOptions);
|
|
|
for (var option in options) {
|
|
|
chart.violinPlots.options[option] = options[option]
|
|
|
}
|
|
|
var vOpts = chart.violinPlots.options;
|
|
|
|
|
|
// Create violin plot objects
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].violin = {};
|
|
|
chart.groupObjs[cName].violin.objs = {};
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Take a new set of options and redraw the violin
|
|
|
* @param updateOptions
|
|
|
*/
|
|
|
chart.violinPlots.change = function (updateOptions) {
|
|
|
if (updateOptions) {
|
|
|
for (var key in updateOptions) {
|
|
|
vOpts[key] = updateOptions[key]
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].violin.objs.g.remove()
|
|
|
}
|
|
|
|
|
|
chart.violinPlots.prepareViolin();
|
|
|
chart.violinPlots.update();
|
|
|
};
|
|
|
|
|
|
chart.violinPlots.reset = function () {
|
|
|
chart.violinPlots.change(defaultOptions)
|
|
|
};
|
|
|
chart.violinPlots.show = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = true;
|
|
|
if (opts.reset) {
|
|
|
chart.violinPlots.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: true};
|
|
|
}
|
|
|
chart.violinPlots.change(opts);
|
|
|
|
|
|
};
|
|
|
|
|
|
chart.violinPlots.hide = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = false;
|
|
|
if (opts.reset) {
|
|
|
chart.violinPlots.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: false};
|
|
|
}
|
|
|
chart.violinPlots.change(opts);
|
|
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Update the violin obj values
|
|
|
*/
|
|
|
chart.violinPlots.update = function () {
|
|
|
var cName, cViolinPlot;
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cViolinPlot = chart.groupObjs[cName].violin;
|
|
|
|
|
|
// Build the violins sideways, so use the yScale for the xScale and make a new yScale
|
|
|
var xVScale = chart.yScale.copy();
|
|
|
|
|
|
|
|
|
// Create the Kernel Density Estimator Function
|
|
|
cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
|
|
|
cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);
|
|
|
|
|
|
var interpolateMax = chart.groupObjs[cName].metrics.max,
|
|
|
interpolateMin = chart.groupObjs[cName].metrics.min;
|
|
|
|
|
|
if (vOpts.clamp == 0 || vOpts.clamp == -1) { //
|
|
|
// When clamp is 0, calculate the min and max that is needed to bring the violin plot to a point
|
|
|
// interpolateMax = the Minimum value greater than the max where y = 0
|
|
|
interpolateMax = d3.min(cViolinPlot.kdedata.filter(function (d) {
|
|
|
return (d.x > chart.groupObjs[cName].metrics.max && d.y == 0)
|
|
|
}), function (d) {
|
|
|
return d.x;
|
|
|
});
|
|
|
// interpolateMin = the Maximum value less than the min where y = 0
|
|
|
interpolateMin = d3.max(cViolinPlot.kdedata.filter(function (d) {
|
|
|
return (d.x < chart.groupObjs[cName].metrics.min && d.y == 0)
|
|
|
}), function (d) {
|
|
|
return d.x;
|
|
|
});
|
|
|
// If clamp is -1 we need to extend the axises so that the violins come to a point
|
|
|
if (vOpts.clamp == -1) {
|
|
|
kdeTester = eKernelTest(eKernel(vOpts.bandwidth), chart.groupObjs[cName].values);
|
|
|
if (!interpolateMax) {
|
|
|
var interMaxY = kdeTester(chart.groupObjs[cName].metrics.max);
|
|
|
var interMaxX = chart.groupObjs[cName].metrics.max;
|
|
|
var count = 25; // Arbitrary limit to make sure we don't get an infinite loop
|
|
|
while (count > 0 && interMaxY != 0) {
|
|
|
interMaxY = kdeTester(interMaxX);
|
|
|
interMaxX += 1;
|
|
|
count -= 1;
|
|
|
}
|
|
|
interpolateMax = interMaxX;
|
|
|
}
|
|
|
if (!interpolateMin) {
|
|
|
var interMinY = kdeTester(chart.groupObjs[cName].metrics.min);
|
|
|
var interMinX = chart.groupObjs[cName].metrics.min;
|
|
|
var count = 25; // Arbitrary limit to make sure we don't get an infinite loop
|
|
|
while (count > 0 && interMinY != 0) {
|
|
|
interMinY = kdeTester(interMinX);
|
|
|
interMinX -= 1;
|
|
|
count -= 1;
|
|
|
}
|
|
|
interpolateMin = interMinX;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
// Check to see if the new values are outside the existing chart range
|
|
|
// If they are assign them to the master _yDomainVP
|
|
|
if (!vOpts._yDomainVP) vOpts._yDomainVP = chart.range.slice(0);
|
|
|
if (interpolateMin && interpolateMin < vOpts._yDomainVP[0]) {
|
|
|
vOpts._yDomainVP[0] = interpolateMin;
|
|
|
}
|
|
|
if (interpolateMax && interpolateMax > vOpts._yDomainVP[1]) {
|
|
|
vOpts._yDomainVP[1] = interpolateMax;
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
if (vOpts.showViolinPlot) {
|
|
|
chart.update();
|
|
|
xVScale = chart.yScale.copy();
|
|
|
|
|
|
// Need to recalculate the KDE because the xVScale changed
|
|
|
cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
|
|
|
cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);
|
|
|
}
|
|
|
|
|
|
cViolinPlot.kdedata = cViolinPlot.kdedata
|
|
|
.filter(function (d) {
|
|
|
return (!interpolateMin || d.x >= interpolateMin)
|
|
|
})
|
|
|
.filter(function (d) {
|
|
|
return (!interpolateMax || d.x <= interpolateMax)
|
|
|
});
|
|
|
}
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cViolinPlot = chart.groupObjs[cName].violin;
|
|
|
|
|
|
// Get the violin width
|
|
|
var objBounds = getObjWidth(vOpts.width, cName);
|
|
|
var width = (objBounds.right - objBounds.left) / 2;
|
|
|
|
|
|
var yVScale = d3.scale.linear()
|
|
|
.range([width, 0])
|
|
|
.domain([0, d3.max(cViolinPlot.kdedata, function (d) {return d.y;})])
|
|
|
.clamp(true);
|
|
|
|
|
|
var area = d3.svg.area()
|
|
|
.interpolate(vOpts.interpolation)
|
|
|
.x(function (d) {return xVScale(d.x);})
|
|
|
.y0(width)
|
|
|
.y1(function (d) {return yVScale(d.y);});
|
|
|
|
|
|
var line = d3.svg.line()
|
|
|
.interpolate(vOpts.interpolation)
|
|
|
.x(function (d) {return xVScale(d.x);})
|
|
|
.y(function (d) {return yVScale(d.y)});
|
|
|
|
|
|
if (cViolinPlot.objs.left.area) {
|
|
|
cViolinPlot.objs.left.area
|
|
|
.datum(cViolinPlot.kdedata)
|
|
|
.attr("d", area);
|
|
|
cViolinPlot.objs.left.line
|
|
|
.datum(cViolinPlot.kdedata)
|
|
|
.attr("d", line);
|
|
|
|
|
|
cViolinPlot.objs.right.area
|
|
|
.datum(cViolinPlot.kdedata)
|
|
|
.attr("d", area);
|
|
|
cViolinPlot.objs.right.line
|
|
|
.datum(cViolinPlot.kdedata)
|
|
|
.attr("d", line);
|
|
|
}
|
|
|
|
|
|
// Rotate the violins
|
|
|
cViolinPlot.objs.left.g.attr("transform", "rotate(90,0,0) translate(0,-" + objBounds.left + ") scale(1,-1)");
|
|
|
cViolinPlot.objs.right.g.attr("transform", "rotate(90,0,0) translate(0,-" + objBounds.right + ")");
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Create the svg elements for the violin plot
|
|
|
*/
|
|
|
chart.violinPlots.prepareViolin = function () {
|
|
|
var cName, cViolinPlot;
|
|
|
|
|
|
if (vOpts.colors) {
|
|
|
chart.violinPlots.color = getColorFunct(vOpts.colors);
|
|
|
} else {
|
|
|
chart.violinPlots.color = chart.colorFunct
|
|
|
}
|
|
|
|
|
|
if (vOpts.show == false) {return}
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cViolinPlot = chart.groupObjs[cName].violin;
|
|
|
|
|
|
cViolinPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "violin-plot");
|
|
|
cViolinPlot.objs.left = {area: null, line: null, g: null};
|
|
|
cViolinPlot.objs.right = {area: null, line: null, g: null};
|
|
|
|
|
|
cViolinPlot.objs.left.g = cViolinPlot.objs.g.append("g");
|
|
|
cViolinPlot.objs.right.g = cViolinPlot.objs.g.append("g");
|
|
|
|
|
|
if (vOpts.showViolinPlot !== false) {
|
|
|
//Area
|
|
|
cViolinPlot.objs.left.area = cViolinPlot.objs.left.g.append("path")
|
|
|
.attr("class", "area")
|
|
|
.style("fill", chart.violinPlots.color(cName));
|
|
|
cViolinPlot.objs.right.area = cViolinPlot.objs.right.g.append("path")
|
|
|
.attr("class", "area")
|
|
|
.style("fill", chart.violinPlots.color(cName));
|
|
|
|
|
|
//Lines
|
|
|
cViolinPlot.objs.left.line = cViolinPlot.objs.left.g.append("path")
|
|
|
.attr("class", "line")
|
|
|
.attr("fill", 'none')
|
|
|
.style("stroke", chart.violinPlots.color(cName));
|
|
|
cViolinPlot.objs.right.line = cViolinPlot.objs.right.g.append("path")
|
|
|
.attr("class", "line")
|
|
|
.attr("fill", 'none')
|
|
|
.style("stroke", chart.violinPlots.color(cName));
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
function kernelDensityEstimator(kernel, x) {
|
|
|
return function (sample) {
|
|
|
return x.map(function (x) {
|
|
|
return {x:x, y:d3.mean(sample, function (v) {return kernel(x - v);})};
|
|
|
});
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function eKernel(scale) {
|
|
|
return function (u) {
|
|
|
return Math.abs(u /= scale) <= 1 ? .75 * (1 - u * u) / scale : 0;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// Used to find the roots for adjusting violin axis
|
|
|
// Given an array, find the value for a single point, even if it is not in the domain
|
|
|
function eKernelTest(kernel, array) {
|
|
|
return function (testX) {
|
|
|
return d3.mean(array, function (v) {return kernel(testX - v);})
|
|
|
}
|
|
|
}
|
|
|
|
|
|
chart.violinPlots.prepareViolin();
|
|
|
|
|
|
d3.select(window).on('resize.' + chart.selector + '.violinPlot', chart.violinPlots.update);
|
|
|
chart.violinPlots.update();
|
|
|
return chart;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Render a box plot on the current chart
|
|
|
* @param options
|
|
|
* @param [options.show=true] Toggle the whole plot on and off
|
|
|
* @param [options.showBox=true] Show the box part of the box plot
|
|
|
* @param [options.showWhiskers=true] Show the whiskers
|
|
|
* @param [options.showMedian=true] Show the median line
|
|
|
* @param [options.showMean=false] Show the mean line
|
|
|
* @param [options.medianCSize=3] The size of the circle on the median
|
|
|
* @param [options.showOutliers=true] Plot outliers
|
|
|
* @param [options.boxwidth=30] The max percent of the group rangeBand that the box can be
|
|
|
* @param [options.lineWidth=boxWidth] The max percent of the group rangeBand that the line can be
|
|
|
* @param [options.outlierScatter=false] Spread out the outliers so they don't all overlap (in development)
|
|
|
* @param [options.outlierCSize=2] Size of the outliers
|
|
|
* @param [options.colors=chart default] The color mapping for the box plot
|
|
|
* @returns {*} The chart object
|
|
|
*/
|
|
|
chart.renderBoxPlot = function (options) {
|
|
|
chart.boxPlots = {};
|
|
|
|
|
|
// Defaults
|
|
|
var defaultOptions = {
|
|
|
show: true,
|
|
|
showBox: true,
|
|
|
showWhiskers: true,
|
|
|
showMedian: true,
|
|
|
showMean: false,
|
|
|
medianCSize: 3.5,
|
|
|
showOutliers: true,
|
|
|
boxWidth: 30,
|
|
|
lineWidth: null,
|
|
|
scatterOutliers: false,
|
|
|
outlierCSize: 2.5,
|
|
|
colors: chart.colorFunct
|
|
|
};
|
|
|
chart.boxPlots.options = shallowCopy(defaultOptions);
|
|
|
for (var option in options) {
|
|
|
chart.boxPlots.options[option] = options[option]
|
|
|
}
|
|
|
var bOpts = chart.boxPlots.options;
|
|
|
|
|
|
//Create box plot objects
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].boxPlot = {};
|
|
|
chart.groupObjs[cName].boxPlot.objs = {};
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Calculates all the outlier points for each group
|
|
|
*/
|
|
|
!function calcAllOutliers() {
|
|
|
|
|
|
/**
|
|
|
* Create lists of the outliers for each content group
|
|
|
* @param cGroup The object to modify
|
|
|
* @return null Modifies the object in place
|
|
|
*/
|
|
|
function calcOutliers(cGroup) {
|
|
|
var cExtremes = [];
|
|
|
var cOutliers = [];
|
|
|
var cOut, idx;
|
|
|
for (idx = 0; idx <= cGroup.values.length; idx++) {
|
|
|
cOut = {value: cGroup.values[idx]};
|
|
|
|
|
|
if (cOut.value < cGroup.metrics.lowerInnerFence) {
|
|
|
if (cOut.value < cGroup.metrics.lowerOuterFence) {
|
|
|
cExtremes.push(cOut);
|
|
|
} else {
|
|
|
cOutliers.push(cOut);
|
|
|
}
|
|
|
} else if (cOut.value > cGroup.metrics.upperInnerFence) {
|
|
|
if (cOut.value > cGroup.metrics.upperOuterFence) {
|
|
|
cExtremes.push(cOut);
|
|
|
} else {
|
|
|
cOutliers.push(cOut);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
cGroup.boxPlot.objs.outliers = cOutliers;
|
|
|
cGroup.boxPlot.objs.extremes = cExtremes;
|
|
|
}
|
|
|
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
calcOutliers(chart.groupObjs[cName]);
|
|
|
}
|
|
|
}();
|
|
|
|
|
|
/**
|
|
|
* Take updated options and redraw the box plot
|
|
|
* @param updateOptions
|
|
|
*/
|
|
|
chart.boxPlots.change = function (updateOptions) {
|
|
|
if (updateOptions) {
|
|
|
for (var key in updateOptions) {
|
|
|
bOpts[key] = updateOptions[key]
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].boxPlot.objs.g.remove()
|
|
|
}
|
|
|
chart.boxPlots.prepareBoxPlot();
|
|
|
chart.boxPlots.update()
|
|
|
};
|
|
|
|
|
|
chart.boxPlots.reset = function () {
|
|
|
chart.boxPlots.change(defaultOptions)
|
|
|
};
|
|
|
chart.boxPlots.show = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = true;
|
|
|
if (opts.reset) {
|
|
|
chart.boxPlots.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: true};
|
|
|
}
|
|
|
chart.boxPlots.change(opts)
|
|
|
|
|
|
};
|
|
|
chart.boxPlots.hide = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = false;
|
|
|
if (opts.reset) {
|
|
|
chart.boxPlots.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: false};
|
|
|
}
|
|
|
chart.boxPlots.change(opts)
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Update the box plot obj values
|
|
|
*/
|
|
|
chart.boxPlots.update = function () {
|
|
|
var cName, cBoxPlot;
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cBoxPlot = chart.groupObjs[cName].boxPlot;
|
|
|
|
|
|
// Get the box width
|
|
|
var objBounds = getObjWidth(bOpts.boxWidth, cName);
|
|
|
var width = (objBounds.right - objBounds.left);
|
|
|
|
|
|
var sMetrics = {}; //temp var for scaled (plottable) metric values
|
|
|
for (var attr in chart.groupObjs[cName].metrics) {
|
|
|
sMetrics[attr] = null;
|
|
|
sMetrics[attr] = chart.yScale(chart.groupObjs[cName].metrics[attr]);
|
|
|
}
|
|
|
|
|
|
// Box
|
|
|
if (cBoxPlot.objs.box) {
|
|
|
cBoxPlot.objs.box
|
|
|
.attr("x", objBounds.left)
|
|
|
.attr('width', width)
|
|
|
.attr("y", sMetrics.quartile3)
|
|
|
.attr("rx", 1)
|
|
|
.attr("ry", 1)
|
|
|
.attr("height", -sMetrics.quartile3 + sMetrics.quartile1)
|
|
|
}
|
|
|
|
|
|
// Lines
|
|
|
var lineBounds = null;
|
|
|
if (bOpts.lineWidth) {
|
|
|
lineBounds = getObjWidth(bOpts.lineWidth, cName)
|
|
|
} else {
|
|
|
lineBounds = objBounds
|
|
|
}
|
|
|
// --Whiskers
|
|
|
if (cBoxPlot.objs.upperWhisker) {
|
|
|
cBoxPlot.objs.upperWhisker.fence
|
|
|
.attr("x1", lineBounds.left)
|
|
|
.attr("x2", lineBounds.right)
|
|
|
.attr('y1', sMetrics.upperInnerFence)
|
|
|
.attr("y2", sMetrics.upperInnerFence);
|
|
|
cBoxPlot.objs.upperWhisker.line
|
|
|
.attr("x1", lineBounds.middle)
|
|
|
.attr("x2", lineBounds.middle)
|
|
|
.attr('y1', sMetrics.quartile3)
|
|
|
.attr("y2", sMetrics.upperInnerFence);
|
|
|
|
|
|
cBoxPlot.objs.lowerWhisker.fence
|
|
|
.attr("x1", lineBounds.left)
|
|
|
.attr("x2", lineBounds.right)
|
|
|
.attr('y1', sMetrics.lowerInnerFence)
|
|
|
.attr("y2", sMetrics.lowerInnerFence);
|
|
|
cBoxPlot.objs.lowerWhisker.line
|
|
|
.attr("x1", lineBounds.middle)
|
|
|
.attr("x2", lineBounds.middle)
|
|
|
.attr('y1', sMetrics.quartile1)
|
|
|
.attr("y2", sMetrics.lowerInnerFence);
|
|
|
}
|
|
|
|
|
|
// --Median
|
|
|
if (cBoxPlot.objs.median) {
|
|
|
cBoxPlot.objs.median.line
|
|
|
.attr("x1", lineBounds.left)
|
|
|
.attr("x2", lineBounds.right)
|
|
|
.attr('y1', sMetrics.median)
|
|
|
.attr("y2", sMetrics.median);
|
|
|
cBoxPlot.objs.median.circle
|
|
|
.attr("cx", lineBounds.middle)
|
|
|
.attr("cy", sMetrics.median)
|
|
|
}
|
|
|
|
|
|
// --Mean
|
|
|
if (cBoxPlot.objs.mean) {
|
|
|
cBoxPlot.objs.mean.line
|
|
|
.attr("x1", lineBounds.left)
|
|
|
.attr("x2", lineBounds.right)
|
|
|
.attr('y1', sMetrics.mean)
|
|
|
.attr("y2", sMetrics.mean);
|
|
|
cBoxPlot.objs.mean.circle
|
|
|
.attr("cx", lineBounds.middle)
|
|
|
.attr("cy", sMetrics.mean);
|
|
|
}
|
|
|
|
|
|
// Outliers
|
|
|
|
|
|
var pt;
|
|
|
if (cBoxPlot.objs.outliers) {
|
|
|
for (pt in cBoxPlot.objs.outliers) {
|
|
|
cBoxPlot.objs.outliers[pt].point
|
|
|
.attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
|
|
|
.attr("cy", chart.yScale(cBoxPlot.objs.outliers[pt].value));
|
|
|
}
|
|
|
}
|
|
|
if (cBoxPlot.objs.extremes) {
|
|
|
for (pt in cBoxPlot.objs.extremes) {
|
|
|
cBoxPlot.objs.extremes[pt].point
|
|
|
.attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
|
|
|
.attr("cy", chart.yScale(cBoxPlot.objs.extremes[pt].value));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Create the svg elements for the box plot
|
|
|
*/
|
|
|
chart.boxPlots.prepareBoxPlot = function () {
|
|
|
var cName, cBoxPlot;
|
|
|
|
|
|
if (bOpts.colors) {
|
|
|
chart.boxPlots.colorFunct = getColorFunct(bOpts.colors);
|
|
|
} else {
|
|
|
chart.boxPlots.colorFunct = chart.colorFunct
|
|
|
}
|
|
|
|
|
|
if (bOpts.show == false) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cBoxPlot = chart.groupObjs[cName].boxPlot;
|
|
|
|
|
|
cBoxPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "box-plot");
|
|
|
|
|
|
// //Plot Box (default show)
|
|
|
// if (bOpts.showBox) {
|
|
|
// cBoxPlot.objs.box = cBoxPlot.objs.g.append("rect")
|
|
|
// .attr("class", "box")
|
|
|
// .style("fill", chart.boxPlots.colorFunct(cName))
|
|
|
// .style("stroke", chart.boxPlots.colorFunct(cName));
|
|
|
// //A stroke is added to the box with the group color, it is
|
|
|
// // hidden by default and can be shown through css with stroke-width
|
|
|
// }
|
|
|
|
|
|
//Plot Median (default show)
|
|
|
if (bOpts.showMedian) {
|
|
|
cBoxPlot.objs.median = {line: null, circle: null};
|
|
|
cBoxPlot.objs.median.line = cBoxPlot.objs.g.append("line")
|
|
|
.attr("class", "median");
|
|
|
cBoxPlot.objs.median.circle = cBoxPlot.objs.g.append("circle")
|
|
|
.attr("class", "median")
|
|
|
.attr('r', bOpts.medianCSize)
|
|
|
.style("fill", chart.boxPlots.colorFunct(cName));
|
|
|
}
|
|
|
|
|
|
// Plot Mean (default no plot)
|
|
|
if (bOpts.showMean) {
|
|
|
cBoxPlot.objs.mean = {line: null, circle: null};
|
|
|
cBoxPlot.objs.mean.line = cBoxPlot.objs.g.append("line")
|
|
|
.attr("class", "mean");
|
|
|
cBoxPlot.objs.mean.circle = cBoxPlot.objs.g.append("circle")
|
|
|
.attr("class", "mean")
|
|
|
.attr('r', bOpts.medianCSize)
|
|
|
.style("fill", chart.boxPlots.colorFunct(cName));
|
|
|
}
|
|
|
|
|
|
// // Plot Whiskers (default show)
|
|
|
// if (bOpts.showWhiskers) {
|
|
|
// cBoxPlot.objs.upperWhisker = {fence: null, line: null};
|
|
|
// cBoxPlot.objs.lowerWhisker = {fence: null, line: null};
|
|
|
// cBoxPlot.objs.upperWhisker.fence = cBoxPlot.objs.g.append("line")
|
|
|
// .attr("class", "upper whisker")
|
|
|
// .style("stroke", chart.boxPlots.colorFunct(cName));
|
|
|
// cBoxPlot.objs.upperWhisker.line = cBoxPlot.objs.g.append("line")
|
|
|
// .attr("class", "upper whisker")
|
|
|
// .style("stroke", chart.boxPlots.colorFunct(cName));
|
|
|
|
|
|
// cBoxPlot.objs.lowerWhisker.fence = cBoxPlot.objs.g.append("line")
|
|
|
// .attr("class", "lower whisker")
|
|
|
// .style("stroke", chart.boxPlots.colorFunct(cName));
|
|
|
// cBoxPlot.objs.lowerWhisker.line = cBoxPlot.objs.g.append("line")
|
|
|
// .attr("class", "lower whisker")
|
|
|
// .style("stroke", chart.boxPlots.colorFunct(cName));
|
|
|
// }
|
|
|
|
|
|
// Plot outliers (default show)
|
|
|
if (bOpts.showOutliers) {
|
|
|
if (!cBoxPlot.objs.outliers) calcAllOutliers();
|
|
|
var pt;
|
|
|
if (cBoxPlot.objs.outliers.length) {
|
|
|
var outDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot outliers");
|
|
|
for (pt in cBoxPlot.objs.outliers) {
|
|
|
cBoxPlot.objs.outliers[pt].point = outDiv.append("circle")
|
|
|
.attr("class", "outlier")
|
|
|
.attr('r', bOpts.outlierCSize)
|
|
|
.style("fill", chart.boxPlots.colorFunct(cName))
|
|
|
.style("opacity", 0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (cBoxPlot.objs.extremes.length) {
|
|
|
var extDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot extremes");
|
|
|
for (pt in cBoxPlot.objs.extremes) {
|
|
|
cBoxPlot.objs.extremes[pt].point = extDiv.append("circle")
|
|
|
.attr("class", "extreme")
|
|
|
.attr('r', bOpts.outlierCSize)
|
|
|
.style("opacity", 0);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
};
|
|
|
chart.boxPlots.prepareBoxPlot();
|
|
|
|
|
|
d3.select(window).on('resize.' + chart.selector + '.boxPlot', chart.boxPlots.update);
|
|
|
chart.boxPlots.update();
|
|
|
return chart;
|
|
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Render a notched box on the current chart
|
|
|
* @param options
|
|
|
* @param [options.show=true] Toggle the whole plot on and off
|
|
|
* @param [options.showNotchBox=true] Show the notch box
|
|
|
* @param [options.showLines=false] Show lines at the confidence intervals
|
|
|
* @param [options.boxWidth=35] The width of the widest part of the box
|
|
|
* @param [options.medianWidth=20] The width of the narrowist part of the box
|
|
|
* @param [options.lineWidth=50] The width of the confidence interval lines
|
|
|
* @param [options.notchStyle=null] null=traditional style, 'box' cuts out the whole notch in right angles
|
|
|
* @param [options.colors=chart default] The color mapping for the notch boxes
|
|
|
* @returns {*} The chart object
|
|
|
*/
|
|
|
chart.renderNotchBoxes = function (options) {
|
|
|
chart.notchBoxes = {};
|
|
|
|
|
|
//Defaults
|
|
|
var defaultOptions = {
|
|
|
show: true,
|
|
|
showNotchBox: true,
|
|
|
showLines: false,
|
|
|
boxWidth: 35,
|
|
|
medianWidth: 20,
|
|
|
lineWidth: 75,
|
|
|
notchStyle: null,
|
|
|
colors: null
|
|
|
};
|
|
|
chart.notchBoxes.options = shallowCopy(defaultOptions);
|
|
|
for (var option in options) {
|
|
|
chart.notchBoxes.options[option] = options[option]
|
|
|
}
|
|
|
var nOpts = chart.notchBoxes.options;
|
|
|
|
|
|
//Create notch objects
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].notchBox = {};
|
|
|
chart.groupObjs[cName].notchBox.objs = {};
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Makes the svg path string for a notched box
|
|
|
* @param cNotch Current notch box object
|
|
|
* @param notchBounds objBound object
|
|
|
* @returns {string} A string in the proper format for a svg polygon
|
|
|
*/
|
|
|
function makeNotchBox(cNotch, notchBounds) {
|
|
|
var scaledValues = [];
|
|
|
if (nOpts.notchStyle == 'box') {
|
|
|
scaledValues = [
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
|
|
|
[notchBounds.medianLeft, chart.yScale(cNotch.metrics.lowerNotch)],
|
|
|
[notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
|
|
|
[notchBounds.medianLeft, chart.yScale(cNotch.metrics.upperNotch)],
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
|
|
|
[notchBounds.medianRight, chart.yScale(cNotch.metrics.upperNotch)],
|
|
|
[notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
|
|
|
[notchBounds.medianRight, chart.yScale(cNotch.metrics.lowerNotch)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
|
|
|
];
|
|
|
} else {
|
|
|
scaledValues = [
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
|
|
|
[notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
|
|
|
[notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
|
|
|
[notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
|
|
|
[notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
|
|
|
];
|
|
|
}
|
|
|
return scaledValues.map(function (d) {
|
|
|
return [d[0], d[1]].join(",");
|
|
|
}).join(" ");
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Calculate the confidence intervals
|
|
|
*/
|
|
|
!function calcNotches() {
|
|
|
var cNotch, modifier;
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
cNotch = chart.groupObjs[cName];
|
|
|
modifier = (1.57 * (cNotch.metrics.iqr / Math.sqrt(cNotch.values.length)));
|
|
|
cNotch.metrics.upperNotch = cNotch.metrics.median + modifier;
|
|
|
cNotch.metrics.lowerNotch = cNotch.metrics.median - modifier;
|
|
|
}
|
|
|
}();
|
|
|
|
|
|
/**
|
|
|
* Take a new set of options and redraw the notch boxes
|
|
|
* @param updateOptions
|
|
|
*/
|
|
|
chart.notchBoxes.change = function (updateOptions) {
|
|
|
if (updateOptions) {
|
|
|
for (var key in updateOptions) {
|
|
|
nOpts[key] = updateOptions[key]
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].notchBox.objs.g.remove()
|
|
|
}
|
|
|
chart.notchBoxes.prepareNotchBoxes();
|
|
|
chart.notchBoxes.update();
|
|
|
};
|
|
|
|
|
|
chart.notchBoxes.reset = function () {
|
|
|
chart.notchBoxes.change(defaultOptions)
|
|
|
};
|
|
|
chart.notchBoxes.show = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = true;
|
|
|
if (opts.reset) {
|
|
|
chart.notchBoxes.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: true};
|
|
|
}
|
|
|
chart.notchBoxes.change(opts)
|
|
|
};
|
|
|
chart.notchBoxes.hide = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = false;
|
|
|
if (opts.reset) {
|
|
|
chart.notchBoxes.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: false};
|
|
|
}
|
|
|
chart.notchBoxes.change(opts)
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Update the notch box obj values
|
|
|
*/
|
|
|
chart.notchBoxes.update = function () {
|
|
|
var cName, cGroup;
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cGroup = chart.groupObjs[cName];
|
|
|
|
|
|
// Get the box size
|
|
|
var boxBounds = getObjWidth(nOpts.boxWidth, cName);
|
|
|
var medianBounds = getObjWidth(nOpts.medianWidth, cName);
|
|
|
|
|
|
var notchBounds = {
|
|
|
boxLeft: boxBounds.left,
|
|
|
boxRight: boxBounds.right,
|
|
|
middle: boxBounds.middle,
|
|
|
medianLeft: medianBounds.left,
|
|
|
medianRight: medianBounds.right
|
|
|
};
|
|
|
|
|
|
// Notch Box
|
|
|
if (cGroup.notchBox.objs.notch) {
|
|
|
cGroup.notchBox.objs.notch
|
|
|
.attr("points", makeNotchBox(cGroup, notchBounds));
|
|
|
}
|
|
|
if (cGroup.notchBox.objs.upperLine) {
|
|
|
var lineBounds = null;
|
|
|
if (nOpts.lineWidth) {
|
|
|
lineBounds = getObjWidth(nOpts.lineWidth, cName)
|
|
|
} else {
|
|
|
lineBounds = objBounds
|
|
|
}
|
|
|
|
|
|
var confidenceLines = {
|
|
|
upper: chart.yScale(cGroup.metrics.upperNotch),
|
|
|
lower: chart.yScale(cGroup.metrics.lowerNotch)
|
|
|
};
|
|
|
cGroup.notchBox.objs.upperLine
|
|
|
.attr("x1", lineBounds.left)
|
|
|
.attr("x2", lineBounds.right)
|
|
|
.attr('y1', confidenceLines.upper)
|
|
|
.attr("y2", confidenceLines.upper);
|
|
|
cGroup.notchBox.objs.lowerLine
|
|
|
.attr("x1", lineBounds.left)
|
|
|
.attr("x2", lineBounds.right)
|
|
|
.attr('y1', confidenceLines.lower)
|
|
|
.attr("y2", confidenceLines.lower);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Create the svg elements for the notch boxes
|
|
|
*/
|
|
|
chart.notchBoxes.prepareNotchBoxes = function () {
|
|
|
var cName, cNotch;
|
|
|
|
|
|
if (nOpts && nOpts.colors) {
|
|
|
chart.notchBoxes.colorFunct = getColorFunct(nOpts.colors);
|
|
|
} else {
|
|
|
chart.notchBoxes.colorFunct = chart.colorFunct
|
|
|
}
|
|
|
|
|
|
if (nOpts.show == false) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cNotch = chart.groupObjs[cName].notchBox;
|
|
|
|
|
|
cNotch.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "notch-plot");
|
|
|
// Plot Box (default show)
|
|
|
if (nOpts.showNotchBox) {
|
|
|
cNotch.objs.notch = cNotch.objs.g.append("polygon")
|
|
|
.attr("class", "notch")
|
|
|
.style("fill", chart.notchBoxes.colorFunct(cName))
|
|
|
.style("stroke", chart.notchBoxes.colorFunct(cName));
|
|
|
//A stroke is added to the notch with the group color, it is
|
|
|
// hidden by default and can be shown through css with stroke-width
|
|
|
}
|
|
|
|
|
|
//Plot Confidence Lines (default hide)
|
|
|
if (nOpts.showLines) {
|
|
|
cNotch.objs.upperLine = cNotch.objs.g.append("line")
|
|
|
.attr("class", "upper confidence line")
|
|
|
.style("stroke", chart.notchBoxes.colorFunct(cName));
|
|
|
|
|
|
cNotch.objs.lowerLine = cNotch.objs.g.append("line")
|
|
|
.attr("class", "lower confidence line")
|
|
|
.style("stroke", chart.notchBoxes.colorFunct(cName));
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
chart.notchBoxes.prepareNotchBoxes();
|
|
|
|
|
|
d3.select(window).on('resize.' + chart.selector + '.notchBox', chart.notchBoxes.update);
|
|
|
chart.notchBoxes.update();
|
|
|
return chart;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Render a raw data in various forms
|
|
|
* @param options
|
|
|
* @param [options.show=true] Toggle the whole plot on and off
|
|
|
* @param [options.showPlot=false] True or false, show points
|
|
|
* @param [options.plotType='none'] Options: no scatter = (false or 'none'); scatter points= (true or [amount=% of width (default=10)]); beeswarm points = ('beeswarm')
|
|
|
* @param [options.pointSize=6] Diameter of the circle in pizels (not the radius)
|
|
|
* @param [options.showLines=['median']] Can equal any of the metrics lines
|
|
|
* @param [options.showbeanLines=false] Options: no lines = false
|
|
|
* @param [options.beanWidth=20] % width
|
|
|
* @param [options.colors=chart default]
|
|
|
* @returns {*} The chart object
|
|
|
*
|
|
|
*/
|
|
|
chart.renderDataPlots = function (options) {
|
|
|
chart.dataPlots = {};
|
|
|
|
|
|
|
|
|
//Defaults
|
|
|
var defaultOptions = {
|
|
|
show: true,
|
|
|
showPlot: false,
|
|
|
plotType: 'none',
|
|
|
pointSize: 6,
|
|
|
showLines: false,//['median'],
|
|
|
showBeanLines: false,
|
|
|
beanWidth: 20,
|
|
|
colors: null
|
|
|
};
|
|
|
|
|
|
chart.dataPlots.options = shallowCopy(defaultOptions);
|
|
|
for (var option in options) {
|
|
|
chart.dataPlots.options[option] = options[option]
|
|
|
}
|
|
|
var dOpts = chart.dataPlots.options;
|
|
|
|
|
|
//Create notch objects
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].dataPlots = {};
|
|
|
chart.groupObjs[cName].dataPlots.objs = {};
|
|
|
}
|
|
|
// The lines don't fit into a group bucket so they live under the dataPlot object
|
|
|
chart.dataPlots.objs = {};
|
|
|
|
|
|
/**
|
|
|
* Take updated options and redraw the data plots
|
|
|
* @param updateOptions
|
|
|
*/
|
|
|
chart.dataPlots.change = function (updateOptions) {
|
|
|
if (updateOptions) {
|
|
|
for (var key in updateOptions) {
|
|
|
dOpts[key] = updateOptions[key]
|
|
|
}
|
|
|
}
|
|
|
|
|
|
chart.dataPlots.objs.g.remove();
|
|
|
for (var cName in chart.groupObjs) {
|
|
|
chart.groupObjs[cName].dataPlots.objs.g.remove()
|
|
|
}
|
|
|
chart.dataPlots.preparePlots();
|
|
|
chart.dataPlots.update()
|
|
|
};
|
|
|
|
|
|
chart.dataPlots.reset = function () {
|
|
|
chart.dataPlots.change(defaultOptions)
|
|
|
};
|
|
|
chart.dataPlots.show = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = true;
|
|
|
if (opts.reset) {
|
|
|
chart.dataPlots.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: true};
|
|
|
}
|
|
|
chart.dataPlots.change(opts)
|
|
|
};
|
|
|
chart.dataPlots.hide = function (opts) {
|
|
|
if (opts !== undefined) {
|
|
|
opts.show = false;
|
|
|
if (opts.reset) {
|
|
|
chart.dataPlots.reset()
|
|
|
}
|
|
|
} else {
|
|
|
opts = {show: false};
|
|
|
}
|
|
|
chart.dataPlots.change(opts)
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Update the data plot obj values
|
|
|
*/
|
|
|
chart.dataPlots.update = function () {
|
|
|
var cName, cGroup, cPlot;
|
|
|
|
|
|
// Metrics lines
|
|
|
if (chart.dataPlots.objs.g) {
|
|
|
var halfBand = chart.xScale.rangeBand() / 2; // find the middle of each band
|
|
|
for (var cMetric in chart.dataPlots.objs.lines) {
|
|
|
chart.dataPlots.objs.lines[cMetric].line
|
|
|
.x(function (d) {
|
|
|
return chart.xScale(d.x) + halfBand
|
|
|
});
|
|
|
chart.dataPlots.objs.lines[cMetric].g
|
|
|
.datum(chart.dataPlots.objs.lines[cMetric].values)
|
|
|
.attr('d', chart.dataPlots.objs.lines[cMetric].line);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var loopValue = 0
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
cGroup = chart.groupObjs[cName];
|
|
|
cPlot = cGroup.dataPlots;
|
|
|
|
|
|
if (cPlot.objs.points) {
|
|
|
if (dOpts.plotType == 'beeswarm') {
|
|
|
var swarmBounds = getObjWidth(100, cName);
|
|
|
var yPtScale = chart.yScale.copy()
|
|
|
.range([Math.floor(chart.yScale.range()[0] / dOpts.pointSize), 0])
|
|
|
.interpolate(d3.interpolateRound)
|
|
|
.domain(chart.yScale.domain());
|
|
|
var maxWidth = Math.floor(chart.xScale.rangeBand() / dOpts.pointSize);
|
|
|
var ptsObj = {};
|
|
|
var cYBucket = null;
|
|
|
// Bucket points
|
|
|
for (var pt = 0; pt < cGroup.values.length; pt++) {
|
|
|
cYBucket = yPtScale(cGroup.values[pt]);
|
|
|
if (ptsObj.hasOwnProperty(cYBucket) !== true) {
|
|
|
ptsObj[cYBucket] = [];
|
|
|
}
|
|
|
ptsObj[cYBucket].push(cPlot.objs.points.pts[pt]
|
|
|
.attr("cx", swarmBounds.middle)
|
|
|
.attr("cy", yPtScale(cGroup.values[pt]) * dOpts.pointSize));
|
|
|
}
|
|
|
// Plot buckets
|
|
|
var rightMax = Math.min(swarmBounds.right - dOpts.pointSize);
|
|
|
for (var row in ptsObj) {
|
|
|
var leftMin = swarmBounds.left + (Math.max((maxWidth - ptsObj[row].length) / 2, 0) * dOpts.pointSize);
|
|
|
var col = 0;
|
|
|
for (pt in ptsObj[row]) {
|
|
|
ptsObj[row][pt].attr("cx", Math.min(leftMin + col * dOpts.pointSize, rightMax) + dOpts.pointSize / 2);
|
|
|
col++
|
|
|
}
|
|
|
}
|
|
|
} else { // For scatter points and points with no scatter
|
|
|
var plotBounds = null,
|
|
|
scatterWidth = 0,
|
|
|
width = 0;
|
|
|
if (dOpts.plotType == 'scatter' || typeof dOpts.plotType == 'number') {
|
|
|
//Default scatter percentage is 20% of box width
|
|
|
scatterWidth = typeof dOpts.plotType == 'number' ? dOpts.plotType : 20;
|
|
|
}
|
|
|
|
|
|
plotBounds = getObjWidth(scatterWidth, cName);
|
|
|
width = plotBounds.right - plotBounds.left;
|
|
|
|
|
|
for (var pt = 0; pt < cGroup.values.length; pt++) {
|
|
|
cPlot.objs.points.pts[pt]
|
|
|
.attr("cx", plotBounds.middle + addJitter(true, width))
|
|
|
.attr("cy", chart.yScale(cGroup.values[pt]));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (cPlot.objs.bean) {
|
|
|
var beanBounds = getObjWidth(dOpts.beanWidth, cName);
|
|
|
for (var pt = 0; pt < cGroup.values.length; pt++) {
|
|
|
cPlot.objs.bean.lines[pt]
|
|
|
.attr("x1", beanBounds.left)
|
|
|
.attr("x2", beanBounds.right)
|
|
|
.attr('y1', function () { return chart.yScale(chart.data[pt+loopValue].value) })
|
|
|
.attr("y2", function () { return chart.yScale(chart.data[pt+loopValue].value) });
|
|
|
}
|
|
|
}
|
|
|
if (globalActive == 1) {
|
|
|
loopValue = loopValue + IDs.length
|
|
|
} else {
|
|
|
loopValue = loopValue + mergedStoreEnsembleLoc.length
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Create the svg elements for the data plots
|
|
|
*/
|
|
|
chart.dataPlots.preparePlots = function () {
|
|
|
var cName, cPlot;
|
|
|
|
|
|
if (dOpts && dOpts.colors) {
|
|
|
chart.dataPlots.colorFunct = getColorFunct(dOpts.colors);
|
|
|
} else {
|
|
|
chart.dataPlots.colorFunct = chart.colorFunct
|
|
|
}
|
|
|
|
|
|
if (dOpts.show == false) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// Metrics lines
|
|
|
chart.dataPlots.objs.g = chart.objs.g.append("g").attr("class", "metrics-lines");
|
|
|
if (dOpts.showLines && dOpts.showLines.length > 0) {
|
|
|
chart.dataPlots.objs.lines = {};
|
|
|
var cMetric;
|
|
|
for (var line in dOpts.showLines) {
|
|
|
if (line == 0) {
|
|
|
cMetric = dOpts.showLines[line];
|
|
|
chart.dataPlots.objs.lines[cMetric] = {};
|
|
|
chart.dataPlots.objs.lines[cMetric].values = [];
|
|
|
for (var cGroup in chart.groupObjs) {
|
|
|
chart.dataPlots.objs.lines[cMetric].values.push({
|
|
|
x: cGroup,
|
|
|
y: chart.groupObjs[cGroup].metrics[cMetric]
|
|
|
})
|
|
|
}
|
|
|
} else {
|
|
|
var loop = 0;
|
|
|
cMetric = dOpts.showLines[line];
|
|
|
chart.dataPlots.objs.lines[cMetric] = {};
|
|
|
chart.dataPlots.objs.lines[cMetric].values = [];
|
|
|
for (var cGroup in chart.groupObjs) {
|
|
|
chart.dataPlots.objs.lines[cMetric].values.push({
|
|
|
x: cGroup,
|
|
|
y: meanGlobalSel[loop]
|
|
|
})
|
|
|
loop++
|
|
|
}
|
|
|
}
|
|
|
chart.dataPlots.objs.lines[cMetric].line = d3.svg.line()
|
|
|
.interpolate("cardinal")
|
|
|
.y(function (d) {
|
|
|
return chart.yScale(d.y)
|
|
|
});
|
|
|
|
|
|
chart.dataPlots.objs.lines[cMetric].g = chart.dataPlots.objs.g.append("path")
|
|
|
.attr("class", "line " + cMetric)
|
|
|
.attr("data-metric", cMetric)
|
|
|
.style("fill", 'none')
|
|
|
.style("stroke", chart.colorFunct(cMetric));
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// mention active models number
|
|
|
var loopValue = 0
|
|
|
|
|
|
for (cName in chart.groupObjs) {
|
|
|
|
|
|
cPlot = chart.groupObjs[cName].dataPlots;
|
|
|
cPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "data-plot");
|
|
|
|
|
|
// Points Plot
|
|
|
if (dOpts.showPlot) {
|
|
|
cPlot.objs.points = {g: null, pts: []};
|
|
|
cPlot.objs.points.g = cPlot.objs.g.append("g").attr("class", "points-plot");
|
|
|
for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
|
|
|
cPlot.objs.points.pts.push(cPlot.objs.points.g.append("circle")
|
|
|
.attr("class", "point")
|
|
|
.attr('r', dOpts.pointSize / 2)// Options is diameter, r takes radius so divide by 2
|
|
|
.style("fill", chart.dataPlots.colorFunct(cName)));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// Bean lines
|
|
|
if (dOpts.showBeanLines) {
|
|
|
cPlot.objs.bean = {g: null, lines: []};
|
|
|
cPlot.objs.bean.g = cPlot.objs.g.append("g").attr("class", "bean-plot");
|
|
|
for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
|
|
|
cPlot.objs.bean.lines.push(cPlot.objs.bean.g.append("line")
|
|
|
.attr("class", "bean-line")
|
|
|
.style("stroke-width", '1')
|
|
|
.style("stroke", function () { return chart.data[pt+loopValue].category; }));
|
|
|
}
|
|
|
}
|
|
|
if (globalActive == 1) {
|
|
|
loopValue = loopValue + IDs.length
|
|
|
} else {
|
|
|
loopValue = loopValue + mergedStoreEnsembleLoc.length
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
chart.dataPlots.preparePlots();
|
|
|
|
|
|
d3.select(window).on('resize.' + chart.selector + '.dataPlot', chart.dataPlots.update);
|
|
|
chart.dataPlots.update();
|
|
|
return chart;
|
|
|
};
|
|
|
|
|
|
return chart;
|
|
|
}
|
|
|
|
|
|
|
|
|
},
|
|
|
legendColViol () {
|
|
|
//==================================================
|
|
|
var viewerWidth = this.WH[0]*2.5
|
|
|
var viewerHeight = this.WH[1]*0.06
|
|
|
var viewerPosTop = viewerHeight * 0.2;
|
|
|
var cellSizeHeat = 10
|
|
|
var legendElementWidth = cellSizeHeat * 3;
|
|
|
|
|
|
// http://bl.ocks.org/mbostock/5577023
|
|
|
var colors = ['#c0c0c0','#fff','#fff','#fff','#000'];
|
|
|
|
|
|
var svgLeg = d3.select("#legendViolin");
|
|
|
svgLeg.selectAll("*").remove();
|
|
|
|
|
|
var svgLeg = d3.select("#legendViolin").append("svg")
|
|
|
.attr("width", viewerWidth/2)
|
|
|
.attr("height", viewerHeight*1)
|
|
|
.style("margin-top", "42px")
|
|
|
|
|
|
var legend = svgLeg.append('g')
|
|
|
.attr("class", "legend")
|
|
|
.attr("transform", "translate(0,0)")
|
|
|
.selectAll(".legendElement")
|
|
|
.data([0, 1, 3, 4, 5])
|
|
|
.enter().append("g")
|
|
|
.attr("class", "legendElement");
|
|
|
|
|
|
legend.append("svg:rect")
|
|
|
.attr("x", function(d, i) {
|
|
|
return (legendElementWidth * i) + 35;
|
|
|
})
|
|
|
.attr("y", viewerPosTop)
|
|
|
.attr("class", "cellLegend bordered")
|
|
|
.attr("width", legendElementWidth)
|
|
|
.attr("height", cellSizeHeat / 2)
|
|
|
.style("fill", function(d, i) {
|
|
|
return colors[i];
|
|
|
});
|
|
|
|
|
|
legend.append("text")
|
|
|
.attr("class", "mono legendElement")
|
|
|
.text(function(d, i) {
|
|
|
if (i == 0) {
|
|
|
return "Mean All";
|
|
|
} else if (i== 4) {
|
|
|
return "Mean Sel.";
|
|
|
} else {
|
|
|
return "";
|
|
|
}
|
|
|
|
|
|
})
|
|
|
.attr("x", function(d, i) {
|
|
|
return (legendElementWidth * i) + 12;
|
|
|
return (legendElementWidth * i) + 12;
|
|
|
})
|
|
|
.attr("y", (viewerPosTop + cellSizeHeat) + 10);
|
|
|
},
|
|
|
},
|
|
|
mounted () {
|
|
|
EventBus.$on('LegendPredict', this.legendColViol)
|
|
|
|
|
|
EventBus.$on('SendStoredEnsemble', data => { this.storedEnsemble = data})
|
|
|
EventBus.$on('activeNow', data => { this.activeCurr = data })
|
|
|
|
|
|
EventBus.$on('SendSelectedPointsUpdateIndicator', data => { this.selectedSimple = data })
|
|
|
EventBus.$on('SendSelectedPointsUpdateIndicatorCM', data => { this.selectedEnsem = data })
|
|
|
EventBus.$on('SendSelectedPointsUpdateIndicator', this.ViolinFun)
|
|
|
EventBus.$on('SendSelectedPointsUpdateIndicatorCM', this.ViolinFun)
|
|
|
|
|
|
EventBus.$on('factorsChanged', data => { this.factorsValid = data })
|
|
|
|
|
|
EventBus.$on('callValidationData', data => { this.ResultsValid = data })
|
|
|
EventBus.$on('callValidation', this.ViolinFun)
|
|
|
|
|
|
EventBus.$on('Responsive', data => {
|
|
|
this.WH = data})
|
|
|
EventBus.$on('ResponsiveandChange', data => {
|
|
|
this.WH = data})
|
|
|
|
|
|
// reset the views
|
|
|
EventBus.$on('resetViews', this.reset)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<style>
|
|
|
/*Primary Chart*/
|
|
|
|
|
|
.chart-wrapper .inner-wrapper {
|
|
|
position: relative;
|
|
|
padding-bottom: 50%; /*Overwritten by the JS*/
|
|
|
width: 100%;
|
|
|
}
|
|
|
.chart-wrapper .outer-box {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
bottom: 0;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
}
|
|
|
|
|
|
.chart-wrapper text {
|
|
|
font-family: sans-serif;
|
|
|
font-size: 18.5px;
|
|
|
}
|
|
|
|
|
|
.chart-wrapper .axis path,
|
|
|
.chart-wrapper .axis line {
|
|
|
fill: none;
|
|
|
stroke: #888;
|
|
|
stroke-width: 2px;
|
|
|
shape-rendering: crispEdges;
|
|
|
}
|
|
|
|
|
|
.chart-wrapper .y.axis .tick line {
|
|
|
stroke: lightgrey;
|
|
|
opacity: 0.6;
|
|
|
stroke-dasharray: 2,1;
|
|
|
stroke-width: 1;
|
|
|
shape-rendering: crispEdges;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chart-wrapper .x.axis .domain {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
.axis text {
|
|
|
font-size: 16px !important;
|
|
|
}
|
|
|
|
|
|
.chart-wrapper div.tooltip {
|
|
|
position: absolute;
|
|
|
text-align: left;
|
|
|
padding: 3px;
|
|
|
font: 12px sans-serif;
|
|
|
background: lightcyan;
|
|
|
border: 0px;
|
|
|
border-radius: 1px;
|
|
|
pointer-events: none;
|
|
|
opacity: 0.7;
|
|
|
}
|
|
|
|
|
|
/*Violin Plot*/
|
|
|
|
|
|
.chart-wrapper .violin-plot .area {
|
|
|
shape-rendering: geometricPrecision;
|
|
|
opacity: 0.4;
|
|
|
}
|
|
|
|
|
|
.chart-wrapper .violin-plot .line {
|
|
|
fill: none;
|
|
|
stroke-width: 2px;
|
|
|
shape-rendering: geometricPrecision;
|
|
|
}
|
|
|
|
|
|
/*Notch Plot*/
|
|
|
.chart-wrapper .notch-plot .notch {
|
|
|
fill-opacity: 0.4;
|
|
|
stroke-width: 2;
|
|
|
}
|
|
|
|
|
|
/* Point Plots*/
|
|
|
.chart-wrapper .points-plot .point {
|
|
|
stroke: black;
|
|
|
stroke-width: 1px;
|
|
|
}
|
|
|
|
|
|
.chart-wrapper .metrics-lines {
|
|
|
stroke-width: 3px; /* metrics lines! */
|
|
|
}
|
|
|
|
|
|
/* Non-Chart Styles for demo*/
|
|
|
.chart-options {
|
|
|
min-width: 200px;
|
|
|
font-size: 18.5px;
|
|
|
font-family: sans-serif;
|
|
|
}
|
|
|
.chart-options button {
|
|
|
margin: 3px;
|
|
|
padding: 3px;
|
|
|
font-size: 18.5px;
|
|
|
}
|
|
|
.chart-options p {
|
|
|
display: inline;
|
|
|
}
|
|
|
|
|
|
.bean-line {
|
|
|
mix-blend-mode: "soft-light" !important;
|
|
|
isolation: isolate !important;
|
|
|
}
|
|
|
|
|
|
@media (max-width:500px){
|
|
|
.chart-options p {display: block;}
|
|
|
}
|
|
|
</style> |