flocc 0.5.20 → 0.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/flocc.js CHANGED
@@ -3218,6 +3218,15 @@
3218
3218
  * such as a {@linkcode LineChartRenderer}, {@linkcode Histogram}, etc.
3219
3219
  */
3220
3220
  _this.renderers = [];
3221
+ /**
3222
+ * Whether the `Environment` tick cycle is currently playing.
3223
+ * Use {@linkcode pause}, {@linkcode resume}, or {@linkcode toggle}
3224
+ * to control playback.
3225
+ * @since 0.5.22
3226
+ */
3227
+ _this.playing = true;
3228
+ /** @hidden */
3229
+ _this._tickIntervalId = null;
3221
3230
  /**
3222
3231
  * This property will always equal the number of tick cycles that
3223
3232
  * have passed since the `Environment` was created. If you call
@@ -3387,6 +3396,9 @@
3387
3396
  * @since 0.0.5
3388
3397
  */
3389
3398
  Environment.prototype.tick = function (opts) {
3399
+ // If paused, skip the tick cycle (use `step()` to advance manually)
3400
+ if (!this.playing)
3401
+ return;
3390
3402
  var _a = this._getTickOptions(opts), activation = _a.activation, activationCount = _a.activationCount, count = _a.count, randomizeOrder = _a.randomizeOrder;
3391
3403
  // for uniform activation, every agent is always activated
3392
3404
  if (activation === "uniform") {
@@ -3454,6 +3466,47 @@
3454
3466
  }
3455
3467
  this.renderers.forEach(function (r) { return r.render(); });
3456
3468
  };
3469
+ /**
3470
+ * Pause the tick cycle. While paused, calling {@linkcode tick} will
3471
+ * be a no-op unless you use {@linkcode step} to advance manually.
3472
+ * @since 0.5.22
3473
+ */
3474
+ Environment.prototype.pause = function () {
3475
+ this.playing = false;
3476
+ };
3477
+ /**
3478
+ * Resume the tick cycle after it has been paused.
3479
+ * @since 0.5.22
3480
+ */
3481
+ Environment.prototype.resume = function () {
3482
+ this.playing = true;
3483
+ };
3484
+ /**
3485
+ * Toggle the tick cycle between playing and paused.
3486
+ * @since 0.5.22
3487
+ */
3488
+ Environment.prototype.toggle = function () {
3489
+ this.playing = !this.playing;
3490
+ };
3491
+ /**
3492
+ * Advance the `Environment` by exactly one tick, regardless of whether
3493
+ * it is paused. This is useful for stepping through the simulation
3494
+ * frame-by-frame while paused.
3495
+ *
3496
+ * ```js
3497
+ * environment.pause();
3498
+ * environment.step(); // advances one tick
3499
+ * ```
3500
+ *
3501
+ * @since 0.5.22
3502
+ */
3503
+ Environment.prototype.step = function (opts) {
3504
+ // Temporarily mark as playing so tick executes, then restore
3505
+ var wasPlaying = this.playing;
3506
+ this.playing = true;
3507
+ this.tick(opts);
3508
+ this.playing = wasPlaying;
3509
+ };
3457
3510
  /**
3458
3511
  * Use a helper with this environment. A helper can be one of:
3459
3512
  * - {@linkcode KDTree}
@@ -3990,7 +4043,10 @@
3990
4043
  width: 500,
3991
4044
  height: 500,
3992
4045
  scale: 1,
3993
- trace: false
4046
+ trace: false,
4047
+ interactive: false,
4048
+ zoomMin: 0.1,
4049
+ zoomMax: 10
3994
4050
  };
3995
4051
  /**
3996
4052
  * A `CanvasRenderer` renders an {@linkcode Environment} spatially in two dimensions.
@@ -4010,6 +4066,12 @@
4010
4066
  * - `"triangle"` — Draws a triangle centered at the `Agent`'s `"x"` / `"y"` values.
4011
4067
  * - Also uses the `"size"` value.
4012
4068
  *
4069
+ * When `interactive` is set to `true` in the options, the renderer supports:
4070
+ * - **Click/hover detection** — Use {@linkcode on} to listen for `"click"`, `"hover"`, and `"unhover"` events on agents.
4071
+ * - **Agent selection** — Clicking an agent selects it (highlighted with a stroke). Access selected agents via {@linkcode selected}.
4072
+ * - **Pan** — Click and drag on empty space to pan.
4073
+ * - **Zoom** — Scroll to zoom in/out (bounded by `zoomMin` / `zoomMax`).
4074
+ *
4013
4075
  * @since 0.0.11
4014
4076
  */
