chartjs-plugin-stanford-diagram
Version:
Stanford Diagram plugin for Chart.js
1,079 lines (906 loc) • 31 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.stanfordDiagramPlugin = {}));
}(this, (function (exports) { 'use strict';
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
/* eslint-disable */
/**
* Assumes h, s, and l are contained in the set [0, 1]
* thanks to https://codepen.io/anon/pen/ZPqQdM
*
* @param a
* @param b
* @param o
* @returns {String}
*/
function interpolateHSL(a, b, o) {
var len = a.length;
var hsl = Array(len);
if (o > 1) {
o = 1;
} else if (o < 0) {
o = 0;
}
for (var i = 0; i < len; i++) {
var a1 = a[i];
hsl[i] = a1 + (b[i] - a1) * o;
}
return hslToRgb(hsl[0], hsl[1], hsl[2]);
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {String} The RGB representation
*/
function hslToRgb(h, s, l) {
var r;
var g;
var b;
if (s === 0) {
// achromatic
r = l;
g = l;
b = l;
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return "rgb(".concat(Math.round(r * 255), ", ").concat(Math.round(g * 255), ", ").concat(Math.round(b * 255), ")");
} // sequence
function range(start, stop, step) {
start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
var i = -1,
n = Math.max(0, Math.ceil((stop - start) / step)) | 0,
range = new Array(n);
while (++i < n) {
range[i] = start + i * step;
}
return range;
} // sequential
function scaleSequential() {
var scale = defaultFunction(transformerLinear()(defaultFunction));
return initInterpolator.apply(scale, arguments);
}
function sequentialLog() {
var scale = loggish(transformerLog()).domain([1, 10]);
scale.copy = function () {
return copy$1(scale, sequentialLog()).base(scale.base());
};
return initInterpolator.apply(scale, arguments);
}
function defaultFunction(x) {
return x;
}
function transformLog(x) {
return Math.log(x);
}
function transformExp(x) {
return Math.exp(x);
}
function transformLogn(x) {
return -Math.log(-x);
}
function transformExpn(x) {
return -Math.exp(-x);
}
function reflect(f) {
return function (x) {
return -f(-x);
};
}
function pow10(x) {
return isFinite(x) ? +('1e' + x) : x < 0 ? 0 : x;
}
function powp(base) {
return base === 10 ? pow10 : base === Math.E ? Math.exp : function (x) {
return Math.pow(base, x);
};
}
function logp(base) {
return base === Math.E ? Math.log : base === 10 && Math.log10 || base === 2 && Math.log2 || (base = Math.log(base), function (x) {
return Math.log(x) / base;
});
}
function initInterpolator(domain, interpolator) {
switch (arguments.length) {
case 0:
break;
case 1:
this.interpolator(domain);
break;
default:
this.interpolator(interpolator).domain(domain);
break;
}
return this;
}
function transformerLinear() {
var x0 = 0,
x1 = 1,
t0,
t1,
k10,
transform,
interpolator = defaultFunction,
unknown;
function scale(x) {
return isNaN(x = +x) ? unknown : interpolator(k10 === 0 ? 0.5 : (x = (transform(x) - t0) * k10, x));
}
scale.domain = function (_) {
return arguments.length ? (t0 = transform(x0 = +_[0]), t1 = transform(x1 = +_[1]), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1];
};
scale.interpolator = function (_) {
return arguments.length ? (interpolator = _, scale) : interpolator;
};
return function (t) {
transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0);
return scale;
};
}
function identity(x) {
return x;
}
function transformerLog() {
var x0 = 0,
x1 = 1,
t0,
t1,
k10,
transform,
interpolator = identity,
clamp = false,
unknown;
function scale(x) {
return isNaN(x = +x) ? unknown : interpolator(k10 === 0 ? 0.5 : (x = (transform(x) - t0) * k10, clamp ? Math.max(0, Math.min(1, x)) : x));
}
scale.domain = function (_) {
var _ref, _ref2;
return arguments.length ? ((_ref = _, _ref2 = _slicedToArray(_ref, 2), x0 = _ref2[0], x1 = _ref2[1], _ref), t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1];
};
scale.clamp = function (_) {
return arguments.length ? (clamp = !!_, scale) : clamp;
};
scale.interpolator = function (_) {
return arguments.length ? (interpolator = _, scale) : interpolator;
};
scale.unknown = function (_) {
return arguments.length ? (unknown = _, scale) : unknown;
};
return function (t) {
transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0);
return scale;
};
}
function ticks(start, stop, count) {
var reverse,
i = -1,
n,
ticks,
step;
stop = +stop, start = +start, count = +count;
if (start === stop && count > 0) return [start];
if (reverse = stop < start) n = start, start = stop, stop = n;
if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step)) return [];
if (step > 0) {
start = Math.ceil(start / step);
stop = Math.floor(stop / step);
ticks = new Array(n = Math.ceil(stop - start + 1));
while (++i < n) {
ticks[i] = (start + i) * step;
}
} else {
start = Math.floor(start * step);
stop = Math.ceil(stop * step);
ticks = new Array(n = Math.ceil(start - stop + 1));
while (++i < n) {
ticks[i] = (start - i) / step;
}
}
if (reverse) ticks.reverse();
return ticks;
}
function tickIncrement(start, stop, count) {
var step = (stop - start) / Math.max(0, count),
power = Math.floor(Math.log(step) / Math.LN10),
error = step / Math.pow(10, power);
return power >= 0 ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) * Math.pow(10, power) : -Math.pow(10, -power) / (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1);
}
function loggish(transform) {
var scale = transform(transformLog, transformExp),
domain = scale.domain,
base = 10,
logs,
pows;
function rescale() {
logs = logp(base), pows = powp(base);
if (domain()[0] < 0) {
logs = reflect(logs), pows = reflect(pows);
transform(transformLogn, transformExpn);
} else {
transform(transformLog, transformExp);
}
return scale;
}
scale.base = function (_) {
return arguments.length ? (base = +_, rescale()) : base;
};
scale.domain = function (_) {
return arguments.length ? (domain(_), rescale()) : domain();
};
scale.ticks = function (count) {
var d = domain(),
u = d[0],
v = d[d.length - 1],
r;
if (r = v < u) i = u, u = v, v = i;
var i = logs(u),
j = logs(v),
p,
k,
t,
n = count == null ? 10 : +count,
z = [];
if (!(base % 1) && j - i < n) {
i = Math.round(i) - 1, j = Math.round(j) + 1;
if (u > 0) for (; i < j; ++i) {
for (k = 1, p = pows(i); k < base; ++k) {
t = p * k;
if (t < u) continue;
if (t > v) break;
z.push(t);
}
} else for (; i < j; ++i) {
for (k = base - 1, p = pows(i); k >= 1; --k) {
t = p * k;
if (t < u) continue;
if (t > v) break;
z.push(t);
}
}
} else {
z = ticks(i, j, Math.min(j - i, n)).map(pows);
}
return r ? z.reverse() : z;
};
scale.tickFormat = function (count, specifier) {
if (specifier == null) specifier = base === 10 ? '.0e' : ',';
if (typeof specifier !== 'function') specifier = exports.format(specifier);
if (count === Infinity) return specifier;
if (count == null) count = 10;
var k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate?
return function (d) {
var i = d / pows(Math.round(logs(d)));
if (i * base < base - 0.5) i *= base;
return i <= k ? specifier(d) : '';
};
};
scale.nice = function () {
return domain(nice(domain(), {
floor: function floor(x) {
return pows(Math.floor(logs(x)));
},
ceil: function ceil(x) {
return pows(Math.ceil(logs(x)));
}
}));
};
return scale;
}
function nice(domain, interval) {
domain = domain.slice();
var i0 = 0,
i1 = domain.length - 1,
x0 = domain[i0],
x1 = domain[i1],
t;
if (x1 < x0) {
t = i0, i0 = i1, i1 = t;
t = x0, x0 = x1, x1 = t;
}
domain[i0] = interval.floor(x0);
domain[i1] = interval.ceil(x1);
return domain;
}
function customTooltipStyle(tooltipModel) {
// Tooltip Element
var tooltipEl = document.getElementById('chartjs-tooltip'); // Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'chartjs-tooltip';
tooltipEl.innerHTML = '<table></table>';
document.body.appendChild(tooltipEl);
} // Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltipEl.style.opacity = '0';
return;
} // Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltipModel.yAlign) {
tooltipEl.classList.add(tooltipModel.yAlign);
} else {
tooltipEl.classList.add('no-transform');
} // Set Text
if (tooltipModel.body) {
var titleLines = tooltipModel.title || [];
var bodyLines = tooltipModel.body.map(function (bodyItem) {
return bodyItem.lines;
});
var innerHtml = '<thead>';
titleLines.forEach(function (title) {
innerHtml += '<tr><th style="text-align: left">' + title.label + '</th><th style="text-align: right">' + title.value + '</th></tr>';
});
innerHtml += '</thead><tbody>';
bodyLines.forEach(function (body, i) {
var colors = tooltipModel.labelColors[i];
var style = 'background: ' + colors.backgroundColor + ';';
style += 'width: 10px; height: 10px; display: inline-block; margin-right: 5px;';
var span = '<span style="' + style + '"></span>';
innerHtml += '<tr><td>' + span + body[0].label + '</td><td style="text-align: right">' + body[0].value + '</td></tr>';
});
innerHtml += '</tbody>';
var tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
} // `this` will be the overall tooltip
var position = this._chart.canvas.getBoundingClientRect(); // Display, position, and set styles for font
tooltipEl.style.opacity = '1';
tooltipEl.style.position = 'absolute';
tooltipEl.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px';
tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px';
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + 'px';
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';
tooltipEl.style.pointerEvents = 'none';
tooltipEl.style.background = tooltipModel.backgroundColor;
tooltipEl.style.color = tooltipModel.bodyFontColor;
tooltipEl.style.borderRadius = tooltipModel.cornerRadius + 'px';
}
/**
* Get the stanfordPlugin options
*
* @param chartOptions
* @returns {*|options.stanfordDiagram|{}}
*/
function getStanfordConfig(chartOptions) {
var plugins = chartOptions.plugins;
var pluginStanfordChart = plugins && plugins.stanfordDiagram ? plugins.stanfordDiagram : null;
return pluginStanfordChart || chartOptions.stanfordDiagram || {};
}
/**
* Draws the region polygon
*
* @param chart - Chart.js instance
* @param regions - Array of regions
* @param countOnlyVisible - Count only the visible points
*/
function drawRegions(chart, regions, countOnlyVisible) {
var ctx = chart.ctx;
if (!regions) return;
regions.forEach(function (element) {
ctx.polygon(getPixelValue(chart, element.points), element.fillColor, element.strokeColor);
if (element.text) {
drawRegionText(chart, element.text, element.points, countOnlyVisible);
}
});
}
/**
* Get the number of points in a polygon
*
* @param chart - Chart.js instance
* @param points - Array of points
* @param countOnlyVisible - Count only the visible points
* @returns {{percentage: string, value: number}}
*/
function countEpochsInRegion(chart, points, countOnlyVisible) {
var total = chart.data.datasets[0].data.reduce(function (accumulator, currentValue) {
return accumulator + Number(currentValue.epochs);
}, 0); // Count how many points are in Region
var count = chart.data.datasets[0].data.reduce(function (accumulator, currentValue) {
if (pointInPolygon(chart, currentValue, points, countOnlyVisible)) {
return accumulator + Number(currentValue.epochs);
}
return accumulator;
}, 0);
return {
count: count,
percentage: calculatePercentageValue(chart, total, count)
};
}
/**
* Calculate percentage value.
*
* @param chart - Chart.js instance
* @param total - total of points
* @param count - number of points in the region
* @return {string} the percentage value as string
*/
function calculatePercentageValue(chart, total, count) {
var options = chart.options.plugins.stanfordDiagram && chart.options.plugins.stanfordDiagram.percentage ? chart.options.plugins.stanfordDiagram.percentage : {};
if (options instanceof Intl.NumberFormat) {
return options.format(count === 0 ? 0 : count / total);
}
var decimalPlaces = isNaN(+options.decimalPlaces) ? 1 : options.decimalPlaces;
var percentage = count === 0 ? 0 : count / total * 100;
if (percentage === 0) {
return percentage.toFixed(decimalPlaces);
}
var roundingMethod;
switch (options.roundingMethod) {
case 'ceil':
roundingMethod = Math.ceil;
break;
case 'floor':
roundingMethod = Math.floor;
break;
case 'round':
default:
roundingMethod = Math.round;
break;
}
return round(roundingMethod, percentage, decimalPlaces).toFixed(decimalPlaces);
}
/**
* Round
* @param method
* @param value
* @param precision
* @return {number}
*/
function round(method, value, precision) {
var multiplier = Math.pow(10, precision || 0);
return method(value * multiplier) / multiplier;
}
/**
* Draws the amount of points in a region
*
* @param chart
* @param text
* @param points
* @param countOnlyVisible
*/
function drawRegionText(chart, text, points, countOnlyVisible) {
var ctx = chart.ctx;
var axisX = chart.scales['x-axis-1'];
var axisY = chart.scales['y-axis-1'];
var _countEpochsInRegion = countEpochsInRegion(chart, points, countOnlyVisible),
count = _countEpochsInRegion.count,
percentage = _countEpochsInRegion.percentage; // Get text
var content = text.format ? text.format(count, percentage) : "".concat(count, " (").concat(percentage, ")");
ctx.save();
ctx.font = text.font ? text.font : "".concat(chart.options.defaultFontSize, "px ").concat(Chart.defaults.global.defaultFontFamily);
ctx.fillStyle = text.color ? text.color : Chart.defaults.global.defaultFontColor;
ctx.textBaseline = 'middle';
ctx.fillText(content, axisX.getPixelForValue(text.x), axisY.getPixelForValue(text.y));
ctx.restore();
}
/**
* Converts an array of points to the pixels in the browser
*
* @param chart
* @param points
* @returns arrayOfPointsWithCorrectScale
*/
function getPixelValue(chart, points) {
var axisX = chart.scales['x-axis-1'];
var axisY = chart.scales['y-axis-1'];
return points.map(function (p) {
return {
x: axisX.getPixelForValue(getPointToDraw(p.x, axisX, axisY)),
y: axisY.getPixelForValue(getPointToDraw(p.y, axisX, axisY))
};
});
}
/**
* Obtains the point to draw.
*
* @param point
* @param axisX
* @param axisY
* @returns {number} point to draw
*/
function getPointToDraw(point, axisX, axisY) {
switch (point) {
case 'MAX_XY':
return Math.min(axisX.max, axisY.max);
case 'MAX_X':
return axisX.max;
case 'MAX_Y':
return axisY.max;
default:
return point;
}
}
/**
* Obtains the point to draw.
*
* @param point
* @param axisX
* @param axisY
* @param comparePoint
* @param axis 'x' or 'y'
*
* @returns {number} point to draw
*/
function getPointToDrawForPolygon(point, axisX, axisY, comparePoint, axis) {
switch (point) {
case 'MAX_XY':
if (axis === 'x' && comparePoint > axisX.max) {
return Math.ceil(comparePoint * axisY.max / axisX.max) + 1;
}
if (axis === 'y' && comparePoint > axisY.max) {
return Math.ceil(comparePoint * axisX.max / axisY.max) + 1;
}
return Math.min(axisX.max, axisY.max) + 1;
case 'MAX_X':
if (axis === 'x' && comparePoint > axisX.max) {
return comparePoint + 1;
}
return axisX.max + 1;
case 'MAX_Y':
if (axis === 'y' && comparePoint > axisY.max) {
return comparePoint + 1;
}
return axisY.max + 1;
default:
return point;
}
}
/**
* Check if point is inside polygon
* thanks to: http://bl.ocks.org/bycoffe/5575904
*
* @param chart
* @param point
* @param polygon
* @param countOnlyVisible
*
* @returns {boolean}
*/
function pointInPolygon(chart, point, polygon, countOnlyVisible) {
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
var axisX = chart.scales['x-axis-1'];
var axisY = chart.scales['y-axis-1'];
var xi;
var yi;
var yj;
var xj;
var intersect;
var inside = false;
var x = point.x,
y = point.y;
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
if (countOnlyVisible) {
xi = getPointToDraw(polygon[i].x, axisX, axisY);
yi = getPointToDraw(polygon[i].y, axisX, axisY);
xj = getPointToDraw(polygon[j].x, axisX, axisY);
yj = getPointToDraw(polygon[j].y, axisX, axisY);
if (typeof polygon[i].x !== 'number') {
xi += 1;
}
if (typeof polygon[i].y !== 'number') {
yi += 1;
}
if (typeof polygon[j].x !== 'number') {
xj += 1;
}
if (typeof polygon[j].y !== 'number') {
yj += 1;
}
} else {
xi = getPointToDrawForPolygon(polygon[i].x, axisX, axisY, x, 'x');
xj = getPointToDrawForPolygon(polygon[j].x, axisX, axisY, x, 'x');
yi = getPointToDrawForPolygon(polygon[i].y, axisX, axisY, y, 'y');
yj = getPointToDrawForPolygon(polygon[j].y, axisX, axisY, y, 'y');
}
intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
if (intersect) {
inside = !inside;
}
}
return inside;
}
/**
* Create color scale, if {draw} is true, it draws the scale
*
* @param chart
* @param maxEpochs
*/
function drawColorScale(chart, maxEpochs) {
var ctx = chart.ctx;
var barWidth = 25;
var barHeight = 5;
var startValue = chart.chartArea.top + 5;
var intervalValue = barHeight;
var endValue = chart.chartArea.bottom;
var linearScale = scaleSequential(function (value) {
return interpolateHSL([0.7, 1, 0.5], [0, 1, 0.5], value);
}).domain([startValue, endValue]);
var startPoint = chart.chartArea.right + 25;
var points = range(startValue, endValue, intervalValue);
var axisX = chart.scales['x-axis-1'];
var axisY = chart.scales['y-axis-1'];
ctx.save();
points.forEach(function (p) {
ctx.fillStyle = linearScale(endValue - p);
ctx.fillRect(startPoint, p, barWidth, barHeight);
}); // get rounded end value
endValue = points[points.length - 1] + intervalValue;
drawLegendAxis(chart, ctx, maxEpochs, startPoint + barWidth, startValue, endValue); // Draw XY line
var minMaxXY = Math.min(axisX.max, axisY.max);
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';
ctx.beginPath();
ctx.moveTo(axisX.getPixelForValue(axisX.min), axisY.getPixelForValue(axisY.min));
ctx.lineTo(axisX.getPixelForValue(minMaxXY), axisY.getPixelForValue(minMaxXY));
ctx.stroke();
ctx.restore();
var legendPosition = startPoint + barWidth + 9 + chart.options.defaultFontSize + 22;
drawLegendLabel(chart, ctx, legendPosition, startValue, endValue);
} // http://usefulangle.com/post/17/html5-canvas-drawing-1px-crisp-straight-lines
// http://usefulangle.com/post/19/html5-canvas-tutorial-how-to-draw-graphical-coordinate-system-with-grids-and-axis
/**
* Draw the color scale legend axis
*
* @param chart
* @param ctx
* @param maxEpochs
* @param startPointLeft
* @param startValue
* @param endValue
*/
function drawLegendAxis(chart, ctx, maxEpochs, startPointLeft, startValue, endValue) {
ctx.lineWidth = 1;
ctx.strokeStyle = '#000000';
ctx.fillStyle = '#000000'; // Vertical Line
ctx.beginPath();
ctx.moveTo(startPointLeft + 0.5, startValue);
ctx.lineTo(startPointLeft + 0.5, endValue);
ctx.stroke(); // Text value at that point
ctx.textAlign = 'start';
ctx.textBaseline = 'middle';
var numberOfTicks = getNumberOfTicks(maxEpochs); // Ticks marks along the positive Y-axis
// Positive Y-axis of graph is negative Y-axis of the canvas
for (var i = 0; i <= numberOfTicks; i++) {
var posY = endValue - (endValue - startValue) / numberOfTicks * i;
ctx.beginPath();
ctx.moveTo(startPointLeft, posY);
ctx.lineTo(startPointLeft + 6, posY);
ctx.stroke();
ctx.font = "".concat(chart.options.defaultFontSize - 1, "px ").concat(Chart.defaults.global.defaultFontFamily);
ctx.fillText('10 ', startPointLeft + 9, posY);
ctx.font = "".concat(chart.options.defaultFontSize - 2, "px ").concat(Chart.defaults.global.defaultFontFamily);
ctx.fillText("".concat(i), startPointLeft + 9 + chart.options.defaultFontSize, posY - 7);
}
}
/**
* Gets number of ticks from maxEpochs value
*
* @param maxEpochs
*/
function getNumberOfTicks(maxEpochs) {
var maxExp = maxEpochs.toExponential();
return +maxExp.split('e', 2)[1];
}
/**
* Draw the color scale legend label
*
* @param chart
* @param ctx
* @param startPointLeft
* @param startValue
* @param endValue
*/
function drawLegendLabel(chart, ctx, startPointLeft, startValue, endValue) {
var text = chart.options.plugins.stanfordDiagram.legendLabel || 'Number of samples (epochs) per point'; // http://www.java2s.com/Tutorials/HTML_CSS/HTML5_Canvas_Reference/rotate.htm
ctx.save();
ctx.font = "".concat(chart.options.defaultFontSize, "px ").concat(Chart.defaults.global.defaultFontFamily);
ctx.strokeStyle = '#000000';
ctx.fillStyle = '#000000';
var metrics = ctx.measureText(text);
ctx.translate(startPointLeft, (endValue - startValue) / 2 + metrics.width / 2);
ctx.rotate(-Math.PI / 2);
ctx.fillText(text, 0, 0);
ctx.restore();
}
/**
* Extension to draw a polygon in a canvas 2DContext
*
* @param pointsArray
* @param fillColor
* @param strokeColor
*/
CanvasRenderingContext2D.prototype.polygon = function (pointsArray, fillColor, strokeColor) {
if (pointsArray.length <= 0) return;
this.save();
this.globalCompositeOperation = 'destination-over'; // draw behind existing pixels https://stackoverflow.com/questions/9165766/html5-canvas-set-z-index/26064753
this.beginPath();
this.moveTo(pointsArray[0].x, pointsArray[0].y);
for (var i = 1; i < pointsArray.length; i++) {
this.lineTo(pointsArray[i].x, pointsArray[i].y);
}
this.closePath();
if (strokeColor) {
this.lineWidth = 1;
this.strokeStyle = strokeColor;
this.stroke();
}
if (fillColor) {
this.fillStyle = fillColor;
this.fill();
}
this.restore();
};
/**
* Stanford Diagram -- chart type
*/
Chart.controllers.stanford = Chart.controllers.line.extend({
update: function update() {
// "Responsive" point radius
this.chart.options.elements.point.radius = Math.max(Math.round(this.chart.height / 200), 1);
Chart.controllers.line.prototype.update.apply(this, arguments);
}
});
Chart.defaults._set('stanford', {
animation: false,
aspectRatio: 1.12,
elements: {
point: {
radius: 2.5,
pointStyle: 'rect'
}
},
hover: {
mode: null
},
legend: {
display: false
},
scales: {
xAxes: [{
id: 'x-axis-1',
// need an ID so datasets can reference the scale
type: 'linear',
// stanford should not use a category axis
position: 'bottom',
ticks: {
suggestedMax: 60,
beginAtZero: true
}
}],
yAxes: [{
id: 'y-axis-1',
type: 'linear',
position: 'left',
ticks: {
suggestedMax: 60,
beginAtZero: true
}
}]
},
showLines: false,
tooltips: {
// Disable the on-canvas tooltip
enabled: false,
custom: customTooltipStyle,
callbacks: {
title: function title(item) {
return [{
label: this._chart.scales['x-axis-1'].options.scaleLabel.labelString,
value: item[0].xLabel
}, {
label: this._chart.scales['y-axis-1'].options.scaleLabel.labelString,
value: item[0].yLabel
}];
},
label: function label(item, data) {
return {
label: this._chart.options.plugins.stanfordDiagram.epochsLabel || 'Epochs',
value: data.datasets[0].data[item.index].epochs
};
}
}
}
});
/**
* Stanford Diagram Plugin
*/
var stanfordDiagramPlugin = {
beforeInit: function beforeInit(c) {
var ns = c.stanfordDiagramPlugin = {
options: getStanfordConfig(c.options.plugins)
};
if (!ns.options.maxEpochs) {
ns.options.maxEpochs = 10000;
}
ns.colorScale = sequentialLog(function (value) {
return interpolateHSL([0.7, 1, 0.5], [0, 1, 0.5], value);
}).domain([1, ns.options.maxEpochs]); // add space for scale
c.options.layout.padding.right += 100;
},
beforeDatasetsUpdate: function beforeDatasetsUpdate(c) {
var ns = c.stanfordDiagramPlugin;
if (ns.colorScale) {
c.data.datasets[0].pointBackgroundColor = [];
for (var i = 0; i < c.data.datasets[0].data.length; i++) {
c.data.datasets[0].pointBackgroundColor[i] = ns.colorScale(c.data.datasets[0].data[i].epochs);
}
}
},
beforeUpdate: function beforeUpdate(c) {
// "Responsive" font-size with a min size of 8px
c.chart.options.defaultFontSize = Math.max(Math.round(c.chart.height / 50), 8);
},
afterRender: function afterRender(c) {
drawColorScale(c, c.stanfordDiagramPlugin.options.maxEpochs);
drawRegions(c, c.stanfordDiagramPlugin.options.regions, !!c.stanfordDiagramPlugin.options.countOnlyVisible);
}
};
exports.calculatePercentageValue = calculatePercentageValue;
exports.countEpochsInRegion = countEpochsInRegion;
exports.getStanfordConfig = getStanfordConfig;
exports.pointInPolygon = pointInPolygon;
exports.stanfordDiagramPlugin = stanfordDiagramPlugin;
})));