flocc 0.5.21 → 0.5.23

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
@@ -817,7 +817,37 @@ var Operators;
817
817
  Operators["agent"] = "agent";
818
818
  Operators["environment"] = "environment";
819
819
  Operators["vector"] = "vector";
820
+ Operators["log"] = "log";
820
821
  })(Operators || (Operators = {}));
822
+ var operatorInfo = {
823
+ add: { min: 2, max: 2 },
824
+ subtract: { min: 2, max: 2 },
825
+ multiply: { min: 2, max: 2 },
826
+ divide: { min: 2, max: 2 },
827
+ mod: { min: 2, max: 2 },
828
+ power: { min: 2, max: 2 },
829
+ get: { min: 1, max: 1 },
830
+ set: { min: 2, max: 2 },
831
+ enqueue: { min: 2, max: 2 },
832
+ local: { min: 1, max: 2 },
833
+ if: { min: 2, max: 3 },
834
+ and: { min: 2, max: 2 },
835
+ or: { min: 2, max: 2 },
836
+ gt: { min: 2, max: 2 },
837
+ gte: { min: 2, max: 2 },
838
+ lt: { min: 2, max: 2 },
839
+ lte: { min: 2, max: 2 },
840
+ eq: { min: 2, max: 2 },
841
+ map: { min: 2, max: 2 },
842
+ filter: { min: 2, max: 2 },
843
+ key: { min: 2, max: 2 },
844
+ method: { min: 2, max: Infinity },
845
+ agent: { min: 0, max: 0 },
846
+ environment: { min: 0, max: 0 },
847
+ vector: { min: 1, max: Infinity },
848
+ log: { min: 1, max: Infinity }
849
+ };
850
+ var ARITHMETIC_OPS = new Set(["add", "subtract", "multiply", "divide", "mod", "power"]);
821
851
  var add = function (a, b) { return a + b; };
822
852
  var subtract = function (a, b) { return a - b; };
823
853
  var multiply = function (a, b) { return a * b; };
@@ -841,6 +871,127 @@ var method = function (obj, name) {
841
871
  return null;
842
872
  return obj[name].apply(obj, args);
843
873
  };