4015
4077
  var CanvasRenderer = /** @class */ (function (_super) {
@@ -4025,15 +4087,33 @@
4025
4087
  * - `connectionOpacity` (*number* = `1`) — For `Environment`s using a `Network`, the opacity of lines
4026
4088
  * - `connectionWidth` (*number* = `1`) — For `Environment`s using a `Network`, the width of lines
4027
4089
  * - `height` (*number* = `500`) — The height, in pixels, of the canvas on which to render
4090
+ * - `interactive` (*boolean* = `false`) — Enables interactive features (click/hover detection, selection, pan, zoom)
4091
+ * - `onSelect` (*function*) — Optional callback when an agent is selected or deselected
4028
4092
  * - `origin` (*{ x: number; y: number }* = `{ x: 0, y: 0 }`) — The coordinate of the upper-left point of the space to be rendered
4029
4093
  * - `scale` (*number* = `1`) — The scale at which to render (the larger the scale, the smaller the size of the space that is actually rendered)
4030
4094
  * - `trace` (*boolean* = `false`) — If `true`, the renderer will not clear old drawings, causing the `Agent`s to appear to *trace* their paths across space
4031
4095
  * - `width` (*number* = `500`) — The width, in pixels, of the canvas on which to render
4096
+ * - `zoomMin` (*number* = `0.1`) — Minimum scale when zooming
4097
+ * - `zoomMax` (*number* = `10`) — Maximum scale when zooming
4032
4098
  */
4033
4099
  function CanvasRenderer(environment, opts) {
4034
4100
  var _this = _super.call(this) || this;
4035
4101
  /** @hidden */
4036
4102
  _this.terrainBuffer = document.createElement("canvas");
4103
+ /** The currently selected agents (only used when `interactive` is `true`). */
4104
+ _this.selected = [];
4105
+ /** @hidden */
4106
+ _this._listeners = new Map();
4107
+ /** @hidden */
4108
+ _this._hoveredAgent = null;
4109
+ /** @hidden */
4110
+ _this._isPanning = false;
4111
+ /** @hidden */
4112
+ _this._panStart = null;
4113
+ /** @hidden */
4114
+ _this._panOriginStart = null;
4115
+ /** @hidden */
4116
+ _this._boundHandlers = {};
4037
4117
  _this.environment = environment;
4038
4118
  environment.renderers.push(_this);
4039
4119
  _this.opts = Object.assign({}, defaultOptions);
@@ -4050,9 +4130,158 @@
4050
4130
  _this.terrainBuffer.width = width;
4051
4131
  _this.terrainBuffer.height = height;
4052
4132
  _this.context.fillStyle = opts.background;
4053
- _this.context.fillRect(0, 0, width, height);
4133
+ _this.context.fillRect(0, 0, _this.width, _this.height);
4134
+ if (_this.opts.interactive) {
4135
+ _this._setupInteractiveListeners();
4136
+ }
4054
4137
  return _this;
4055
4138
  }
4139
+ /**
4140
+ * Register a callback for an interactive event.
4141
+ * Supported event names: `"click"`, `"hover"`, `"unhover"`.
4142
+ *
4143
+ * ```js
4144
+ * renderer.on("click", (agent, event) => {
4145
+ * console.log("Clicked agent:", agent.id);
4146
+ * });
4147
+ * ```
4148
+ *
4149
+ * @param eventName - The event to listen for.
4150
+ * @param callback - The callback, invoked with the `Agent` and the `MouseEvent`.
4151
+ */
4152
+ CanvasRenderer.prototype.on = function (eventName, callback) {
4153
+ if (!this._listeners.has(eventName)) {
4154
+ this._listeners.set(eventName, []);
4155
+ }
4156
+ this._listeners.get(eventName).push(callback);
4157
+ };
4158
+ /** @hidden */
4159
+ CanvasRenderer.prototype._emit = function (eventName, agent, event) {
4160
+ var callbacks = this._listeners.get(eventName);
4161
+ if (callbacks) {
4162
+ callbacks.forEach(function (cb) { return cb(agent, event); });
4163
+ }
4164
+ };
4165
+ /**
4166
+ * Given a mouse event, return the agent at that position (if any).
4167
+ * Hit-testing accounts for the agent's shape and size.
4168
+ * @hidden
4169
+ */
4170
+ CanvasRenderer.prototype._agentAtPoint = function (clientX, clientY) {
4171
+ var rect = this.canvas.getBoundingClientRect();
4172
+ var dpr = window.devicePixelRatio;
4173
+ var canvasX = (clientX - rect.left) * dpr;
4174
+ var canvasY = (clientY - rect.top) * dpr;
4175
+ var agents = this.environment.getAgents();
4176
+ // Iterate in reverse so topmost-drawn agent is found first
4177
+ for (var i = agents.length - 1; i >= 0; i--) {
4178
+ var agent = agents[i];
4179
+ var data = agent.getData();
4180
+ var ax = this.x(data.x);
4181
+ var ay = this.y(data.y);
4182
+ var shape = data.shape;
4183
+ var size = (data.size || 1) * dpr;
4184
+ if (shape === "rect") {
4185
+ var w = (data.width || 1) * dpr;
4186
+ var h = (data.height || 1) * dpr;
4187
+ var rx = ax - w / 2;
4188
+ var ry = ay - h / 2;
4189
+ if (canvasX >= rx && canvasX <= rx + w && canvasY >= ry && canvasY <= ry + h) {
4190
+ return agent;
4191
+ }
4192
+ }
4193
+ else if (shape === "triangle") {
4194
+ // Simple bounding-box hit test for triangles
4195
+ var halfSize = size / 2;
4196
+ if (canvasX >= ax - halfSize &&
4197
+ canvasX <= ax + halfSize &&
4198
+ canvasY >= ay - halfSize &&
4199
+ canvasY <= ay + halfSize) {
4200
+ return agent;
4201
+ }
4202
+ }
4203
+ else {
4204
+ // Default: circle (and arrow) — distance-based hit test
4205
+ var dx = canvasX - ax;
4206
+ var dy = canvasY - ay;
4207
+ var hitRadius = Math.max(size, 4 * dpr); // minimum hit area for tiny agents
4208
+ if (dx * dx + dy * dy <= hitRadius * hitRadius) {
4209
+ return agent;
4210
+ }
4211
+ }
4212
+ }
4213
+ return null;
4214
+ };
4215
+ /** @hidden */
4216
+ CanvasRenderer.prototype._setupInteractiveListeners = function () {
4217
+ var _this = this;
4218
+ var onMouseDown = function (e) {
4219
+ var agent = _this._agentAtPoint(e.clientX, e.clientY);
4220
+ if (agent) {
4221
+ // Agent click — select it
4222
+ _this.selected = [agent];
4223
+ if (_this.opts.onSelect)
4224
+ _this.opts.onSelect(agent);
4225
+ _this._emit("click", agent, e);
4226
+ _this.render();
4227
+ }
4228
+ else {
4229
+ // Empty space — deselect and start panning
4230
+ if (_this.selected.length > 0) {
4231
+ _this.selected = [];
4232
+ if (_this.opts.onSelect)
4233
+ _this.opts.onSelect(null);
4234
+ _this.render();
4235
+ }
4236
+ _this._isPanning = true;
4237
+ _this._panStart = { x: e.clientX, y: e.clientY };
4238
+ _this._panOriginStart = { x: _this.opts.origin.x, y: _this.opts.origin.y };
4239
+ }
4240
+ };
4241
+ var onMouseMove = function (e) {
4242
+ if (_this._isPanning && _this._panStart && _this._panOriginStart) {
4243
+ var dpr = window.devicePixelRatio;
4244
+ var dx = e.clientX - _this._panStart.x;
4245
+ var dy = e.clientY - _this._panStart.y;
4246
+ _this.opts.origin = {
4247
+ x: _this._panOriginStart.x - dx / (_this.opts.scale * dpr),
4248
+ y: _this._panOriginStart.y - dy / (_this.opts.scale * dpr)
4249
+ };
4250
+ _this.render();
4251
+ return;
4252
+ }
4253
+ // Hover detection
4254
+ var agent = _this._agentAtPoint(e.clientX, e.clientY);
4255
+ if (agent !== _this._hoveredAgent) {
4256
+ if (_this._hoveredAgent) {
4257
+ _this._emit("unhover", _this._hoveredAgent, e);
4258
+ }
4259
+ if (agent) {
4260
+ _this._emit("hover", agent, e);
4261
+ }
4262
+ _this._hoveredAgent = agent;
4263
+ }
4264
+ };
4265
+ var onMouseUp = function (e) {
4266
+ _this._isPanning = false;
4267
+ _this._panStart = null;
4268
+ _this._panOriginStart = null;
4269
+ };
4270
+ var onWheel = function (e) {
4271
+ e.preventDefault();
4272
+ var _a = _this.opts, zoomMin = _a.zoomMin, zoomMax = _a.zoomMax;
4273
+ var delta = e.deltaY > 0 ? 0.9 : 1.1;
4274
+ var newScale = _this.opts.scale * delta;
4275
+ newScale = Math.max(zoomMin, Math.min(zoomMax, newScale));
4276
+ _this.opts.scale = newScale;
4277
+ _this.render();
4278
+ };
4279
+ this._boundHandlers = { mousedown: onMouseDown, mousemove: onMouseMove, mouseup: onMouseUp, wheel: onWheel };
4280
+ this.canvas.addEventListener("mousedown", onMouseDown);
4281
+ this.canvas.addEventListener("mousemove", onMouseMove);
4282
+ this.canvas.addEventListener("mouseup", onMouseUp);
4283
+ this.canvas.addEventListener("wheel", onWheel, { passive: false });
4284
+ };
4056
4285
  /** @hidden */
4057
4286
  CanvasRenderer.prototype.x = function (v) {
4058
4287
  var _a = this.opts, origin = _a.origin, scale = _a.scale;
@@ -4089,21 +4318,21 @@
4089
4318
  };
4090
4319
  /** @hidden */
4091
4320
  CanvasRenderer.prototype.drawPathWrap = function (points) {
4092
- var _this = this;
4093
4321
  var _a = this, width = _a.width, height = _a.height;
4094
4322
  var right = false;
4095
4323
  var left = false;
4096
4324
  var lower = false;
4097
4325
  var upper = false;
4326
+ // points are already in DPR-scaled pixel space, so compare directly
4098
4327
  points.forEach(function (_a) {
4099
4328
  var px = _a[0], py = _a[1];
4100
- if (_this.x(px) >= width)
4329
+ if (px >= width)
4101
4330
  right = true;
4102
- if (_this.x(px) < 0)
4331
+ if (px < 0)
4103
4332
  left = true;
4104
- if (_this.y(py) >= height)
4333
+ if (py >= height)
4105
4334
  lower = true;
4106
- if (_this.y(py) < 0)
4335
+ if (py < 0)
4107
4336
  upper = true;
4108
4337
  });
4109
4338
  if (right)
@@ -4132,24 +4361,26 @@
4132
4361
  /** @hidden */
4133
4362
  CanvasRenderer.prototype.drawCircleWrap = function (x, y, size) {
4134
4363
  var _a = this, width = _a.width, height = _a.height;
4364
+ var worldWidth = this.opts.width;
4365
+ var worldHeight = this.opts.height;
4135
4366
  if (this.x(x + size) >= width) {
4136
- this.drawCircle(x - width, y, size);
4367
+ this.drawCircle(x - worldWidth, y, size);
4137
4368
  if (this.y(y + size) >= height)
4138
- this.drawCircle(x - width, y - height, size);
4369
+ this.drawCircle(x - worldWidth, y - worldHeight, size);
4139
4370
  if (this.y(y - size) < 0)
4140
- this.drawCircle(x - width, y + height, size);
4371
+ this.drawCircle(x - worldWidth, y + worldHeight, size);
4141
4372
  }
4142
4373
  if (this.x(x - size) < 0) {
4143
- this.drawCircle(x + width, y, size);
4374
+ this.drawCircle(x + worldWidth, y, size);
4144
4375
  if (this.y(y + size) >= height)
4145
- this.drawCircle(x + width, y - height, size);
4376
+ this.drawCircle(x + worldWidth, y - worldHeight, size);
4146
4377
  if (this.y(y - size) < 0)
4147
- this.drawCircle(x + width, y + height, size);
4378
+ this.drawCircle(x + worldWidth, y + worldHeight, size);
4148
4379
  }
4149
4380
  if (this.y(y + size) > height)
4150
- this.drawCircle(x, y - height, size);
4381
+ this.drawCircle(x, y - worldHeight, size);
4151
4382
  if (this.y(y - size) < 0)
4152
- this.drawCircle(x, y + height, size);
4383
+ this.drawCircle(x, y + worldHeight, size);
4153
4384
  };
4154
4385
  /**
4155
4386
  * Draw a rectangle centered at (x, y). Automatically calculates the offset
@@ -4163,25 +4394,55 @@
4163
4394
  };
4164
4395
  /** @hidden */
4165
4396
  CanvasRenderer.prototype.drawRectWrap = function (x, y, w, h) {
4166
- var _a = this.opts, width = _a.width, height = _a.height;
4397
+ var _a = this, width = _a.width, height = _a.height;
4398
+ var worldWidth = this.opts.width;
4399
+ var worldHeight = this.opts.height;
4167
4400
  if (this.x(x + w / 2) >= width) {
4168
- this.drawRect(x - width, y, w, h);
4401
+ this.drawRect(x - worldWidth, y, w, h);
4169
4402
  if (this.y(y + h / 2) >= height)
4170
- this.drawRect(x - width, y - height, w, h);
4171
- if (this.y(y - height / 2) < 0)
4172
- this.drawRect(x - width, y + height, w, h);
4403
+ this.drawRect(x - worldWidth, y - worldHeight, w, h);
4404
+ if (this.y(y - h / 2) < 0)
4405
+ this.drawRect(x - worldWidth, y + worldHeight, w, h);
4173
4406
  }
4174
4407
  if (this.x(x - w / 2) < 0) {
4175
- this.drawRect(x + width, y, w, h);
4408
+ this.drawRect(x + worldWidth, y, w, h);
4176
4409
  if (this.y(y + h / 2) >= height)
4177
- this.drawRect(x + width, y - height, w, h);
4178
- if (this.y(y - height / 2) < 0)
4179
- this.drawRect(x + width, y + height, w, h);
4410
+ this.drawRect(x + worldWidth, y - worldHeight, w, h);
4411
+ if (this.y(y - h / 2) < 0)
4412
+ this.drawRect(x + worldWidth, y + worldHeight, w, h);
4180
4413
  }
4181
4414
  if (this.y(y + h / 2) > height)
4182
- this.drawRect(x, y - height, w, h);
4183
- if (this.y(y - height / 2) < 0)
4184
- this.drawRect(x, y + height, w, h);
4415
+ this.drawRect(x, y - worldHeight, w, h);
4416
+ if (this.y(y - h / 2) < 0)
4417
+ this.drawRect(x, y + worldHeight, w, h);
4418
+ };
4419
+ /**
4420
+ * Draw a selection highlight around the given agent.
4421
+ * @hidden
4422
+ */
4423
+ CanvasRenderer.prototype._drawSelectionHighlight = function (agent) {
4424
+ var bufferContext = this.buffer.getContext("2d");
4425
+ var dpr = window.devicePixelRatio;
4426
+ var data = agent.getData();
4427
+ var ax = this.x(data.x);
4428
+ var ay = this.y(data.y);
4429
+ var shape = data.shape;
4430
+ var size = (data.size || 1) * dpr;
4431
+ bufferContext.save();
4432
+ bufferContext.strokeStyle = "#0af";
4433
+ bufferContext.lineWidth = 2 * dpr;
4434
+ if (shape === "rect") {
4435
+ var w = (data.width || 1) * dpr;
4436
+ var h = (data.height || 1) * dpr;
4437
+ bufferContext.strokeRect(ax - w / 2 - 2 * dpr, ay - h / 2 - 2 * dpr, w + 4 * dpr, h + 4 * dpr);
4438
+ }
4439
+ else {
4440
+ bufferContext.beginPath();
4441
+ var highlightRadius = Math.max(size, 4 * dpr) + 3 * dpr;
4442
+ bufferContext.arc(ax, ay, highlightRadius, 0, 2 * Math.PI);
4443
+ bufferContext.stroke();
4444
+ }
4445
+ bufferContext.restore();
4185
4446
  };
4186
4447
  CanvasRenderer.prototype.render = function () {
4187
4448
  var _this = this;
@@ -4194,22 +4455,24 @@
4194
4455
  // if "trace" is truthy, don't clear the canvas with every frame
4195
4456
  // to trace the paths of agents
4196
4457
  if (!trace) {
4197
- context.clearRect(0, 0, width * dpr, height * dpr);
4458
+ context.clearRect(0, 0, width, height);
4198
4459
  context.fillStyle = opts.background;
4199
- context.fillRect(0, 0, width * dpr, height * dpr);
4460
+ context.fillRect(0, 0, width, height);
4200
4461
  }
4201
4462
  // automatically position agents in an environment that uses a network helper
4202
4463
  if (opts.autoPosition && environment.helpers.network) {
4203
4464
  environment.getAgents().forEach(function (agent) {
4204
4465
  var network = _this.environment.helpers.network;
4205
- var _a = _this, width = _a.width, height = _a.height;
4466
+ // Use CSS pixel dimensions (opts), not the DPI-scaled canvas dimensions,
4467
+ // since x() and y() already apply the devicePixelRatio transform.
4468
+ var _a = _this.opts, w = _a.width, h = _a.height;
4206
4469
  // only set once
4207
4470
  if ((agent.get("x") === null || agent.get("y") === null) &&
4208
4471
  network.isInNetwork(agent)) {
4209
4472
  var idx = network.indexOf(agent);
4210
4473
  var angle = idx / network.agents.length;
4211
- var x = width / 2 + 0.4 * width * Math.cos(2 * Math.PI * angle);
4212
- var y = height / 2 + 0.4 * height * Math.sin(2 * Math.PI * angle);
4474
+ var x = w / 2 + 0.4 * w * Math.cos(2 * Math.PI * angle);
4475
+ var y = h / 2 + 0.4 * h * Math.sin(2 * Math.PI * angle);
4213
4476
  agent.set({ x: x, y: y });
4214
4477
  }
4215
4478
  });
@@ -4254,8 +4517,8 @@
4254
4517
  context.globalAlpha = _this.opts.connectionOpacity;
4255
4518
  context.strokeStyle = _this.opts.connectionColor;
4256
4519
  context.lineWidth = _this.opts.connectionWidth;
4257
- context.moveTo(_this.x(x), _this.x(y));
4258
- context.lineTo(_this.x(nx), _this.x(ny));
4520
+ context.moveTo(_this.x(x), _this.y(y));
4521
+ context.lineTo(_this.x(nx), _this.y(ny));
4259
4522
  context.stroke();
4260
4523
  context.closePath();
4261
4524
  context.restore();
@@ -4287,10 +4550,11 @@
4287
4550
  }
4288
4551
  else if (shape === "triangle") {
4289
4552
  bufferContext.beginPath();
4553
+ var scaledSize = size * dpr;
4290
4554
  var points = [
4291
- [_this.x(x), _this.y(y) - size / 2],
4292
- [_this.x(x) + size / 2, _this.y(y) + size / 2],
4293
- [_this.x(x) - size / 2, _this.y(y) + size / 2]
4555
+ [_this.x(x), _this.y(y) - scaledSize / 2],
4556
+ [_this.x(x) + scaledSize / 2, _this.y(y) + scaledSize / 2],
4557
+ [_this.x(x) - scaledSize / 2, _this.y(y) + scaledSize / 2]
4294
4558
  ];
4295
4559
  _this.drawPath(points);
4296
4560
  if (environment.opts.torus)
@@ -4316,6 +4580,12 @@
4316
4580
  bufferContext.restore();
4317
4581
  }
4318
4582
  });
4583
+ // Draw selection highlights for selected agents
4584
+ if (opts.interactive && this.selected.length > 0) {
4585
+ this.selected.forEach(function (agent) {
4586
+ _this._drawSelectionHighlight(agent);
4587
+ });
4588
+ }
4319
4589
  context.drawImage(buffer, 0, 0);
4320
4590
  };
4321
4591
  return CanvasRenderer;
@@ -4406,11 +4676,13 @@
4406
4676
  };
