monkeytype/static/js/chartjs-plugin-annotation.js
2020-11-01 21:40:16 -08:00

1114 lines
37 KiB
JavaScript

/*!
* chartjs-plugin-annotation.js
* http://chartjs.org/
* Version: 0.5.7
*
* Copyright 2016 Evert Timberg
* Released under the MIT license
* https://github.com/chartjs/Chart.Annotation.js/blob/master/LICENSE.md
*/
(function e(t, n, r) {
function s(o, u) {
if (!n[o]) {
if (!t[o]) {
var a = typeof require == "function" && require;
if (!u && a) return a(o, !0);
if (i) return i(o, !0);
var f = new Error("Cannot find module '" + o + "'");
throw ((f.code = "MODULE_NOT_FOUND"), f);
}
var l = (n[o] = { exports: {} });
t[o][0].call(
l.exports,
function (e) {
var n = t[o][1][e];
return s(n ? n : e);
},
l,
l.exports,
e,
t,
n,
r
);
}
return n[o].exports;
}
var i = typeof require == "function" && require;
for (var o = 0; o < r.length; o++) s(r[o]);
return s;
})(
{
1: [function (require, module, exports) {}, {}],
2: [
function (require, module, exports) {
module.exports = function (Chart) {
var chartHelpers = Chart.helpers;
var helpers = require("./helpers.js")(Chart);
var events = require("./events.js")(Chart);
var annotationTypes = Chart.Annotation.types;
function setAfterDataLimitsHook(axisOptions) {
helpers.decorate(axisOptions, "afterDataLimits", function (
previous,
scale
) {
if (previous) previous(scale);
helpers.adjustScaleRange(scale);
});
}
function draw(drawTime) {
return function (chartInstance, easingDecimal) {
var defaultDrawTime = chartInstance.annotation.options.drawTime;
helpers
.elements(chartInstance)
.filter(function (element) {
return (
drawTime === (element.options.drawTime || defaultDrawTime)
);
})
.forEach(function (element) {
element.transition(easingDecimal).draw();
});
};
}
return {
beforeInit: function (chartInstance) {
var chartOptions = chartInstance.options;
// Initialize chart instance plugin namespace
var ns = (chartInstance.annotation = {
elements: {},
options: helpers.initConfig(chartOptions.annotation || {}),
onDestroy: [],
firstRun: true,
supported: false,
});
// Add the annotation scale adjuster to each scale's afterDataLimits hook
chartInstance.ensureScalesHaveIDs();
if (chartOptions.scales) {
ns.supported = true;
chartHelpers.each(
chartOptions.scales.xAxes,
setAfterDataLimitsHook
);
chartHelpers.each(
chartOptions.scales.yAxes,
setAfterDataLimitsHook
);
}
},
beforeUpdate: function (chartInstance) {
var ns = chartInstance.annotation;
if (!ns.supported) {
return;
}
if (!ns.firstRun) {
ns.options = helpers.initConfig(
chartInstance.options.annotation || {}
);
} else {
ns.firstRun = false;
}
var elementIds = [];
// Add new elements, or update existing ones
ns.options.annotations.forEach(function (annotation) {
var id = annotation.id || helpers.objectId();
// No element with that ID exists, and it's a valid annotation type
if (!ns.elements[id] && annotationTypes[annotation.type]) {
var cls = annotationTypes[annotation.type];
var element = new cls({
id: id,
options: annotation,
chartInstance: chartInstance,
});
element.initialize();
ns.elements[id] = element;
annotation.id = id;
elementIds.push(id);
} else if (ns.elements[id]) {
// Nothing to do for update, since the element config references
// the same object that exists in the chart annotation config
elementIds.push(id);
}
});
// Delete removed elements
Object.keys(ns.elements).forEach(function (id) {
if (elementIds.indexOf(id) === -1) {
ns.elements[id].destroy();
delete ns.elements[id];
}
});
},
afterScaleUpdate: function (chartInstance) {
helpers.elements(chartInstance).forEach(function (element) {
element.configure();
});
},
beforeDatasetsDraw: draw("beforeDatasetsDraw"),
afterDatasetsDraw: draw("afterDatasetsDraw"),
afterDraw: draw("afterDraw"),
afterInit: function (chartInstance) {
// Detect and intercept events that happen on an annotation element
var watchFor = chartInstance.annotation.options.events;
if (chartHelpers.isArray(watchFor) && watchFor.length > 0) {
var canvas = chartInstance.chart.canvas;
var eventHandler = events.dispatcher.bind(chartInstance);
events
.collapseHoverEvents(watchFor)
.forEach(function (eventName) {
chartHelpers.addEvent(canvas, eventName, eventHandler);
chartInstance.annotation.onDestroy.push(function () {
chartHelpers.removeEvent(canvas, eventName, eventHandler);
});
});
}
},
destroy: function (chartInstance) {
var deregisterers = chartInstance.annotation.onDestroy;
while (deregisterers.length > 0) {
deregisterers.pop()();
}
},
};
};
},
{ "./events.js": 4, "./helpers.js": 5 },
],
3: [
function (require, module, exports) {
module.exports = function (Chart) {
var chartHelpers = Chart.helpers;
var AnnotationElement = Chart.Element.extend({
initialize: function () {
this.hidden = false;
this.hovering = false;
this._model = chartHelpers.clone(this._model) || {};
this.setDataLimits();
},
destroy: function () {},
setDataLimits: function () {},
configure: function () {},
inRange: function () {},
getCenterPoint: function () {},
getWidth: function () {},
getHeight: function () {},
getArea: function () {},
draw: function () {},
});
return AnnotationElement;
};
},
{},
],
4: [
function (require, module, exports) {
module.exports = function (Chart) {
var chartHelpers = Chart.helpers;
var helpers = require("./helpers.js")(Chart);
function collapseHoverEvents(events) {
var hover = false;
var filteredEvents = events.filter(function (eventName) {
switch (eventName) {
case "mouseenter":
case "mouseover":
case "mouseout":
case "mouseleave":
hover = true;
return false;
default:
return true;
}
});
if (hover && filteredEvents.indexOf("mousemove") === -1) {
filteredEvents.push("mousemove");
}
return filteredEvents;
}
function dispatcher(e) {
var ns = this.annotation;
var elements = helpers.elements(this);
var position = chartHelpers.getRelativePosition(e, this.chart);
var element = helpers.getNearestItems(elements, position);
var events = collapseHoverEvents(ns.options.events);
var dblClickSpeed = ns.options.dblClickSpeed;
var eventHandlers = [];
var eventHandlerName = helpers.getEventHandlerName(e.type);
var options = (element || {}).options;
// Detect hover events
if (e.type === "mousemove") {
if (element && !element.hovering) {
// hover started
["mouseenter", "mouseover"].forEach(function (eventName) {
var eventHandlerName = helpers.getEventHandlerName(eventName);
var hoverEvent = helpers.createMouseEvent(eventName, e); // recreate the event to match the handler
element.hovering = true;
if (typeof options[eventHandlerName] === "function") {
eventHandlers.push([
options[eventHandlerName],
hoverEvent,
element,
]);
}
});
} else if (!element) {
// hover ended
elements.forEach(function (element) {
if (element.hovering) {
element.hovering = false;
var options = element.options;
["mouseout", "mouseleave"].forEach(function (eventName) {
var eventHandlerName = helpers.getEventHandlerName(
eventName
);
var hoverEvent = helpers.createMouseEvent(eventName, e); // recreate the event to match the handler
if (typeof options[eventHandlerName] === "function") {
eventHandlers.push([
options[eventHandlerName],
hoverEvent,
element,
]);
}
});
}
});
}
}
// Suppress duplicate click events during a double click
// 1. click -> 2. click -> 3. dblclick
//
// 1: wait dblClickSpeed ms, then fire click
// 2: cancel (1) if it is waiting then wait dblClickSpeed ms then fire click, else fire click immediately
// 3: cancel (1) or (2) if waiting, then fire dblclick
if (
element &&
events.indexOf("dblclick") > -1 &&
typeof options.onDblclick === "function"
) {
if (e.type === "click" && typeof options.onClick === "function") {
clearTimeout(element.clickTimeout);
element.clickTimeout = setTimeout(function () {
delete element.clickTimeout;
options.onClick.call(element, e);
}, dblClickSpeed);
e.stopImmediatePropagation();
e.preventDefault();
return;
} else if (e.type === "dblclick" && element.clickTimeout) {
clearTimeout(element.clickTimeout);
delete element.clickTimeout;
}
}
// Dispatch the event to the usual handler, but only if we haven't substituted it
if (
element &&
typeof options[eventHandlerName] === "function" &&
eventHandlers.length === 0
) {
eventHandlers.push([options[eventHandlerName], e, element]);
}
if (eventHandlers.length > 0) {
e.stopImmediatePropagation();
e.preventDefault();
eventHandlers.forEach(function (eventHandler) {
// [handler, event, element]
eventHandler[0].call(eventHandler[2], eventHandler[1]);
});
}
}
return {
dispatcher: dispatcher,
collapseHoverEvents: collapseHoverEvents,
};
};
},
{ "./helpers.js": 5 },
],
5: [
function (require, module, exports) {
function noop() {}
function elements(chartInstance) {
// Turn the elements object into an array of elements
var elements = chartInstance.annotation.elements;
return Object.keys(elements).map(function (id) {
return elements[id];
});
}
function objectId() {
return Math.random().toString(36).substr(2, 6);
}
function isValid(rawValue) {
if (rawValue === null || typeof rawValue === "undefined") {
return false;
} else if (typeof rawValue === "number") {
return isFinite(rawValue);
} else {
return !!rawValue;
}
}
function decorate(obj, prop, func) {
var prefix = "$";
if (!obj[prefix + prop]) {
if (obj[prop]) {
obj[prefix + prop] = obj[prop].bind(obj);
obj[prop] = function () {
var args = [obj[prefix + prop]].concat(
Array.prototype.slice.call(arguments)
);
return func.apply(obj, args);
};
} else {
obj[prop] = function () {
var args = [undefined].concat(
Array.prototype.slice.call(arguments)
);
return func.apply(obj, args);
};
}
}
}
function callEach(fns, method) {
fns.forEach(function (fn) {
(method ? fn[method] : fn)();
});
}
function getEventHandlerName(eventName) {
return "on" + eventName[0].toUpperCase() + eventName.substring(1);
}
function createMouseEvent(type, previousEvent) {
try {
return new MouseEvent(type, previousEvent);
} catch (exception) {
try {
var m = document.createEvent("MouseEvent");
m.initMouseEvent(
type,
previousEvent.canBubble,
previousEvent.cancelable,
previousEvent.view,
previousEvent.detail,
previousEvent.screenX,
previousEvent.screenY,
previousEvent.clientX,
previousEvent.clientY,
previousEvent.ctrlKey,
previousEvent.altKey,
previousEvent.shiftKey,
previousEvent.metaKey,
previousEvent.button,
previousEvent.relatedTarget
);
return m;
} catch (exception2) {
var e = document.createEvent("Event");
e.initEvent(
type,
previousEvent.canBubble,
previousEvent.cancelable
);
return e;
}
}
}
module.exports = function (Chart) {
var chartHelpers = Chart.helpers;
function initConfig(config) {
config = chartHelpers.configMerge(
Chart.Annotation.defaults,
config
);
if (chartHelpers.isArray(config.annotations)) {
config.annotations.forEach(function (annotation) {
annotation.label = chartHelpers.configMerge(
Chart.Annotation.labelDefaults,
annotation.label
);
});
}
return config;
}
function getScaleLimits(scaleId, annotations, scaleMin, scaleMax) {
var ranges = annotations
.filter(function (annotation) {
return !!annotation._model.ranges[scaleId];
})
.map(function (annotation) {
return annotation._model.ranges[scaleId];
});
var min = ranges
.map(function (range) {
return Number(range.min);
})
.reduce(function (a, b) {
return isFinite(b) && !isNaN(b) && b < a ? b : a;
}, scaleMin);
var max = ranges
.map(function (range) {
return Number(range.max);
})
.reduce(function (a, b) {
return isFinite(b) && !isNaN(b) && b > a ? b : a;
}, scaleMax);
return {
min: min,
max: max,
};
}
function adjustScaleRange(scale) {
// Adjust the scale range to include annotation values
var range = getScaleLimits(
scale.id,
elements(scale.chart),
scale.min,
scale.max
);
if (
typeof scale.options.ticks.min === "undefined" &&
typeof scale.options.ticks.suggestedMin === "undefined"
) {
scale.min = range.min;
}
if (
typeof scale.options.ticks.max === "undefined" &&
typeof scale.options.ticks.suggestedMax === "undefined"
) {
scale.max = range.max;
}
if (scale.handleTickRangeOptions) {
scale.handleTickRangeOptions();
}
}
function getNearestItems(annotations, position) {
var minDistance = Number.POSITIVE_INFINITY;
return annotations
.filter(function (element) {
return element.inRange(position.x, position.y);
})
.reduce(function (nearestItems, element) {
var center = element.getCenterPoint();
var distance = chartHelpers.distanceBetweenPoints(
position,
center
);
if (distance < minDistance) {
nearestItems = [element];
minDistance = distance;
} else if (distance === minDistance) {
// Can have multiple items at the same distance in which case we sort by size
nearestItems.push(element);
}
return nearestItems;
}, [])
.sort(function (a, b) {
// If there are multiple elements equally close,
// sort them by size, then by index
var sizeA = a.getArea(),
sizeB = b.getArea();
return sizeA > sizeB || sizeA < sizeB
? sizeA - sizeB
: a._index - b._index;
})
.slice(0, 1)[0]; // return only the top item
}
return {
initConfig: initConfig,
elements: elements,
callEach: callEach,
noop: noop,
objectId: objectId,
isValid: isValid,
decorate: decorate,
adjustScaleRange: adjustScaleRange,
getNearestItems: getNearestItems,
getEventHandlerName: getEventHandlerName,
createMouseEvent: createMouseEvent,
};
};
},
{},
],
6: [
function (require, module, exports) {
// Get the chart variable
var Chart = require("chart.js");
Chart = typeof Chart === "function" ? Chart : window.Chart;
// Configure plugin namespace
Chart.Annotation = Chart.Annotation || {};
Chart.Annotation.drawTimeOptions = {
afterDraw: "afterDraw",
afterDatasetsDraw: "afterDatasetsDraw",
beforeDatasetsDraw: "beforeDatasetsDraw",
};
Chart.Annotation.defaults = {
drawTime: "afterDatasetsDraw",
dblClickSpeed: 350, // ms
events: [],
annotations: [],
};
Chart.Annotation.labelDefaults = {
backgroundColor: "rgba(0,0,0,0.8)",
fontFamily: Chart.defaults.global.defaultFontFamily,
fontSize: Chart.defaults.global.defaultFontSize,
fontStyle: "bold",
fontColor: "#fff",
xPadding: 6,
yPadding: 6,
cornerRadius: 6,
position: "center",
xAdjust: 0,
yAdjust: 0,
enabled: false,
content: null,
};
Chart.Annotation.Element = require("./element.js")(Chart);
Chart.Annotation.types = {
line: require("./types/line.js")(Chart),
box: require("./types/box.js")(Chart),
};
var annotationPlugin = require("./annotation.js")(Chart);
module.exports = annotationPlugin;
Chart.pluginService.register(annotationPlugin);
},
{
"./annotation.js": 2,
"./element.js": 3,
"./types/box.js": 7,
"./types/line.js": 8,
"chart.js": 1,
},
],
7: [
function (require, module, exports) {
// Box Annotation implementation
module.exports = function (Chart) {
var helpers = require("../helpers.js")(Chart);
var BoxAnnotation = Chart.Annotation.Element.extend({
setDataLimits: function () {
var model = this._model;
var options = this.options;
var chartInstance = this.chartInstance;
var xScale = chartInstance.scales[options.xScaleID];
var yScale = chartInstance.scales[options.yScaleID];
var chartArea = chartInstance.chartArea;
// Set the data range for this annotation
model.ranges = {};
if (!chartArea) {
return;
}
var min = 0;
var max = 0;
if (xScale) {
min = helpers.isValid(options.xMin)
? options.xMin
: xScale.getPixelForValue(chartArea.left);
max = helpers.isValid(options.xMax)
? options.xMax
: xScale.getPixelForValue(chartArea.right);
model.ranges[options.xScaleID] = {
min: Math.min(min, max),
max: Math.max(min, max),
};
}
if (yScale) {
min = helpers.isValid(options.yMin)
? options.yMin
: yScale.getPixelForValue(chartArea.bottom);
max = helpers.isValid(options.yMax)
? options.yMax
: yScale.getPixelForValue(chartArea.top);
model.ranges[options.yScaleID] = {
min: Math.min(min, max),
max: Math.max(min, max),
};
}
},
configure: function () {
var model = this._model;
var options = this.options;
var chartInstance = this.chartInstance;
var xScale = chartInstance.scales[options.xScaleID];
var yScale = chartInstance.scales[options.yScaleID];
var chartArea = chartInstance.chartArea;
// clip annotations to the chart area
model.clip = {
x1: chartArea.left,
x2: chartArea.right,
y1: chartArea.top,
y2: chartArea.bottom,
};
var left = chartArea.left,
top = chartArea.top,
right = chartArea.right,
bottom = chartArea.bottom;
var min, max;
if (xScale) {
min = helpers.isValid(options.xMin)
? xScale.getPixelForValue(options.xMin)
: chartArea.left;
max = helpers.isValid(options.xMax)
? xScale.getPixelForValue(options.xMax)
: chartArea.right;
left = Math.min(min, max);
right = Math.max(min, max);
}
if (yScale) {
min = helpers.isValid(options.yMin)
? yScale.getPixelForValue(options.yMin)
: chartArea.bottom;
max = helpers.isValid(options.yMax)
? yScale.getPixelForValue(options.yMax)
: chartArea.top;
top = Math.min(min, max);
bottom = Math.max(min, max);
}
// Ensure model has rect coordinates
model.left = left;
model.top = top;
model.right = right;
model.bottom = bottom;
// Stylistic options
model.borderColor = options.borderColor;
model.borderWidth = options.borderWidth;
model.backgroundColor = options.backgroundColor;
},
inRange: function (mouseX, mouseY) {
var model = this._model;
return (
model &&
mouseX >= model.left &&
mouseX <= model.right &&
mouseY >= model.top &&
mouseY <= model.bottom
);
},
getCenterPoint: function () {
var model = this._model;
return {
x: (model.right + model.left) / 2,
y: (model.bottom + model.top) / 2,
};
},
getWidth: function () {
var model = this._model;
return Math.abs(model.right - model.left);
},
getHeight: function () {
var model = this._model;
return Math.abs(model.bottom - model.top);
},
getArea: function () {
return this.getWidth() * this.getHeight();
},
draw: function () {
var view = this._view;
var ctx = this.chartInstance.chart.ctx;
ctx.save();
// Canvas setup
ctx.beginPath();
ctx.rect(
view.clip.x1,
view.clip.y1,
view.clip.x2 - view.clip.x1,
view.clip.y2 - view.clip.y1
);
ctx.clip();
ctx.lineWidth = view.borderWidth;
ctx.strokeStyle = view.borderColor;
ctx.fillStyle = view.backgroundColor;
// Draw
var width = view.right - view.left,
height = view.bottom - view.top;
ctx.fillRect(view.left, view.top, width, height);
ctx.strokeRect(view.left, view.top, width, height);
ctx.restore();
},
});
return BoxAnnotation;
};
},
{ "../helpers.js": 5 },
],
8: [
function (require, module, exports) {
// Line Annotation implementation
module.exports = function (Chart) {
var chartHelpers = Chart.helpers;
var helpers = require("../helpers.js")(Chart);
var horizontalKeyword = "horizontal";
var verticalKeyword = "vertical";
var LineAnnotation = Chart.Annotation.Element.extend({
setDataLimits: function () {
var model = this._model;
var options = this.options;
// Set the data range for this annotation
model.ranges = {};
model.ranges[options.scaleID] = {
min: options.value,
max: options.endValue || options.value,
};
},
configure: function () {
var model = this._model;
var options = this.options;
var chartInstance = this.chartInstance;
var ctx = chartInstance.chart.ctx;
var scale = chartInstance.scales[options.scaleID];
var pixel, endPixel;
if (scale) {
pixel = helpers.isValid(options.value)
? scale.getPixelForValue(options.value)
: NaN;
endPixel = helpers.isValid(options.endValue)
? scale.getPixelForValue(options.endValue)
: pixel;
}
if (isNaN(pixel)) {
return;
}
var chartArea = chartInstance.chartArea;
// clip annotations to the chart area
model.clip = {
x1: chartArea.left,
x2: chartArea.right,
y1: chartArea.top,
y2: chartArea.bottom,
};
if (this.options.mode == horizontalKeyword) {
model.x1 = chartArea.left;
model.x2 = chartArea.right;
model.y1 = pixel;
model.y2 = endPixel;
} else {
model.y1 = chartArea.top;
model.y2 = chartArea.bottom;
model.x1 = pixel;
model.x2 = endPixel;
}
model.line = new LineFunction(model);
model.mode = options.mode;
// Figure out the label:
model.labelBackgroundColor = options.label.backgroundColor;
model.labelFontFamily = options.label.fontFamily;
model.labelFontSize = options.label.fontSize;
model.labelFontStyle = options.label.fontStyle;
model.labelFontColor = options.label.fontColor;
model.labelXPadding = options.label.xPadding;
model.labelYPadding = options.label.yPadding;
model.labelCornerRadius = options.label.cornerRadius;
model.labelPosition = options.label.position;
model.labelXAdjust = options.label.xAdjust;
model.labelYAdjust = options.label.yAdjust;
model.labelEnabled = options.label.enabled;
model.labelContent = options.label.content;
ctx.font = chartHelpers.fontString(
model.labelFontSize,
model.labelFontStyle,
model.labelFontFamily
);
var textWidth = ctx.measureText(model.labelContent).width;
var textHeight = ctx.measureText("M").width;
var labelPosition = calculateLabelPosition(
model,
textWidth,
textHeight,
model.labelXPadding,
model.labelYPadding
);
model.labelX = labelPosition.x - model.labelXPadding;
model.labelY = labelPosition.y - model.labelYPadding;
model.labelWidth = textWidth + 2 * model.labelXPadding;
model.labelHeight = textHeight + 2 * model.labelYPadding;
model.borderColor = options.borderColor;
model.borderWidth = options.borderWidth;
model.borderDash = options.borderDash || [];
model.borderDashOffset = options.borderDashOffset || 0;
},
inRange: function (mouseX, mouseY) {
var model = this._model;
return (
// On the line
(model.line &&
model.line.intersects(mouseX, mouseY, this.getHeight())) ||
// On the label
(model.labelEnabled &&
model.labelContent &&
mouseX >= model.labelX &&
mouseX <= model.labelX + model.labelWidth &&
mouseY >= model.labelY &&
mouseY <= model.labelY + model.labelHeight)
);
},
getCenterPoint: function () {
return {
x: (this._model.x2 + this._model.x1) / 2,
y: (this._model.y2 + this._model.y1) / 2,
};
},
getWidth: function () {
return Math.abs(this._model.right - this._model.left);
},
getHeight: function () {
return this._model.borderWidth || 1;
},
getArea: function () {
return Math.sqrt(
Math.pow(this.getWidth(), 2) + Math.pow(this.getHeight(), 2)
);
},
draw: function () {
var view = this._view;
var ctx = this.chartInstance.chart.ctx;
if (!view.clip) {
return;
}
ctx.save();
// Canvas setup
ctx.beginPath();
ctx.rect(
view.clip.x1,
view.clip.y1,
view.clip.x2 - view.clip.x1,
view.clip.y2 - view.clip.y1
);
ctx.clip();
ctx.lineWidth = view.borderWidth;
ctx.strokeStyle = view.borderColor;
if (ctx.setLineDash) {
ctx.setLineDash(view.borderDash);
}
ctx.lineDashOffset = view.borderDashOffset;
// Draw
ctx.beginPath();
ctx.moveTo(view.x1, view.y1);
ctx.lineTo(view.x2, view.y2);
ctx.stroke();
if (view.labelEnabled && view.labelContent) {
ctx.beginPath();
ctx.rect(
view.clip.x1,
view.clip.y1,
view.clip.x2 - view.clip.x1,
view.clip.y2 - view.clip.y1
);
ctx.clip();
ctx.fillStyle = view.labelBackgroundColor;
// Draw the tooltip
chartHelpers.drawRoundedRectangle(
ctx,
view.labelX, // x
view.labelY, // y
view.labelWidth, // width
view.labelHeight, // height
view.labelCornerRadius // radius
);
ctx.fill();
// Draw the text
ctx.font = chartHelpers.fontString(
view.labelFontSize,
view.labelFontStyle,
view.labelFontFamily
);
ctx.fillStyle = view.labelFontColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(
view.labelContent,
view.labelX + view.labelWidth / 2,
view.labelY + view.labelHeight / 2
);
}
ctx.restore();
},
});
function LineFunction(view) {
// Describe the line in slope-intercept form (y = mx + b).
// Note that the axes are rotated 90° CCW, which causes the
// x- and y-axes to be swapped.
var m = (view.x2 - view.x1) / (view.y2 - view.y1);
var b = view.x1 || 0;
this.m = m;
this.b = b;
this.getX = function (y) {
// Coordinates are relative to the origin of the canvas
return m * (y - view.y1) + b;
};
this.getY = function (x) {
return (x - b) / m + view.y1;
};
this.intersects = function (x, y, epsilon) {
epsilon = epsilon || 0.001;
var dy = this.getY(x),
dx = this.getX(y);
return (
(!isFinite(dy) || Math.abs(y - dy) < epsilon) &&
(!isFinite(dx) || Math.abs(x - dx) < epsilon)
);
};
}
function calculateLabelPosition(
view,
width,
height,
padWidth,
padHeight
) {
var line = view.line;
var ret = {},
xa = 0,
ya = 0;
switch (true) {
// top align
case view.mode == verticalKeyword && view.labelPosition == "top":
ya = padHeight + view.labelYAdjust;
xa = width / 2 + view.labelXAdjust;
ret.y = view.y1 + ya;
ret.x = (isFinite(line.m) ? line.getX(ret.y) : view.x1) - xa;
break;
// bottom align
case view.mode == verticalKeyword &&
view.labelPosition == "bottom":
ya = height + padHeight + view.labelYAdjust;
xa = width / 2 + view.labelXAdjust;
ret.y = view.y2 - ya;
ret.x = (isFinite(line.m) ? line.getX(ret.y) : view.x1) - xa;
break;
// left align
case view.mode == horizontalKeyword &&
view.labelPosition == "left":
xa = padWidth + view.labelXAdjust;
ya = -(height / 2) + view.labelYAdjust;
ret.x = view.x1 + xa;
ret.y = line.getY(ret.x) + ya;
break;
// right align
case view.mode == horizontalKeyword &&
view.labelPosition == "right":
xa = width + padWidth + view.labelXAdjust;
ya = -(height / 2) + view.labelYAdjust;
ret.x = view.x2 - xa;
ret.y = line.getY(ret.x) + ya;
break;
// center align
default:
ret.x = (view.x1 + view.x2 - width) / 2 + view.labelXAdjust;
ret.y = (view.y1 + view.y2 - height) / 2 + view.labelYAdjust;
}
return ret;
}
return LineAnnotation;
};
},
{ "../helpers.js": 5 },
],
},
{},
[6]
);