UNPKG

chartjs-plugin-stanford-diagram

Version:
1,079 lines (906 loc) 31 kB
(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; })));