4407
4677
  Histogram.prototype.x = function (value) {
4408
4678
  var _a = this, width = _a.width, markerWidth = _a.markerWidth;
4409
- return remap(value, 0, width, markerWidth + PADDING_AT_LEFT, width - PADDING_AT_RIGHT);
4679
+ var dpr = window.devicePixelRatio;
4680
+ return remap(value, 0, width, markerWidth + PADDING_AT_LEFT * dpr, width - PADDING_AT_RIGHT * dpr);
4410
4681
  };
4411
4682
  Histogram.prototype.y = function (value) {
4412
4683
  var _a = this, height = _a.height, maxValue = _a.maxValue;
4413
- return remap(value, 0, maxValue, height - PADDING_AT_BOTTOM, 0);
4684
+ var dpr = window.devicePixelRatio;
4685
+ return remap(value, 0, maxValue, height - PADDING_AT_BOTTOM * dpr, 0);
4414
4686
  };
4415
4687
  Histogram.prototype.setMaxValue = function () {
4416
4688
  var _this = this;
@@ -4439,11 +4711,12 @@
4439
4711
  var context = this.canvas.getContext("2d");
4440
4712
  var _a = this, height = _a.height, width = _a.width;
4441
4713
  var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, buckets = _b.buckets, min = _b.min, max$1 = _b.max;
4714
+ var dpr = window.devicePixelRatio;
4442
4715
  var yMin = 0;
4443
4716
  var yMax = this.maxValue;
4444
4717
  var markers = extractRoundNumbers({ min: yMin, max: yMax });
4445
4718
  context.fillStyle = "black";
4446
- context.font = 14 * window.devicePixelRatio + "px Helvetica";
4719
+ context.font = 14 * dpr + "px Helvetica";
4447
4720
  // determine the width of the longest marker
4448
4721
  this.markerWidth = max(markers.map(function (marker) { return context.measureText(marker.toLocaleString()).width; }));
4449
4722
  // draw horizontal lines
@@ -4452,9 +4725,9 @@
4452
4725
  context.textBaseline = "middle";
4453
4726
  context.fillText(marker.toLocaleString(), _this.markerWidth, _this.y(marker));
4454
4727
  context.beginPath();
4455
- context.moveTo(_this.markerWidth + 10, _this.y(marker));
4728
+ context.moveTo(_this.markerWidth + 10 * dpr, _this.y(marker));
4456
4729
  context.lineTo(_this.width, _this.y(marker));
4457
- context.setLineDash(LINE_DASH);
4730
+ context.setLineDash(LINE_DASH.map(function (v) { return v * dpr; }));
4458
4731
  context.stroke();
4459
4732
  });