874
+ /**
875
+ * Format a single value for step display. If isFirst is true and the value is
876
+ * a known operator string, render it bare (unquoted).
877
+ */
878
+ function formatStepValue(val, isFirst) {
879
+ if (val === null || val === undefined)
880
+ return String(val);
881
+ if (typeof val === "number" || typeof val === "boolean")
882
+ return String(val);
883
+ if (typeof val === "string") {
884
+ if (isFirst && val in Operators)
885
+ return val;
886
+ return JSON.stringify(val);
887
+ }
888
+ if (val instanceof Array) {
889
+ if (val.length === 0)
890
+ return "[]";
891
+ var parts = val.map(function (s, i) { return formatStepValue(s, i === 0); });
892
+ return "(" + parts.join(" ") + ")";
893
+ }
894
+ if (typeof val === "object") {
895
+ try {
896
+ return JSON.stringify(val);
897
+ }
898
+ catch (_a) {
899
+ return String(val);
900
+ }
901
+ }
902
+ return String(val);
903
+ }
904
+ /**
905
+ * Format a step into a readable string representation for logging.
906
+ */
907
+ function formatStep(step) {
908
+ return formatStepValue(step, false);
909
+ }
910
+ /**
911
+ * Format a value as an S-expression atom (no recursion into arrays).
912
+ */
913
+ function formatAtom(val) {
914
+ if (val === null || val === undefined)
915
+ return "null";
916
+ if (typeof val === "number" || typeof val === "boolean")
917
+ return String(val);
918
+ if (typeof val === "string")
919
+ return JSON.stringify(val);
920
+ if (typeof val === "object" && !Array.isArray(val)) {
921
+ try {
922
+ return JSON.stringify(val);
923
+ }
924
+ catch (_a) {
925
+ return "[object]";
926
+ }
927
+ }
928
+ return String(val);
929
+ }
930
+ /**
931
+ * Format a value for S-expression display. The first element (operator) of
932
+ * a step is rendered as a bare identifier; other string values are quoted.
933
+ */
934
+ function formatSexpValue(val, isOperator) {
935
+ if (isOperator && typeof val === "string")
936
+ return val;
937
+ return formatAtom(val);
938
+ }
939
+ /**
940
+ * Pretty-print a single step as an S-expression with width-based wrapping.
941
+ */
942
+ function prettyFormatStep(step, indentUnit, maxLineWidth, currentIndent) {
943
+ if (!Array.isArray(step))
944
+ return formatAtom(step);
945
+ if (step.length === 0)
946
+ return "()";
947
+ // Try compact first
948
+ var compact = "(" + step.map(function (s, i) {
949
+ if (Array.isArray(s))
950
+ return prettyFormatStep(s, indentUnit, Infinity, "");
951
+ return formatSexpValue(s, i === 0);
952
+ }).join(" ") + ")";
953
+ if (currentIndent.length + compact.length <= maxLineWidth)
954
+ return compact;
955
+ // Wrap: operator on first line, each arg indented
956
+ var op = Array.isArray(step[0]) ? prettyFormatStep(step[0], indentUnit, maxLineWidth, currentIndent) : formatSexpValue(step[0], true);
957
+ var args = step.slice(1);
958
+ if (args.length === 0)
959
+ return "(" + op + ")";
960
+ var childIndent = currentIndent + indentUnit;
961
+ var formattedArgs = args.map(function (a) { return prettyFormatStep(a, indentUnit, maxLineWidth, childIndent); });
962
+ return "(" + op + "\n" + formattedArgs.map(function (a) { return childIndent + a; }).join("\n") + ")";
963
+ }
964
+ function validOperatorNames() {
965
+ return Object.keys(operatorInfo).join(", ");
966
+ }
967
+ function checkArity(op, argCount, strict) {
968
+ if (strict === void 0) { strict = false; }
969
+ var info = operatorInfo[op];
970
+ if (!info)
971
+ return null;
972
+ if (info.min === info.max) {
973
+ if (argCount < info.min) {
974
+ return "Rule: \"" + op + "\" expects " + info.min + " argument" + (info.min !== 1 ? "s" : "") + ", got " + argCount;
975
+ }
976
+ if (strict && argCount > info.max) {
977
+ return "Rule: \"" + op + "\" expects " + info.min + " argument" + (info.min !== 1 ? "s" : "") + ", got " + argCount;
978
+ }
979
+ }
980
+ else if (info.max === Infinity) {
981
+ if (argCount < info.min) {
982
+ return "Rule: \"" + op + "\" expects at least " + info.min + " argument" + (info.min !== 1 ? "s" : "") + ", got " + argCount;
983
+ }
984
+ }
985
+ else {
986
+ if (argCount < info.min) {
987
+ return "Rule: \"" + op + "\" expects " + info.min + "-" + info.max + " arguments, got " + argCount;
988
+ }
989
+ if (strict && argCount > info.max) {
990
+ return "Rule: \"" + op + "\" expects " + info.min + "-" + info.max + " arguments, got " + argCount;
991
+ }
992
+ }
993
+ return null;
994
+ }
844
995
  /**
845
996
  * The `Rule` class is an experimental interface for adding behavior to {@linkcode Agent}s. A `Rule` object may be used in place of a `tick` function to be added as `Agent` behavior using `agent.set('tick', tickRule)`. As a trivial example, consider the following `Rule`, which increments the `Agent`'s `x` value with every time step:
846
997
  *
@@ -895,6 +1046,7 @@ var Rule = /** @class */ (function () {
895
1046
  * |`"agent"`|`0`|No arguments; returns the `Agent`|
896
1047
  * |`"environment"`|`0`|No arguments, returns the `Environment`|
897
1048
  * |`"vector"`|`any`|Creates an n-dimensional {@linkcode Vector} from the supplied arguments|
1049
+ * |`"log"`|`any`|Logs expression(s) and returns the last evaluated value|
898
1050
  */
899
1051
  function Rule(environment, steps) {
900
1052
  var _this = this;
@@ -903,10 +1055,12 @@ var Rule = /** @class */ (function () {
903
1055
  /** @hidden */
904
1056
  this.locals = {};
905
1057
  /**
906
- * interpret single array step
907
- * @since 0.3.0
908
- * @hidden
1058
+ * When true, evaluation traces are logged and captured.
909
1059
  */
1060
+ this.trace = false;
1061
+ this.traceLog = [];
1062
+ /** @hidden */
1063
+ this._traceDepth = 0;
910
1064
  this.evaluate = function (agent, step) {
911
1065
  var first = step && step.length > 0 ? step[0] : null;
912
1066
  if (first === undefined || first === null)
@@ -917,30 +1071,79 @@ var Rule = /** @class */ (function () {
917
1071
  return _this.evaluate(agent, step.slice(1));
918
1072
  return innerStep;
919
1073
  }
920
- if (first === "log") {
921
- console.log("logging", step.slice(1), _this.evaluate(agent, step.slice(1)));
922
- return null;
1074
+ // Trace: check if this is a known operator call to trace
1075
+ var shouldTrace = _this.trace && typeof first === "string" && first in Operators;
1076
+ if (shouldTrace)
1077
+ _this._traceDepth++;
1078
+ var result = _this._evaluateOp(agent, step, first);
1079
+ if (shouldTrace) {
1080
+ _this._traceLogEntry(step, result);
1081
+ _this._traceDepth--;
1082
+ }
1083
+ return result;
1084
+ };
1085
+ /** @hidden */
1086
+ this._evaluateOp = function (agent, step, first) {
1087
+ if (first === Operators.log) {
1088
+ var args = step.slice(1);
1089
+ if (args.length === 0) {
1090
+ console.warn("Rule: \"log\" expects at least 1 argument, got 0");
1091
+ return null;
1092
+ }
1093
+ var lastValue = null;
1094
+ for (var i = 0; i < args.length; i++) {
1095
+ var expr = args[i];
1096
+ var evaluated = _this.evaluate(agent, [expr]);
1097
+ console.log("Rule log: " + formatStep(expr) + " \u2192 " + formatStep(evaluated));
1098
+ lastValue = evaluated;
1099
+ }
1100
+ return lastValue;
923
1101
  }
924
1102
  if (!(first in Operators) && step.length > 1) {
1103
+ if (typeof first === "string") {
1104
+ console.warn("Rule: unknown operator \"" + first + "\". Valid operators: " + validOperatorNames());
1105
+ }
925
1106
  return step;
926
1107
  }
1108
+ var argCount = step.length - 1;
1109
+ // Arity check at runtime for known operators
1110
+ if (typeof first === "string" && first in Operators) {
1111
+ var arityMsg = checkArity(first, argCount);
1112
+ if (arityMsg) {
1113
+ console.warn(arityMsg);
1114
+ }
1115
+ }
927
1116
  var a = step.length > 1 ? [step[1]] : undefined;
928
1117
  var b = step.length > 2 ? [step[2]] : undefined;
929
1118
  var c = step.length > 3 ? [step[3]] : undefined;
930
- if (first === Operators.add)
931
- return add(_this.evaluate(agent, a), _this.evaluate(agent, b));
932
- if (first === Operators.subtract)
933
- return subtract(_this.evaluate(agent, a), _this.evaluate(agent, b));
934
- if (first === Operators.multiply)
935
- return multiply(_this.evaluate(agent, a), _this.evaluate(agent, b));
936
- if (first === Operators.divide)
937
- return divide(_this.evaluate(agent, a), _this.evaluate(agent, b));
938
- if (first === Operators.mod)
939
- return mod(_this.evaluate(agent, a), _this.evaluate(agent, b));
940
- if (first === Operators.power)
941
- return power(_this.evaluate(agent, a), _this.evaluate(agent, b));
942
- if (first === Operators.get)
943
- return get(agent, _this.evaluate(agent, a));
1119
+ // Arithmetic operators with type checking
1120
+ if (ARITHMETIC_OPS.has(first)) {
1121
+ var va = _this.evaluate(agent, a);
1122
+ var vb = _this.evaluate(agent, b);
1123
+ if (typeof va !== "number" || typeof vb !== "number") {
1124
+ console.warn("Rule: \"" + first + "\" expects numeric arguments, got " + typeof va + " and " + typeof vb);
1125
+ }
1126
+ if (first === Operators.add)
1127
+ return add(va, vb);
1128
+ if (first === Operators.subtract)
1129
+ return subtract(va, vb);
1130
+ if (first === Operators.multiply)
1131
+ return multiply(va, vb);
1132
+ if (first === Operators.divide)
1133
+ return divide(va, vb);
1134
+ if (first === Operators.mod)
1135
+ return mod(va, vb);
1136
+ if (first === Operators.power)
1137
+ return power(va, vb);
1138
+ }
1139
+ if (first === Operators.get) {
1140
+ var keyName = _this.evaluate(agent, a);
1141
+ var result = get(agent, keyName);
1142
+ if (result === null || result === undefined) {
1143
+ console.warn("Rule: \"get\" returned null for key \"" + keyName + "\" \u2014 key may not exist on agent");
1144
+ }
1145
+ return result;
1146
+ }
944
1147
  if (first === Operators.set)
945
1148
  return set(agent, _this.evaluate(agent, a), _this.evaluate(agent, b));
946
1149
  if (first === Operators.enqueue) {
@@ -1028,13 +1231,151 @@ var Rule = /** @class */ (function () {
1028
1231
  this.environment = environment;
1029
1232
  this.steps = steps;
1030
1233
  }
1234
+ /**
1235
+ * Format arbitrary steps as pretty-printed S-expressions.
1236
+ */
1237
+ Rule.formatSteps = function (steps, options) {
1238
+ var indent = (options && options.indent) || " ";
1239
+ var maxLineWidth = (options && options.maxLineWidth) || 60;
1240
+ // Check if it's multi-step (array of arrays)
1241
+ var isMultiStep = steps.length > 0 && Array.isArray(steps[0]);
1242
+ if (isMultiStep) {
1243
+ return steps.map(function (s) { return prettyFormatStep(s, indent, maxLineWidth, ""); }).join("\n\n");
1244
+ }
1245
+ return prettyFormatStep(steps, indent, maxLineWidth, "");
1246
+ };
1247
+ /**
1248
+ * Pretty-print the rule's step tree as S-expressions.
1249
+ */
1250
+ Rule.prototype.format = function (options) {
1251
+ return Rule.formatSteps(this.steps, options);
1252
+ };
1253
+ /**
1254
+ * Returns a formatted S-expression representation of the rule.
1255
+ */
1256
+ Rule.prototype.toString = function () {
1257
+ return this.format();
1258
+ };
1259
+ /**
1260
+ * Validate the rule's step tree and return an array of diagnostics.
1261
+ * Does not throw — returns diagnostics for inspection.
1262
+ * @since 0.6.0
1263
+ */
1264
+ Rule.prototype.validate = function () {
1265
+ var diagnostics = [];
1266
+ if (!this.steps || (Array.isArray(this.steps) && this.steps.length === 0)) {
1267
+ diagnostics.push({
1268
+ path: "root",
1269
+ level: "warning",
1270
+ message: "Empty steps array"
1271
+ });
1272
+ return diagnostics;
1273
+ }
1274
+ // Check if steps is a single step (first element is a string operator)
1275
+ // or an array of steps (first element is an array)
1276
+ var isMultiStep = Array.isArray(this.steps[0]);
1277
+ if (isMultiStep) {
1278
+ // Multiple top-level steps
1279
+ for (var i = 0; i < this.steps.length; i++) {
1280
+ var step = this.steps[i];
1281
+ if (!Array.isArray(step)) {
1282
+ diagnostics.push({
1283
+ path: "[" + i + "]",
1284
+ level: "warning",
1285
+ message: "Step is a bare value (" + typeof step + ": " + JSON.stringify(step) + ") instead of an array"
1286
+ });
1287
+ }
1288
+ else {
1289
+ this._validateStep(step, "[" + i + "]", diagnostics);
1290
+ }
1291
+ }
1292
+ }
1293
+ else {
1294
+ // Single step (the steps array IS the step)
1295
+ this._validateStep(this.steps, "root", diagnostics);
1296
+ }
1297
+ return diagnostics;
1298
+ };
1299
+ /** @hidden */
1300
+ Rule.prototype._validateStep = function (step, path, diagnostics) {
1301
+ if (step.length === 0) {
1302
+ diagnostics.push({
1303
+ path: path,
1304
+ level: "warning",
1305
+ message: "Empty step array"
1306
+ });
1307
+ return;
1308
+ }
1309
+ var first = step[0];
1310
+ // If first element is an array, it's a nested step sequence
1311
+ if (Array.isArray(first)) {
1312
+ this._validateStep(first, path + "[0]", diagnostics);
1313
+ for (var i = 1; i < step.length; i++) {
1314
+ if (Array.isArray(step[i])) {
1315
+ this._validateStep(step[i], path + ("[" + i + "]"), diagnostics);
1316
+ }
1317
+ }
1318
+ return;
1319
+ }
1320
+ if (typeof first === "string") {
1321
+ var argCount = step.length - 1;
1322
+ // Check if operator is known
1323
+ if (!(first in Operators)) {
1324
+ if (step.length > 1) {
1325
+ diagnostics.push({
1326
+ path: path,
1327
+ level: "error",
1328
+ message: "Unknown operator \"" + first + "\". Valid operators: " + validOperatorNames()
1329
+ });
1330
+ }
1331
+ return;
1332
+ }
1333
+ // Check arity (strict: also warn on too many args)
1334
+ var arityMsg = checkArity(first, argCount, true);
1335
+ if (arityMsg) {
1336
+ diagnostics.push({
1337
+ path: path,
1338
+ level: "error",
1339
+ message: arityMsg
1340
+ });
1341
+ }
1342
+ // Recurse into sub-steps
1343
+ for (var i = 1; i < step.length; i++) {
1344
+ if (Array.isArray(step[i])) {
1345
+ this._validateStep(step[i], path + ("[" + i + "]"), diagnostics);
1346
+ }
1347
+ }
1348
+ }
1349
+ };
1350
+ /**
1351
+ * interpret single array step
1352
+ * @since 0.3.0
1353
+ * @hidden
1354
+ */
1355
+ /** @hidden */
1356
+ Rule.prototype._traceLogEntry = function (step, result) {
1357
+ var indent = " ".repeat(this._traceDepth - 1);
1358
+ var formatted = formatStep(step);
1359
+ var resultFormatted = formatStep(result);
1360
+ var line = "Rule trace: " + indent + formatted + " \u2192 " + resultFormatted;
1361
+ console.log(line);
1362
+ this.traceLog.push(line);
1363
+ };
1031
1364
  /**
1032
1365
  * @since 0.3.0
1033
1366
  * @hidden
1034
1367
  */
1035
1368
  Rule.prototype.call = function (agent) {
1369
+ if (this.trace) {
1370
+ this.traceLog = [];
1371
+ this._traceDepth = 0;
1372
+ }
1036
1373
  return this.evaluate(agent, this.steps);
1037
1374
  };
1375
+ /**
1376
+ * Static operator info mapping operator names to their expected argument counts.
1377
+ */
1378
+ Rule.operatorInfo = operatorInfo;
1038
1379
  return Rule;
1039
1380
  }());
1040
1381
 
@@ -3212,6 +3553,15 @@ var Environment = /** @class */ (function (_super) {
3212
3553
  * such as a {@linkcode LineChartRenderer}, {@linkcode Histogram}, etc.
3213
3554
  */
3214
3555
  _this.renderers = [];
3556
+ /**
3557
+ * Whether the `Environment` tick cycle is currently playing.
3558
+ * Use {@linkcode pause}, {@linkcode resume}, or {@linkcode toggle}
3559
+ * to control playback.
3560
+ * @since 0.5.22
3561
+ */
3562
+ _this.playing = true;
3563
+ /** @hidden */
3564
+ _this._tickIntervalId = null;
3215
3565
  /**
3216
3566
  * This property will always equal the number of tick cycles that
3217
3567
  * have passed since the `Environment` was created. If you call
@@ -3381,6 +3731,9 @@ var Environment = /** @class */ (function (_super) {
3381
3731
  * @since 0.0.5
3382
3732
  */
3383
3733
  Environment.prototype.tick = function (opts) {
3734
+ // If paused, skip the tick cycle (use `step()` to advance manually)
3735
+ if (!this.playing)
3736
+ return;
3384
3737
  var _a = this._getTickOptions(opts), activation = _a.activation, activationCount = _a.activationCount, count = _a.count, randomizeOrder = _a.randomizeOrder;
3385
3738
  // for uniform activation, every agent is always activated
3386
3739
  if (activation === "uniform") {
@@ -3448,6 +3801,47 @@ var Environment = /** @class */ (function (_super) {
3448
3801
  }
3449
3802
  this.renderers.forEach(function (r) { return r.render(); });
3450
3803
  };
3804
+ /**
3805
+ * Pause the tick cycle. While paused, calling {@linkcode tick} will
3806
+ * be a no-op unless you use {@linkcode step} to advance manually.
3807
+ * @since 0.5.22
3808
+ */
3809
+ Environment.prototype.pause = function () {
3810
+ this.playing = false;
3811
+ };
3812
+ /**
3813
+ * Resume the tick cycle after it has been paused.
3814
+ * @since 0.5.22
3815
+ */
3816
+ Environment.prototype.resume = function () {
3817
+ this.playing = true;
3818
+ };
3819
+ /**
3820
+ * Toggle the tick cycle between playing and paused.
3821
+ * @since 0.5.22
3822
+ */
3823
+ Environment.prototype.toggle = function () {
3824
+ this.playing = !this.playing;
3825
+ };
3826
+ /**
3827
+ * Advance the `Environment` by exactly one tick, regardless of whether
3828
+ * it is paused. This is useful for stepping through the simulation
3829
+ * frame-by-frame while paused.
3830
+ *
3831
+ * ```js
3832
+ * environment.pause();
3833
+ * environment.step(); // advances one tick
3834
+ * ```
3835
+ *
3836
+ * @since 0.5.22
3837
+ */
3838
+ Environment.prototype.step = function (opts) {
3839
+ // Temporarily mark as playing so tick executes, then restore
3840
+ var wasPlaying = this.playing;
3841
+ this.playing = true;
3842
+ this.tick(opts);
3843
+ this.playing = wasPlaying;
3844
+ };
3451
3845
  /**
3452
3846
  * Use a helper with this environment. A helper can be one of:
3453
3847
  * - {@linkcode KDTree}
@@ -3984,7 +4378,10 @@ var defaultOptions = {
3984
4378
  width: 500,
3985
4379
  height: 500,
3986
4380
  scale: 1,
3987
- trace: false
4381
+ trace: false,
4382
+ interactive: false,
4383
+ zoomMin: 0.1,
4384
+ zoomMax: 10
3988
4385
  };
3989
4386
  /**
3990
4387
  * A `CanvasRenderer` renders an {@linkcode Environment} spatially in two dimensions.
@@ -4004,6 +4401,12 @@ var defaultOptions = {
4004
4401
  * - `"triangle"` &mdash; Draws a triangle centered at the `Agent`'s `"x"` / `"y"` values.
4005
4402
  * - Also uses the `"size"` value.
4006
4403
  *
4404
+ * When `interactive` is set to `true` in the options, the renderer supports:
4405
+ * - **Click/hover detection** &mdash; Use {@linkcode on} to listen for `"click"`, `"hover"`, and `"unhover"` events on agents.
4406
+ * - **Agent selection** &mdash; Clicking an agent selects it (highlighted with a stroke). Access selected agents via {@linkcode selected}.
4407
+ * - **Pan** &mdash; Click and drag on empty space to pan.
4408
+ * - **Zoom** &mdash; Scroll to zoom in/out (bounded by `zoomMin` / `zoomMax`).
4409
+ *
4007
4410
  * @since 0.0.11
4008
4411
  */
4009
4412
  var CanvasRenderer = /** @class */ (function (_super) {
@@ -4019,15 +4422,33 @@ var CanvasRenderer = /** @class */ (function (_super) {
4019
4422
  * - `connectionOpacity` (*number* = `1`) &mdash; For `Environment`s using a `Network`, the opacity of lines
4020
4423
  * - `connectionWidth` (*number* = `1`) &mdash; For `Environment`s using a `Network`, the width of lines
4021
4424
  * - `height` (*number* = `500`) &mdash; The height, in pixels, of the canvas on which to render
4425
+ * - `interactive` (*boolean* = `false`) &mdash; Enables interactive features (click/hover detection, selection, pan, zoom)
4426
+ * - `onSelect` (*function*) &mdash; Optional callback when an agent is selected or deselected
4022
4427
  * - `origin` (*{ x: number; y: number }* = `{ x: 0, y: 0 }`) &mdash; The coordinate of the upper-left point of the space to be rendered
4023
4428
  * - `scale` (*number* = `1`) &mdash; The scale at which to render (the larger the scale, the smaller the size of the space that is actually rendered)
4024
4429
  * - `trace` (*boolean* = `false`) &mdash; If `true`, the renderer will not clear old drawings, causing the `Agent`s to appear to *trace* their paths across space
4025
4430
  * - `width` (*number* = `500`) &mdash; The width, in pixels, of the canvas on which to render
4431
+ * - `zoomMin` (*number* = `0.1`) &mdash; Minimum scale when zooming
4432
+ * - `zoomMax` (*number* = `10`) &mdash; Maximum scale when zooming
4026
4433
  */
4027
4434
  function CanvasRenderer(environment, opts) {
4028
4435
  var _this = _super.call(this) || this;
4029
4436
  /** @hidden */
4030
4437
  _this.terrainBuffer = document.createElement("canvas");
4438
+ /** The currently selected agents (only used when `interactive` is `true`). */
4439
+ _this.selected = [];
4440
+ /** @hidden */
4441
+ _this._listeners = new Map();
4442
+ /** @hidden */
4443
+ _this._hoveredAgent = null;
4444
+ /** @hidden */
4445
+ _this._isPanning = false;
4446
+ /** @hidden */
4447
+ _this._panStart = null;
4448
+ /** @hidden */
4449
+ _this._panOriginStart = null;
4450
+ /** @hidden */
4451
+ _this._boundHandlers = {};
4031
4452
  _this.environment = environment;
4032
4453
  environment.renderers.push(_this);
4033
4454
  _this.opts = Object.assign({}, defaultOptions);
@@ -4044,9 +4465,158 @@ var CanvasRenderer = /** @class */ (function (_super) {
4044
4465
  _this.terrainBuffer.width = width;
4045
4466
  _this.terrainBuffer.height = height;
4046
4467
  _this.context.fillStyle = opts.background;
4047
- _this.context.fillRect(0, 0, width, height);
4468
+ _this.context.fillRect(0, 0, _this.width, _this.height);
4469
+ if (_this.opts.interactive) {
4470
+ _this._setupInteractiveListeners();
4471
+ }
4048
4472
  return _this;
4049
4473
  }
4474
+ /**
4475
+ * Register a callback for an interactive event.
4476
+ * Supported event names: `"click"`, `"hover"`, `"unhover"`.
4477
+ *
4478
+ * ```js
4479
+ * renderer.on("click", (agent, event) => {
4480
+ * console.log("Clicked agent:", agent.id);
4481
+ * });
4482
+ * ```
4483
+ *
4484
+ * @param eventName - The event to listen for.
4485
+ * @param callback - The callback, invoked with the `Agent` and the `MouseEvent`.
4486
+ */
4487
+ CanvasRenderer.prototype.on = function (eventName, callback) {
4488
+ if (!this._listeners.has(eventName)) {
4489
+ this._listeners.set(eventName, []);
4490
+ }
4491
+ this._listeners.get(eventName).push(callback);
4492
+ };
4493
+ /** @hidden */
4494
+ CanvasRenderer.prototype._emit = function (eventName, agent, event) {
4495
+ var callbacks = this._listeners.get(eventName);
4496
+ if (callbacks) {
4497
+ callbacks.forEach(function (cb) { return cb(agent, event); });
4498
+ }
4499
+ };
4500
+ /**
4501
+ * Given a mouse event, return the agent at that position (if any).
4502
+ * Hit-testing accounts for the agent's shape and size.
4503
+ * @hidden
4504
+ */
4505
+ CanvasRenderer.prototype._agentAtPoint = function (clientX, clientY) {
4506
+ var rect = this.canvas.getBoundingClientRect();
4507
+ var dpr = window.devicePixelRatio;
4508
+ var canvasX = (clientX - rect.left) * dpr;
4509
+ var canvasY = (clientY - rect.top) * dpr;
4510
+ var agents = this.environment.getAgents();
4511
+ // Iterate in reverse so topmost-drawn agent is found first
4512
+ for (var i = agents.length - 1; i >= 0; i--) {
4513
+ var agent = agents[i];
4514
+ var data = agent.getData();
4515
+ var ax = this.x(data.x);
4516
+ var ay = this.y(data.y);
4517
+ var shape = data.shape;
4518
+ var size = (data.size || 1) * dpr;
4519
+ if (shape === "rect") {
4520
+ var w = (data.width || 1) * dpr;
4521
+ var h = (data.height || 1) * dpr;
4522
+ var rx = ax - w / 2;
4523
+ var ry = ay - h / 2;
4524
+ if (canvasX >= rx && canvasX <= rx + w && canvasY >= ry && canvasY <= ry + h) {
4525
+ return agent;
4526
+ }
4527
+ }
4528
+ else if (shape === "triangle") {
4529
+ // Simple bounding-box hit test for triangles
4530
+ var halfSize = size / 2;
4531
+ if (canvasX >= ax - halfSize &&
4532
+ canvasX <= ax + halfSize &&
4533
+ canvasY >= ay - halfSize &&
4534
+ canvasY <= ay + halfSize) {
4535
+ return agent;
4536
+ }
4537
+ }
4538
+ else {
4539
+ // Default: circle (and arrow) — distance-based hit test
4540
+ var dx = canvasX - ax;
4541
+ var dy = canvasY - ay;
4542
+ var hitRadius = Math.max(size, 4 * dpr); // minimum hit area for tiny agents
4543
+ if (dx * dx + dy * dy <= hitRadius * hitRadius) {
4544
+ return agent;
4545
+ }
4546
+ }
4547
+ }
4548
+ return null;
4549
+ };
4550
+ /** @hidden */
4551
+ CanvasRenderer.prototype._setupInteractiveListeners = function () {
4552
+ var _this = this;
4553
+ var onMouseDown = function (e) {
4554
+ var agent = _this._agentAtPoint(e.clientX, e.clientY);
4555
+ if (agent) {
4556
+ // Agent click — select it
4557
+ _this.selected = [agent];
4558
+ if (_this.opts.onSelect)
4559
+ _this.opts.onSelect(agent);
4560
+ _this._emit("click", agent, e);
4561
+ _this.render();
4562
+ }
4563
+ else {
4564
+ // Empty space — deselect and start panning
4565
+ if (_this.selected.length > 0) {
4566
+ _this.selected = [];
4567
+ if (_this.opts.onSelect)
4568
+ _this.opts.onSelect(null);
4569
+ _this.render();
4570
+ }
4571
+ _this._isPanning = true;
4572
+ _this._panStart = { x: e.clientX, y: e.clientY };
4573
+ _this._panOriginStart = { x: _this.opts.origin.x, y: _this.opts.origin.y };
4574
+ }
4575
+ };
4576
+ var onMouseMove = function (e) {
4577
+ if (_this._isPanning && _this._panStart && _this._panOriginStart) {
4578
+ var dpr = window.devicePixelRatio;
4579
+ var dx = e.clientX - _this._panStart.x;
4580
+ var dy = e.clientY - _this._panStart.y;
4581
+ _this.opts.origin = {
4582
+ x: _this._panOriginStart.x - dx / (_this.opts.scale * dpr),
4583
+ y: _this._panOriginStart.y - dy / (_this.opts.scale * dpr)
4584
+ };
4585
+ _this.render();
4586
+ return;
4587
+ }
4588
+ // Hover detection
4589
+ var agent = _this._agentAtPoint(e.clientX, e.clientY);
4590
+ if (agent !== _this._hoveredAgent) {
4591
+ if (_this._hoveredAgent) {
4592
+ _this._emit("unhover", _this._hoveredAgent, e);
4593
+ }
4594
+ if (agent) {
4595
+ _this._emit("hover", agent, e);
4596
+ }
4597
+ _this._hoveredAgent = agent;
4598
+ }
4599
+ };
4600
+ var onMouseUp = function (e) {
4601
+ _this._isPanning = false;
4602
+ _this._panStart = null;
4603
+ _this._panOriginStart = null;
4604
+ };
4605
+ var onWheel = function (e) {
4606
+ e.preventDefault();
4607
+ var _a = _this.opts, zoomMin = _a.zoomMin, zoomMax = _a.zoomMax;
4608
+ var delta = e.deltaY > 0 ? 0.9 : 1.1;
4609
+ var newScale = _this.opts.scale * delta;
4610
+ newScale = Math.max(zoomMin, Math.min(zoomMax, newScale));
4611
+ _this.opts.scale = newScale;
4612
+ _this.render();
4613
+ };
4614
+ this._boundHandlers = { mousedown: onMouseDown, mousemove: onMouseMove, mouseup: onMouseUp, wheel: onWheel };
4615
+ this.canvas.addEventListener("mousedown", onMouseDown);
4616
+ this.canvas.addEventListener("mousemove", onMouseMove);
4617
+ this.canvas.addEventListener("mouseup", onMouseUp);
4618
+ this.canvas.addEventListener("wheel", onWheel, { passive: false });
4619
+ };
4050
4620
  /** @hidden */
4051
4621
  CanvasRenderer.prototype.x = function (v) {
4052
4622
  var _a = this.opts, origin = _a.origin, scale = _a.scale;
@@ -4083,21 +4653,21 @@ var CanvasRenderer = /** @class */ (function (_super) {
4083
4653
  };
4084
4654
  /** @hidden */
4085
4655
  CanvasRenderer.prototype.drawPathWrap = function (points) {
4086
- var _this = this;
4087
4656
  var _a = this, width = _a.width, height = _a.height;
4088
4657
  var right = false;
4089
4658
  var left = false;
4090
4659
  var lower = false;
4091
4660
  var upper = false;
4661
+ // points are already in DPR-scaled pixel space, so compare directly
4092
4662
  points.forEach(function (_a) {
4093
4663
  var px = _a[0], py = _a[1];
4094
- if (_this.x(px) >= width)
4664
+ if (px >= width)
4095
4665
  right = true;
4096
- if (_this.x(px) < 0)
4666
+ if (px < 0)
4097
4667
  left = true;
4098
- if (_this.y(py) >= height)
4668
+ if (py >= height)
4099
4669
  lower = true;
4100
- if (_this.y(py) < 0)
4670
+ if (py < 0)
4101
4671
  upper = true;
4102
4672
  });
4103
4673
  if (right)
@@ -4126,24 +4696,26 @@ var CanvasRenderer = /** @class */ (function (_super) {
4126
4696
  /** @hidden */
4127
4697
  CanvasRenderer.prototype.drawCircleWrap = function (x, y, size) {
4128
4698
  var _a = this, width = _a.width, height = _a.height;
4699
+ var worldWidth = this.opts.width;
4700
+ var worldHeight = this.opts.height;
4129
4701
  if (this.x(x + size) >= width) {
4130
- this.drawCircle(x - width, y, size);
4702
+ this.drawCircle(x - worldWidth, y, size);
4131
4703
  if (this.y(y + size) >= height)
4132
- this.drawCircle(x - width, y - height, size);
4704
+ this.drawCircle(x - worldWidth, y - worldHeight, size);
4133
4705
  if (this.y(y - size) < 0)
4134
- this.drawCircle(x - width, y + height, size);
4706
+ this.drawCircle(x - worldWidth, y + worldHeight, size);
4135
4707
  }
4136
4708
  if (this.x(x - size) < 0) {
4137
- this.drawCircle(x + width, y, size);
4709
+ this.drawCircle(x + worldWidth, y, size);
4138
4710
  if (this.y(y + size) >= height)
4139
- this.drawCircle(x + width, y - height, size);
4711
+ this.drawCircle(x + worldWidth, y - worldHeight, size);
4140
4712
  if (this.y(y - size) < 0)
4141
- this.drawCircle(x + width, y + height, size);
4713
+ this.drawCircle(x + worldWidth, y + worldHeight, size);
4142
4714
  }
4143
4715
  if (this.y(y + size) > height)
4144
- this.drawCircle(x, y - height, size);
4716
+ this.drawCircle(x, y - worldHeight, size);
4145
4717
  if (this.y(y - size) < 0)
4146
- this.drawCircle(x, y + height, size);
4718
+ this.drawCircle(x, y + worldHeight, size);
4147
4719
  };
4148
4720
  /**
4149
4721
  * Draw a rectangle centered at (x, y). Automatically calculates the offset
@@ -4157,25 +4729,55 @@ var CanvasRenderer = /** @class */ (function (_super) {
4157
4729
  };
4158
4730
  /** @hidden */
4159
4731
  CanvasRenderer.prototype.drawRectWrap = function (x, y, w, h) {
4160
- var _a = this.opts, width = _a.width, height = _a.height;
4732
+ var _a = this, width = _a.width, height = _a.height;
4733
+ var worldWidth = this.opts.width;
4734
+ var worldHeight = this.opts.height;
4161
4735
  if (this.x(x + w / 2) >= width) {
4162
- this.drawRect(x - width, y, w, h);
4736
+ this.drawRect(x - worldWidth, y, w, h);
4163
4737
  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);
4738
+ this.drawRect(x - worldWidth, y - worldHeight, w, h);
4739
+ if (this.y(y - h / 2) < 0)
4740
+ this.drawRect(x - worldWidth, y + worldHeight, w, h);
4167
4741
  }
4168
4742
  if (this.x(x - w / 2) < 0) {
4169
- this.drawRect(x + width, y, w, h);
4743
+ this.drawRect(x + worldWidth, y, w, h);
4170
4744
  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);
4745
+ this.drawRect(x + worldWidth, y - worldHeight, w, h);
4746
+ if (this.y(y - h / 2) < 0)
4747
+ this.drawRect(x + worldWidth, y + worldHeight, w, h);
4174
4748
  }
4175
4749
  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);
4750
+ this.drawRect(x, y - worldHeight, w, h);
4751
+ if (this.y(y - h / 2) < 0)
4752
+ this.drawRect(x, y + worldHeight, w, h);
4753
+ };
4754
+ /**
4755
+ * Draw a selection highlight around the given agent.
4756
+ * @hidden
4757
+ */
4758
+ CanvasRenderer.prototype._drawSelectionHighlight = function (agent) {
4759
+ var bufferContext = this.buffer.getContext("2d");
4760
+ var dpr = window.devicePixelRatio;
4761
+ var data = agent.getData();
4762
+ var ax = this.x(data.x);
4763
+ var ay = this.y(data.y);
4764
+ var shape = data.shape;
4765
+ var size = (data.size || 1) * dpr;
4766
+ bufferContext.save();
4767
+ bufferContext.strokeStyle = "#0af";
4768
+ bufferContext.lineWidth = 2 * dpr;
4769
+ if (shape === "rect") {
4770
+ var w = (data.width || 1) * dpr;
4771
+ var h = (data.height || 1) * dpr;
4772
+ bufferContext.strokeRect(ax - w / 2 - 2 * dpr, ay - h / 2 - 2 * dpr, w + 4 * dpr, h + 4 * dpr);
4773
+ }
4774
+ else {
4775
+ bufferContext.beginPath();
4776
+ var highlightRadius = Math.max(size, 4 * dpr) + 3 * dpr;
4777
+ bufferContext.arc(ax, ay, highlightRadius, 0, 2 * Math.PI);
4778
+ bufferContext.stroke();
4779
+ }
4780
+ bufferContext.restore();
4179
4781
  };
4180
4782
  CanvasRenderer.prototype.render = function () {
4181
4783
  var _this = this;
@@ -4188,22 +4790,24 @@ var CanvasRenderer = /** @class */ (function (_super) {
4188
4790
  // if "trace" is truthy, don't clear the canvas with every frame
4189
4791
  // to trace the paths of agents
4190
4792
  if (!trace) {
4191
- context.clearRect(0, 0, width * dpr, height * dpr);
4793
+ context.clearRect(0, 0, width, height);
4192
4794
  context.fillStyle = opts.background;
4193
- context.fillRect(0, 0, width * dpr, height * dpr);
4795
+ context.fillRect(0, 0, width, height);
4194
4796
  }
4195
4797
  // automatically position agents in an environment that uses a network helper
4196
4798
  if (opts.autoPosition && environment.helpers.network) {
4197
4799
  environment.getAgents().forEach(function (agent) {
4198
4800
  var network = _this.environment.helpers.network;
4199
- var _a = _this, width = _a.width, height = _a.height;
4801
+ // Use CSS pixel dimensions (opts), not the DPI-scaled canvas dimensions,
4802
+ // since x() and y() already apply the devicePixelRatio transform.
4803
+ var _a = _this.opts, w = _a.width, h = _a.height;
4200
4804
  // only set once
4201
4805
  if ((agent.get("x") === null || agent.get("y") === null) &&
4202
4806
  network.isInNetwork(agent)) {
4203
4807
  var idx = network.indexOf(agent);
4204
4808
  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);
4809
+ var x = w / 2 + 0.4 * w * Math.cos(2 * Math.PI * angle);
4810
+ var y = h / 2 + 0.4 * h * Math.sin(2 * Math.PI * angle);
4207
4811
  agent.set({ x: x, y: y });
4208
4812
  }
4209
4813
  });
@@ -4248,8 +4852,8 @@ var CanvasRenderer = /** @class */ (function (_super) {
4248
4852
  context.globalAlpha = _this.opts.connectionOpacity;
4249
4853
  context.strokeStyle = _this.opts.connectionColor;
4250
4854
  context.lineWidth = _this.opts.connectionWidth;
4251
- context.moveTo(_this.x(x), _this.x(y));
4252
- context.lineTo(_this.x(nx), _this.x(ny));
4855
+ context.moveTo(_this.x(x), _this.y(y));
4856
+ context.lineTo(_this.x(nx), _this.y(ny));
4253
4857
  context.stroke();
4254
4858
  context.closePath();
4255
4859
  context.restore();
@@ -4281,10 +4885,11 @@ var CanvasRenderer = /** @class */ (function (_super) {
4281
4885
  }
4282
4886
  else if (shape === "triangle") {
4283
4887
  bufferContext.beginPath();
4888
+ var scaledSize = size * dpr;
4284
4889
  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]
4890
+ [_this.x(x), _this.y(y) - scaledSize / 2],
4891
+ [_this.x(x) + scaledSize / 2, _this.y(y) + scaledSize / 2],
4892
+ [_this.x(x) - scaledSize / 2, _this.y(y) + scaledSize / 2]
4288
4893
  ];
4289
4894
  _this.drawPath(points);
4290
4895
  if (environment.opts.torus)
@@ -4310,6 +4915,12 @@ var CanvasRenderer = /** @class */ (function (_super) {
4310
4915
  bufferContext.restore();
4311
4916
  }
4312
4917
  });
4918
+ // Draw selection highlights for selected agents
4919
+ if (opts.interactive && this.selected.length > 0) {
4920
+ this.selected.forEach(function (agent) {
4921
+ _this._drawSelectionHighlight(agent);
4922
+ });
4923
+ }
4313
4924
  context.drawImage(buffer, 0, 0);
4314
4925
  };
4315
4926
  return CanvasRenderer;
@@ -4400,11 +5011,13 @@ var Histogram = /** @class */ (function (_super) {
4400
5011
  };
4401
5012
  Histogram.prototype.x = function (value) {
4402
5013
  var _a = this, width = _a.width, markerWidth = _a.markerWidth;
4403
- return remap(value, 0, width, markerWidth + PADDING_AT_LEFT, width - PADDING_AT_RIGHT);
5014
+ var dpr = window.devicePixelRatio;
5015
+ return remap(value, 0, width, markerWidth + PADDING_AT_LEFT * dpr, width - PADDING_AT_RIGHT * dpr);
4404
5016
  };
4405
5017
  Histogram.prototype.y = function (value) {
4406
5018
  var _a = this, height = _a.height, maxValue = _a.maxValue;
4407
- return remap(value, 0, maxValue, height - PADDING_AT_BOTTOM, 0);
5019
+ var dpr = window.devicePixelRatio;
5020
+ return remap(value, 0, maxValue, height - PADDING_AT_BOTTOM * dpr, 0);
4408
5021
  };
4409
5022
  Histogram.prototype.setMaxValue = function () {
4410
5023
  var _this = this;
@@ -4433,11 +5046,12 @@ var Histogram = /** @class */ (function (_super) {
4433
5046
  var context = this.canvas.getContext("2d");
4434
5047
  var _a = this, height = _a.height, width = _a.width;
4435
5048
  var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, buckets = _b.buckets, min = _b.min, max$1 = _b.max;
5049
+ var dpr = window.devicePixelRatio;
4436
5050
  var yMin = 0;
4437
5051
  var yMax = this.maxValue;
4438
5052
  var markers = extractRoundNumbers({ min: yMin, max: yMax });
4439
5053
  context.fillStyle = "black";
4440
- context.font = 14 * window.devicePixelRatio + "px Helvetica";
5054
+ context.font = 14 * dpr + "px Helvetica";
4441
5055
  // determine the width of the longest marker
4442
5056
  this.markerWidth = max(markers.map(function (marker) { return context.measureText(marker.toLocaleString()).width; }));
4443
5057
  // draw horizontal lines
@@ -4446,9 +5060,9 @@ var Histogram = /** @class */ (function (_super) {
4446
5060
  context.textBaseline = "middle";
4447
5061
  context.fillText(marker.toLocaleString(), _this.markerWidth, _this.y(marker));
4448
5062
  context.beginPath();
4449
- context.moveTo(_this.markerWidth + 10, _this.y(marker));
5063
+ context.moveTo(_this.markerWidth + 10 * dpr, _this.y(marker));
4450
5064
  context.lineTo(_this.width, _this.y(marker));
4451
- context.setLineDash(LINE_DASH);
5065
+ context.setLineDash(LINE_DASH.map(function (v) { return v * dpr; }));
4452
5066
  context.stroke();
4453
5067
  });
4454
5068
  var numBuckets = bucketValues.length - (aboveMax ? 1 : 0) - (belowMin ? 1 : 0);
@@ -4471,9 +5085,9 @@ var Histogram = /** @class */ (function (_super) {
4471
5085
  .forEach(function (label, i) {
4472
5086
  context.save();
4473
5087
  context.translate(_this.x((i * width) / bucketValues.length +
4474
- (0.5 * width) / bucketValues.length), height - 50);
5088
+ (0.5 * width) / bucketValues.length), height - 50 * dpr);
4475
5089
  context.rotate(Math.PI / 4);
4476
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5090
+ context.font = 12 * dpr + "px Helvetica";
4477
5091
  context.textAlign = "left";
4478
5092
  context.textBaseline = "middle";
4479
5093
  context.fillText(label, 0, 0);
@@ -4483,22 +5097,23 @@ var Histogram = /** @class */ (function (_super) {
4483
5097
  Histogram.prototype.drawBuckets = function (bucketValues, offset) {
4484
5098
  var _this = this;
4485
5099
  if (offset === void 0) { offset = 0; }
4486
- var canvas = this.canvas;
5100
+ var _a = this, canvas = _a.canvas, width = _a.width, height = _a.height;
4487
5101
  var metric = this._metric;
4488
5102
  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;
5103
+ var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, color = _b.color;
5104
+ var dpr = window.devicePixelRatio;
4490
5105
  var context = canvas.getContext("2d");
4491
5106
  context.fillStyle = Array.isArray(color)
4492
5107
  ? color[offset % color.length]
4493
5108
  : color;
4494
5109
  var numBuckets = bucketValues.length;
4495
- var barWidth = (width - PADDING_AT_LEFT - PADDING_AT_RIGHT - this.markerWidth) /
5110
+ var barWidth = (width - PADDING_AT_LEFT * dpr - PADDING_AT_RIGHT * dpr - this.markerWidth) /
4496
5111
  numBuckets;
4497
5112
  barWidth *= 0.8;
4498
5113
  bucketValues.forEach(function (value, i) {
4499
5114
  var mappedValue = remap(value, 0, _this.maxValue, 0, 1);
4500
5115
  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));
5116
+ 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
5117
  });
4503
5118
  };
4504
5119
  Histogram.prototype.getBucketValues = function (metric) {
@@ -4635,12 +5250,16 @@ var LineChartRenderer = /** @class */ (function (_super) {
4635
5250
  var height = this.height;
4636
5251
  var range = this.opts.range;
4637
5252
  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;
5253
+ var dpr = window.devicePixelRatio;
5254
+ var paddingBottom = PADDING_BOTTOM * dpr;
5255
+ var pxPerUnit = (height - 2 * paddingBottom) / (max - min);
5256
+ return Math.round(height - (value - min) * pxPerUnit) - 2 * paddingBottom;
4640
5257
  };
4641
5258
  LineChartRenderer.prototype.drawBackground = function () {
4642
5259
  var _this = this;
4643
5260
  var _a = this, context = _a.context, width = _a.width, height = _a.height, opts = _a.opts, t = _a.t;
5261
+ var dpr = window.devicePixelRatio;
5262
+ var paddingBottom = PADDING_BOTTOM * dpr;
4644
5263
  // draw background and lines
4645
5264
  context.fillStyle = this.opts.background;
4646
5265
  context.fillRect(0, 0, width, height);
@@ -4648,27 +5267,27 @@ var LineChartRenderer = /** @class */ (function (_super) {
4648
5267
  var markers = extractRoundNumbers(range);
4649
5268
  var textMaxWidth = 0;
4650
5269
  // write values on vertical axis
4651
- context.font = 14 * window.devicePixelRatio + "px Helvetica";
5270
+ context.font = 14 * dpr + "px Helvetica";
4652
5271
  context.fillStyle = "#000";
4653
5272
  context.textBaseline = "middle";
4654
5273
  markers.forEach(function (marker) {
4655
- if (_this.y(marker) < 10 || _this.y(marker) + 10 > height)
5274
+ if (_this.y(marker) < 10 * dpr || _this.y(marker) + 10 * dpr > height)
4656
5275
  return;
4657
5276
  var width = context.measureText(marker.toLocaleString()).width;
4658
5277
  if (width > textMaxWidth)
4659
5278
  textMaxWidth = width;
4660
- context.fillText(marker.toLocaleString(), 5, _this.y(marker));
5279
+ context.fillText(marker.toLocaleString(), 5 * dpr, _this.y(marker));
4661
5280
  });
4662
5281
  // draw horizontal lines for vertical axis
4663
5282
  context.save();
4664
5283
  context.strokeStyle = "#999";
4665
5284
  markers.forEach(function (marker) {
4666
- if (_this.y(marker) >= height - PADDING_BOTTOM)
5285
+ if (_this.y(marker) >= height - paddingBottom)
4667
5286
  return;
4668
5287
  context.beginPath();
4669
- context.moveTo(textMaxWidth + 10, _this.y(marker));
5288
+ context.moveTo(textMaxWidth + 10 * dpr, _this.y(marker));
4670
5289
  context.lineTo(_this.x(Math.max(width, _this.environment.time)), _this.y(marker));
4671
- context.setLineDash(lineDash);
5290
+ context.setLineDash(lineDash.map(function (v) { return v * dpr; }));
4672
5291
  context.stroke();
4673
5292
  });
4674
5293
  context.restore();
@@ -4685,12 +5304,12 @@ var LineChartRenderer = /** @class */ (function (_super) {
4685
5304
  _this.x(marker) - width / 2 < textMaxWidth) {
4686
5305
  return;
4687
5306
  }
4688
- context.font = 11 * window.devicePixelRatio + "px Helvetica";
4689
- context.fillText(marker.toLocaleString(), _this.x(marker), height - PADDING_BOTTOM);
5307
+ context.font = 11 * dpr + "px Helvetica";
5308
+ context.fillText(marker.toLocaleString(), _this.x(marker), height - paddingBottom);
4690
5309
  context.strokeStyle = "black";
4691
5310
  context.lineWidth = 1;
4692
5311
  context.beginPath();
4693
- context.moveTo(_this.x(marker), height - 4);
5312
+ context.moveTo(_this.x(marker), height - 4 * dpr);
4694
5313
  context.lineTo(_this.x(marker), height);
4695
5314
  context.stroke();
4696
5315
  });
@@ -5038,7 +5657,8 @@ var Heatmap = /** @class */ (function (_super) {
5038
5657
  */
5039
5658
  Heatmap.prototype.x = function (value) {
5040
5659
  var width = this.width;
5041
- return remap(value, this.getMin("x"), this.getMax("x"), PADDING_AT_LEFT$1, width);
5660
+ var dpr = window.devicePixelRatio;
5661
+ return remap(value, this.getMin("x"), this.getMax("x"), PADDING_AT_LEFT$1 * dpr, width);
5042
5662
  };
5043
5663
  /**
5044
5664
  * Map a value (on the range y-min to y-max) onto canvas space to draw it along the y-axis.
@@ -5046,7 +5666,8 @@ var Heatmap = /** @class */ (function (_super) {
5046
5666
  */
5047
5667
  Heatmap.prototype.y = function (value) {
5048
5668
  var height = this.height;
5049
- return remap(value, this.getMin("y"), this.getMax("y"), height - PADDING_AT_BOTTOM$1, 0);
5669
+ var dpr = window.devicePixelRatio;
5670
+ return remap(value, this.getMin("y"), this.getMax("y"), height - PADDING_AT_BOTTOM$1 * dpr, 0);
5050
5671
  };
5051
5672
  /** @hidden */
5052
5673
  Heatmap.prototype.getKey = function (axis) {
@@ -5089,52 +5710,55 @@ var Heatmap = /** @class */ (function (_super) {
5089
5710
  Heatmap.prototype.drawMarkers = function () {
5090
5711
  var _a = this, context = _a.context, width = _a.width, height = _a.height;
5091
5712
  var _b = this.opts, from = _b.from, to = _b.to;
5713
+ var dpr = window.devicePixelRatio;
5714
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5715
+ var padBottom = PADDING_AT_BOTTOM$1 * dpr;
5092
5716
  context.strokeStyle = "black";
5093
5717
  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);
5718
+ context.moveTo(padLeft - 1, 0);
5719
+ context.lineTo(padLeft - 1, height - padBottom + 1);
5720
+ context.lineTo(width, height - padBottom + 1);
5097
5721
  context.stroke();
5098
5722
  context.lineWidth = 0;
5099
- var gradient = context.createLinearGradient(10, 0, PADDING_AT_LEFT$1 - 10, 0);
5723
+ var gradient = context.createLinearGradient(10 * dpr, 0, padLeft - 10 * dpr, 0);
5100
5724
  gradient.addColorStop(0, from);
5101
5725
  gradient.addColorStop(1, to);
5102
5726
  context.fillStyle = gradient;
5103
- context.fillRect(10, height - PADDING_AT_BOTTOM$1 + 20, PADDING_AT_LEFT$1 - 24, 20);
5727
+ context.fillRect(10 * dpr, height - padBottom + 20 * dpr, padLeft - 24 * dpr, 20 * dpr);
5104
5728
  context.fillStyle = "black";
5105
5729
  var step = (this.getMax("x") - this.getMin("x")) / this.getBuckets("x");
5106
5730
  var originalStep = step;
5107
- while (Math.abs(this.x(step) - this.x(0)) < 35)
5731
+ while (Math.abs(this.x(step) - this.x(0)) < 35 * dpr)
5108
5732
  step *= 2;
5109
5733
  for (var marker = this.getMin("x"); marker <= this.getMax("x"); marker += originalStep) {
5110
- if (this.x(marker) + 10 > width)
5734
+ if (this.x(marker) + 10 * dpr > width)
5111
5735
  continue;
5112
- context.moveTo(this.x(marker), height - PADDING_AT_BOTTOM$1);
5113
- context.lineTo(this.x(marker), height - PADDING_AT_BOTTOM$1 + 10);
5736
+ context.moveTo(this.x(marker), height - padBottom);
5737
+ context.lineTo(this.x(marker), height - padBottom + 10 * dpr);
5114
5738
  context.stroke();
5115
5739
  if (Math.abs(((marker - this.getMin("x")) / step) % 1) < 0.001 ||
5116
5740
  Math.abs((((marker - this.getMin("x")) / step) % 1) - 1) < 0.001) {
5117
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5741
+ context.font = 12 * dpr + "px Helvetica";
5118
5742
  context.textAlign = "center";
5119
- context.fillText(marker.toLocaleString(), this.x(marker), height - PADDING_AT_BOTTOM$1 + 24);
5743
+ context.fillText(marker.toLocaleString(), this.x(marker), height - padBottom + 24 * dpr);
5120
5744
  }
5121
5745
  }
5122
5746
  step = (this.getMax("y") - this.getMin("y")) / this.getBuckets("y");
5123
5747
  originalStep = step;
5124
- while (Math.abs(this.y(step) - this.y(0)) < 20)
5748
+ while (Math.abs(this.y(step) - this.y(0)) < 20 * dpr)
5125
5749
  step *= 2;
5126
5750
  for (var marker = this.getMin("y"); marker <= this.getMax("y"); marker += originalStep) {
5127
- if (this.y(marker) - 10 < 0)
5751
+ if (this.y(marker) - 10 * dpr < 0)
5128
5752
  continue;
5129
- context.moveTo(PADDING_AT_LEFT$1, this.y(marker));
5130
- context.lineTo(PADDING_AT_LEFT$1 - 10, this.y(marker));
5753
+ context.moveTo(padLeft, this.y(marker));
5754
+ context.lineTo(padLeft - 10 * dpr, this.y(marker));
5131
5755
  context.stroke();
5132
5756
  if (Math.abs(((marker - this.getMin("y")) / step) % 1) < 0.001 ||
5133
5757
  Math.abs((((marker - this.getMin("y")) / step) % 1) - 1) < 0.001) {
5134
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5758
+ context.font = 12 * dpr + "px Helvetica";
5135
5759
  context.textAlign = "right";
5136
5760
  context.textBaseline = "middle";
5137
- context.fillText(marker.toLocaleString(), PADDING_AT_LEFT$1 - 14, this.y(marker));
5761
+ context.fillText(marker.toLocaleString(), padLeft - 14 * dpr, this.y(marker));
5138
5762
  }
5139
5763
  }
5140
5764
  };
@@ -5142,6 +5766,8 @@ var Heatmap = /** @class */ (function (_super) {
5142
5766
  Heatmap.prototype.updateScale = function () {
5143
5767
  var _a = this, context = _a.context, environment = _a.environment, height = _a.height;
5144
5768
  var scale = this.opts.scale;
5769
+ var dpr = window.devicePixelRatio;
5770
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5145
5771
  var max = scale === "relative" ? this.localMax : this.opts.max;
5146
5772
  if (max === undefined) {
5147
5773
  if (!this.lastUpdatedScale) {
@@ -5150,13 +5776,13 @@ var Heatmap = /** @class */ (function (_super) {
5150
5776
  max = environment.getAgents().length;
5151
5777
  }
5152
5778
  if (!this.lastUpdatedScale || +new Date() - +this.lastUpdatedScale > 250) {
5153
- context.clearRect(0, height - 20, PADDING_AT_LEFT$1, 20);
5779
+ context.clearRect(0, height - 20 * dpr, padLeft, 20 * dpr);
5154
5780
  context.fillStyle = "black";
5155
- context.font = 12 * window.devicePixelRatio + "px Helvetica";
5781
+ context.font = 12 * dpr + "px Helvetica";
5156
5782
  context.textAlign = "center";
5157
5783
  context.textBaseline = "bottom";
5158
- context.fillText("0", 10, height - 5);
5159
- context.fillText(max.toString(), PADDING_AT_LEFT$1 - 16, height - 5);
5784
+ context.fillText("0", 10 * dpr, height - 5 * dpr);
5785
+ context.fillText(max.toString(), padLeft - 16 * dpr, height - 5 * dpr);
5160
5786
  this.lastUpdatedScale = new Date();
5161
5787
  }
5162
5788
  };
@@ -5164,6 +5790,9 @@ var Heatmap = /** @class */ (function (_super) {
5164
5790
  Heatmap.prototype.drawRectangles = function () {
5165
5791
  var _a = this, canvas = _a.canvas, environment = _a.environment, width = _a.width, height = _a.height;
5166
5792
  var _b = this.opts, scale = _b.scale, from = _b.from, to = _b.to;
5793
+ var dpr = window.devicePixelRatio;
5794
+ var padLeft = PADDING_AT_LEFT$1 * dpr;
5795
+ var padBottom = PADDING_AT_BOTTOM$1 * dpr;
5167
5796
  var context = canvas.getContext("2d");
5168
5797
  var xBuckets = this.getBuckets("x");
5169
5798
  var yBuckets = this.getBuckets("y");
@@ -5172,9 +5801,9 @@ var Heatmap = /** @class */ (function (_super) {
5172
5801
  max = environment.getAgents().length;
5173
5802
  // clear background by drawing background rectangle
5174
5803
  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;
5804
+ context.fillRect(padLeft, 0, width - padLeft, height - padBottom);
5805
+ var w = (width - padLeft) / xBuckets;
5806
+ var h = (height - padBottom) / yBuckets;
5178
5807
  for (var row = 0; row < yBuckets; row++) {
5179
5808
  for (var column = 0; column < xBuckets; column++) {
5180
5809
  var index = row * xBuckets + column;
@@ -5182,7 +5811,7 @@ var Heatmap = /** @class */ (function (_super) {
5182
5811
  var a = clamp(remap(this.buckets[index], 0, max, 0, 1), 0, 1);
5183
5812
  context.fillStyle = to;
5184
5813
  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);
5814
+ 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
5815
  }
5187
5816
  }
5188
5817
  context.globalAlpha = 1;
@@ -5238,6 +5867,6 @@ var Heatmap = /** @class */ (function (_super) {
5238
5867
  /**
5239
5868
  * The current version of the Flocc library.
5240
5869
  */
5241
- var version = "0.5.21";
5870
+ var version = "0.5.23";
5242
5871
 
5243
5872
  export { ASCIIRenderer, Agent, CanvasRenderer, Colors, Environment, GridEnvironment, Heatmap, Histogram, KDTree, LineChartRenderer, Network, NumArray, Rule, TableRenderer, Terrain, version as VERSION, Vector, utils };