50
$.color.make = function(r, g, b, a) {
55
o.a = a != null ? a : 1;
56
o.add = function(c, d) {
57
for (var i = 0; i < c.length; ++i) o[c.charAt(i)] += d;
60
o.scale = function(c, f) {
61
for (var i = 0; i < c.length; ++i) o[c.charAt(i)] *= f;
64
o.toString = function() {
66
return 'rgb(' + [o.r, o.g, o.b].join(',') + ')';
68
return 'rgba(' + [o.r, o.g, o.b, o.a].join(',') + ')';
71
o.normalize = function() {
72
function clamp(min, value, max) {
73
return value < min ? min : value > max ? max : value;
75
o.r = clamp(0, parseInt(o.r), 255);
76
o.g = clamp(0, parseInt(o.g), 255);
77
o.b = clamp(0, parseInt(o.b), 255);
78
o.a = clamp(0, o.a, 1);
81
o.clone = function() {
82
return $.color.make(o.r, o.b, o.g, o.a);
86
$.color.extract = function(elem, css) {
89
c = elem.css(css).toLowerCase();
90
if (c != '' && c != 'transparent') break;
92
} while (elem.length && !$.nodeName(elem.get(0), 'body'));
93
if (c == 'rgba(0, 0, 0, 0)') c = 'transparent';
94
return $.color.parse(c);
96
$.color.parse = function(str) {
99
if ((res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)))
100
return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
101
if ((res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))
102
return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
103
if ((res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)))
104
return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55);
106
(res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(
110
return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55, parseFloat(res[4]));
111
if ((res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)))
112
return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
113
if ((res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)))
114
return m(parseInt(res[1] + res[1], 16), parseInt(res[2] + res[2], 16), parseInt(res[3] + res[3], 16));
115
var name = $.trim(str).toLowerCase();
116
if (name == 'transparent') return m(255, 255, 255, 0);
118
res = lookupColors[name] || [0, 0, 0];
119
return m(res[0], res[1], res[2]);
124
azure: [240, 255, 255],
125
beige: [245, 245, 220],
128
brown: [165, 42, 42],
130
darkblue: [0, 0, 139],
131
darkcyan: [0, 139, 139],
132
darkgrey: [169, 169, 169],
133
darkgreen: [0, 100, 0],
134
darkkhaki: [189, 183, 107],
135
darkmagenta: [139, 0, 139],
136
darkolivegreen: [85, 107, 47],
137
darkorange: [255, 140, 0],
138
darkorchid: [153, 50, 204],
139
darkred: [139, 0, 0],
140
darksalmon: [233, 150, 122],
141
darkviolet: [148, 0, 211],
142
fuchsia: [255, 0, 255],
145
indigo: [75, 0, 130],
146
khaki: [240, 230, 140],
147
lightblue: [173, 216, 230],
148
lightcyan: [224, 255, 255],
149
lightgreen: [144, 238, 144],
150
lightgrey: [211, 211, 211],
151
lightpink: [255, 182, 193],
152
lightyellow: [255, 255, 224],
154
magenta: [255, 0, 255],
157
olive: [128, 128, 0],
158
orange: [255, 165, 0],
159
pink: [255, 192, 203],
160
purple: [128, 0, 128],
161
violet: [128, 0, 128],
163
silver: [192, 192, 192],
164
white: [255, 255, 255],
165
yellow: [255, 255, 0],
173
let hasOwnProperty = Object.prototype.hasOwnProperty;
182
$.fn.detach = function() {
183
return this.each(function() {
184
if (this.parentNode) {
185
this.parentNode.removeChild(this);
201
function Canvas(cls, container) {
202
var element = container.children('.' + cls)[0];
204
if (element == null) {
205
element = document.createElement('canvas');
206
element.className = cls;
209
.css({ direction: 'ltr', position: 'absolute', left: 0, top: 0 })
210
.appendTo(container);
214
if (!element.getContext) {
215
if (window.G_vmlCanvasManager) {
216
element = window.G_vmlCanvasManager.initElement(element);
219
"Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."
225
this.element = element;
227
var context = (this.context = element.getContext('2d'));
237
let devicePixelRatio = window.devicePixelRatio || 1,
239
context.webkitBackingStorePixelRatio ||
240
context.mozBackingStorePixelRatio ||
241
context.msBackingStorePixelRatio ||
242
context.oBackingStorePixelRatio ||
243
context.backingStorePixelRatio ||
246
this.pixelRatio = devicePixelRatio / backingStoreRatio;
250
this.resize(container.width(), container.height());
254
this.textContainer = null;
260
this._textCache = {};
261
this._textSizeCache = window.flotTextSizeCache = window.flotTextSizeCache || {};
269
Canvas.prototype.resize = function(width, height) {
270
if (width <= 0 || height <= 0) {
271
throw new Error('Invalid dimensions for plot, width = ' + width + ', height = ' + height);
274
let element = this.element,
275
context = this.context,
276
pixelRatio = this.pixelRatio;
285
if (this.width != width) {
286
element.width = width * pixelRatio;
287
element.style.width = width + 'px';
291
if (this.height != height) {
292
element.height = height * pixelRatio;
293
element.style.height = height + 'px';
294
this.height = height;
308
context.scale(pixelRatio, pixelRatio);
313
Canvas.prototype.clear = function() {
314
this.context.clearRect(0, 0, this.width, this.height);
319
Canvas.prototype.render = function() {
320
let cache = this._textCache;
325
for (let layerKey in cache) {
326
if (hasOwnProperty.call(cache, layerKey)) {
327
let layer = this.getTextLayer(layerKey),
328
layerCache = cache[layerKey];
332
for (let styleKey in layerCache) {
333
if (hasOwnProperty.call(layerCache, styleKey)) {
334
let styleCache = layerCache[styleKey];
335
for (let key in styleCache) {
336
if (hasOwnProperty.call(styleCache, key)) {
337
let positions = styleCache[key].positions;
339
for (var i = 0, position; (position = positions[i]); i++) {
340
if (position.active) {
341
if (!position.rendered) {
342
layer.append(position.element);
343
position.rendered = true;
346
positions.splice(i--, 1);
347
if (position.rendered) {
348
position.element.detach();
353
if (positions.length == 0) {
354
delete styleCache[key];
372
Canvas.prototype.getTextLayer = function(classes) {
373
let layer = this.text[classes];
380
if (this.textContainer == null) {
381
this.textContainer = $("<div class='flot-text flot-temp-elem'></div>")
383
position: 'absolute',
388
'font-size': 'smaller',
391
.insertAfter(this.element);
394
layer = this.text[classes] = $('<div></div>')
397
position: 'absolute',
403
.appendTo(this.textContainer);
449
Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
450
let textStyle, layerCache, styleCache, info;
458
if (typeof font === 'object') {
477
layerCache = this._textCache[layer];
479
if (layerCache == null) {
480
layerCache = this._textCache[layer] = {};
483
styleCache = layerCache[textStyle];
485
if (styleCache == null) {
486
styleCache = layerCache[textStyle] = {};
489
info = styleCache[text];
494
var element = $('<div></div>')
497
position: 'absolute',
501
.appendTo(this.getTextLayer(layer));
503
if (typeof font === 'object') {
508
} else if (typeof font === 'string') {
509
element.addClass(font);
512
info = styleCache[text] = { element: element, positions: [] };
514
let size = this._textSizeCache[text];
516
info.width = size.width;
517
info.height = size.height;
519
info.width = element.outerWidth(true);
520
info.height = element.outerHeight(true);
521
this._textSizeCache[text] = { width: info.width, height: info.height };
549
Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
550
let info = this.getTextInfo(layer, text, font, angle, width),
551
positions = info.positions;
555
if (halign == 'center') {
557
} else if (halign == 'right') {
561
if (valign == 'middle') {
562
y -= info.height / 2;
563
} else if (valign == 'bottom') {
570
for (var i = 0, position; (position = positions[i]); i++) {
571
if (position.x == x && position.y == y) {
572
position.active = true;
585
element: positions.length ? info.element.clone() : info.element,
590
positions.push(position);
594
position.element.css({
597
'text-align': halign,
621
Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
623
let layerCache = this._textCache[layer];
624
if (layerCache != null) {
625
for (let styleKey in layerCache) {
626
if (hasOwnProperty.call(layerCache, styleKey)) {
627
let styleCache = layerCache[styleKey];
628
for (let key in styleCache) {
629
if (hasOwnProperty.call(styleCache, key)) {
630
var positions = styleCache[key].positions;
631
for (var i = 0, position; (position = positions[i]); i++) {
632
position.active = false;
640
var positions = this.getTextInfo(layer, text, font, angle).positions;
641
for (var i = 0, position; (position = positions[i]); i++) {
642
if (position.x == x && position.y == y) {
643
position.active = false;
652
function Plot(placeholder, data_, options_, plugins) {
661
colors: ['#edc240', '#afd8f8', '#cb4b4b', '#4da74d', '#9440ed'],
665
labelFormatter: null,
666
labelBoxBorderColor: '#ccc',
670
backgroundColor: null,
671
backgroundOpacity: 0.85,
682
inverseTransform: null,
685
autoscaleMargin: null,
692
alignTicksWithAxis: null,
698
autoscaleMargin: 0.02,
709
fillColor: '#ffffff',
733
highlightColor: null,
739
backgroundColor: null,
744
eventSectionHeight: 0,
747
minBorderMargin: null,
749
markingsColor: '#f4f4f4',
750
markingsLineWidth: 2,
755
mouseActiveRadius: 10,
758
redrawOverlayInterval: 1000 / 60,
769
plotOffset = { left: 0, right: 0, top: 0, bottom: 0 },
775
processDatapoints: [],
788
plot.setData = setData;
789
plot.setupGrid = setupGrid;
791
plot.getPlaceholder = function() {
794
plot.getCanvas = function() {
795
return surface.element;
797
plot.getPlotOffset = function() {
800
plot.width = function() {
803
plot.height = function() {
806
plot.offset = function() {
807
let o = eventHolder.offset();
808
o.left += plotOffset.left;
809
o.top += plotOffset.top;
812
plot.getData = function() {
815
plot.getAxes = function() {
817
$.each(xaxes.concat(yaxes), function(_, axis) {
818
if (axis) res[axis.direction + (axis.n != 1 ? axis.n : '') + 'axis'] = axis;
822
plot.getXAxes = function() {
825
plot.getYAxes = function() {
828
plot.c2p = canvasToAxisCoords;
829
plot.p2c = axisToCanvasCoords;
830
plot.getOptions = function() {
833
plot.highlight = highlight;
834
plot.unhighlight = unhighlight;
835
plot.triggerRedrawOverlay = triggerRedrawOverlay;
836
plot.pointOffset = function(point) {
838
left: parseInt(xaxes[axisNumber(point, 'x') - 1].p2c(+point.x) + plotOffset.left, 10),
839
top: parseInt(yaxes[axisNumber(point, 'y') - 1].p2c(+point.y) + plotOffset.top, 10),
842
plot.shutdown = shutdown;
843
plot.destroy = function() {
845
placeholder.removeData('plot').empty();
860
plot.resize = function() {
861
let width = placeholder.width(),
862
height = placeholder.height();
863
surface.resize(width, height);
864
overlay.resize(width, height);
872
parseOptions(options_);
879
function executeHooks(hook, args) {
880
args = [plot].concat(args);
881
for (var i = 0; i < hook.length; ++i) hook[i].apply(this, args);
884
function initPlugins() {
891
for (let i = 0; i < plugins.length; ++i) {
893
p.init(plot, classes);
894
if (p.options) $.extend(true, options, p.options);
898
function parseOptions(opts) {
899
$.extend(true, options, opts);
906
if (opts && opts.colors) {
907
options.colors = opts.colors;
910
if (options.xaxis.color == null)
911
options.xaxis.color = $.color
912
.parse(options.grid.color)
915
if (options.yaxis.color == null)
916
options.yaxis.color = $.color
917
.parse(options.grid.color)
921
if (options.xaxis.tickColor == null)
923
options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
924
if (options.yaxis.tickColor == null)
926
options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
928
if (options.grid.borderColor == null) options.grid.borderColor = options.grid.color;
929
if (options.grid.tickColor == null)
930
options.grid.tickColor = $.color
931
.parse(options.grid.color)
944
fontSize = placeholder.css('font-size'),
945
fontSizeDefault = fontSize ? +fontSize.replace('px', '') : 13,
947
style: placeholder.css('font-style'),
948
size: Math.round(0.8 * fontSizeDefault),
949
variant: placeholder.css('font-variant'),
950
weight: placeholder.css('font-weight'),
951
family: placeholder.css('font-family'),
954
axisCount = options.xaxes.length || 1;
955
for (i = 0; i < axisCount; ++i) {
956
axisOptions = options.xaxes[i];
957
if (axisOptions && !axisOptions.tickColor) {
958
axisOptions.tickColor = axisOptions.color;
961
axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
962
options.xaxes[i] = axisOptions;
964
if (axisOptions.font) {
965
axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
966
if (!axisOptions.font.color) {
967
axisOptions.font.color = axisOptions.color;
969
if (!axisOptions.font.lineHeight) {
970
axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
975
axisCount = options.yaxes.length || 1;
976
for (i = 0; i < axisCount; ++i) {
977
axisOptions = options.yaxes[i];
978
if (axisOptions && !axisOptions.tickColor) {
979
axisOptions.tickColor = axisOptions.color;
982
axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
983
options.yaxes[i] = axisOptions;
985
if (axisOptions.font) {
986
axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
987
if (!axisOptions.font.color) {
988
axisOptions.font.color = axisOptions.color;
990
if (!axisOptions.font.lineHeight) {
991
axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
997
if (options.xaxis.noTicks && options.xaxis.ticks == null) options.xaxis.ticks = options.xaxis.noTicks;
998
if (options.yaxis.noTicks && options.yaxis.ticks == null) options.yaxis.ticks = options.yaxis.noTicks;
999
if (options.x2axis) {
1000
options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
1001
options.xaxes[1].position = 'top';
1003
if (options.x2axis.min == null) {
1004
options.xaxes[1].min = null;
1006
if (options.x2axis.max == null) {
1007
options.xaxes[1].max = null;
1010
if (options.y2axis) {
1011
options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
1012
options.yaxes[1].position = 'right';
1014
if (options.y2axis.min == null) {
1015
options.yaxes[1].min = null;
1017
if (options.y2axis.max == null) {
1018
options.yaxes[1].max = null;
1021
if (options.grid.coloredAreas) options.grid.markings = options.grid.coloredAreas;
1022
if (options.grid.coloredAreasColor) options.grid.markingsColor = options.grid.coloredAreasColor;
1023
if (options.lines) $.extend(true, options.series.lines, options.lines);
1024
if (options.points) $.extend(true, options.series.points, options.points);
1025
if (options.bars) $.extend(true, options.series.bars, options.bars);
1026
if (options.shadowSize != null) options.series.shadowSize = options.shadowSize;
1027
if (options.highlightColor != null) options.series.highlightColor = options.highlightColor;
1030
for (i = 0; i < options.xaxes.length; ++i) getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
1031
for (i = 0; i < options.yaxes.length; ++i) getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
1034
for (var n in hooks) if (options.hooks[n] && options.hooks[n].length) hooks[n] = hooks[n].concat(options.hooks[n]);
1036
executeHooks(hooks.processOptions, [options]);
1039
function setData(d) {
1040
series = parseData(d);
1041
fillInSeriesOptions();
1045
function parseData(d) {
1047
for (let i = 0; i < d.length; ++i) {
1048
let s = $.extend(true, {}, options.series);
1050
if (d[i].data != null) {
1054
$.extend(true, s, d[i]);
1057
} else s.data = d[i];
1064
function axisNumber(obj, coord) {
1065
var a = obj[coord + 'axis'];
1066
if (typeof a == 'object')
1069
if (typeof a != 'number') a = 1;
1073
function allAxes() {
1075
return $.grep(xaxes.concat(yaxes), function(a) {
1080
function canvasToAxisCoords(pos) {
1085
for (i = 0; i < xaxes.length; ++i) {
1087
if (axis) res['x' + axis.n] = axis.c2p(pos.left);
1090
for (i = 0; i < yaxes.length; ++i) {
1092
if (axis) res['y' + axis.n] = axis.c2p(pos.top);
1095
if (res.x1 !== undefined) res.x = res.x1;
1096
if (res.y1 !== undefined) res.y = res.y1;
1101
function axisToCanvasCoords(pos) {
1108
for (i = 0; i < xaxes.length; ++i) {
1110
if (axis && axis.used) {
1112
if (pos[key] == null && axis.n == 1) key = 'x';
1114
if (pos[key] != null) {
1115
res.left = axis.p2c(pos[key]);
1121
for (i = 0; i < yaxes.length; ++i) {
1123
if (axis && axis.used) {
1125
if (pos[key] == null && axis.n == 1) key = 'y';
1127
if (pos[key] != null) {
1128
res.top = axis.p2c(pos[key]);
1137
function getOrCreateAxis(axes, number) {
1138
if (!axes[number - 1])
1139
axes[number - 1] = {
1141
direction: axes == xaxes ? 'x' : 'y',
1142
options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis),
1145
return axes[number - 1];
1148
function fillInSeriesOptions() {
1149
var neededColors = series.length,
1156
for (i = 0; i < series.length; ++i) {
1157
let sc = series[i].color;
1160
if (typeof sc == 'number' && sc > maxIndex) {
1169
if (neededColors <= maxIndex) {
1170
neededColors = maxIndex + 1;
1178
colorPool = options.colors,
1179
colorPoolSize = colorPool.length,
1182
for (i = 0; i < neededColors; i++) {
1183
c = $.color.parse(colorPool[i % colorPoolSize] || '#666');
1193
if (i % colorPoolSize == 0 && i) {
1194
if (variation >= 0) {
1195
if (variation < 0.5) {
1196
variation = -variation - 0.2;
1197
} else variation = 0;
1198
} else variation = -variation;
1201
colors[i] = c.scale('rgb', 1 + variation);
1208
for (i = 0; i < series.length; ++i) {
1212
if (s.color == null) {
1213
s.color = colors[colori].toString();
1215
} else if (typeof s.color == 'number') s.color = colors[s.color].toString();
1218
if (s.lines.show == null) {
1222
if (s[v] && s[v].show) {
1226
if (show) s.lines.show = true;
1232
if (s.lines.zero == null) {
1233
s.lines.zero = !!s.lines.fill;
1237
s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, 'x'));
1238
s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, 'y'));
1242
function processData() {
1243
let topSentry = Number.POSITIVE_INFINITY,
1244
bottomSentry = Number.NEGATIVE_INFINITY,
1245
fakeInfinity = Number.MAX_VALUE,
1259
function updateAxis(axis, min, max) {
1260
if (min < axis.datamin && min != -fakeInfinity) axis.datamin = min;
1261
if (max > axis.datamax && max != fakeInfinity) axis.datamax = max;
1264
$.each(allAxes(), function(_, axis) {
1266
axis.datamin = topSentry;
1267
axis.datamax = bottomSentry;
1271
for (i = 0; i < series.length; ++i) {
1273
s.datapoints = { points: [] };
1275
executeHooks(hooks.processRawData, [s, s.data, s.datapoints]);
1279
for (i = 0; i < series.length; ++i) {
1283
format = s.datapoints.format;
1288
format.push({ x: true, number: true, required: true });
1289
format.push({ y: true, number: true, required: true });
1291
if (s.stack || s.bars.show || (s.lines.show && s.lines.fill)) {
1292
let autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
1293
format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
1294
if (s.bars.horizontal) {
1295
delete format[format.length - 1].y;
1296
format[format.length - 1].x = true;
1300
s.datapoints.format = format;
1303
if (s.datapoints.pointsize != null) continue;
1305
s.datapoints.pointsize = format.length;
1307
ps = s.datapoints.pointsize;
1308
points = s.datapoints.points;
1310
let insertSteps = s.lines.show && s.lines.steps;
1311
s.xaxis.used = s.yaxis.used = true;
1313
for (j = k = 0; j < data.length; ++j, k += ps) {
1316
let nullify = p == null;
1318
for (m = 0; m < ps; ++m) {
1323
if (f.number && val != null) {
1325
if (isNaN(val)) val = null;
1326
else if (val == Infinity) val = fakeInfinity;
1327
else if (val == -Infinity) val = -fakeInfinity;
1331
if (f.required) nullify = true;
1333
if (f.defaultValue != null) val = f.defaultValue;
1337
points[k + m] = val;
1342
for (m = 0; m < ps; ++m) {
1343
val = points[k + m];
1347
if (f.autoscale !== false) {
1349
updateAxis(s.xaxis, val, val);
1352
updateAxis(s.yaxis, val, val);
1356
points[k + m] = null;
1360
if (insertSteps && k > 0 && (!nullify || points[k - ps] != null)) {
1362
for (m = 0; m < ps; ++m) points[k + ps + m] = points[k + m];
1365
points[k + 1] = points[k - ps + 1] || 0;
1368
if (nullify) points[k] = p[0];
1377
for (i = 0; i < series.length; ++i) {
1379
points = s.datapoints.points;
1380
ps = s.datapoints.pointsize;
1383
if (s.transform === 'negative-Y') {
1384
for (j = 0; j < points.length; j += ps) {
1385
if (points[j] == null) continue;
1387
val = points[j + 1];
1388
points[j + 1] = -val;
1392
executeHooks(hooks.processDatapoints, [s, s.datapoints]);
1396
for (i = 0; i < series.length; ++i) {
1398
points = s.datapoints.points;
1399
ps = s.datapoints.pointsize;
1400
format = s.datapoints.format;
1402
var xmin = topSentry,
1404
xmax = bottomSentry,
1405
ymax = bottomSentry;
1407
for (j = 0; j < points.length; j += ps) {
1408
if (points[j] == null) continue;
1410
for (m = 0; m < ps; ++m) {
1411
val = points[j + m];
1413
if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) continue;
1416
if (val < xmin) xmin = val;
1417
if (val > xmax) xmax = val;
1420
if (val < ymin) ymin = val;
1421
if (val > ymax) ymax = val;
1430
switch (s.bars.align) {
1435
delta = -s.bars.barWidth;
1438
delta = -s.bars.barWidth / 2;
1441
if (s.bars.horizontal) {
1443
ymax += delta + s.bars.barWidth;
1446
xmax += delta + s.bars.barWidth;
1450
updateAxis(s.xaxis, xmin, xmax);
1451
updateAxis(s.yaxis, ymin, ymax);
1454
$.each(allAxes(), function(_, axis) {
1455
if (axis.datamin == topSentry) axis.datamin = null;
1456
if (axis.datamax == bottomSentry) axis.datamax = null;
1460
function setupCanvases() {
1464
placeholder.find('.flot-temp-elem').remove();
1466
if (placeholder.css('position') == 'static') placeholder.css('position', 'relative');
1468
surface = new Canvas('flot-base', placeholder);
1469
overlay = new Canvas('flot-overlay', placeholder);
1471
ctx = surface.context;
1472
octx = overlay.context;
1475
eventHolder = $(overlay.element).unbind();
1479
var existing = placeholder.data('plot');
1482
existing.shutdown();
1487
placeholder.data('plot', plot);
1490
function bindEvents() {
1492
if (options.grid.hoverable) {
1493
eventHolder.mousemove(onMouseMove);
1501
eventHolder.bind('mouseleave', onMouseLeave);
1504
if (options.grid.clickable) eventHolder.click(onClick);
1506
executeHooks(hooks.bindEvents, [eventHolder]);
1509
function shutdown() {
1510
if (redrawTimeout) clearTimeout(redrawTimeout);
1512
eventHolder.unbind('mousemove', onMouseMove);
1513
eventHolder.unbind('mouseleave', onMouseLeave);
1514
eventHolder.unbind('click', onClick);
1516
executeHooks(hooks.shutdown, [eventHolder]);
1519
function setTransformationHelpers(axis) {
1523
function identity(x) {
1529
t = axis.options.transform || identity,
1530
it = axis.options.inverseTransform;
1534
if (axis.direction == 'x') {
1535
s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
1536
m = Math.min(t(axis.max), t(axis.min));
1538
s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
1540
m = Math.max(t(axis.max), t(axis.min));
1546
axis.p2c = function(p) {
1550
axis.p2c = function(p) {
1551
return (t(p) - m) * s;
1555
axis.c2p = function(c) {
1559
axis.c2p = function(c) {
1560
return it(m + c / s);
1564
function measureTickLabels(axis) {
1565
let opts = axis.options,
1566
ticks = axis.ticks || [],
1567
labelWidth = opts.labelWidth || 0,
1568
labelHeight = opts.labelHeight || 0,
1569
maxWidth = labelWidth || (axis.direction == 'x' ? Math.floor(surface.width / (ticks.length || 1)) : null),
1570
legacyStyles = axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis',
1571
layer = 'flot-' + axis.direction + '-axis flot-' + axis.direction + axis.n + '-axis ' + legacyStyles,
1572
font = opts.font || 'flot-tick-label tickLabel';
1574
for (let i = 0; i < ticks.length; ++i) {
1577
if (!t.label) continue;
1579
let info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
1582
labelWidth = Math.max(labelWidth, info.width + 1);
1583
labelHeight = Math.max(labelHeight, info.height);
1586
axis.labelWidth = opts.labelWidth || labelWidth;
1587
axis.labelHeight = opts.labelHeight || labelHeight;
1590
function allocateAxisBoxFirstPhase(axis) {
1597
let lw = axis.labelWidth,
1598
lh = axis.labelHeight,
1599
pos = axis.options.position,
1600
isXAxis = axis.direction === 'x',
1601
tickLength = axis.options.tickLength,
1602
axisMargin = options.grid.axisMargin,
1603
padding = options.grid.labelMargin,
1604
eventSectionPadding = options.grid.eventSectionHeight,
1612
$.each(isXAxis ? xaxes : yaxes, function(i, a) {
1613
if (a && (a.show || a.reserveSpace)) {
1616
} else if (a.options.position === pos) {
1637
if (tickLength == null) {
1638
tickLength = first ? 'full' : 5;
1641
if (!isNaN(+tickLength)) padding += +tickLength;
1646
lh += eventSectionPadding;
1648
if (pos == 'bottom') {
1649
plotOffset.bottom += lh + axisMargin;
1650
axis.box = { top: surface.height - plotOffset.bottom, height: lh };
1652
axis.box = { top: plotOffset.top + axisMargin, height: lh };
1653
plotOffset.top += lh + axisMargin;
1658
if (pos == 'left') {
1659
axis.box = { left: plotOffset.left + axisMargin, width: lw };
1660
plotOffset.left += lw + axisMargin;
1662
plotOffset.right += lw + axisMargin;
1663
axis.box = { left: surface.width - plotOffset.right, width: lw };
1668
axis.position = pos;
1669
axis.tickLength = tickLength;
1670
axis.box.padding = padding;
1671
axis.box.eventSectionPadding = eventSectionPadding;
1672
axis.innermost = innermost;
1675
function allocateAxisBoxSecondPhase(axis) {
1678
if (axis.direction == 'x') {
1679
axis.box.left = plotOffset.left - axis.labelWidth / 2;
1680
axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
1682
axis.box.top = plotOffset.top - axis.labelHeight / 2;
1683
axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
1687
function adjustLayoutForThingsStickingOut() {
1691
let minMargin = options.grid.minBorderMargin,
1697
if (minMargin == null) {
1699
for (i = 0; i < series.length; ++i)
1700
minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth / 2));
1713
$.each(allAxes(), function(_, axis) {
1714
if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
1715
if (axis.direction === 'x') {
1716
margins.left = Math.max(margins.left, axis.labelWidth / 2);
1717
margins.right = Math.max(margins.right, axis.labelWidth / 2);
1719
margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
1720
margins.top = Math.max(margins.top, axis.labelHeight / 2);
1725
plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
1726
plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
1727
plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
1728
plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
1731
function setupGrid() {
1734
showGrid = options.grid.show;
1738
for (var a in plotOffset) {
1739
let margin = options.grid.margin || 0;
1740
plotOffset[a] = typeof margin == 'number' ? margin : margin[a] || 0;
1743
executeHooks(hooks.processOffset, [plotOffset]);
1747
for (var a in plotOffset) {
1748
if (typeof options.grid.borderWidth == 'object') {
1749
plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
1751
plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
1755
$.each(axes, function(_, axis) {
1756
let axisOpts = axis.options;
1757
axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
1758
axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
1762
executeHooks(hooks.processRange, []);
1765
var allocatedAxes = $.grep(axes, function(axis) {
1766
return axis.show || axis.reserveSpace;
1770
for (var i = 0; i < 2; i++) {
1771
$.each(allocatedAxes, function(_, axis) {
1773
setupTickGeneration(axis);
1775
snaped = snapRangeToTicks(axis, axis.ticks) || snaped;
1777
measureTickLabels(axis);
1780
if (snaped && hooks.processRange.length > 0) {
1781
executeHooks(hooks.processRange, []);
1791
for (i = allocatedAxes.length - 1; i >= 0; --i) allocateAxisBoxFirstPhase(allocatedAxes[i]);
1795
adjustLayoutForThingsStickingOut();
1797
$.each(allocatedAxes, function(_, axis) {
1798
allocateAxisBoxSecondPhase(axis);
1802
plotWidth = surface.width - plotOffset.left - plotOffset.right;
1803
plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
1806
$.each(axes, function(_, axis) {
1807
setTransformationHelpers(axis);
1817
function setRange(axis) {
1818
let opts = axis.options,
1819
min = +(opts.min != null ? opts.min : axis.datamin),
1820
max = +(opts.max != null ? opts.max : axis.datamax),
1826
let wideFactor = 0.25;
1827
let widen = Math.abs(max == 0 ? 1 : max * wideFactor);
1829
if (opts.min == null) {
1834
if (opts.max == null || opts.min != null) {
1839
let margin = opts.autoscaleMargin;
1840
if (margin != null) {
1841
if (opts.min == null) {
1842
min -= delta * margin;
1845
if (min < 0 && axis.datamin != null && axis.datamin >= 0) min = 0;
1847
if (opts.max == null) {
1848
max += delta * margin;
1849
if (max > 0 && axis.datamax != null && axis.datamax <= 0) max = 0;
1857
function setupTickGeneration(axis) {
1858
let opts = axis.options;
1862
if (typeof opts.ticks == 'number' && opts.ticks > 0) noTicks = opts.ticks;
1865
else noTicks = 0.3 * Math.sqrt(axis.direction == 'x' ? surface.width : surface.height);
1867
let delta = (axis.max - axis.min) / noTicks,
1868
dec = -Math.floor(Math.log(delta) / Math.LN10),
1869
maxDec = opts.tickDecimals;
1871
if (maxDec != null && dec > maxDec) {
1875
let magn = Math.pow(10, -dec),
1876
norm = delta / magn,
1881
} else if (norm < 3) {
1884
if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1888
} else if (norm < 7.5) {
1896
if (opts.minTickSize != null && size < opts.minTickSize) {
1897
size = opts.minTickSize;
1901
axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1902
axis.tickSize = opts.tickSize || size;
1905
if (opts.tickDecimals === null || opts.tickDecimals === undefined) {
1906
axis.scaledDecimals = axis.tickDecimals + dec;
1912
if (opts.mode == 'time' && !axis.tickGenerator) {
1913
throw new Error('Time mode requires the flot.time plugin.');
1919
if (!axis.tickGenerator) {
1920
axis.tickGenerator = function(axis) {
1922
start = floorInBase(axis.min, axis.tickSize),
1929
v = start + i * axis.tickSize;
1932
} while (v < axis.max && v != prev);
1936
axis.tickFormatter = function(value, axis) {
1937
let factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
1938
var formatted = '' + Math.round(value * factor) / factor;
1943
if (axis.tickDecimals != null) {
1944
var decimal = formatted.indexOf('.');
1945
let precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
1946
if (precision < axis.tickDecimals) {
1947
return (precision ? formatted : formatted + '.') + ('' + factor).substr(1, axis.tickDecimals - precision);
1955
if ($.isFunction(opts.tickFormatter))
1956
axis.tickFormatter = function(v, axis) {
1957
return '' + opts.tickFormatter(v, axis);
1960
if (opts.alignTicksWithAxis != null) {
1961
var otherAxis = (axis.direction == 'x' ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
1962
if (otherAxis && otherAxis.used && otherAxis != axis) {
1964
let niceTicks = axis.tickGenerator(axis);
1965
if (niceTicks.length > 0) {
1966
if (opts.min == null) axis.min = Math.min(axis.min, niceTicks[0]);
1967
if (opts.max == null && niceTicks.length > 1) axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
1970
axis.tickGenerator = function(axis) {
1975
for (i = 0; i < otherAxis.ticks.length; ++i) {
1976
v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
1977
v = axis.min + v * (axis.max - axis.min);
1985
if (!axis.mode && opts.tickDecimals == null) {
1986
let extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
1987
ts = axis.tickGenerator(axis);
1992
if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) axis.tickDecimals = extraDec;
1998
function setTicks(axis) {
1999
var oticks = axis.options.ticks,
2001
if (oticks == null || (typeof oticks == 'number' && oticks > 0)) ticks = axis.tickGenerator(axis);
2003
if ($.isFunction(oticks))
2005
ticks = oticks(axis);
2006
else ticks = oticks;
2012
for (i = 0; i < ticks.length; ++i) {
2015
if (typeof t == 'object') {
2017
if (t.length > 1) label = t[1];
2019
if (label == null) label = axis.tickFormatter(v, axis);
2020
if (!isNaN(v)) axis.ticks.push({ v: v, label: label });
2024
function snapRangeToTicks(axis, ticks) {
2025
let changed = false;
2026
if (axis.options.autoscaleMargin && ticks.length > 0) {
2028
if (axis.options.min == null) {
2029
axis.min = Math.min(axis.min, ticks[0].v);
2032
if (axis.options.max == null && ticks.length > 1) {
2033
axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
2043
executeHooks(hooks.drawBackground, [ctx]);
2045
let grid = options.grid;
2048
if (grid.show && grid.backgroundColor) drawBackground();
2050
if (grid.show && !grid.aboveData) {
2054
for (let i = 0; i < series.length; ++i) {
2055
executeHooks(hooks.drawSeries, [ctx, series[i]]);
2056
drawSeries(series[i]);
2059
executeHooks(hooks.draw, [ctx]);
2061
if (grid.show && grid.aboveData) {
2070
triggerRedrawOverlay();
2073
function extractRange(ranges, coord) {
2080
for (let i = 0; i < axes.length; ++i) {
2082
if (axis.direction == coord) {
2083
key = coord + axis.n + 'axis';
2084
if (!ranges[key] && axis.n == 1) key = coord + 'axis';
2086
from = ranges[key].from;
2087
to = ranges[key].to;
2095
axis = coord == 'x' ? xaxes[0] : yaxes[0];
2096
from = ranges[coord + '1'];
2097
to = ranges[coord + '2'];
2101
if (from != null && to != null && from > to) {
2107
return { from: from, to: to, axis: axis };
2110
function drawBackground() {
2112
ctx.translate(plotOffset.left, plotOffset.top);
2114
ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, 'rgba(255, 255, 255, 0)');
2115
ctx.fillRect(0, 0, plotWidth, plotHeight);
2119
function drawGrid() {
2120
let i, axes, bw, bc;
2123
ctx.translate(plotOffset.left, plotOffset.top);
2126
let markings = options.grid.markings;
2128
if ($.isFunction(markings)) {
2129
axes = plot.getAxes();
2132
axes.xmin = axes.xaxis.min;
2133
axes.xmax = axes.xaxis.max;
2134
axes.ymin = axes.yaxis.min;
2135
axes.ymax = axes.yaxis.max;
2137
markings = markings(axes);
2140
for (i = 0; i < markings.length; ++i) {
2141
let m = markings[i],
2142
xrange = extractRange(m, 'x'),
2143
yrange = extractRange(m, 'y');
2146
if (xrange.from == null) xrange.from = xrange.axis.min;
2147
if (xrange.to == null) xrange.to = xrange.axis.max;
2148
if (yrange.from == null) yrange.from = yrange.axis.min;
2149
if (yrange.to == null) yrange.to = yrange.axis.max;
2153
xrange.to < xrange.axis.min ||
2154
xrange.from > xrange.axis.max ||
2155
yrange.to < yrange.axis.min ||
2156
yrange.from > yrange.axis.max
2160
xrange.from = Math.max(xrange.from, xrange.axis.min);
2161
xrange.to = Math.min(xrange.to, xrange.axis.max);
2162
yrange.from = Math.max(yrange.from, yrange.axis.min);
2163
yrange.to = Math.min(yrange.to, yrange.axis.max);
2165
let xequal = xrange.from === xrange.to,
2166
yequal = yrange.from === yrange.to;
2168
if (xequal && yequal) {
2173
xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
2174
xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
2175
yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
2176
yrange.to = Math.floor(yrange.axis.p2c(yrange.to));
2178
if (xequal || yequal) {
2179
let lineWidth = m.lineWidth || options.grid.markingsLineWidth,
2180
subPixel = lineWidth % 2 ? 0.5 : 0;
2182
ctx.strokeStyle = m.color || options.grid.markingsColor;
2183
ctx.lineWidth = lineWidth;
2185
ctx.moveTo(xrange.to + subPixel, yrange.from);
2186
ctx.lineTo(xrange.to + subPixel, yrange.to);
2188
ctx.moveTo(xrange.from, yrange.to + subPixel);
2189
ctx.lineTo(xrange.to, yrange.to + subPixel);
2193
ctx.fillStyle = m.color || options.grid.markingsColor;
2194
ctx.fillRect(xrange.from, yrange.to, xrange.to - xrange.from, yrange.from - yrange.to);
2201
bw = options.grid.borderWidth;
2203
for (let j = 0; j < axes.length; ++j) {
2206
t = axis.tickLength,
2211
if (!axis.show || axis.ticks.length == 0) continue;
2216
if (axis.direction == 'x') {
2218
if (t == 'full') y = axis.position == 'top' ? 0 : plotHeight;
2219
else y = box.top - plotOffset.top + (axis.position == 'top' ? box.height : 0);
2222
if (t == 'full') x = axis.position == 'left' ? 0 : plotWidth;
2223
else x = box.left - plotOffset.left + (axis.position == 'left' ? box.width : 0);
2227
if (!axis.innermost) {
2228
ctx.strokeStyle = axis.options.color;
2231
if (axis.direction == 'x') xoff = plotWidth + 1;
2232
else yoff = plotHeight + 1;
2234
if (ctx.lineWidth == 1) {
2235
if (axis.direction == 'x') {
2236
y = Math.floor(y) + 0.5;
2238
x = Math.floor(x) + 0.5;
2243
ctx.lineTo(x + xoff, y + yoff);
2249
ctx.strokeStyle = axis.options.tickColor;
2252
for (i = 0; i < axis.ticks.length; ++i) {
2253
let v = axis.ticks[i].v;
2262
(t == 'full' && ((typeof bw == 'object' && bw[axis.position] > 0) || bw > 0) && (v == axis.min || v == axis.max))
2266
if (axis.direction == 'x') {
2268
yoff = t == 'full' ? -plotHeight : t;
2270
if (axis.position == 'top') yoff = -yoff;
2273
xoff = t == 'full' ? -plotWidth : t;
2275
if (axis.position == 'left') xoff = -xoff;
2278
if (ctx.lineWidth == 1) {
2279
if (axis.direction == 'x') x = Math.floor(x) + 0.5;
2280
else y = Math.floor(y) + 0.5;
2284
ctx.lineTo(x + xoff, y + yoff);
2294
bc = options.grid.borderColor;
2295
if (typeof bw == 'object' || typeof bc == 'object') {
2296
if (typeof bw !== 'object') {
2297
bw = { top: bw, right: bw, bottom: bw, left: bw };
2299
if (typeof bc !== 'object') {
2300
bc = { top: bc, right: bc, bottom: bc, left: bc };
2304
ctx.strokeStyle = bc.top;
2305
ctx.lineWidth = bw.top;
2307
ctx.moveTo(0 - bw.left, 0 - bw.top / 2);
2308
ctx.lineTo(plotWidth, 0 - bw.top / 2);
2313
ctx.strokeStyle = bc.right;
2314
ctx.lineWidth = bw.right;
2316
ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
2317
ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
2321
if (bw.bottom > 0) {
2322
ctx.strokeStyle = bc.bottom;
2323
ctx.lineWidth = bw.bottom;
2325
ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
2326
ctx.lineTo(0, plotHeight + bw.bottom / 2);
2331
ctx.strokeStyle = bc.left;
2332
ctx.lineWidth = bw.left;
2334
ctx.moveTo(0 - bw.left / 2, plotHeight + bw.bottom);
2335
ctx.lineTo(0 - bw.left / 2, 0);
2340
ctx.strokeStyle = options.grid.borderColor;
2341
ctx.strokeRect(-bw / 2, -bw / 2, plotWidth + bw, plotHeight + bw);
2348
function drawAxisLabels() {
2349
$.each(allAxes(), function(_, axis) {
2351
legacyStyles = axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis',
2352
layer = 'flot-' + axis.direction + '-axis flot-' + axis.direction + axis.n + '-axis ' + legacyStyles,
2353
font = axis.options.font || 'flot-tick-label tickLabel',
2364
surface.removeText(layer);
2366
if (!axis.show || axis.ticks.length == 0) return;
2368
for (let i = 0; i < axis.ticks.length; ++i) {
2369
tick = axis.ticks[i];
2370
if (!tick.label || tick.v < axis.min || tick.v > axis.max) continue;
2372
if (axis.direction == 'x') {
2374
x = plotOffset.left + axis.p2c(tick.v);
2375
if (axis.position == 'bottom') {
2376
y = box.top + box.padding + box.eventSectionPadding;
2378
y = box.top + box.height - box.padding;
2383
y = plotOffset.top + axis.p2c(tick.v);
2384
if (axis.position == 'left') {
2385
x = box.left + box.width - box.padding;
2388
x = box.left + box.padding;
2392
surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
2397
function drawOrphanedPoints(series) {
2401
let abandonedPoints = [];
2404
let datapoints = series.datapoints;
2406
let emptyPoints = [];
2407
for (let j = 0; j < datapoints.pointsize - 2; j++) {
2408
emptyPoints.push(0);
2410
for (let i = 0; i < datapoints.points.length; i += datapoints.pointsize) {
2411
var x = datapoints.points[i],
2412
y = datapoints.points[i + 1];
2413
if (i === datapoints.points.length - datapoints.pointsize) {
2416
afterX = datapoints.points[i + datapoints.pointsize];
2418
if (x !== null && y !== null && beforeX === null && afterX === null) {
2419
abandonedPoints.push(x);
2420
abandonedPoints.push(y);
2421
abandonedPoints.push.apply(abandonedPoints, emptyPoints);
2425
var olddatapoints = datapoints.points;
2426
datapoints.points = abandonedPoints;
2428
series.points.radius = series.lines.lineWidth / 2;
2430
drawSeriesPoints(series);
2432
datapoints.points = olddatapoints;
2435
function drawSeries(series) {
2436
if (series.lines.show) {
2437
drawSeriesLines(series);
2438
if (!series.points.show && !series.bars.show) {
2440
drawOrphanedPoints(series);
2443
if (series.bars.show) drawSeriesBars(series);
2444
if (series.points.show) drawSeriesPoints(series);
2447
function drawSeriesLines(series) {
2448
function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
2449
let points = datapoints.points,
2450
ps = datapoints.pointsize,
2455
for (let i = ps; i < points.length; i += ps) {
2456
var x1 = points[i - ps],
2457
y1 = points[i - ps + 1],
2461
if (x1 == null || x2 == null) continue;
2464
if (y1 <= y2 && y1 < axisy.min) {
2465
if (y2 < axisy.min) continue;
2467
x1 = ((axisy.min - y1) / (y2 - y1)) * (x2 - x1) + x1;
2469
} else if (y2 <= y1 && y2 < axisy.min) {
2470
if (y1 < axisy.min) continue;
2471
x2 = ((axisy.min - y1) / (y2 - y1)) * (x2 - x1) + x1;
2476
if (y1 >= y2 && y1 > axisy.max) {
2477
if (y2 > axisy.max) continue;
2478
x1 = ((axisy.max - y1) / (y2 - y1)) * (x2 - x1) + x1;
2480
} else if (y2 >= y1 && y2 > axisy.max) {
2481
if (y1 > axisy.max) continue;
2482
x2 = ((axisy.max - y1) / (y2 - y1)) * (x2 - x1) + x1;
2487
if (x1 <= x2 && x1 < axisx.min) {
2488
if (x2 < axisx.min) continue;
2489
y1 = ((axisx.min - x1) / (x2 - x1)) * (y2 - y1) + y1;
2491
} else if (x2 <= x1 && x2 < axisx.min) {
2492
if (x1 < axisx.min) continue;
2493
y2 = ((axisx.min - x1) / (x2 - x1)) * (y2 - y1) + y1;
2498
if (x1 >= x2 && x1 > axisx.max) {
2499
if (x2 > axisx.max) continue;
2500
y1 = ((axisx.max - x1) / (x2 - x1)) * (y2 - y1) + y1;
2502
} else if (x2 >= x1 && x2 > axisx.max) {
2503
if (x1 > axisx.max) continue;
2504
y2 = ((axisx.max - x1) / (x2 - x1)) * (y2 - y1) + y1;
2508
if (x1 != prevx || y1 != prevy) ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
2512
ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
2517
function plotLineArea(datapoints, axisx, axisy) {
2518
let points = datapoints.points,
2519
ps = datapoints.pointsize,
2520
bottom = Math.min(Math.max(0, axisy.min), axisy.max),
2531
if (ps > 0 && i > points.length + ps) break;
2535
let x1 = points[i - ps],
2536
y1 = points[i - ps + ypos],
2538
y2 = points[i + ypos];
2541
if (ps > 0 && x1 != null && x2 == null) {
2549
if (ps < 0 && i == segmentStart + ps) {
2555
i = segmentStart = segmentEnd + ps;
2560
if (x1 == null || x2 == null) continue;
2565
if (x1 <= x2 && x1 < axisx.min) {
2566
if (x2 < axisx.min) continue;
2567
y1 = ((axisx.min - x1) / (x2 - x1)) * (y2 - y1) + y1;
2569
} else if (x2 <= x1 && x2 < axisx.min) {
2570
if (x1 < axisx.min) continue;
2571
y2 = ((axisx.min - x1) / (x2 - x1)) * (y2 - y1) + y1;
2576
if (x1 >= x2 && x1 > axisx.max) {
2577
if (x2 > axisx.max) continue;
2578
y1 = ((axisx.max - x1) / (x2 - x1)) * (y2 - y1) + y1;
2580
} else if (x2 >= x1 && x2 > axisx.max) {
2581
if (x1 > axisx.max) continue;
2582
y2 = ((axisx.max - x1) / (x2 - x1)) * (y2 - y1) + y1;
2589
ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
2594
if (y1 >= axisy.max && y2 >= axisy.max) {
2595
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
2596
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
2598
} else if (y1 <= axisy.min && y2 <= axisy.min) {
2599
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
2600
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
2615
if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
2616
x1 = ((axisy.min - y1) / (y2 - y1)) * (x2 - x1) + x1;
2618
} else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
2619
x2 = ((axisy.min - y1) / (y2 - y1)) * (x2 - x1) + x1;
2624
if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
2625
x1 = ((axisy.max - y1) / (y2 - y1)) * (x2 - x1) + x1;
2627
} else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
2628
x2 = ((axisy.max - y1) / (y2 - y1)) * (x2 - x1) + x1;
2635
ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
2642
ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
2643
ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
2647
ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
2648
ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
2654
ctx.translate(plotOffset.left, plotOffset.top);
2655
ctx.lineJoin = 'round';
2657
let lw = series.lines.lineWidth,
2658
sw = series.shadowSize;
2660
if (lw > 0 && sw > 0) {
2663
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
2665
var angle = Math.PI / 18;
2668
Math.sin(angle) * (lw / 2 + sw / 2),
2669
Math.cos(angle) * (lw / 2 + sw / 2),
2673
ctx.lineWidth = sw / 2;
2676
Math.sin(angle) * (lw / 2 + sw / 4),
2677
Math.cos(angle) * (lw / 2 + sw / 4),
2684
ctx.strokeStyle = series.color;
2685
let fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
2687
ctx.fillStyle = fillStyle;
2688
plotLineArea(series.datapoints, series.xaxis, series.yaxis);
2691
if (lw > 0) plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
2695
function drawSeriesPoints(series) {
2696
function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
2697
var points = datapoints.points,
2698
ps = datapoints.pointsize;
2700
for (let i = 0; i < points.length; i += ps) {
2703
if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) continue;
2707
y = axisy.p2c(y) + offset;
2708
if (symbol == 'circle') ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
2709
else symbol(ctx, x, y, radius, shadow);
2713
ctx.fillStyle = fillStyle;
2721
ctx.translate(plotOffset.left, plotOffset.top);
2723
let lw = series.points.lineWidth,
2724
sw = series.shadowSize,
2725
radius = series.points.radius,
2726
symbol = series.points.symbol;
2733
if (lw == 0) lw = 0.0001;
2735
if (lw > 0 && sw > 0) {
2739
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
2740
plotPoints(series.datapoints, radius, null, w + w / 2, true, series.xaxis, series.yaxis, symbol);
2742
ctx.strokeStyle = 'rgba(0,0,0,0.2)';
2743
plotPoints(series.datapoints, radius, null, w / 2, true, series.xaxis, series.yaxis, symbol);
2747
ctx.strokeStyle = series.color;
2751
getFillStyle(series.points, series.color),
2761
function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2762
var left, right, bottom, top, drawLeft, drawRight, drawTop, drawBottom, tmp;
2768
drawBottom = drawRight = drawTop = true;
2773
bottom = y + barRight;
2784
drawLeft = drawRight = drawTop = true;
2787
right = x + barRight;
2802
if (right < axisx.min || left > axisx.max || top < axisy.min || bottom > axisy.max) return;
2804
if (left < axisx.min) {
2809
if (right > axisx.max) {
2814
if (bottom < axisy.min) {
2819
if (top > axisy.max) {
2824
left = axisx.p2c(left);
2825
bottom = axisy.p2c(bottom);
2826
right = axisx.p2c(right);
2827
top = axisy.p2c(top);
2830
if (fillStyleCallback) {
2831
c.fillStyle = fillStyleCallback(bottom, top);
2832
c.fillRect(left, top, right - left, bottom - top);
2836
if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
2840
c.moveTo(left, bottom);
2841
if (drawLeft) c.lineTo(left, top);
2842
else c.moveTo(left, top);
2843
if (drawTop) c.lineTo(right, top);
2844
else c.moveTo(right, top);
2845
if (drawRight) c.lineTo(right, bottom);
2846
else c.moveTo(right, bottom);
2847
if (drawBottom) c.lineTo(left, bottom);
2848
else c.moveTo(left, bottom);
2853
function drawSeriesBars(series) {
2854
function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
2855
var points = datapoints.points,
2856
ps = datapoints.pointsize;
2858
for (let i = 0; i < points.length; i += ps) {
2859
if (points[i] == null) continue;
2870
series.bars.horizontal,
2871
series.bars.lineWidth
2877
ctx.translate(plotOffset.left, plotOffset.top);
2880
ctx.lineWidth = series.bars.lineWidth;
2881
ctx.strokeStyle = series.color;
2885
switch (series.bars.align) {
2890
barLeft = -series.bars.barWidth;
2893
barLeft = -series.bars.barWidth / 2;
2896
var fillStyleCallback = series.bars.fill
2897
? function(bottom, top) {
2898
return getFillStyle(series.bars, series.color, bottom, top);
2901
plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
2905
function getFillStyle(filloptions, seriesColor, bottom, top) {
2906
let fill = filloptions.fill;
2907
if (!fill) return null;
2909
if (filloptions.fillColor) return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2911
let c = $.color.parse(seriesColor);
2912
c.a = typeof fill == 'number' ? fill : 0.4;
2914
return c.toString();
2917
function insertLegend() {
2918
if (options.legend.container != null) {
2919
$(options.legend.container).html('');
2921
placeholder.find('.legend').remove();
2924
if (!options.legend.show) {
2931
lf = options.legend.labelFormatter,
2937
for (var i = 0; i < series.length; ++i) {
2940
label = lf ? lf(s.label, s) : s.label;
2952
if (options.legend.sorted) {
2953
if ($.isFunction(options.legend.sorted)) {
2954
entries.sort(options.legend.sorted);
2955
} else if (options.legend.sorted == 'reverse') {
2958
var ascending = options.legend.sorted != 'descending';
2959
entries.sort(function(a, b) {
2961
return a.label == b.label ? 0 : a.label < b.label != ascending ? 1 : -1;
2968
for (var i = 0; i < entries.length; ++i) {
2969
let entry = entries[i];
2971
if (i % options.legend.noColumns == 0) {
2972
if (rowStarted) fragments.push('</tr>');
2973
fragments.push('<tr>');
2978
'<td class="legendColorBox"><div style="border:1px solid ' +
2979
options.legend.labelBoxBorderColor +
2980
';padding:1px"><div style="width:4px;height:0;border:5px solid ' +
2982
';overflow:hidden"></div></div></td>' +
2983
'<td class="legendLabel">' +
2989
if (rowStarted) fragments.push('</tr>');
2991
if (fragments.length == 0) return;
2993
var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join('') + '</table>';
2994
if (options.legend.container != null) $(options.legend.container).html(table);
2997
p = options.legend.position,
2998
m = options.legend.margin;
2999
if (m[0] == null) m = [m, m];
3000
if (p.charAt(0) == 'n') pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
3001
else if (p.charAt(0) == 's') pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
3002
if (p.charAt(1) == 'e') pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
3003
else if (p.charAt(1) == 'w') pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
3005
'<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos + ';') + '</div>'
3006
).appendTo(placeholder);
3007
if (options.legend.backgroundOpacity != 0.0) {
3011
let c = options.legend.backgroundColor;
3013
c = options.grid.backgroundColor;
3014
if (c && typeof c == 'string') c = $.color.parse(c);
3015
else c = $.color.extract(legend, 'background-color');
3019
let div = legend.children();
3021
'<div style="position:absolute;width:' +
3027
'background-color:' +
3032
.css('opacity', options.legend.backgroundOpacity);
3039
var highlights = [],
3040
redrawTimeout = null;
3043
function findNearbyItem(mouseX, mouseY, seriesFilter) {
3044
let maxDistance = options.grid.mouseActiveRadius,
3045
smallestDistance = maxDistance * maxDistance + 1,
3051
for (i = series.length - 1; i >= 0; --i) {
3052
if (!seriesFilter(series[i])) continue;
3057
points = s.datapoints.points,
3058
mx = axisx.c2p(mouseX),
3059
my = axisy.c2p(mouseY),
3060
maxx = maxDistance / axisx.scale,
3061
maxy = maxDistance / axisy.scale;
3063
ps = s.datapoints.pointsize;
3066
if (axisx.options.inverseTransform) maxx = Number.MAX_VALUE;
3067
if (axisy.options.inverseTransform) maxy = Number.MAX_VALUE;
3069
if (s.lines.show || s.points.show) {
3070
for (j = 0; j < points.length; j += ps) {
3073
if (x == null) continue;
3077
if (x - mx > maxx || x - mx < -maxx || y - my > maxy || y - my < -maxy) continue;
3081
let dx = Math.abs(axisx.p2c(x) - mouseX),
3082
dy = Math.abs(axisy.p2c(y) - mouseY),
3083
dist = dx * dx + dy * dy;
3087
if (dist < smallestDistance) {
3088
smallestDistance = dist;
3094
if (s.bars.show && !item) {
3097
var barLeft, barRight;
3099
switch (s.bars.align) {
3104
barLeft = -s.bars.barWidth;
3107
barLeft = -s.bars.barWidth / 2;
3110
barRight = barLeft + s.bars.barWidth;
3112
for (j = 0; j < points.length; j += ps) {
3116
if (x == null) continue;
3120
series[i].bars.horizontal
3121
? mx <= Math.max(b, x) && mx >= Math.min(b, x) && my >= y + barLeft && my <= y + barRight
3122
: mx >= x + barLeft && mx <= x + barRight && my >= Math.min(b, y) && my <= Math.max(b, y)
3132
ps = series[i].datapoints.pointsize;
3135
datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
3145
function onMouseMove(e) {
3146
if (options.grid.hoverable)
3147
triggerClickHoverEvent('plothover', e, function(s) {
3148
return s['hoverable'] != false;
3152
function onMouseLeave(e) {
3153
if (options.grid.hoverable)
3154
triggerClickHoverEvent('plothover', e, function() {
3159
function onClick(e) {
3160
if (plot.isSelecting) {
3164
triggerClickHoverEvent('plotclick', e, function(s) {
3165
return s['clickable'] != false;
3171
function triggerClickHoverEvent(eventname, event, seriesFilter) {
3172
let offset = eventHolder.offset(),
3173
canvasX = event.pageX - offset.left - plotOffset.left,
3174
canvasY = event.pageY - offset.top - plotOffset.top,
3175
pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
3177
pos.pageX = event.pageX;
3178
pos.pageY = event.pageY;
3181
pos.ctrlKey = event.ctrlKey;
3182
pos.metaKey = event.metaKey;
3184
let item = findNearbyItem(canvasX, canvasY, seriesFilter);
3188
item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
3189
item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
3192
if (options.grid.autoHighlight) {
3194
for (let i = 0; i < highlights.length; ++i) {
3195
let h = highlights[i];
3197
h.auto == eventname &&
3198
!(item && h.series == item.series && h.point[0] == item.datapoint[0] && h.point[1] == item.datapoint[1])
3200
unhighlight(h.series, h.point);
3203
if (item) highlight(item.series, item.datapoint, eventname);
3206
placeholder.trigger(eventname, [pos, item]);
3209
function triggerRedrawOverlay() {
3210
let t = options.interaction.redrawOverlayInterval;
3217
if (!redrawTimeout) redrawTimeout = setTimeout(drawOverlay, t);
3220
function drawOverlay() {
3221
redrawTimeout = null;
3226
octx.translate(plotOffset.left, plotOffset.top);
3229
for (i = 0; i < highlights.length; ++i) {
3232
if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point);
3233
else drawPointHighlight(hi.series, hi.point);
3237
executeHooks(hooks.drawOverlay, [octx]);
3240
function highlight(s, point, auto) {
3241
if (typeof s == 'number') s = series[s];
3243
if (typeof point == 'number') {
3244
let ps = s.datapoints.pointsize;
3245
point = s.datapoints.points.slice(ps * point, ps * (point + 1));
3248
let i = indexOfHighlight(s, point);
3250
highlights.push({ series: s, point: point, auto: auto });
3252
triggerRedrawOverlay();
3253
} else if (!auto) highlights[i].auto = false;
3256
function unhighlight(s, point) {
3257
if (s == null && point == null) {
3259
triggerRedrawOverlay();
3263
if (typeof s == 'number') s = series[s];
3265
if (typeof point == 'number') {
3266
let ps = s.datapoints.pointsize;
3267
point = s.datapoints.points.slice(ps * point, ps * (point + 1));
3270
let i = indexOfHighlight(s, point);
3272
highlights.splice(i, 1);
3274
triggerRedrawOverlay();
3278
function indexOfHighlight(s, p) {
3279
for (let i = 0; i < highlights.length; ++i) {
3280
let h = highlights[i];
3281
if (h.series == s && h.point[0] == p[0] && h.point[1] == p[1]) return i;
3286
function drawPointHighlight(series, point) {
3289
axisx = series.xaxis,
3290
axisy = series.yaxis,
3292
typeof series.highlightColor === 'string'
3293
? series.highlightColor
3295
.parse(series.color)
3299
if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) return;
3301
let pointRadius = series.points.radius + series.points.lineWidth / 2;
3302
octx.lineWidth = pointRadius;
3303
octx.strokeStyle = highlightColor;
3304
let radius = 1.5 * pointRadius;
3309
if (series.points.symbol == 'circle') octx.arc(x, y, radius, 0, 2 * Math.PI, false);
3310
else series.points.symbol(octx, x, y, radius, false);
3315
function drawBarHighlight(series, point) {
3316
var highlightColor =
3317
typeof series.highlightColor === 'string'
3318
? series.highlightColor
3320
.parse(series.color)
3323
fillStyle = highlightColor,
3326
switch (series.bars.align) {
3331
barLeft = -series.bars.barWidth;
3334
barLeft = -series.bars.barWidth / 2;
3337
octx.lineWidth = series.bars.lineWidth;
3338
octx.strokeStyle = highlightColor;
3345
barLeft + series.bars.barWidth,
3352
series.bars.horizontal,
3353
series.bars.lineWidth
3357
function getColorOrGradient(spec, bottom, top, defaultColor) {
3358
if (typeof spec == 'string') return spec;
3363
let gradient = ctx.createLinearGradient(0, top, 0, bottom);
3365
for (let i = 0, l = spec.colors.length; i < l; ++i) {
3366
let c = spec.colors[i];
3367
if (typeof c != 'string') {
3368
let co = $.color.parse(defaultColor);
3369
if (c.brightness != null) co = co.scale('rgb', c.brightness);
3370
if (c.opacity != null) co.a *= c.opacity;
3373
gradient.addColorStop(i / (l - 1), c);
3383
$.plot = function(placeholder, data, options) {
3385
let plot = new Plot($(placeholder), data, options, $.plot.plugins);
3390
$.plot.version = '0.8.3';
3392
$.plot.plugins = [];
3396
$.fn.plot = function(data, options) {
3397
return this.each(function() {
3398
$.plot(this, data, options);
3403
function floorInBase(n, base) {
3404
return base * Math.floor(n / base);