4460
4733
  var numBuckets = bucketValues.length - (aboveMax ? 1 : 0) - (belowMin ? 1 : 0);
@@ -4477,9 +4750,9 @@
4477
4750
  .forEach(function (label, i) {
4478
4751
  context.save();
4479
4752
  context.translate(_this.x((i * width) / bucketValues.length +
4480
- (0.5 * width) / bucketValues.length), height - 50);
4753
+ (0.5 * width) / bucketValues.length), height - 50 * dpr);
4481
4754
  context.rotate(Math.PI / 4);
4482
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
4755
+ context.font = 12 * dpr + "px Helvetica";
4483
4756
  context.textAlign = "left";
4484
4757
  context.textBaseline = "middle";
4485
4758
  context.fillText(label, 0, 0);
@@ -4489,22 +4762,23 @@
4489
4762
  Histogram.prototype.drawBuckets = function (bucketValues, offset) {
4490
4763
  var _this = this;
4491
4764
  if (offset === void 0) { offset = 0; }
4492
- var canvas = this.canvas;
4765
+ var _a = this, canvas = _a.canvas, width = _a.width, height = _a.height;
4493
4766
  var metric = this._metric;
4494
4767
  var numMetrics = Array.isArray(metric) ? metric.length : 1;
4495
- var _a = this.opts, aboveMax = _a.aboveMax, belowMin = _a.belowMin, color = _a.color, width = _a.width, height = _a.height;
4768
+ var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, color = _b.color;
4769
+ var dpr = window.devicePixelRatio;
4496
4770
  var context = canvas.getContext("2d");
4497
4771
  context.fillStyle = Array.isArray(color)
4498
4772
  ? color[offset % color.length]
4499
4773
  : color;
4500
4774
  var numBuckets = bucketValues.length;
4501
- var barWidth = (width - PADDING_AT_LEFT - PADDING_AT_RIGHT - this.markerWidth) /
4775
+ var barWidth = (width - PADDING_AT_LEFT * dpr - PADDING_AT_RIGHT * dpr - this.markerWidth) /
4502
4776
  numBuckets;
4503
4777
  barWidth *= 0.8;
4504
4778
  bucketValues.forEach(function (value, i) {
4505
4779
  var mappedValue = remap(value, 0, _this.maxValue, 0, 1);
4506
4780
  var x = _this.x(((0.1 + i) * width) / numBuckets);
4507
- context.fillRect(x + (offset * barWidth - (numMetrics - 1)) / numMetrics + offset, remap(mappedValue, 0, 1, height - PADDING_AT_BOTTOM, 0), barWidth / numMetrics, remap(mappedValue, 0, 1, 0, height - PADDING_AT_BOTTOM));
4781
+ context.fillRect(x + (offset * barWidth - (numMetrics - 1)) / numMetrics + offset, remap(mappedValue, 0, 1, height - PADDING_AT_BOTTOM * dpr, 0), barWidth / numMetrics, remap(mappedValue, 0, 1, 0, height - PADDING_AT_BOTTOM * dpr));
4508
4782
  });
4509
4783
  };
