flocc 0.5.21 → 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.es.js CHANGED
@@ -3212,6 +3212,15 @@ var Environment = /** @class */ (function (_super) {
3212
3212
  * such as a {@linkcode LineChartRenderer}, {@linkcode Histogram}, etc.
3213
3213
  */
3214
3214
  _this.renderers = [];
3215
+ /**
3216
+ * Whether the `Environment` tick cycle is currently playing.
3217
+ * Use {@linkcode pause}, {@linkcode resume}, or {@linkcode toggle}
3218
+ * to control playback.
3219
+ * @since 0.5.22
3220
+ */
3221
+ _this.playing = true;
3222
+ /** @hidden */
3223
+ _this._tickIntervalId = null;
3215
3224
  /**
3216
3225
  * This property will always equal the number of tick cycles that
3217
3226
  * have passed since the `Environment` was created. If you call
@@ -3381,6 +3390,9 @@ var Environment = /** @class */ (function (_super) {
3381
3390
  * @since 0.0.5
3382
3391
  */
3383
3392
  Environment.prototype.tick = function (opts) {
3393
+ // If paused, skip the tick cycle (use `step()` to advance manually)
3394
+ if (!this.playing)
3395
+ return;
3384
3396
  var _a = this._getTickOptions(opts), activation = _a.activation, activationCount = _a.activationCount, count = _a.count, randomizeOrder = _a.randomizeOrder;
3385
3397
  // for uniform activation, every agent is always activated
3386
3398
  if (activation === "uniform") {
@@ -3448,6 +3460,47 @@ var Environment = /** @class */ (function (_super) {
3448
3460
  }
3449
3461
  this.renderers.forEach(function (r) { return r.render(); });
3450
3462
  };
3463
+ /**
3464
+ * Pause the tick cycle. While paused, calling {@linkcode tick} will
3465
+ * be a no-op unless you use {@linkcode step} to advance manually.
3466
+ * @since 0.5.22
3467
+ */
3468
+ Environment.prototype.pause = function () {
3469
+ this.playing = false;
3470
+ };
3471
+ /**
3472
+ * Resume the tick cycle after it has been paused.
3473
+ * @since 0.5.22
3474
+ */
3475
+ Environment.prototype.resume = function () {
3476
+ this.playing = true;
3477
+ };
3478
+ /**
3479
+ * Toggle the tick cycle between playing and paused.
3480
+ * @since 0.5.22
3481
+ */
3482
+ Environment.prototype.toggle = function () {
3483
+ this.playing = !this.playing;
3484
+ };
3485
+ /**
3486
+ * Advance the `Environment` by exactly one tick, regardless of whether
3487
+ * it is paused. This is useful for stepping through the simulation
3488
+ * frame-by-frame while paused.
3489
+ *
3490
+ * ```js
3491
+ * environment.pause();
3492
+ * environment.step(); // advances one tick
3493
+ * ```
3494
+ *
3495
+ * @since 0.5.22
3496
+ */
3497
+ Environment.prototype.step = function (opts) {
3498
+ // Temporarily mark as playing so tick executes, then restore
3499
+ var wasPlaying = this.playing;
3500
+ this.playing = true;
3501
+ this.tick(opts);
3502
+ this.playing = wasPlaying;
3503
+ };
3451
3504
  /**
3452
3505
  * Use a helper with this environment. A helper can be one of:
3453
3506
  * - {@linkcode KDTree}
@@ -3984,7 +4037,10 @@ var defaultOptions = {
3984
4037
  width: 500,
3985
4038
  height: 500,
3986
4039
  scale: 1,
3987
- trace: false
4040
+ trace: false,
4041
+ interactive: false,
4042
+ zoomMin: 0.1,
4043
+ zoomMax: 10
3988
4044
  };
3989
4045
  /**
3990
4046
  * A `CanvasRenderer` renders an {@linkcode Environment} spatially in two dimensions.
@@ -4004,6 +4060,12 @@ var defaultOptions = {
4004
4060
  * - `"triangle"` — Draws a triangle centered at the `Agent`'s `"x"` / `"y"` values.
4005
4061
  * - Also uses the `"size"` value.
4006
4062
  *
4063
+ * When `interactive` is set to `true` in the options, the renderer supports:
4064
+ * - **Click/hover detection** — Use {@linkcode on} to listen for `"click"`, `"hover"`, and `"unhover"` events on agents.
4065
+ * - **Agent selection** — Clicking an agent selects it (highlighted with a stroke). Access selected agents via {@linkcode selected}.
4066
+ * - **Pan** — Click and drag on empty space to pan.
4067
+ * - **Zoom** — Scroll to zoom in/out (bounded by `zoomMin` / `zoomMax`).
4068
+ *
4007
4069
  * @since 0.0.11
4008
4070
  */
4009
4071
  var CanvasRenderer = /** @class */ (function (_super) {
@@ -4019,15 +4081,33 @@ var CanvasRenderer = /** @class */ (function (_super) {
4019
4081
  * - `connectionOpacity` (*number* = `1`) — For `Environment`s using a `Network`, the opacity of lines
4020
4082
  * - `connectionWidth` (*number* = `1`) — For `Environment`s using a `Network`, the width of lines
4021
4083
  * - `height` (*number* = `500`) — The height, in pixels, of the canvas on which to render
4084
+ * - `interactive` (*boolean* = `false`) — Enables interactive features (click/hover detection, selection, pan, zoom)
4085
+ * - `onSelect` (*function*) — Optional callback when an agent is selected or deselected
4022
4086
  * - `origin` (*{ x: number; y: number }* = `{ x: 0, y: 0 }`) — The coordinate of the upper-left point of the space to be rendered
4023
4087
  * - `scale` (*number* = `1`) — The scale at which to render (the larger the scale, the smaller the size of the space that is actually rendered)
4024
4088
  * - `trace` (*boolean* = `false`) — If `true`, the renderer will not clear old drawings, causing the `Agent`s to appear to *trace* their paths across space
4025
4089
  * - `width` (*number* = `500`) — The width, in pixels, of the canvas on which to render
4090
+ * - `zoomMin` (*number* = `0.1`) — Minimum scale when zooming
4091
+ * - `zoomMax` (*number* = `10`) — Maximum scale when zooming
4026
4092
  */
4027
4093
  function CanvasRenderer(environment, opts) {
4028
4094
  var _this = _super.call(this) || this;
4029
4095
  /** @hidden */
4030
4096
  _this.terrainBuffer = document.createElement("canvas");
4097
+ /** The currently selected agents (only used when `interactive` is `true`). */
4098
+ _this.selected = [];
4099
+ /** @hidden */
4100
+ _this._listeners = new Map();
4101
+ /** @hidden */
4102
+ _this._hoveredAgent = null;
4103
+ /** @hidden */
4104
+ _this._isPanning = false;
4105
+ /** @hidden */
4106
+ _this._panStart = null;
4107
+ /** @hidden */
4108
+ _this._panOriginStart = null;
4109
+ /** @hidden */
4110
+ _this._boundHandlers = {};
4031
4111
  _this.environment = environment;
4032
4112
  environment.renderers.push(_this);
4033
4113
  _this.opts = Object.assign({}, defaultOptions);
@@ -4044,9 +4124,158 @@ var CanvasRenderer = /** @class */ (function (_super) {
4044
4124
  _this.terrainBuffer.width = width;
4045
4125
  _this.terrainBuffer.height = height;
4046
4126
  _this.context.fillStyle = opts.background;
4047
- _this.context.fillRect(0, 0, width, height);
4127
+ _this.context.fillRect(0, 0, _this.width, _this.height);
4128
+ if (_this.opts.interactive) {
4129
+ _this._setupInteractiveListeners();
4130
+ }
4048
4131
  return _this;
4049
4132
  }
4133
+ /**
4134
+ * Register a callback for an interactive event.
4135
+ * Supported event names: `"click"`, `"hover"`, `"unhover"`.
4136
+ *
4137
+ * ```js
4138
+ * renderer.on("click", (agent, event) => {
4139
+ * console.log("Clicked agent:", agent.id);
4140
+ * });
4141
+ * ```
4142
+ *
4143
+ * @param eventName - The event to listen for.
4144
+ * @param callback - The callback, invoked with the `Agent` and the `MouseEvent`.
4145
+ */
4146
+ CanvasRenderer.prototype.on = function (eventName, callback) {
4147
+ if (!this._listeners.has(eventName)) {
4148
+ this._listeners.set(eventName, []);
4149
+ }
4150
+ this._listeners.get(eventName).push(callback);
4151
+ };
4152
+ /** @hidden */
4153
+ CanvasRenderer.prototype._emit = function (eventName, agent, event) {
4154
+ var callbacks = this._listeners.get(eventName);
4155
+ if (callbacks) {
4156
+ callbacks.forEach(function (cb) { return cb(agent, event); });
4157
+ }
4158
+ };
4159
+ /**
4160
+ * Given a mouse event, return the agent at that position (if any).
4161
+ * Hit-testing accounts for the agent's shape and size.
4162
+ * @hidden
4163
+ */
4164
+ CanvasRenderer.prototype._agentAtPoint = function (clientX, clientY) {
4165
+ var rect = this.canvas.getBoundingClientRect();
4166
+ var dpr = window.devicePixelRatio;
4167
+ var canvasX = (clientX - rect.left) * dpr;
4168
+ var canvasY = (clientY - rect.top) * dpr;
4169
+ var agents = this.environment.getAgents();
4170
+ // Iterate in reverse so topmost-drawn agent is found first
4171
+ for (var i = agents.length - 1; i >= 0; i--) {
4172
+ var agent = agents[i];
4173
+ var data = agent.getData();
4174
+ var ax = this.x(data.x);
4175
+ var ay = this.y(data.y);
4176
+ var shape = data.shape;
4177
+ var size = (data.size || 1) * dpr;
4178
+ if (shape === "rect") {
4179
+ var w = (data.width || 1) * dpr;
4180
+ var h = (data.height || 1) * dpr;
4181
+ var rx = ax - w / 2;
4182
+ var ry = ay - h / 2;
4183
+ if (canvasX >= rx && canvasX <= rx + w && canvasY >= ry && canvasY <= ry + h) {
4184
+ return agent;
4185
+ }
4186
+ }
4187
+ else if (shape === "triangle") {
4188
+ // Simple bounding-box hit test for triangles
4189
+ var halfSize = size / 2;
4190
+ if (canvasX >= ax - halfSize &&
4191
+ canvasX <= ax + halfSize &&
4192
+ canvasY >= ay - halfSize &&
4193
+ canvasY <= ay + halfSize) {
4194
+ return agent;
4195
+ }
4196
+ }
4197
+ else {
4198
+ // Default: circle (and arrow) — distance-based hit test
4199
+ var dx = canvasX - ax;
4200
+ var dy = canvasY - ay;
4201
+ var hitRadius = Math.max(size, 4 * dpr); // minimum hit area for tiny agents
4202
+ if (dx * dx + dy * dy <= hitRadius * hitRadius) {
4203
+ return agent;
4204
+ }
4205
+ }
4206
+ }
4207
+ return null;
4208
+ };
4209
+ /** @hidden */
4210
+ CanvasRenderer.prototype._setupInteractiveListeners = function () {
4211
+ var _this = this;
4212
+ var onMouseDown = function (e) {
4213
+ var agent = _this._agentAtPoint(e.clientX, e.clientY);
4214
+ if (agent) {
4215
+ // Agent click — select it
4216
+ _this.selected = [agent];
4217
+ if (_this.opts.onSelect)
4218
+ _this.opts.onSelect(agent);
4219
+ _this._emit("click", agent, e);
4220
+ _this.render();
4221
+ }
4222
+ else {
4223
+ // Empty space — deselect and start panning
4224
+ if (_this.selected.length > 0) {
4225
+ _this.selected = [];
4226
+ if (_this.opts.onSelect)
4227
+ _this.opts.onSelect(null);
4228
+ _this.render();
4229
+ }
4230
+ _this._isPanning = true;
4231
+ _this._panStart = { x: e.clientX, y: e.clientY };
4232
+ _this._panOriginStart = { x: _this.opts.origin.x, y: _this.opts.origin.y };
4233
+ }
4234
+ };
4235
+ var onMouseMove = function (e) {
4236
+ if (_this._isPanning && _this._panStart && _this._panOriginStart) {
4237
+ var dpr = window.devicePixelRatio;
4238
+ var dx = e.clientX - _this._panStart.x;
4239
+ var dy = e.clientY - _this._panStart.y;
4240
+ _this.opts.origin = {
4241
+ x: _this._panOriginStart.x - dx / (_this.opts.scale * dpr),
4242
+ y: _this._panOriginStart.y - dy / (_this.opts.scale * dpr)
4243
+ };
4244
+ _this.render();
4245
+ return;
4246
+ }
4247
+ // Hover detection
4248
+ var agent = _this._agentAtPoint(e.clientX, e.clientY);
4249
+ if (agent !== _this._hoveredAgent) {
4250
+ if (_this._hoveredAgent) {
4251
+ _this._emit("unhover", _this._hoveredAgent, e);
4252
+ }
4253
+ if (agent) {
4254
+ _this._emit("hover", agent, e);
4255
+ }
4256
+ _this._hoveredAgent = agent;
4257
+ }
4258
+ };
4259
+ var onMouseUp = function (e) {
4260
+ _this._isPanning = false;
4261
+ _this._panStart = null;
4262
+ _this._panOriginStart = null;
4263
+ };
4264
+ var onWheel = function (e) {
4265
+ e.preventDefault();
4266
+ var _a = _this.opts, zoomMin = _a.zoomMin, zoomMax = _a.zoomMax;
4267
+ var delta = e.deltaY > 0 ? 0.9 : 1.1;
4268
+ var newScale = _this.opts.scale * delta;
4269
+ newScale = Math.max(zoomMin, Math.min(zoomMax, newScale));
4270
+ _this.opts.scale = newScale;
4271
+ _this.render();
4272
+ };
4273
+ this._boundHandlers = { mousedown: onMouseDown, mousemove: onMouseMove, mouseup: onMouseUp, wheel: onWheel };
4274
+ this.canvas.addEventListener("mousedown", onMouseDown);
4275
+ this.canvas.addEventListener("mousemove", onMouseMove);
4276
+ this.canvas.addEventListener("mouseup", onMouseUp);
4277
+ this.canvas.addEventListener("wheel", onWheel, { passive: false });
4278
+ };
4050
4279
  /** @hidden */
4051
4280
  CanvasRenderer.prototype.x = function (v) {
4052
4281
  var _a = this.opts, origin = _a.origin, scale = _a.scale;
@@ -4083,21 +4312,21 @@ var CanvasRenderer = /** @class */ (function (_super) {
4083
4312
  };
4084
4313
  /** @hidden */
4085
4314
  CanvasRenderer.prototype.drawPathWrap = function (points) {
4086
- var _this = this;
4087
4315
  var _a = this, width = _a.width, height = _a.height;
4088
4316
  var right = false;
4089
4317
  var left = false;
4090
4318
  var lower = false;
4091
4319
  var upper = false;
4320
+ // points are already in DPR-scaled pixel space, so compare directly
4092
4321
  points.forEach(function (_a) {
4093
4322
  var px = _a[0], py = _a[1];
4094
- if (_this.x(px) >= width)
4323
+ if (px >= width)
4095
4324
  right = true;
4096
- if (_this.x(px) < 0)
4325
+ if (px < 0)
4097
4326
  left = true;
4098
- if (_this.y(py) >= height)
4327
+ if (py >= height)
4099
4328
  lower = true;
4100
- if (_this.y(py) < 0)
4329
+ if (py < 0)
4101
4330
  upper = true;
4102
4331
  });
4103
4332
  if (right)
@@ -4126,24 +4355,26 @@ var CanvasRenderer = /** @class */ (function (_super) {
4126
4355
  /** @hidden */
4127
4356
  CanvasRenderer.prototype.drawCircleWrap = function (x, y, size) {
4128
4357
  var _a = this, width = _a.width, height = _a.height;
4358
+ var worldWidth = this.opts.width;
4359
+ var worldHeight = this.opts.height;
4129
4360
  if (this.x(x + size) >= width) {
4130
- this.drawCircle(x - width, y, size);
4361
+ this.drawCircle(x - worldWidth, y, size);
4131
4362
  if (this.y(y + size) >= height)
4132
- this.drawCircle(x - width, y - height, size);
4363
+ this.drawCircle(x - worldWidth, y - worldHeight, size);
4133
4364
  if (this.y(y - size) < 0)
4134
- this.drawCircle(x - width, y + height, size);
4365
+ this.drawCircle(x - worldWidth, y + worldHeight, size);
4135
4366
  }
4136
4367
  if (this.x(x - size) < 0) {
4137
- this.drawCircle(x + width, y, size);
4368
+ this.drawCircle(x + worldWidth, y, size);
4138
4369
  if (this.y(y + size) >= height)
4139
- this.drawCircle(x + width, y - height, size);
4370
+ this.drawCircle(x + worldWidth, y - worldHeight, size);
4140
4371
  if (this.y(y - size) < 0)
4141
- this.drawCircle(x + width, y + height, size);
4372
+ this.drawCircle(x + worldWidth, y + worldHeight, size);
4142
4373
  }
4143
4374
  if (this.y(y + size) > height)
4144
- this.drawCircle(x, y - height, size);
4375
+ this.drawCircle(x, y - worldHeight, size);
4145
4376
  if (this.y(y - size) < 0)
4146
- this.drawCircle(x, y + height, size);
4377
+ this.drawCircle(x, y + worldHeight, size);
4147
4378
  };
4148
4379
  /**
4149
4380
  * Draw a rectangle centered at (x, y). Automatically calculates the offset
@@ -4157,25 +4388,55 @@ var CanvasRenderer = /** @class */ (function (_super) {
4157
4388
  };
4158
4389
  /** @hidden */
4159
4390
  CanvasRenderer.prototype.drawRectWrap = function (x, y, w, h) {
4160
- var _a = this.opts, width = _a.width, height = _a.height;
4391
+ var _a = this, width = _a.width, height = _a.height;
4392
+ var worldWidth = this.opts.width;
4393
+ var worldHeight = this.opts.height;
4161
4394
  if (this.x(x + w / 2) >= width) {
4162
- this.drawRect(x - width, y, w, h);
4395
+ this.drawRect(x - worldWidth, y, w, h);
4163
4396
  if (this.y(y + h / 2) >= height)
4164
- this.drawRect(x - width, y - height, w, h);
4165
- if (this.y(y - height / 2) < 0)
4166
- this.drawRect(x - width, y + height, w, h);
4397
+ this.drawRect(x - worldWidth, y - worldHeight, w, h);
4398
+ if (this.y(y - h / 2) < 0)
4399
+ this.drawRect(x - worldWidth, y + worldHeight, w, h);
4167
4400
  }
4168
4401
  if (this.x(x - w / 2) < 0) {
4169
- this.drawRect(x + width, y, w, h);
4402
+ this.drawRect(x + worldWidth, y, w, h);
4170
4403
  if (this.y(y + h / 2) >= height)
4171
- this.drawRect(x + width, y - height, w, h);
4172
- if (this.y(y - height / 2) < 0)
4173
- this.drawRect(x + width, y + height, w, h);
4404
+ this.drawRect(x + worldWidth, y - worldHeight, w, h);
4405
+ if (this.y(y - h / 2) < 0)
4406
+ this.drawRect(x + worldWidth, y + worldHeight, w, h);
4174
4407
  }
4175
4408
  if (this.y(y + h / 2) > height)
4176
- this.drawRect(x, y - height, w, h);
4177
- if (this.y(y - height / 2) < 0)
4178
- this.drawRect(x, y + height, w, h);
4409
+ this.drawRect(x, y - worldHeight, w, h);
4410
+ if (this.y(y - h / 2) < 0)
4411
+ this.drawRect(x, y + worldHeight, w, h);
4412
+ };
4413
+ /**
4414
+ * Draw a selection highlight around the given agent.
4415
+ * @hidden
4416
+ */
4417
+ CanvasRenderer.prototype._drawSelectionHighlight = function (agent) {
4418
+ var bufferContext = this.buffer.getContext("2d");
4419
+ var dpr = window.devicePixelRatio;
4420
+ var data = agent.getData();
4421
+ var ax = this.x(data.x);
4422
+ var ay = this.y(data.y);
4423
+ var shape = data.shape;
4424
+ var size = (data.size || 1) * dpr;
4425
+ bufferContext.save();
4426
+ bufferContext.strokeStyle = "#0af";
4427
+ bufferContext.lineWidth = 2 * dpr;
4428
+ if (shape === "rect") {
4429
+ var w = (data.width || 1) * dpr;
4430
+ var h = (data.height || 1) * dpr;
4431
+ bufferContext.strokeRect(ax - w / 2 - 2 * dpr, ay - h / 2 - 2 * dpr, w + 4 * dpr, h + 4 * dpr);
4432
+ }
4433
+ else {
4434
+ bufferContext.beginPath();
4435
+ var highlightRadius = Math.max(size, 4 * dpr) + 3 * dpr;
4436
+ bufferContext.arc(ax, ay, highlightRadius, 0, 2 * Math.PI);
4437
+ bufferContext.stroke();
4438
+ }
4439
+ bufferContext.restore();
4179
4440
  };
4180
4441
  CanvasRenderer.prototype.render = function () {
4181
4442
  var _this = this;
@@ -4188,22 +4449,24 @@ var CanvasRenderer = /** @class */ (function (_super) {
4188
4449
  // if "trace" is truthy, don't clear the canvas with every frame
4189
4450
  // to trace the paths of agents
4190
4451
  if (!trace) {
4191
- context.clearRect(0, 0, width * dpr, height * dpr);
4452
+ context.clearRect(0, 0, width, height);
4192
4453
  context.fillStyle = opts.background;
4193
- context.fillRect(0, 0, width * dpr, height * dpr);
4454
+ context.fillRect(0, 0, width, height);
4194
4455
  }
4195
4456
  // automatically position agents in an environment that uses a network helper
4196
4457
  if (opts.autoPosition && environment.helpers.network) {
4197
4458
  environment.getAgents().forEach(function (agent) {
4198
4459
  var network = _this.environment.helpers.network;
4199
- var _a = _this, width = _a.width, height = _a.height;
4460
+ // Use CSS pixel dimensions (opts), not the DPI-scaled canvas dimensions,
4461
+ // since x() and y() already apply the devicePixelRatio transform.
4462
+ var _a = _this.opts, w = _a.width, h = _a.height;
4200
4463
  // only set once
4201
4464
  if ((agent.get("x") === null || agent.get("y") === null) &&
4202
4465
  network.isInNetwork(agent)) {
4203
4466
  var idx = network.indexOf(agent);
4204
4467
  var angle = idx / network.agents.length;
4205
- var x = width / 2 + 0.4 * width * Math.cos(2 * Math.PI * angle);
4206
- var y = height / 2 + 0.4 * height * Math.sin(2 * Math.PI * angle);
4468
+ var x = w / 2 + 0.4 * w * Math.cos(2 * Math.PI * angle);
4469
+ var y = h / 2 + 0.4 * h * Math.sin(2 * Math.PI * angle);
4207
4470
  agent.set({ x: x, y: y });
4208
4471
  }
4209
4472
  });
@@ -4248,8 +4511,8 @@ var CanvasRenderer = /** @class */ (function (_super) {
4248
4511
  context.globalAlpha = _this.opts.connectionOpacity;
4249
4512
  context.strokeStyle = _this.opts.connectionColor;
4250
4513
  context.lineWidth = _this.opts.connectionWidth;
4251
- context.moveTo(_this.x(x), _this.x(y));
4252
- context.lineTo(_this.x(nx), _this.x(ny));
4514
+ context.moveTo(_this.x(x), _this.y(y));
4515
+ context.lineTo(_this.x(nx), _this.y(ny));
4253
4516
  context.stroke();
4254
4517
  context.closePath();
4255
4518
  context.restore();
@@ -4281,10 +4544,11 @@ var CanvasRenderer = /** @class */ (function (_super) {
4281
4544
  }
4282
4545
  else if (shape === "triangle") {
4283
4546
  bufferContext.beginPath();
4547
+ var scaledSize = size * dpr;
4284
4548
  var points = [
4285
- [_this.x(x), _this.y(y) - size / 2],
4286
- [_this.x(x) + size / 2, _this.y(y) + size / 2],
4287
- [_this.x(x) - size / 2, _this.y(y) + size / 2]
4549
+ [_this.x(x), _this.y(y) - scaledSize / 2],
4550
+ [_this.x(x) + scaledSize / 2, _this.y(y) + scaledSize / 2],
4551
+ [_this.x(x) - scaledSize / 2, _this.y(y) + scaledSize / 2]
4288
4552
  ];
4289
4553
  _this.drawPath(points);
4290
4554
  if (environment.opts.torus)
@@ -4310,6 +4574,12 @@ var CanvasRenderer = /** @class */ (function (_super) {
4310
4574
  bufferContext.restore();
4311
4575
  }
4312
4576
  });
4577
+ // Draw selection highlights for selected agents
4578
+ if (opts.interactive && this.selected.length > 0) {
4579
+ this.selected.forEach(function (agent) {
4580
+ _this._drawSelectionHighlight(agent);
4581
+ });
4582
+ }
4313
4583
  context.drawImage(buffer, 0, 0);
4314
4584
  };
4315
4585
  return CanvasRenderer;
@@ -4400,11 +4670,13 @@ var Histogram = /** @class */ (function (_super) {
4400
4670
  };
4401
4671
  Histogram.prototype.x = function (value) {
4402
4672
  var _a = this, width = _a.width, markerWidth = _a.markerWidth;
4403
- return remap(value, 0, width, markerWidth + PADDING_AT_LEFT, width - PADDING_AT_RIGHT);
4673
+ var dpr = window.devicePixelRatio;
4674
+ return remap(value, 0, width, markerWidth + PADDING_AT_LEFT * dpr, width - PADDING_AT_RIGHT * dpr);
4404
4675
  };
4405
4676
  Histogram.prototype.y = function (value) {
4406
4677
  var _a = this, height = _a.height, maxValue = _a.maxValue;
4407
- return remap(value, 0, maxValue, height - PADDING_AT_BOTTOM, 0);
4678
+ var dpr = window.devicePixelRatio;
4679
+ return remap(value, 0, maxValue, height - PADDING_AT_BOTTOM * dpr, 0);
4408
4680
  };
4409
4681
  Histogram.prototype.setMaxValue = function () {
4410
4682
  var _this = this;
@@ -4433,11 +4705,12 @@ var Histogram = /** @class */ (function (_super) {
4433
4705
  var context = this.canvas.getContext("2d");
4434
4706
  var _a = this, height = _a.height, width = _a.width;
4435
4707
  var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, buckets = _b.buckets, min = _b.min, max$1 = _b.max;
4708
+ var dpr = window.devicePixelRatio;
4436
4709
  var yMin = 0;
4437
4710
  var yMax = this.maxValue;
4438
4711
  var markers = extractRoundNumbers({ min: yMin, max: yMax });
4439
4712
  context.fillStyle = "black";
4440
- context.font = 14 * window.devicePixelRatio + "px Helvetica";
4713
+ context.font = 14 * dpr + "px Helvetica";
4441
4714
  // determine the width of the longest marker
4442
4715
  this.markerWidth = max(markers.map(function (marker) { return context.measureText(marker.toLocaleString()).width; }));
4443
4716
  // draw horizontal lines
@@ -4446,9 +4719,9 @@ var Histogram = /** @class */ (function (_super) {
4446
4719
  context.textBaseline = "middle";
4447
4720
  context.fillText(marker.toLocaleString(), _this.markerWidth, _this.y(marker));
4448
4721
  context.beginPath();
4449
- context.moveTo(_this.markerWidth + 10, _this.y(marker));
4722
+ context.moveTo(_this.markerWidth + 10 * dpr, _this.y(marker));
4450
4723
  context.lineTo(_this.width, _this.y(marker));
4451
- context.setLineDash(LINE_DASH);
4724
+ context.setLineDash(LINE_DASH.map(function (v) { return v * dpr; }));
4452
4725
  context.stroke();
4453
4726
  });
4454
4727
  var numBuckets = bucketValues.length - (aboveMax ? 1 : 0) - (belowMin ? 1 : 0);
@@ -4471,9 +4744,9 @@ var Histogram = /** @class */ (function (_super) {
4471
4744
  .forEach(function (label, i) {
4472
4745
  context.save();
4473
4746
  context.translate(_this.x((i * width) / bucketValues.length +
4474
- (0.5 * width) / bucketValues.length), height - 50);
4747
+ (0.5 * width) / bucketValues.length), height - 50 * dpr);
4475
4748
  context.rotate(Math.PI / 4);
4476
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
4749
+ context.font = 12 * dpr + "px Helvetica";
4477
4750
  context.textAlign = "left";
4478
4751
  context.textBaseline = "middle";
4479
4752
  context.fillText(label, 0, 0);
@@ -4483,22 +4756,23 @@ var Histogram = /** @class */ (function (_super) {
4483
4756
  Histogram.prototype.drawBuckets = function (bucketValues, offset) {
4484
4757
  var _this = this;
4485
4758
  if (offset === void 0) { offset = 0; }
4486
- var canvas = this.canvas;
4759
+ var _a = this, canvas = _a.canvas, width = _a.width, height = _a.height;
4487
4760
  var metric = this._metric;
4488
4761
  var numMetrics = Array.isArray(metric) ? metric.length : 1;
4489
- var _a = this.opts, aboveMax = _a.aboveMax, belowMin = _a.belowMin, color = _a.color, width = _a.width, height = _a.height;
4762
+ var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, color = _b.color;
4763
+ var dpr = window.devicePixelRatio;
4490
4764
  var context = canvas.getContext("2d");
4491
4765
  context.fillStyle = Array.isArray(color)
4492
4766
  ? color[offset % color.length]
4493
4767
  : color;
4494
4768
  var numBuckets = bucketValues.length;
4495
- var barWidth = (width - PADDING_AT_LEFT - PADDING_AT_RIGHT - this.markerWidth) /
4769
+ var barWidth = (width - PADDING_AT_LEFT * dpr - PADDING_AT_RIGHT * dpr - this.markerWidth) /
4496
4770
  numBuckets;
4497
4771
  barWidth *= 0.8;
4498
4772
  bucketValues.forEach(function (value, i) {
4499
4773
  var mappedValue = remap(value, 0, _this.maxValue, 0, 1);
4500
4774
  var x = _this.x(((0.1 + i) * width) / numBuckets);
4501
- 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));
4775
+ 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));
4502
4776
  });
4503
4777
  };
4504
4778
  Histogram.prototype.getBucketValues = function (metric) {
@@ -4635,12 +4909,16 @@ var LineChartRenderer = /** @class */ (function (_super) {
4635
4909
  var height = this.height;
4636
4910
  var range = this.opts.range;
4637
4911
  var min = range.min, max = range.max;
4638
- var pxPerUnit = (height - 2 * PADDING_BOTTOM) / (max - min);
4639
- return Math.round(height - (value - min) * pxPerUnit) - 2 * PADDING_BOTTOM;
4912
+ var dpr = window.devicePixelRatio;
4913
+ var paddingBottom = PADDING_BOTTOM * dpr;
4914
+ var pxPerUnit = (height - 2 * paddingBottom) / (max - min);
4915
+ return Math.round(height - (value - min) * pxPerUnit) - 2 * paddingBottom;
4640
4916
  };
4641
4917
  LineChartRenderer.prototype.drawBackground = function () {
4642
4918
  var _this = this;
4643
4919
  var _a = this, context = _a.context, width = _a.width, height = _a.height, opts = _a.opts, t = _a.t;
4920
+ var dpr = window.devicePixelRatio;
4921
+ var paddingBottom = PADDING_BOTTOM * dpr;
4644
4922
  // draw background and lines
4645
4923
  context.fillStyle = this.opts.background;
4646
4924
  context.fillRect(0, 0, width, height);
@@ -4648,27 +4926,27 @@ var LineChartRenderer = /** @class */ (function (_super) {
4648
4926
  var markers = extractRoundNumbers(range);
4649
4927
  var textMaxWidth = 0;
4650
4928
  // write values on vertical axis
4651
- context.font = 14 * window.devicePixelRatio + "px Helvetica";
4929
+ context.font = 14 * dpr + "px Helvetica";
4652
4930
  context.fillStyle = "#000";
4653
4931
  context.textBaseline = "middle";
4654
4932
  markers.forEach(function (marker) {
4655
- if (_this.y(marker) < 10 || _this.y(marker) + 10 > height)
4933
+ if (_this.y(marker) < 10 * dpr || _this.y(marker) + 10 * dpr > height)
4656
4934
  return;
4657
4935
  var width = context.measureText(marker.toLocaleString()).width;
4658
4936
  if (width > textMaxWidth)
4659
4937
  textMaxWidth = width;
4660
- context.fillText(marker.toLocaleString(), 5, _this.y(marker));
4938
+ context.fillText(marker.toLocaleString(), 5 * dpr, _this.y(marker));
4661
4939
  });
4662
4940
  // draw horizontal lines for vertical axis
4663
4941
  context.save();
4664
4942
  context.strokeStyle = "#999";
4665
4943
  markers.forEach(function (marker) {
4666
- if (_this.y(marker) >= height - PADDING_BOTTOM)
4944
+ if (_this.y(marker) >= height - paddingBottom)
4667
4945
  return;
4668
4946
  context.beginPath();
4669
- context.moveTo(textMaxWidth + 10, _this.y(marker));
4947
+ context.moveTo(textMaxWidth + 10 * dpr, _this.y(marker));
4670
4948
  context.lineTo(_this.x(Math.max(width, _this.environment.time)), _this.y(marker));
4671
- context.setLineDash(lineDash);
4949
+ context.setLineDash(lineDash.map(function (v) { return v * dpr; }));
4672
4950
  context.stroke();
4673
4951
  });
4674
4952
  context.restore();
@@ -4685,12 +4963,12 @@ var LineChartRenderer = /** @class */ (function (_super) {
4685
4963
  _this.x(marker) - width / 2 < textMaxWidth) {
4686
4964
  return;
4687
4965
  }
4688
- context.font = 11 * window.devicePixelRatio + "px Helvetica";
4689
- context.fillText(marker.toLocaleString(), _this.x(marker), height - PADDING_BOTTOM);
4966
+ context.font = 11 * dpr + "px Helvetica";
4967
+ context.fillText(marker.toLocaleString(), _this.x(marker), height - paddingBottom);
4690
4968
  context.strokeStyle = "black";
4691
4969
  context.lineWidth = 1;
4692
4970
  context.beginPath();
4693
- context.moveTo(_this.x(marker), height - 4);
4971
+ context.moveTo(_this.x(marker), height - 4 * dpr);
4694
4972
  context.lineTo(_this.x(marker), height);
4695
4973
  context.stroke();
4696
4974
  });
@@ -5038,7 +5316,8 @@ var Heatmap = /** @class */ (function (_super) {
5038
5316
  */
5039
5317
  Heatmap.prototype.x = function (value) {
5040
5318
  var width = this.width;
5041
- return remap(value, this.getMin("x"), this.getMax("x"), PADDING_AT_LEFT$1, width);
5319
+ var dpr = window.devicePixelRatio;
5320
+ return remap(value, this.getMin("x"), this.getMax("x"), PADDING_AT_LEFT$1 * dpr, width);
5042
5321
  };
5043
5322
  /**
5044
5323
  * Map a value (on the range y-min to y-max) onto canvas space to draw it along the y-axis.
@@ -5046,7 +5325,8 @@ var Heatmap = /** @class */ (function (_super) {
5046
5325
  */
5047
5326
  Heatmap.prototype.y = function (value) {
5048
5327
  var height = this.height;
5049
- return remap(value, this.getMin("y"), this.getMax("y"), height - PADDING_AT_BOTTOM$1, 0);
5328
+ var dpr = window.devicePixelRatio;
5329
+ return remap(value, this.getMin("y"), this.getMax("y"), height - PADDING_AT_BOTTOM$1 * dpr, 0);
5050
5330
  };
5051
5331
  /** @hidden */
5052
5332
  Heatmap.prototype.getKey = function (axis) {
@@ -5089,52 +5369,55 @@ var Heatmap = /** @class */ (function (_super) {
5089
5369
  Heatmap.prototype.drawMarkers = function () {
5090
5370
  var _a = this, context = _a.context, width = _a.width, height = _a.height;
5091
5371
  var _b = this.opts, from = _b.from, to = _b.to;
5372
+ var dpr = window.devicePixelRatio;
5373
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5374
+ var padBottom = PADDING_AT_BOTTOM$1 * dpr;
5092
5375
  context.strokeStyle = "black";
5093
5376
  context.lineWidth = 1;
5094
- context.moveTo(PADDING_AT_LEFT$1 - 1, 0);
5095
- context.lineTo(PADDING_AT_LEFT$1 - 1, height - PADDING_AT_BOTTOM$1 + 1);
5096
- context.lineTo(width, height - PADDING_AT_BOTTOM$1 + 1);
5377
+ context.moveTo(padLeft - 1, 0);
5378
+ context.lineTo(padLeft - 1, height - padBottom + 1);
5379
+ context.lineTo(width, height - padBottom + 1);
5097
5380
  context.stroke();
5098
5381
  context.lineWidth = 0;
5099
- var gradient = context.createLinearGradient(10, 0, PADDING_AT_LEFT$1 - 10, 0);
5382
+ var gradient = context.createLinearGradient(10 * dpr, 0, padLeft - 10 * dpr, 0);
5100
5383
  gradient.addColorStop(0, from);
5101
5384
  gradient.addColorStop(1, to);
5102
5385
  context.fillStyle = gradient;
5103
- context.fillRect(10, height - PADDING_AT_BOTTOM$1 + 20, PADDING_AT_LEFT$1 - 24, 20);
5386
+ context.fillRect(10 * dpr, height - padBottom + 20 * dpr, padLeft - 24 * dpr, 20 * dpr);
5104
5387
  context.fillStyle = "black";
5105
5388
  var step = (this.getMax("x") - this.getMin("x")) / this.getBuckets("x");
5106
5389
  var originalStep = step;
5107
- while (Math.abs(this.x(step) - this.x(0)) < 35)
5390
+ while (Math.abs(this.x(step) - this.x(0)) < 35 * dpr)
5108
5391
  step *= 2;
5109
5392
  for (var marker = this.getMin("x"); marker <= this.getMax("x"); marker += originalStep) {
5110
- if (this.x(marker) + 10 > width)
5393
+ if (this.x(marker) + 10 * dpr > width)
5111
5394
  continue;
5112
- context.moveTo(this.x(marker), height - PADDING_AT_BOTTOM$1);
5113
- context.lineTo(this.x(marker), height - PADDING_AT_BOTTOM$1 + 10);
5395
+ context.moveTo(this.x(marker), height - padBottom);
5396
+ context.lineTo(this.x(marker), height - padBottom + 10 * dpr);
5114
5397
  context.stroke();
5115
5398
  if (Math.abs(((marker - this.getMin("x")) / step) % 1) < 0.001 ||
5116
5399
  Math.abs((((marker - this.getMin("x")) / step) % 1) - 1) < 0.001) {
5117
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5400
+ context.font = 12 * dpr + "px Helvetica";
5118
5401
  context.textAlign = "center";
5119
- context.fillText(marker.toLocaleString(), this.x(marker), height - PADDING_AT_BOTTOM$1 + 24);
5402
+ context.fillText(marker.toLocaleString(), this.x(marker), height - padBottom + 24 * dpr);
5120
5403
  }
5121
5404
  }
5122
5405
  step = (this.getMax("y") - this.getMin("y")) / this.getBuckets("y");
5123
5406
  originalStep = step;
5124
- while (Math.abs(this.y(step) - this.y(0)) < 20)
5407
+ while (Math.abs(this.y(step) - this.y(0)) < 20 * dpr)
5125
5408
  step *= 2;
5126
5409
  for (var marker = this.getMin("y"); marker <= this.getMax("y"); marker += originalStep) {
5127
- if (this.y(marker) - 10 < 0)
5410
+ if (this.y(marker) - 10 * dpr < 0)
5128
5411
  continue;
5129
- context.moveTo(PADDING_AT_LEFT$1, this.y(marker));
5130
- context.lineTo(PADDING_AT_LEFT$1 - 10, this.y(marker));
5412
+ context.moveTo(padLeft, this.y(marker));
5413
+ context.lineTo(padLeft - 10 * dpr, this.y(marker));
5131
5414
  context.stroke();
5132
5415
  if (Math.abs(((marker - this.getMin("y")) / step) % 1) < 0.001 ||
5133
5416
  Math.abs((((marker - this.getMin("y")) / step) % 1) - 1) < 0.001) {
5134
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5417
+ context.font = 12 * dpr + "px Helvetica";
5135
5418
  context.textAlign = "right";
5136
5419
  context.textBaseline = "middle";
5137
- context.fillText(marker.toLocaleString(), PADDING_AT_LEFT$1 - 14, this.y(marker));
5420
+ context.fillText(marker.toLocaleString(), padLeft - 14 * dpr, this.y(marker));
5138
5421
  }
5139
5422
  }
5140
5423
  };
@@ -5142,6 +5425,8 @@ var Heatmap = /** @class */ (function (_super) {
5142
5425
  Heatmap.prototype.updateScale = function () {
5143
5426
  var _a = this, context = _a.context, environment = _a.environment, height = _a.height;
5144
5427
  var scale = this.opts.scale;
5428
+ var dpr = window.devicePixelRatio;
5429
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5145
5430
  var max = scale === "relative" ? this.localMax : this.opts.max;
5146
5431
  if (max === undefined) {
5147
5432
  if (!this.lastUpdatedScale) {
@@ -5150,13 +5435,13 @@ var Heatmap = /** @class */ (function (_super) {
5150
5435
  max = environment.getAgents().length;
5151
5436
  }
5152
5437
  if (!this.lastUpdatedScale || +new Date() - +this.lastUpdatedScale > 250) {
5153
- context.clearRect(0, height - 20, PADDING_AT_LEFT$1, 20);
5438
+ context.clearRect(0, height - 20 * dpr, padLeft, 20 * dpr);
5154
5439
  context.fillStyle = "black";
5155
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5440
+ context.font = 12 * dpr + "px Helvetica";
5156
5441
  context.textAlign = "center";
5157
5442
  context.textBaseline = "bottom";
5158
- context.fillText("0", 10, height - 5);
5159
- context.fillText(max.toString(), PADDING_AT_LEFT$1 - 16, height - 5);
5443
+ context.fillText("0", 10 * dpr, height - 5 * dpr);
5444
+ context.fillText(max.toString(), padLeft - 16 * dpr, height - 5 * dpr);
5160
5445
  this.lastUpdatedScale = new Date();
5161
5446
  }
5162
5447
  };
@@ -5164,6 +5449,9 @@ var Heatmap = /** @class */ (function (_super) {
5164
5449
  Heatmap.prototype.drawRectangles = function () {
5165
5450
  var _a = this, canvas = _a.canvas, environment = _a.environment, width = _a.width, height = _a.height;
5166
5451
  var _b = this.opts, scale = _b.scale, from = _b.from, to = _b.to;
5452
+ var dpr = window.devicePixelRatio;
5453
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5454
+ var padBottom = PADDING_AT_BOTTOM$1 * dpr;
5167
5455
  var context = canvas.getContext("2d");
5168
5456
  var xBuckets = this.getBuckets("x");
5169
5457
  var yBuckets = this.getBuckets("y");
@@ -5172,9 +5460,9 @@ var Heatmap = /** @class */ (function (_super) {
5172
5460
  max = environment.getAgents().length;
5173
5461
  // clear background by drawing background rectangle
5174
5462
  context.fillStyle = from;
5175
- context.fillRect(PADDING_AT_LEFT$1, 0, width, height - PADDING_AT_BOTTOM$1);
5176
- var w = width / xBuckets;
5177
- var h = height / yBuckets;
5463
+ context.fillRect(padLeft, 0, width - padLeft, height - padBottom);
5464
+ var w = (width - padLeft) / xBuckets;
5465
+ var h = (height - padBottom) / yBuckets;
5178
5466
  for (var row = 0; row < yBuckets; row++) {
5179
5467
  for (var column = 0; column < xBuckets; column++) {
5180
5468
  var index = row * xBuckets + column;
@@ -5182,7 +5470,7 @@ var Heatmap = /** @class */ (function (_super) {
5182
5470
  var a = clamp(remap(this.buckets[index], 0, max, 0, 1), 0, 1);
5183
5471
  context.fillStyle = to;
5184
5472
  context.globalAlpha = a;
5185
- 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);
5473
+ 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);
5186
5474
  }
5187
5475
  }
5188
5476
  context.globalAlpha = 1;
@@ -5238,6 +5526,6 @@ var Heatmap = /** @class */ (function (_super) {
5238
5526
  /**
5239
5527
  * The current version of the Flocc library.
5240
5528
  */
5241
- var version = "0.5.21";
5529
+ var version = "0.5.22";
5242
5530
 
5243
5531
  export { ASCIIRenderer, Agent, CanvasRenderer, Colors, Environment, GridEnvironment, Heatmap, Histogram, KDTree, LineChartRenderer, Network, NumArray, Rule, TableRenderer, Terrain, version as VERSION, Vector, utils };