4510
4784
  Histogram.prototype.getBucketValues = function (metric) {
@@ -4632,7 +4906,7 @@
4632
4906
  if (opts.autoScroll && t >= width) {
4633
4907
  x -= t - width;
4634
4908
  }
4635
- else if (opts.autoScale && t >= width) {
4909
+ else if (opts.autoScale) {
4636
4910
  x *= width / t;
4637
4911
  }
4638
4912
  return x | 0;
@@ -4641,12 +4915,16 @@
4641
4915
  var height = this.height;
4642
4916
  var range = this.opts.range;
4643
4917
  var min = range.min, max = range.max;
4644
- var pxPerUnit = (height - 2 * PADDING_BOTTOM) / (max - min);
4645
- return Math.round(height - (value - min) * pxPerUnit) - 2 * PADDING_BOTTOM;
4918
+ var dpr = window.devicePixelRatio;
4919
+ var paddingBottom = PADDING_BOTTOM * dpr;
4920
+ var pxPerUnit = (height - 2 * paddingBottom) / (max - min);
4921
+ return Math.round(height - (value - min) * pxPerUnit) - 2 * paddingBottom;
4646
4922
  };
4647
4923
  LineChartRenderer.prototype.drawBackground = function () {
4648
4924
  var _this = this;
4649
4925
  var _a = this, context = _a.context, width = _a.width, height = _a.height, opts = _a.opts, t = _a.t;
4926
+ var dpr = window.devicePixelRatio;
4927
+ var paddingBottom = PADDING_BOTTOM * dpr;
4650
4928
  // draw background and lines
4651
4929
  context.fillStyle = this.opts.background;
4652
4930
  context.fillRect(0, 0, width, height);
@@ -4654,33 +4932,33 @@
4654
4932
  var markers = extractRoundNumbers(range);
4655
4933
  var textMaxWidth = 0;
4656
4934
  // write values on vertical axis
4657
- context.font = 14 * window.devicePixelRatio + "px Helvetica";
4935
+ context.font = 14 * dpr + "px Helvetica";
4658
4936
  context.fillStyle = "#000";
4659
4937
  context.textBaseline = "middle";
4660
4938
  markers.forEach(function (marker) {
4661
- if (_this.y(marker) < 10 || _this.y(marker) + 10 > height)
4939
+ if (_this.y(marker) < 10 * dpr || _this.y(marker) + 10 * dpr > height)
4662
4940
  return;
4663
4941
  var width = context.measureText(marker.toLocaleString()).width;
4664
4942
  if (width > textMaxWidth)
4665
4943
  textMaxWidth = width;
4666
- context.fillText(marker.toLocaleString(), 5, _this.y(marker));
4944
+ context.fillText(marker.toLocaleString(), 5 * dpr, _this.y(marker));
4667
4945
  });
4668
4946
  // draw horizontal lines for vertical axis
4669
4947
  context.save();
4670
4948
  context.strokeStyle = "#999";
4671
4949
  markers.forEach(function (marker) {
4672
- if (_this.y(marker) >= height - PADDING_BOTTOM)
4950
+ if (_this.y(marker) >= height - paddingBottom)
4673
4951
  return;
4674
4952
  context.beginPath();
4675
- context.moveTo(textMaxWidth + 10, _this.y(marker));
4953
+ context.moveTo(textMaxWidth + 10 * dpr, _this.y(marker));
4676
4954
  context.lineTo(_this.x(Math.max(width, _this.environment.time)), _this.y(marker));
4677
- context.setLineDash(lineDash);
4955
+ context.setLineDash(lineDash.map(function (v) { return v * dpr; }));
4678
4956
  context.stroke();
4679
4957
  });
4680
4958
  context.restore();
4681
4959
  // draw time values for horizontal axis
4682
4960
  var min = opts.autoScroll && t >= width ? t - width : 0;
4683
- var max = opts.autoScale && t >= width ? t : width;
4961
+ var max = opts.autoScale ? Math.max(t, 5) : width;
4684
4962
  var timeRange = { min: min, max: max };
4685
4963
  var timeMarkers = extractRoundNumbers(timeRange);
4686
4964
  context.save();
@@ -4691,12 +4969,12 @@
4691
4969
  _this.x(marker) - width / 2 < textMaxWidth) {
4692
4970
  return;
4693
4971
  }
4694
- context.font = 11 * window.devicePixelRatio + "px Helvetica";
4695
- context.fillText(marker.toLocaleString(), _this.x(marker), height - PADDING_BOTTOM);
4972
+ context.font = 11 * dpr + "px Helvetica";
4973
+ context.fillText(marker.toLocaleString(), _this.x(marker), height - paddingBottom);
4696
4974
  context.strokeStyle = "black";
4697
4975
  context.lineWidth = 1;
4698
4976
  context.beginPath();
4699
- context.moveTo(_this.x(marker), height - 4);
4977
+ context.moveTo(_this.x(marker), height - 4 * dpr);
4700
4978
  context.lineTo(_this.x(marker), height);
4701
4979
  context.stroke();
4702
4980
  });
@@ -5044,7 +5322,8 @@
5044
5322
  */
5045
5323
  Heatmap.prototype.x = function (value) {
5046
5324
  var width = this.width;
5047
- return remap(value, this.getMin("x"), this.getMax("x"), PADDING_AT_LEFT$1, width);
5325
+ var dpr = window.devicePixelRatio;
5326
+ return remap(value, this.getMin("x"), this.getMax("x"), PADDING_AT_LEFT$1 * dpr, width);
5048
5327
  };
5049
5328
  /**
5050
5329
  * Map a value (on the range y-min to y-max) onto canvas space to draw it along the y-axis.
@@ -5052,7 +5331,8 @@
5052
5331
  */
5053
5332
  Heatmap.prototype.y = function (value) {
5054
5333
  var height = this.height;
5055
- return remap(value, this.getMin("y"), this.getMax("y"), height - PADDING_AT_BOTTOM$1, 0);
5334
+ var dpr = window.devicePixelRatio;
5335
+ return remap(value, this.getMin("y"), this.getMax("y"), height - PADDING_AT_BOTTOM$1 * dpr, 0);
5056
5336
  };
5057
5337
  /** @hidden */
5058
5338
  Heatmap.prototype.getKey = function (axis) {
@@ -5095,52 +5375,55 @@
5095
5375
  Heatmap.prototype.drawMarkers = function () {
5096
5376
  var _a = this, context = _a.context, width = _a.width, height = _a.height;
5097
5377
  var _b = this.opts, from = _b.from, to = _b.to;
5378
+ var dpr = window.devicePixelRatio;
5379
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5380
+ var padBottom = PADDING_AT_BOTTOM$1 * dpr;
5098
5381
  context.strokeStyle = "black";
5099
5382
  context.lineWidth = 1;
5100
- context.moveTo(PADDING_AT_LEFT$1 - 1, 0);
5101
- context.lineTo(PADDING_AT_LEFT$1 - 1, height - PADDING_AT_BOTTOM$1 + 1);
5102
- context.lineTo(width, height - PADDING_AT_BOTTOM$1 + 1);
5383
+ context.moveTo(padLeft - 1, 0);
5384
+ context.lineTo(padLeft - 1, height - padBottom + 1);
5385
+ context.lineTo(width, height - padBottom + 1);
5103
5386
  context.stroke();
5104
5387
  context.lineWidth = 0;
5105
- var gradient = context.createLinearGradient(10, 0, PADDING_AT_LEFT$1 - 10, 0);
5388
+ var gradient = context.createLinearGradient(10 * dpr, 0, padLeft - 10 * dpr, 0);
5106
5389
  gradient.addColorStop(0, from);
5107
5390
  gradient.addColorStop(1, to);
5108
5391
  context.fillStyle = gradient;
5109
- context.fillRect(10, height - PADDING_AT_BOTTOM$1 + 20, PADDING_AT_LEFT$1 - 24, 20);
5392
+ context.fillRect(10 * dpr, height - padBottom + 20 * dpr, padLeft - 24 * dpr, 20 * dpr);
5110
5393
  context.fillStyle = "black";
5111
5394
  var step = (this.getMax("x") - this.getMin("x")) / this.getBuckets("x");
5112
5395
  var originalStep = step;
5113
- while (Math.abs(this.x(step) - this.x(0)) < 35)
5396
+ while (Math.abs(this.x(step) - this.x(0)) < 35 * dpr)
5114
5397
  step *= 2;
5115
5398
  for (var marker = this.getMin("x"); marker <= this.getMax("x"); marker += originalStep) {
5116
- if (this.x(marker) + 10 > width)
5399
+ if (this.x(marker) + 10 * dpr > width)
5117
5400
  continue;
5118
- context.moveTo(this.x(marker), height - PADDING_AT_BOTTOM$1);
5119
- context.lineTo(this.x(marker), height - PADDING_AT_BOTTOM$1 + 10);
5401
+ context.moveTo(this.x(marker), height - padBottom);
5402
+ context.lineTo(this.x(marker), height - padBottom + 10 * dpr);
5120
5403
  context.stroke();
5121
5404
  if (Math.abs(((marker - this.getMin("x")) / step) % 1) < 0.001 ||
5122
5405
  Math.abs((((marker - this.getMin("x")) / step) % 1) - 1) < 0.001) {
5123
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5406
+ context.font = 12 * dpr + "px Helvetica";
5124
5407
  context.textAlign = "center";
5125
- context.fillText(marker.toLocaleString(), this.x(marker), height - PADDING_AT_BOTTOM$1 + 24);
5408
+ context.fillText(marker.toLocaleString(), this.x(marker), height - padBottom + 24 * dpr);
5126
5409
  }
5127
5410
  }
5128
5411
  step = (this.getMax("y") - this.getMin("y")) / this.getBuckets("y");
5129
5412
  originalStep = step;
5130
- while (Math.abs(this.y(step) - this.y(0)) < 20)
5413
+ while (Math.abs(this.y(step) - this.y(0)) < 20 * dpr)
5131
5414
  step *= 2;
5132
5415
  for (var marker = this.getMin("y"); marker <= this.getMax("y"); marker += originalStep) {
5133
- if (this.y(marker) - 10 < 0)
5416
+ if (this.y(marker) - 10 * dpr < 0)
5134
5417
  continue;
5135
- context.moveTo(PADDING_AT_LEFT$1, this.y(marker));
5136
- context.lineTo(PADDING_AT_LEFT$1 - 10, this.y(marker));
5418
+ context.moveTo(padLeft, this.y(marker));
5419
+ context.lineTo(padLeft - 10 * dpr, this.y(marker));
5137
5420
  context.stroke();
5138
5421
  if (Math.abs(((marker - this.getMin("y")) / step) % 1) < 0.001 ||
5139
5422
  Math.abs((((marker - this.getMin("y")) / step) % 1) - 1) < 0.001) {
5140
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5423
+ context.font = 12 * dpr + "px Helvetica";
5141
5424
  context.textAlign = "right";
5142
5425
  context.textBaseline = "middle";
5143
- context.fillText(marker.toLocaleString(), PADDING_AT_LEFT$1 - 14, this.y(marker));
5426
+ context.fillText(marker.toLocaleString(), padLeft - 14 * dpr, this.y(marker));
5144
5427
  }
5145
5428
  }
5146
5429
  };
@@ -5148,6 +5431,8 @@
5148
5431
  Heatmap.prototype.updateScale = function () {
5149
5432
  var _a = this, context = _a.context, environment = _a.environment, height = _a.height;
5150
5433
  var scale = this.opts.scale;
5434
+ var dpr = window.devicePixelRatio;
5435
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5151
5436
  var max = scale === "relative" ? this.localMax : this.opts.max;
5152
5437
  if (max === undefined) {
5153
5438
  if (!this.lastUpdatedScale) {
@@ -5156,13 +5441,13 @@
5156
5441
  max = environment.getAgents().length;
5157
5442
  }
5158
5443
  if (!this.lastUpdatedScale || +new Date() - +this.lastUpdatedScale > 250) {
5159
- context.clearRect(0, height - 20, PADDING_AT_LEFT$1, 20);
5444
+ context.clearRect(0, height - 20 * dpr, padLeft, 20 * dpr);
5160
5445
  context.fillStyle = "black";
5161
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5446
+ context.font = 12 * dpr + "px Helvetica";
5162
5447
  context.textAlign = "center";
5163
5448
  context.textBaseline = "bottom";
5164
- context.fillText("0", 10, height - 5);
5165
- context.fillText(max.toString(), PADDING_AT_LEFT$1 - 16, height - 5);
5449
+ context.fillText("0", 10 * dpr, height - 5 * dpr);
5450
+ context.fillText(max.toString(), padLeft - 16 * dpr, height - 5 * dpr);
5166
5451
  this.lastUpdatedScale = new Date();
5167
5452
  }
5168
5453
  };
@@ -5170,6 +5455,9 @@
5170
5455
  Heatmap.prototype.drawRectangles = function () {
5171
5456
  var _a = this, canvas = _a.canvas, environment = _a.environment, width = _a.width, height = _a.height;
5172
5457
  var _b = this.opts, scale = _b.scale, from = _b.from, to = _b.to;
5458
+ var dpr = window.devicePixelRatio;
5459
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5460
+ var padBottom = PADDING_AT_BOTTOM$1 * dpr;
5173
5461
  var context = canvas.getContext("2d");
5174
5462
  var xBuckets = this.getBuckets("x");
5175
5463
  var yBuckets = this.getBuckets("y");
@@ -5178,9 +5466,9 @@
5178
5466
  max = environment.getAgents().length;
5179
5467
  // clear background by drawing background rectangle
5180
5468
  context.fillStyle = from;
5181
- context.fillRect(PADDING_AT_LEFT$1, 0, width, height - PADDING_AT_BOTTOM$1);
5182
- var w = width / xBuckets;
5183
- var h = height / yBuckets;
5469
+ context.fillRect(padLeft, 0, width - padLeft, height - padBottom);
5470
+ var w = (width - padLeft) / xBuckets;
5471
+ var h = (height - padBottom) / yBuckets;
5184
5472
  for (var row = 0; row < yBuckets; row++) {
5185
5473
  for (var column = 0; column < xBuckets; column++) {
5186
5474
  var index = row * xBuckets + column;
@@ -5188,7 +5476,7 @@
5188
5476
  var a = clamp(remap(this.buckets[index], 0, max, 0, 1), 0, 1);
5189
5477
  context.fillStyle = to;
5190
5478
  context.globalAlpha = a;
5191
- context.fillRect(this.x(remap(column, 0, xBuckets, this.getMin("x"), this.getMax("x"))), this.y(remap(row, -1, yBuckets - 1, this.getMin("y"), this.getMax("y"))), (w * (width - PADDING_AT_LEFT$1)) / width, (h * (height - PADDING_AT_BOTTOM$1)) / height);
5479
+ context.fillRect(this.x(remap(column, 0, xBuckets, this.getMin("x"), this.getMax("x"))), this.y(remap(row, -1, yBuckets - 1, this.getMin("y"), this.getMax("y"))), w, h);
5192
5480
  }
5193
5481
  }
5194
5482
  context.globalAlpha = 1;
@@ -5244,7 +5532,7 @@
5244
5532
  /**
5245
5533
  * The current version of the Flocc library.
5246
5534
  */
5247
- var version = "0.5.20";
5535
+ var version = "0.5.22";
5248
5536
 
5249
5537
  exports.ASCIIRenderer = ASCIIRenderer;
5250
5538
  exports.Agent = Agent;