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/environments/Environment.d.ts +38 -0
- package/dist/flocc.es.js +736 -107
- package/dist/flocc.js +736 -107
- package/dist/helpers/Rule.d.ts +56 -1
- package/dist/main.d.ts +1 -1
- package/dist/renderers/CanvasRenderer.d.ts +58 -0
- package/package.json +1 -1
- package/dist/environments/NewEnvironment.d.ts +0 -76
- package/dist/helpers/BS.d.ts +0 -6
- package/dist/types/instanceOfPoint.d.ts +0 -2
- package/dist/types/isAgent.d.ts +0 -2
- package/dist/utils/copyArray.d.ts +0 -10
- package/dist/utils/lcg.d.ts +0 -1
- package/dist/utils/randomizeOrder.d.ts +0 -12
package/dist/flocc.js
CHANGED
|
@@ -823,7 +823,37 @@
|
|
|
823
823
|
Operators["agent"] = "agent";
|
|
824
824
|
Operators["environment"] = "environment";
|
|
825
825
|
Operators["vector"] = "vector";
|
|
826
|
+
Operators["log"] = "log";
|
|
826
827
|
})(Operators || (Operators = {}));
|
|
828
|
+
var operatorInfo = {
|
|
829
|
+
add: { min: 2, max: 2 },
|
|
830
|
+
subtract: { min: 2, max: 2 },
|
|
831
|
+
multiply: { min: 2, max: 2 },
|
|
832
|
+
divide: { min: 2, max: 2 },
|
|
833
|
+
mod: { min: 2, max: 2 },
|
|
834
|
+
power: { min: 2, max: 2 },
|
|
835
|
+
get: { min: 1, max: 1 },
|
|
836
|
+
set: { min: 2, max: 2 },
|
|
837
|
+
enqueue: { min: 2, max: 2 },
|
|
838
|
+
local: { min: 1, max: 2 },
|
|
839
|
+
if: { min: 2, max: 3 },
|
|
840
|
+
and: { min: 2, max: 2 },
|
|
841
|
+
or: { min: 2, max: 2 },
|
|
842
|
+
gt: { min: 2, max: 2 },
|
|
843
|
+
gte: { min: 2, max: 2 },
|
|
844
|
+
lt: { min: 2, max: 2 },
|
|
845
|
+
lte: { min: 2, max: 2 },
|
|
846
|
+
eq: { min: 2, max: 2 },
|
|
847
|
+
map: { min: 2, max: 2 },
|
|
848
|
+
filter: { min: 2, max: 2 },
|
|
849
|
+
key: { min: 2, max: 2 },
|
|
850
|
+
method: { min: 2, max: Infinity },
|
|
851
|
+
agent: { min: 0, max: 0 },
|
|
852
|
+
environment: { min: 0, max: 0 },
|
|
853
|
+
vector: { min: 1, max: Infinity },
|
|
854
|
+
log: { min: 1, max: Infinity }
|
|
855
|
+
};
|
|
856
|
+
var ARITHMETIC_OPS = new Set(["add", "subtract", "multiply", "divide", "mod", "power"]);
|
|
827
857
|
var add = function (a, b) { return a + b; };
|
|
828
858
|
var subtract = function (a, b) { return a - b; };
|
|
829
859
|
var multiply = function (a, b) { return a * b; };
|
|
@@ -847,6 +877,127 @@
|
|
|
847
877
|
return null;
|
|
848
878
|
return obj[name].apply(obj, args);
|
|
849
879
|
};
|
|
880
|
+
/**
|
|
881
|
+
* Format a single value for step display. If isFirst is true and the value is
|
|
882
|
+
* a known operator string, render it bare (unquoted).
|
|
883
|
+
*/
|
|
884
|
+
function formatStepValue(val, isFirst) {
|
|
885
|
+
if (val === null || val === undefined)
|
|
886
|
+
return String(val);
|
|
887
|
+
if (typeof val === "number" || typeof val === "boolean")
|
|
888
|
+
return String(val);
|
|
889
|
+
if (typeof val === "string") {
|
|
890
|
+
if (isFirst && val in Operators)
|
|
891
|
+
return val;
|
|
892
|
+
return JSON.stringify(val);
|
|
893
|
+
}
|
|
894
|
+
if (val instanceof Array) {
|
|
895
|
+
if (val.length === 0)
|
|
896
|
+
return "[]";
|
|
897
|
+
var parts = val.map(function (s, i) { return formatStepValue(s, i === 0); });
|
|
898
|
+
return "(" + parts.join(" ") + ")";
|
|
899
|
+
}
|
|
900
|
+
if (typeof val === "object") {
|
|
901
|
+
try {
|
|
902
|
+
return JSON.stringify(val);
|
|
903
|
+
}
|
|
904
|
+
catch (_a) {
|
|
905
|
+
return String(val);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return String(val);
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Format a step into a readable string representation for logging.
|
|
912
|
+
*/
|
|
913
|
+
function formatStep(step) {
|
|
914
|
+
return formatStepValue(step, false);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Format a value as an S-expression atom (no recursion into arrays).
|
|
918
|
+
*/
|
|
919
|
+
function formatAtom(val) {
|
|
920
|
+
if (val === null || val === undefined)
|
|
921
|
+
return "null";
|
|
922
|
+
if (typeof val === "number" || typeof val === "boolean")
|
|
923
|
+
return String(val);
|
|
924
|
+
if (typeof val === "string")
|
|
925
|
+
return JSON.stringify(val);
|
|
926
|
+
if (typeof val === "object" && !Array.isArray(val)) {
|
|
927
|
+
try {
|
|
928
|
+
return JSON.stringify(val);
|
|
929
|
+
}
|
|
930
|
+
catch (_a) {
|
|
931
|
+
return "[object]";
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return String(val);
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Format a value for S-expression display. The first element (operator) of
|
|
938
|
+
* a step is rendered as a bare identifier; other string values are quoted.
|
|
939
|
+
*/
|
|
940
|
+
function formatSexpValue(val, isOperator) {
|
|
941
|
+
if (isOperator && typeof val === "string")
|
|
942
|
+
return val;
|
|
943
|
+
return formatAtom(val);
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Pretty-print a single step as an S-expression with width-based wrapping.
|
|
947
|
+
*/
|
|
948
|
+
function prettyFormatStep(step, indentUnit, maxLineWidth, currentIndent) {
|
|
949
|
+
if (!Array.isArray(step))
|
|
950
|
+
return formatAtom(step);
|
|
951
|
+
if (step.length === 0)
|
|
952
|
+
return "()";
|
|
953
|
+
// Try compact first
|
|
954
|
+
var compact = "(" + step.map(function (s, i) {
|
|
955
|
+
if (Array.isArray(s))
|
|
956
|
+
return prettyFormatStep(s, indentUnit, Infinity, "");
|
|
957
|
+
return formatSexpValue(s, i === 0);
|
|
958
|
+
}).join(" ") + ")";
|
|
959
|
+
if (currentIndent.length + compact.length <= maxLineWidth)
|
|
960
|
+
return compact;
|
|
961
|
+
// Wrap: operator on first line, each arg indented
|
|
962
|
+
var op = Array.isArray(step[0]) ? prettyFormatStep(step[0], indentUnit, maxLineWidth, currentIndent) : formatSexpValue(step[0], true);
|
|
963
|
+
var args = step.slice(1);
|
|
964
|
+
if (args.length === 0)
|
|
965
|
+
return "(" + op + ")";
|
|
966
|
+
var childIndent = currentIndent + indentUnit;
|
|
967
|
+
var formattedArgs = args.map(function (a) { return prettyFormatStep(a, indentUnit, maxLineWidth, childIndent); });
|
|
968
|
+
return "(" + op + "\n" + formattedArgs.map(function (a) { return childIndent + a; }).join("\n") + ")";
|
|
969
|
+
}
|
|
970
|
+
function validOperatorNames() {
|
|
971
|
+
return Object.keys(operatorInfo).join(", ");
|
|
972
|
+
}
|
|
973
|
+
function checkArity(op, argCount, strict) {
|
|
974
|
+
if (strict === void 0) { strict = false; }
|
|
975
|
+
var info = operatorInfo[op];
|
|
976
|
+
if (!info)
|
|
977
|
+
return null;
|
|
978
|
+
if (info.min === info.max) {
|
|
979
|
+
if (argCount < info.min) {
|
|
980
|
+
return "Rule: \"" + op + "\" expects " + info.min + " argument" + (info.min !== 1 ? "s" : "") + ", got " + argCount;
|
|
981
|
+
}
|
|
982
|
+
if (strict && argCount > info.max) {
|
|
983
|
+
return "Rule: \"" + op + "\" expects " + info.min + " argument" + (info.min !== 1 ? "s" : "") + ", got " + argCount;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
else if (info.max === Infinity) {
|
|
987
|
+
if (argCount < info.min) {
|
|
988
|
+
return "Rule: \"" + op + "\" expects at least " + info.min + " argument" + (info.min !== 1 ? "s" : "") + ", got " + argCount;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
if (argCount < info.min) {
|
|
993
|
+
return "Rule: \"" + op + "\" expects " + info.min + "-" + info.max + " arguments, got " + argCount;
|
|
994
|
+
}
|
|
995
|
+
if (strict && argCount > info.max) {
|
|
996
|
+
return "Rule: \"" + op + "\" expects " + info.min + "-" + info.max + " arguments, got " + argCount;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
850
1001
|
/**
|
|
851
1002
|
* 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:
|
|
852
1003
|
*
|
|
@@ -901,6 +1052,7 @@
|
|
|
901
1052
|
* |`"agent"`|`0`|No arguments; returns the `Agent`|
|
|
902
1053
|
* |`"environment"`|`0`|No arguments, returns the `Environment`|
|
|
903
1054
|
* |`"vector"`|`any`|Creates an n-dimensional {@linkcode Vector} from the supplied arguments|
|
|
1055
|
+
* |`"log"`|`any`|Logs expression(s) and returns the last evaluated value|
|
|
904
1056
|
*/
|
|
905
1057
|
function Rule(environment, steps) {
|
|
906
1058
|
var _this = this;
|
|
@@ -909,10 +1061,12 @@
|
|
|
909
1061
|
/** @hidden */
|
|
910
1062
|
this.locals = {};
|
|
911
1063
|
/**
|
|
912
|
-
*
|
|
913
|
-
* @since 0.3.0
|
|
914
|
-
* @hidden
|
|
1064
|
+
* When true, evaluation traces are logged and captured.
|
|
915
1065
|
*/
|
|
1066
|
+
this.trace = false;
|
|
1067
|
+
this.traceLog = [];
|
|
1068
|
+
/** @hidden */
|
|
1069
|
+
this._traceDepth = 0;
|
|
916
1070
|
this.evaluate = function (agent, step) {
|
|
917
1071
|
var first = step && step.length > 0 ? step[0] : null;
|
|
918
1072
|
if (first === undefined || first === null)
|
|
@@ -923,30 +1077,79 @@
|
|
|
923
1077
|
return _this.evaluate(agent, step.slice(1));
|
|
924
1078
|
return innerStep;
|
|
925
1079
|
}
|
|
926
|
-
if
|
|
927
|
-
|
|
928
|
-
|
|
1080
|
+
// Trace: check if this is a known operator call to trace
|
|
1081
|
+
var shouldTrace = _this.trace && typeof first === "string" && first in Operators;
|
|
1082
|
+
if (shouldTrace)
|
|
1083
|
+
_this._traceDepth++;
|
|
1084
|
+
var result = _this._evaluateOp(agent, step, first);
|
|
1085
|
+
if (shouldTrace) {
|
|
1086
|
+
_this._traceLogEntry(step, result);
|
|
1087
|
+
_this._traceDepth--;
|
|
1088
|
+
}
|
|
1089
|
+
return result;
|
|
1090
|
+
};
|
|
1091
|
+
/** @hidden */
|
|
1092
|
+
this._evaluateOp = function (agent, step, first) {
|
|
1093
|
+
if (first === Operators.log) {
|
|
1094
|
+
var args = step.slice(1);
|
|
1095
|
+
if (args.length === 0) {
|
|
1096
|
+
console.warn("Rule: \"log\" expects at least 1 argument, got 0");
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
var lastValue = null;
|
|
1100
|
+
for (var i = 0; i < args.length; i++) {
|
|
1101
|
+
var expr = args[i];
|
|
1102
|
+
var evaluated = _this.evaluate(agent, [expr]);
|
|
1103
|
+
console.log("Rule log: " + formatStep(expr) + " \u2192 " + formatStep(evaluated));
|
|
1104
|
+
lastValue = evaluated;
|
|
1105
|
+
}
|
|
1106
|
+
return lastValue;
|
|
929
1107
|
}
|
|
930
1108
|
if (!(first in Operators) && step.length > 1) {
|
|
1109
|
+
if (typeof first === "string") {
|
|
1110
|
+
console.warn("Rule: unknown operator \"" + first + "\". Valid operators: " + validOperatorNames());
|
|
1111
|
+
}
|
|
931
1112
|
return step;
|
|
932
1113
|
}
|
|
1114
|
+
var argCount = step.length - 1;
|
|
1115
|
+
// Arity check at runtime for known operators
|
|
1116
|
+
if (typeof first === "string" && first in Operators) {
|
|
1117
|
+
var arityMsg = checkArity(first, argCount);
|
|
1118
|
+
if (arityMsg) {
|
|
1119
|
+
console.warn(arityMsg);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
933
1122
|
var a = step.length > 1 ? [step[1]] : undefined;
|
|
934
1123
|
var b = step.length > 2 ? [step[2]] : undefined;
|
|
935
1124
|
var c = step.length > 3 ? [step[3]] : undefined;
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1125
|
+
// Arithmetic operators with type checking
|
|
1126
|
+
if (ARITHMETIC_OPS.has(first)) {
|
|
1127
|
+
var va = _this.evaluate(agent, a);
|
|
1128
|
+
var vb = _this.evaluate(agent, b);
|
|
1129
|
+
if (typeof va !== "number" || typeof vb !== "number") {
|
|
1130
|
+
console.warn("Rule: \"" + first + "\" expects numeric arguments, got " + typeof va + " and " + typeof vb);
|
|
1131
|
+
}
|
|
1132
|
+
if (first === Operators.add)
|
|
1133
|
+
return add(va, vb);
|
|
1134
|
+
if (first === Operators.subtract)
|
|
1135
|
+
return subtract(va, vb);
|
|
1136
|
+
if (first === Operators.multiply)
|
|
1137
|
+
return multiply(va, vb);
|
|
1138
|
+
if (first === Operators.divide)
|
|
1139
|
+
return divide(va, vb);
|
|
1140
|
+
if (first === Operators.mod)
|
|
1141
|
+
return mod(va, vb);
|
|
1142
|
+
if (first === Operators.power)
|
|
1143
|
+
return power(va, vb);
|
|
1144
|
+
}
|
|
1145
|
+
if (first === Operators.get) {
|
|
1146
|
+
var keyName = _this.evaluate(agent, a);
|
|
1147
|
+
var result = get(agent, keyName);
|
|
1148
|
+
if (result === null || result === undefined) {
|
|
1149
|
+
console.warn("Rule: \"get\" returned null for key \"" + keyName + "\" \u2014 key may not exist on agent");
|
|
1150
|
+
}
|
|
1151
|
+
return result;
|
|
1152
|
+
}
|
|
950
1153
|
if (first === Operators.set)
|
|
951
1154
|
return set(agent, _this.evaluate(agent, a), _this.evaluate(agent, b));
|
|
952
1155
|
if (first === Operators.enqueue) {
|
|
@@ -1034,13 +1237,151 @@
|
|
|
1034
1237
|
this.environment = environment;
|
|
1035
1238
|
this.steps = steps;
|
|
1036
1239
|
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Format arbitrary steps as pretty-printed S-expressions.
|
|
1242
|
+
*/
|
|
1243
|
+
Rule.formatSteps = function (steps, options) {
|
|
1244
|
+
var indent = (options && options.indent) || " ";
|
|
1245
|
+
var maxLineWidth = (options && options.maxLineWidth) || 60;
|
|
1246
|
+
// Check if it's multi-step (array of arrays)
|
|
1247
|
+
var isMultiStep = steps.length > 0 && Array.isArray(steps[0]);
|
|
1248
|
+
if (isMultiStep) {
|
|
1249
|
+
return steps.map(function (s) { return prettyFormatStep(s, indent, maxLineWidth, ""); }).join("\n\n");
|
|
1250
|
+
}
|
|
1251
|
+
return prettyFormatStep(steps, indent, maxLineWidth, "");
|
|
1252
|
+
};
|
|
1253
|
+
/**
|
|
1254
|
+
* Pretty-print the rule's step tree as S-expressions.
|
|
1255
|
+
*/
|
|
1256
|
+
Rule.prototype.format = function (options) {
|
|
1257
|
+
return Rule.formatSteps(this.steps, options);
|
|
1258
|
+
};
|
|
1259
|
+
/**
|
|
1260
|
+
* Returns a formatted S-expression representation of the rule.
|
|
1261
|
+
*/
|
|
1262
|
+
Rule.prototype.toString = function () {
|
|
1263
|
+
return this.format();
|
|
1264
|
+
};
|
|
1265
|
+
/**
|
|
1266
|
+
* Validate the rule's step tree and return an array of diagnostics.
|
|
1267
|
+
* Does not throw — returns diagnostics for inspection.
|
|
1268
|
+
* @since 0.6.0
|
|
1269
|
+
*/
|
|
1270
|
+
Rule.prototype.validate = function () {
|
|
1271
|
+
var diagnostics = [];
|
|
1272
|
+
if (!this.steps || (Array.isArray(this.steps) && this.steps.length === 0)) {
|
|
1273
|
+
diagnostics.push({
|
|
1274
|
+
path: "root",
|
|
1275
|
+
level: "warning",
|
|
1276
|
+
message: "Empty steps array"
|
|
1277
|
+
});
|
|
1278
|
+
return diagnostics;
|
|
1279
|
+
}
|
|
1280
|
+
// Check if steps is a single step (first element is a string operator)
|
|
1281
|
+
// or an array of steps (first element is an array)
|
|
1282
|
+
var isMultiStep = Array.isArray(this.steps[0]);
|
|
1283
|
+
if (isMultiStep) {
|
|
1284
|
+
// Multiple top-level steps
|
|
1285
|
+
for (var i = 0; i < this.steps.length; i++) {
|
|
1286
|
+
var step = this.steps[i];
|
|
1287
|
+
if (!Array.isArray(step)) {
|
|
1288
|
+
diagnostics.push({
|
|
1289
|
+
path: "[" + i + "]",
|
|
1290
|
+
level: "warning",
|
|
1291
|
+
message: "Step is a bare value (" + typeof step + ": " + JSON.stringify(step) + ") instead of an array"
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
this._validateStep(step, "[" + i + "]", diagnostics);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
// Single step (the steps array IS the step)
|
|
1301
|
+
this._validateStep(this.steps, "root", diagnostics);
|
|
1302
|
+
}
|
|
1303
|
+
return diagnostics;
|
|
1304
|
+
};
|
|
1305
|
+
/** @hidden */
|
|
1306
|
+
Rule.prototype._validateStep = function (step, path, diagnostics) {
|
|
1307
|
+
if (step.length === 0) {
|
|
1308
|
+
diagnostics.push({
|
|
1309
|
+
path: path,
|
|
1310
|
+
level: "warning",
|
|
1311
|
+
message: "Empty step array"
|
|
1312
|
+
});
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
var first = step[0];
|
|
1316
|
+
// If first element is an array, it's a nested step sequence
|
|
1317
|
+
if (Array.isArray(first)) {
|
|
1318
|
+
this._validateStep(first, path + "[0]", diagnostics);
|
|
1319
|
+
for (var i = 1; i < step.length; i++) {
|
|
1320
|
+
if (Array.isArray(step[i])) {
|
|
1321
|
+
this._validateStep(step[i], path + ("[" + i + "]"), diagnostics);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
if (typeof first === "string") {
|
|
1327
|
+
var argCount = step.length - 1;
|
|
1328
|
+
// Check if operator is known
|
|
1329
|
+
if (!(first in Operators)) {
|
|
1330
|
+
if (step.length > 1) {
|
|
1331
|
+
diagnostics.push({
|
|
1332
|
+
path: path,
|
|
1333
|
+
level: "error",
|
|
1334
|
+
message: "Unknown operator \"" + first + "\". Valid operators: " + validOperatorNames()
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
// Check arity (strict: also warn on too many args)
|
|
1340
|
+
var arityMsg = checkArity(first, argCount, true);
|
|
1341
|
+
if (arityMsg) {
|
|
1342
|
+
diagnostics.push({
|
|
1343
|
+
path: path,
|
|
1344
|
+
level: "error",
|
|
1345
|
+
message: arityMsg
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
// Recurse into sub-steps
|
|
1349
|
+
for (var i = 1; i < step.length; i++) {
|
|
1350
|
+
if (Array.isArray(step[i])) {
|
|
1351
|
+
this._validateStep(step[i], path + ("[" + i + "]"), diagnostics);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
/**
|
|
1357
|
+
* interpret single array step
|
|
1358
|
+
* @since 0.3.0
|
|
1359
|
+
* @hidden
|
|
1360
|
+
*/
|
|
1361
|
+
/** @hidden */
|
|
1362
|
+
Rule.prototype._traceLogEntry = function (step, result) {
|
|
1363
|
+
var indent = " ".repeat(this._traceDepth - 1);
|
|
1364
|
+
var formatted = formatStep(step);
|
|
1365
|
+
var resultFormatted = formatStep(result);
|
|
1366
|
+
var line = "Rule trace: " + indent + formatted + " \u2192 " + resultFormatted;
|
|
1367
|
+
console.log(line);
|
|
1368
|
+
this.traceLog.push(line);
|
|
1369
|
+
};
|
|
1037
1370
|
/**
|
|
1038
1371
|
* @since 0.3.0
|
|
1039
1372
|
* @hidden
|
|
1040
1373
|
*/
|
|
1041
1374
|
Rule.prototype.call = function (agent) {
|
|
1375
|
+
if (this.trace) {
|
|
1376
|
+
this.traceLog = [];
|
|
1377
|
+
this._traceDepth = 0;
|
|
1378
|
+
}
|
|
1042
1379
|
return this.evaluate(agent, this.steps);
|
|
1043
1380
|
};
|
|
1381
|
+
/**
|
|
1382
|
+
* Static operator info mapping operator names to their expected argument counts.
|
|
1383
|
+
*/
|
|
1384
|
+
Rule.operatorInfo = operatorInfo;
|
|
1044
1385
|
return Rule;
|
|
1045
1386
|
}());
|
|
1046
1387
|
|
|
@@ -3218,6 +3559,15 @@
|
|
|
3218
3559
|
* such as a {@linkcode LineChartRenderer}, {@linkcode Histogram}, etc.
|
|
3219
3560
|
*/
|
|
3220
3561
|
_this.renderers = [];
|
|
3562
|
+
/**
|
|
3563
|
+
* Whether the `Environment` tick cycle is currently playing.
|
|
3564
|
+
* Use {@linkcode pause}, {@linkcode resume}, or {@linkcode toggle}
|
|
3565
|
+
* to control playback.
|
|
3566
|
+
* @since 0.5.22
|
|
3567
|
+
*/
|
|
3568
|
+
_this.playing = true;
|
|
3569
|
+
/** @hidden */
|
|
3570
|
+
_this._tickIntervalId = null;
|
|
3221
3571
|
/**
|
|
3222
3572
|
* This property will always equal the number of tick cycles that
|
|
3223
3573
|
* have passed since the `Environment` was created. If you call
|
|
@@ -3387,6 +3737,9 @@
|
|
|
3387
3737
|
* @since 0.0.5
|
|
3388
3738
|
*/
|
|
3389
3739
|
Environment.prototype.tick = function (opts) {
|
|
3740
|
+
// If paused, skip the tick cycle (use `step()` to advance manually)
|
|
3741
|
+
if (!this.playing)
|
|
3742
|
+
return;
|
|
3390
3743
|
var _a = this._getTickOptions(opts), activation = _a.activation, activationCount = _a.activationCount, count = _a.count, randomizeOrder = _a.randomizeOrder;
|
|
3391
3744
|
// for uniform activation, every agent is always activated
|
|
3392
3745
|
if (activation === "uniform") {
|
|
@@ -3454,6 +3807,47 @@
|
|
|
3454
3807
|
}
|
|
3455
3808
|
this.renderers.forEach(function (r) { return r.render(); });
|
|
3456
3809
|
};
|
|
3810
|
+
/**
|
|
3811
|
+
* Pause the tick cycle. While paused, calling {@linkcode tick} will
|
|
3812
|
+
* be a no-op unless you use {@linkcode step} to advance manually.
|
|
3813
|
+
* @since 0.5.22
|
|
3814
|
+
*/
|
|
3815
|
+
Environment.prototype.pause = function () {
|
|
3816
|
+
this.playing = false;
|
|
3817
|
+
};
|
|
3818
|
+
/**
|
|
3819
|
+
* Resume the tick cycle after it has been paused.
|
|
3820
|
+
* @since 0.5.22
|
|
3821
|
+
*/
|
|
3822
|
+
Environment.prototype.resume = function () {
|
|
3823
|
+
this.playing = true;
|
|
3824
|
+
};
|
|
3825
|
+
/**
|
|
3826
|
+
* Toggle the tick cycle between playing and paused.
|
|
3827
|
+
* @since 0.5.22
|
|
3828
|
+
*/
|
|
3829
|
+
Environment.prototype.toggle = function () {
|
|
3830
|
+
this.playing = !this.playing;
|
|
3831
|
+
};
|
|
3832
|
+
/**
|
|
3833
|
+
* Advance the `Environment` by exactly one tick, regardless of whether
|
|
3834
|
+
* it is paused. This is useful for stepping through the simulation
|
|
3835
|
+
* frame-by-frame while paused.
|
|
3836
|
+
*
|
|
3837
|
+
* ```js
|
|
3838
|
+
* environment.pause();
|
|
3839
|
+
* environment.step(); // advances one tick
|
|
3840
|
+
* ```
|
|
3841
|
+
*
|
|
3842
|
+
* @since 0.5.22
|
|
3843
|
+
*/
|
|
3844
|
+
Environment.prototype.step = function (opts) {
|
|
3845
|
+
// Temporarily mark as playing so tick executes, then restore
|
|
3846
|
+
var wasPlaying = this.playing;
|
|
3847
|
+
this.playing = true;
|
|
3848
|
+
this.tick(opts);
|
|
3849
|
+
this.playing = wasPlaying;
|
|
3850
|
+
};
|
|
3457
3851
|
/**
|
|
3458
3852
|
* Use a helper with this environment. A helper can be one of:
|
|
3459
3853
|
* - {@linkcode KDTree}
|
|
@@ -3990,7 +4384,10 @@
|
|
|
3990
4384
|
width: 500,
|
|
3991
4385
|
height: 500,
|
|
3992
4386
|
scale: 1,
|
|
3993
|
-
trace: false
|
|
4387
|
+
trace: false,
|
|
4388
|
+
interactive: false,
|
|
4389
|
+
zoomMin: 0.1,
|
|
4390
|
+
zoomMax: 10
|
|
3994
4391
|
};
|
|
3995
4392
|
/**
|
|
3996
4393
|
* A `CanvasRenderer` renders an {@linkcode Environment} spatially in two dimensions.
|
|
@@ -4010,6 +4407,12 @@
|
|
|
4010
4407
|
* - `"triangle"` — Draws a triangle centered at the `Agent`'s `"x"` / `"y"` values.
|
|
4011
4408
|
* - Also uses the `"size"` value.
|
|
4012
4409
|
*
|
|
4410
|
+
* When `interactive` is set to `true` in the options, the renderer supports:
|
|
4411
|
+
* - **Click/hover detection** — Use {@linkcode on} to listen for `"click"`, `"hover"`, and `"unhover"` events on agents.
|
|
4412
|
+
* - **Agent selection** — Clicking an agent selects it (highlighted with a stroke). Access selected agents via {@linkcode selected}.
|
|
4413
|
+
* - **Pan** — Click and drag on empty space to pan.
|
|
4414
|
+
* - **Zoom** — Scroll to zoom in/out (bounded by `zoomMin` / `zoomMax`).
|
|
4415
|
+
*
|
|
4013
4416
|
* @since 0.0.11
|
|
4014
4417
|
*/
|
|
4015
4418
|
var CanvasRenderer = /** @class */ (function (_super) {
|
|
@@ -4025,15 +4428,33 @@
|
|
|
4025
4428
|
* - `connectionOpacity` (*number* = `1`) — For `Environment`s using a `Network`, the opacity of lines
|
|
4026
4429
|
* - `connectionWidth` (*number* = `1`) — For `Environment`s using a `Network`, the width of lines
|
|
4027
4430
|
* - `height` (*number* = `500`) — The height, in pixels, of the canvas on which to render
|
|
4431
|
+
* - `interactive` (*boolean* = `false`) — Enables interactive features (click/hover detection, selection, pan, zoom)
|
|
4432
|
+
* - `onSelect` (*function*) — Optional callback when an agent is selected or deselected
|
|
4028
4433
|
* - `origin` (*{ x: number; y: number }* = `{ x: 0, y: 0 }`) — The coordinate of the upper-left point of the space to be rendered
|
|
4029
4434
|
* - `scale` (*number* = `1`) — The scale at which to render (the larger the scale, the smaller the size of the space that is actually rendered)
|
|
4030
4435
|
* - `trace` (*boolean* = `false`) — If `true`, the renderer will not clear old drawings, causing the `Agent`s to appear to *trace* their paths across space
|
|
4031
4436
|
* - `width` (*number* = `500`) — The width, in pixels, of the canvas on which to render
|
|
4437
|
+
* - `zoomMin` (*number* = `0.1`) — Minimum scale when zooming
|
|
4438
|
+
* - `zoomMax` (*number* = `10`) — Maximum scale when zooming
|
|
4032
4439
|
*/
|
|
4033
4440
|
function CanvasRenderer(environment, opts) {
|
|
4034
4441
|
var _this = _super.call(this) || this;
|
|
4035
4442
|
/** @hidden */
|
|
4036
4443
|
_this.terrainBuffer = document.createElement("canvas");
|
|
4444
|
+
/** The currently selected agents (only used when `interactive` is `true`). */
|
|
4445
|
+
_this.selected = [];
|
|
4446
|
+
/** @hidden */
|
|
4447
|
+
_this._listeners = new Map();
|
|
4448
|
+
/** @hidden */
|
|
4449
|
+
_this._hoveredAgent = null;
|
|
4450
|
+
/** @hidden */
|
|
4451
|
+
_this._isPanning = false;
|
|
4452
|
+
/** @hidden */
|
|
4453
|
+
_this._panStart = null;
|
|
4454
|
+
/** @hidden */
|
|
4455
|
+
_this._panOriginStart = null;
|
|
4456
|
+
/** @hidden */
|
|
4457
|
+
_this._boundHandlers = {};
|
|
4037
4458
|
_this.environment = environment;
|
|
4038
4459
|
environment.renderers.push(_this);
|
|
4039
4460
|
_this.opts = Object.assign({}, defaultOptions);
|
|
@@ -4050,9 +4471,158 @@
|
|
|
4050
4471
|
_this.terrainBuffer.width = width;
|
|
4051
4472
|
_this.terrainBuffer.height = height;
|
|
4052
4473
|
_this.context.fillStyle = opts.background;
|
|
4053
|
-
_this.context.fillRect(0, 0, width, height);
|
|
4474
|
+
_this.context.fillRect(0, 0, _this.width, _this.height);
|
|
4475
|
+
if (_this.opts.interactive) {
|
|
4476
|
+
_this._setupInteractiveListeners();
|
|
4477
|
+
}
|
|
4054
4478
|
return _this;
|
|
4055
4479
|
}
|
|
4480
|
+
/**
|
|
4481
|
+
* Register a callback for an interactive event.
|
|
4482
|
+
* Supported event names: `"click"`, `"hover"`, `"unhover"`.
|
|
4483
|
+
*
|
|
4484
|
+
* ```js
|
|
4485
|
+
* renderer.on("click", (agent, event) => {
|
|
4486
|
+
* console.log("Clicked agent:", agent.id);
|
|
4487
|
+
* });
|
|
4488
|
+
* ```
|
|
4489
|
+
*
|
|
4490
|
+
* @param eventName - The event to listen for.
|
|
4491
|
+
* @param callback - The callback, invoked with the `Agent` and the `MouseEvent`.
|
|
4492
|
+
*/
|
|
4493
|
+
CanvasRenderer.prototype.on = function (eventName, callback) {
|
|
4494
|
+
if (!this._listeners.has(eventName)) {
|
|
4495
|
+
this._listeners.set(eventName, []);
|
|
4496
|
+
}
|
|
4497
|
+
this._listeners.get(eventName).push(callback);
|
|
4498
|
+
};
|
|
4499
|
+
/** @hidden */
|
|
4500
|
+
CanvasRenderer.prototype._emit = function (eventName, agent, event) {
|
|
4501
|
+
var callbacks = this._listeners.get(eventName);
|
|
4502
|
+
if (callbacks) {
|
|
4503
|
+
callbacks.forEach(function (cb) { return cb(agent, event); });
|
|
4504
|
+
}
|
|
4505
|
+
};
|
|
4506
|
+
/**
|
|
4507
|
+
* Given a mouse event, return the agent at that position (if any).
|
|
4508
|
+
* Hit-testing accounts for the agent's shape and size.
|
|
4509
|
+
* @hidden
|
|
4510
|
+
*/
|
|
4511
|
+
CanvasRenderer.prototype._agentAtPoint = function (clientX, clientY) {
|
|
4512
|
+
var rect = this.canvas.getBoundingClientRect();
|
|
4513
|
+
var dpr = window.devicePixelRatio;
|
|
4514
|
+
var canvasX = (clientX - rect.left) * dpr;
|
|
4515
|
+
var canvasY = (clientY - rect.top) * dpr;
|
|
4516
|
+
var agents = this.environment.getAgents();
|
|
4517
|
+
// Iterate in reverse so topmost-drawn agent is found first
|
|
4518
|
+
for (var i = agents.length - 1; i >= 0; i--) {
|
|
4519
|
+
var agent = agents[i];
|
|
4520
|
+
var data = agent.getData();
|
|
4521
|
+
var ax = this.x(data.x);
|
|
4522
|
+
var ay = this.y(data.y);
|
|
4523
|
+
var shape = data.shape;
|
|
4524
|
+
var size = (data.size || 1) * dpr;
|
|
4525
|
+
if (shape === "rect") {
|
|
4526
|
+
var w = (data.width || 1) * dpr;
|
|
4527
|
+
var h = (data.height || 1) * dpr;
|
|
4528
|
+
var rx = ax - w / 2;
|
|
4529
|
+
var ry = ay - h / 2;
|
|
4530
|
+
if (canvasX >= rx && canvasX <= rx + w && canvasY >= ry && canvasY <= ry + h) {
|
|
4531
|
+
return agent;
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
else if (shape === "triangle") {
|
|
4535
|
+
// Simple bounding-box hit test for triangles
|
|
4536
|
+
var halfSize = size / 2;
|
|
4537
|
+
if (canvasX >= ax - halfSize &&
|
|
4538
|
+
canvasX <= ax + halfSize &&
|
|
4539
|
+
canvasY >= ay - halfSize &&
|
|
4540
|
+
canvasY <= ay + halfSize) {
|
|
4541
|
+
return agent;
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
else {
|
|
4545
|
+
// Default: circle (and arrow) — distance-based hit test
|
|
4546
|
+
var dx = canvasX - ax;
|
|
4547
|
+
var dy = canvasY - ay;
|
|
4548
|
+
var hitRadius = Math.max(size, 4 * dpr); // minimum hit area for tiny agents
|
|
4549
|
+
if (dx * dx + dy * dy <= hitRadius * hitRadius) {
|
|
4550
|
+
return agent;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
return null;
|
|
4555
|
+
};
|
|
4556
|
+
/** @hidden */
|
|
4557
|
+
CanvasRenderer.prototype._setupInteractiveListeners = function () {
|
|
4558
|
+
var _this = this;
|
|
4559
|
+
var onMouseDown = function (e) {
|
|
4560
|
+
var agent = _this._agentAtPoint(e.clientX, e.clientY);
|
|
4561
|
+
if (agent) {
|
|
4562
|
+
// Agent click — select it
|
|
4563
|
+
_this.selected = [agent];
|
|
4564
|
+
if (_this.opts.onSelect)
|
|
4565
|
+
_this.opts.onSelect(agent);
|
|
4566
|
+
_this._emit("click", agent, e);
|
|
4567
|
+
_this.render();
|
|
4568
|
+
}
|
|
4569
|
+
else {
|
|
4570
|
+
// Empty space — deselect and start panning
|
|
4571
|
+
if (_this.selected.length > 0) {
|
|
4572
|
+
_this.selected = [];
|
|
4573
|
+
if (_this.opts.onSelect)
|
|
4574
|
+
_this.opts.onSelect(null);
|
|
4575
|
+
_this.render();
|
|
4576
|
+
}
|
|
4577
|
+
_this._isPanning = true;
|
|
4578
|
+
_this._panStart = { x: e.clientX, y: e.clientY };
|
|
4579
|
+
_this._panOriginStart = { x: _this.opts.origin.x, y: _this.opts.origin.y };
|
|
4580
|
+
}
|
|
4581
|
+
};
|
|
4582
|
+
var onMouseMove = function (e) {
|
|
4583
|
+
if (_this._isPanning && _this._panStart && _this._panOriginStart) {
|
|
4584
|
+
var dpr = window.devicePixelRatio;
|
|
4585
|
+
var dx = e.clientX - _this._panStart.x;
|
|
4586
|
+
var dy = e.clientY - _this._panStart.y;
|
|
4587
|
+
_this.opts.origin = {
|
|
4588
|
+
x: _this._panOriginStart.x - dx / (_this.opts.scale * dpr),
|
|
4589
|
+
y: _this._panOriginStart.y - dy / (_this.opts.scale * dpr)
|
|
4590
|
+
};
|
|
4591
|
+
_this.render();
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
4594
|
+
// Hover detection
|
|
4595
|
+
var agent = _this._agentAtPoint(e.clientX, e.clientY);
|
|
4596
|
+
if (agent !== _this._hoveredAgent) {
|
|
4597
|
+
if (_this._hoveredAgent) {
|
|
4598
|
+
_this._emit("unhover", _this._hoveredAgent, e);
|
|
4599
|
+
}
|
|
4600
|
+
if (agent) {
|
|
4601
|
+
_this._emit("hover", agent, e);
|
|
4602
|
+
}
|
|
4603
|
+
_this._hoveredAgent = agent;
|
|
4604
|
+
}
|
|
4605
|
+
};
|
|
4606
|
+
var onMouseUp = function (e) {
|
|
4607
|
+
_this._isPanning = false;
|
|
4608
|
+
_this._panStart = null;
|
|
4609
|
+
_this._panOriginStart = null;
|
|
4610
|
+
};
|
|
4611
|
+
var onWheel = function (e) {
|
|
4612
|
+
e.preventDefault();
|
|
4613
|
+
var _a = _this.opts, zoomMin = _a.zoomMin, zoomMax = _a.zoomMax;
|
|
4614
|
+
var delta = e.deltaY > 0 ? 0.9 : 1.1;
|
|
4615
|
+
var newScale = _this.opts.scale * delta;
|
|
4616
|
+
newScale = Math.max(zoomMin, Math.min(zoomMax, newScale));
|
|
4617
|
+
_this.opts.scale = newScale;
|
|
4618
|
+
_this.render();
|
|
4619
|
+
};
|
|
4620
|
+
this._boundHandlers = { mousedown: onMouseDown, mousemove: onMouseMove, mouseup: onMouseUp, wheel: onWheel };
|
|
4621
|
+
this.canvas.addEventListener("mousedown", onMouseDown);
|
|
4622
|
+
this.canvas.addEventListener("mousemove", onMouseMove);
|
|
4623
|
+
this.canvas.addEventListener("mouseup", onMouseUp);
|
|
4624
|
+
this.canvas.addEventListener("wheel", onWheel, { passive: false });
|
|
4625
|
+
};
|
|
4056
4626
|
/** @hidden */
|
|
4057
4627
|
CanvasRenderer.prototype.x = function (v) {
|
|
4058
4628
|
var _a = this.opts, origin = _a.origin, scale = _a.scale;
|
|
@@ -4089,21 +4659,21 @@
|
|
|
4089
4659
|
};
|
|
4090
4660
|
/** @hidden */
|
|
4091
4661
|
CanvasRenderer.prototype.drawPathWrap = function (points) {
|
|
4092
|
-
var _this = this;
|
|
4093
4662
|
var _a = this, width = _a.width, height = _a.height;
|
|
4094
4663
|
var right = false;
|
|
4095
4664
|
var left = false;
|
|
4096
4665
|
var lower = false;
|
|
4097
4666
|
var upper = false;
|
|
4667
|
+
// points are already in DPR-scaled pixel space, so compare directly
|
|
4098
4668
|
points.forEach(function (_a) {
|
|
4099
4669
|
var px = _a[0], py = _a[1];
|
|
4100
|
-
if (
|
|
4670
|
+
if (px >= width)
|
|
4101
4671
|
right = true;
|
|
4102
|
-
if (
|
|
4672
|
+
if (px < 0)
|
|
4103
4673
|
left = true;
|
|
4104
|
-
if (
|
|
4674
|
+
if (py >= height)
|
|
4105
4675
|
lower = true;
|
|
4106
|
-
if (
|
|
4676
|
+
if (py < 0)
|
|
4107
4677
|
upper = true;
|
|
4108
4678
|
});
|
|
4109
4679
|
if (right)
|
|
@@ -4132,24 +4702,26 @@
|
|
|
4132
4702
|
/** @hidden */
|
|
4133
4703
|
CanvasRenderer.prototype.drawCircleWrap = function (x, y, size) {
|
|
4134
4704
|
var _a = this, width = _a.width, height = _a.height;
|
|
4705
|
+
var worldWidth = this.opts.width;
|
|
4706
|
+
var worldHeight = this.opts.height;
|
|
4135
4707
|
if (this.x(x + size) >= width) {
|
|
4136
|
-
this.drawCircle(x -
|
|
4708
|
+
this.drawCircle(x - worldWidth, y, size);
|
|
4137
4709
|
if (this.y(y + size) >= height)
|
|
4138
|
-
this.drawCircle(x -
|
|
4710
|
+
this.drawCircle(x - worldWidth, y - worldHeight, size);
|
|
4139
4711
|
if (this.y(y - size) < 0)
|
|
4140
|
-
this.drawCircle(x -
|
|
4712
|
+
this.drawCircle(x - worldWidth, y + worldHeight, size);
|
|
4141
4713
|
}
|
|
4142
4714
|
if (this.x(x - size) < 0) {
|
|
4143
|
-
this.drawCircle(x +
|
|
4715
|
+
this.drawCircle(x + worldWidth, y, size);
|
|
4144
4716
|
if (this.y(y + size) >= height)
|
|
4145
|
-
this.drawCircle(x +
|
|
4717
|
+
this.drawCircle(x + worldWidth, y - worldHeight, size);
|
|
4146
4718
|
if (this.y(y - size) < 0)
|
|
4147
|
-
this.drawCircle(x +
|
|
4719
|
+
this.drawCircle(x + worldWidth, y + worldHeight, size);
|
|
4148
4720
|
}
|
|
4149
4721
|
if (this.y(y + size) > height)
|
|
4150
|
-
this.drawCircle(x, y -
|
|
4722
|
+
this.drawCircle(x, y - worldHeight, size);
|
|
4151
4723
|
if (this.y(y - size) < 0)
|
|
4152
|
-
this.drawCircle(x, y +
|
|
4724
|
+
this.drawCircle(x, y + worldHeight, size);
|
|
4153
4725
|
};
|
|
4154
4726
|
/**
|
|
4155
4727
|
* Draw a rectangle centered at (x, y). Automatically calculates the offset
|
|
@@ -4163,25 +4735,55 @@
|
|
|
4163
4735
|
};
|
|
4164
4736
|
/** @hidden */
|
|
4165
4737
|
CanvasRenderer.prototype.drawRectWrap = function (x, y, w, h) {
|
|
4166
|
-
var _a = this
|
|
4738
|
+
var _a = this, width = _a.width, height = _a.height;
|
|
4739
|
+
var worldWidth = this.opts.width;
|
|
4740
|
+
var worldHeight = this.opts.height;
|
|
4167
4741
|
if (this.x(x + w / 2) >= width) {
|
|
4168
|
-
this.drawRect(x -
|
|
4742
|
+
this.drawRect(x - worldWidth, y, w, h);
|
|
4169
4743
|
if (this.y(y + h / 2) >= height)
|
|
4170
|
-
this.drawRect(x -
|
|
4171
|
-
if (this.y(y -
|
|
4172
|
-
this.drawRect(x -
|
|
4744
|
+
this.drawRect(x - worldWidth, y - worldHeight, w, h);
|
|
4745
|
+
if (this.y(y - h / 2) < 0)
|
|
4746
|
+
this.drawRect(x - worldWidth, y + worldHeight, w, h);
|
|
4173
4747
|
}
|
|
4174
4748
|
if (this.x(x - w / 2) < 0) {
|
|
4175
|
-
this.drawRect(x +
|
|
4749
|
+
this.drawRect(x + worldWidth, y, w, h);
|
|
4176
4750
|
if (this.y(y + h / 2) >= height)
|
|
4177
|
-
this.drawRect(x +
|
|
4178
|
-
if (this.y(y -
|
|
4179
|
-
this.drawRect(x +
|
|
4751
|
+
this.drawRect(x + worldWidth, y - worldHeight, w, h);
|
|
4752
|
+
if (this.y(y - h / 2) < 0)
|
|
4753
|
+
this.drawRect(x + worldWidth, y + worldHeight, w, h);
|
|
4180
4754
|
}
|
|
4181
4755
|
if (this.y(y + h / 2) > height)
|
|
4182
|
-
this.drawRect(x, y -
|
|
4183
|
-
if (this.y(y -
|
|
4184
|
-
this.drawRect(x, y +
|
|
4756
|
+
this.drawRect(x, y - worldHeight, w, h);
|
|
4757
|
+
if (this.y(y - h / 2) < 0)
|
|
4758
|
+
this.drawRect(x, y + worldHeight, w, h);
|
|
4759
|
+
};
|
|
4760
|
+
/**
|
|
4761
|
+
* Draw a selection highlight around the given agent.
|
|
4762
|
+
* @hidden
|
|
4763
|
+
*/
|
|
4764
|
+
CanvasRenderer.prototype._drawSelectionHighlight = function (agent) {
|
|
4765
|
+
var bufferContext = this.buffer.getContext("2d");
|
|
4766
|
+
var dpr = window.devicePixelRatio;
|
|
4767
|
+
var data = agent.getData();
|
|
4768
|
+
var ax = this.x(data.x);
|
|
4769
|
+
var ay = this.y(data.y);
|
|
4770
|
+
var shape = data.shape;
|
|
4771
|
+
var size = (data.size || 1) * dpr;
|
|
4772
|
+
bufferContext.save();
|
|
4773
|
+
bufferContext.strokeStyle = "#0af";
|
|
4774
|
+
bufferContext.lineWidth = 2 * dpr;
|
|
4775
|
+
if (shape === "rect") {
|
|
4776
|
+
var w = (data.width || 1) * dpr;
|
|
4777
|
+
var h = (data.height || 1) * dpr;
|
|
4778
|
+
bufferContext.strokeRect(ax - w / 2 - 2 * dpr, ay - h / 2 - 2 * dpr, w + 4 * dpr, h + 4 * dpr);
|
|
4779
|
+
}
|
|
4780
|
+
else {
|
|
4781
|
+
bufferContext.beginPath();
|
|
4782
|
+
var highlightRadius = Math.max(size, 4 * dpr) + 3 * dpr;
|
|
4783
|
+
bufferContext.arc(ax, ay, highlightRadius, 0, 2 * Math.PI);
|
|
4784
|
+
bufferContext.stroke();
|
|
4785
|
+
}
|
|
4786
|
+
bufferContext.restore();
|
|
4185
4787
|
};
|
|
4186
4788
|
CanvasRenderer.prototype.render = function () {
|
|
4187
4789
|
var _this = this;
|
|
@@ -4194,22 +4796,24 @@
|
|
|
4194
4796
|
// if "trace" is truthy, don't clear the canvas with every frame
|
|
4195
4797
|
// to trace the paths of agents
|
|
4196
4798
|
if (!trace) {
|
|
4197
|
-
context.clearRect(0, 0, width
|
|
4799
|
+
context.clearRect(0, 0, width, height);
|
|
4198
4800
|
context.fillStyle = opts.background;
|
|
4199
|
-
context.fillRect(0, 0, width
|
|
4801
|
+
context.fillRect(0, 0, width, height);
|
|
4200
4802
|
}
|
|
4201
4803
|
// automatically position agents in an environment that uses a network helper
|
|
4202
4804
|
if (opts.autoPosition && environment.helpers.network) {
|
|
4203
4805
|
environment.getAgents().forEach(function (agent) {
|
|
4204
4806
|
var network = _this.environment.helpers.network;
|
|
4205
|
-
|
|
4807
|
+
// Use CSS pixel dimensions (opts), not the DPI-scaled canvas dimensions,
|
|
4808
|
+
// since x() and y() already apply the devicePixelRatio transform.
|
|
4809
|
+
var _a = _this.opts, w = _a.width, h = _a.height;
|
|
4206
4810
|
// only set once
|
|
4207
4811
|
if ((agent.get("x") === null || agent.get("y") === null) &&
|
|
4208
4812
|
network.isInNetwork(agent)) {
|
|
4209
4813
|
var idx = network.indexOf(agent);
|
|
4210
4814
|
var angle = idx / network.agents.length;
|
|
4211
|
-
var x =
|
|
4212
|
-
var y =
|
|
4815
|
+
var x = w / 2 + 0.4 * w * Math.cos(2 * Math.PI * angle);
|
|
4816
|
+
var y = h / 2 + 0.4 * h * Math.sin(2 * Math.PI * angle);
|
|
4213
4817
|
agent.set({ x: x, y: y });
|
|
4214
4818
|
}
|
|
4215
4819
|
});
|
|
@@ -4254,8 +4858,8 @@
|
|
|
4254
4858
|
context.globalAlpha = _this.opts.connectionOpacity;
|
|
4255
4859
|
context.strokeStyle = _this.opts.connectionColor;
|
|
4256
4860
|
context.lineWidth = _this.opts.connectionWidth;
|
|
4257
|
-
context.moveTo(_this.x(x), _this.
|
|
4258
|
-
context.lineTo(_this.x(nx), _this.
|
|
4861
|
+
context.moveTo(_this.x(x), _this.y(y));
|
|
4862
|
+
context.lineTo(_this.x(nx), _this.y(ny));
|
|
4259
4863
|
context.stroke();
|
|
4260
4864
|
context.closePath();
|
|
4261
4865
|
context.restore();
|
|
@@ -4287,10 +4891,11 @@
|
|
|
4287
4891
|
}
|
|
4288
4892
|
else if (shape === "triangle") {
|
|
4289
4893
|
bufferContext.beginPath();
|
|
4894
|
+
var scaledSize = size * dpr;
|
|
4290
4895
|
var points = [
|
|
4291
|
-
[_this.x(x), _this.y(y) -
|
|
4292
|
-
[_this.x(x) +
|
|
4293
|
-
[_this.x(x) -
|
|
4896
|
+
[_this.x(x), _this.y(y) - scaledSize / 2],
|
|
4897
|
+
[_this.x(x) + scaledSize / 2, _this.y(y) + scaledSize / 2],
|
|
4898
|
+
[_this.x(x) - scaledSize / 2, _this.y(y) + scaledSize / 2]
|
|
4294
4899
|
];
|
|
4295
4900
|
_this.drawPath(points);
|
|
4296
4901
|
if (environment.opts.torus)
|
|
@@ -4316,6 +4921,12 @@
|
|
|
4316
4921
|
bufferContext.restore();
|
|
4317
4922
|
}
|
|
4318
4923
|
});
|
|
4924
|
+
// Draw selection highlights for selected agents
|
|
4925
|
+
if (opts.interactive && this.selected.length > 0) {
|
|
4926
|
+
this.selected.forEach(function (agent) {
|
|
4927
|
+
_this._drawSelectionHighlight(agent);
|
|
4928
|
+
});
|
|
4929
|
+
}
|
|
4319
4930
|
context.drawImage(buffer, 0, 0);
|
|
4320
4931
|
};
|
|
4321
4932
|
return CanvasRenderer;
|
|
@@ -4406,11 +5017,13 @@
|
|
|
4406
5017
|
};
|
|
4407
5018
|
Histogram.prototype.x = function (value) {
|
|
4408
5019
|
var _a = this, width = _a.width, markerWidth = _a.markerWidth;
|
|
4409
|
-
|
|
5020
|
+
var dpr = window.devicePixelRatio;
|
|
5021
|
+
return remap(value, 0, width, markerWidth + PADDING_AT_LEFT * dpr, width - PADDING_AT_RIGHT * dpr);
|
|
4410
5022
|
};
|
|
4411
5023
|
Histogram.prototype.y = function (value) {
|
|
4412
5024
|
var _a = this, height = _a.height, maxValue = _a.maxValue;
|
|
4413
|
-
|
|
5025
|
+
var dpr = window.devicePixelRatio;
|
|
5026
|
+
return remap(value, 0, maxValue, height - PADDING_AT_BOTTOM * dpr, 0);
|
|
4414
5027
|
};
|
|
4415
5028
|
Histogram.prototype.setMaxValue = function () {
|
|
4416
5029
|
var _this = this;
|
|
@@ -4439,11 +5052,12 @@
|
|
|
4439
5052
|
var context = this.canvas.getContext("2d");
|
|
4440
5053
|
var _a = this, height = _a.height, width = _a.width;
|
|
4441
5054
|
var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, buckets = _b.buckets, min = _b.min, max$1 = _b.max;
|
|
5055
|
+
var dpr = window.devicePixelRatio;
|
|
4442
5056
|
var yMin = 0;
|
|
4443
5057
|
var yMax = this.maxValue;
|
|
4444
5058
|
var markers = extractRoundNumbers({ min: yMin, max: yMax });
|
|
4445
5059
|
context.fillStyle = "black";
|
|
4446
|
-
context.font = 14 *
|
|
5060
|
+
context.font = 14 * dpr + "px Helvetica";
|
|
4447
5061
|
// determine the width of the longest marker
|
|
4448
5062
|
this.markerWidth = max(markers.map(function (marker) { return context.measureText(marker.toLocaleString()).width; }));
|
|
4449
5063
|
// draw horizontal lines
|
|
@@ -4452,9 +5066,9 @@
|
|
|
4452
5066
|
context.textBaseline = "middle";
|
|
4453
5067
|
context.fillText(marker.toLocaleString(), _this.markerWidth, _this.y(marker));
|
|
4454
5068
|
context.beginPath();
|
|
4455
|
-
context.moveTo(_this.markerWidth + 10, _this.y(marker));
|
|
5069
|
+
context.moveTo(_this.markerWidth + 10 * dpr, _this.y(marker));
|
|
4456
5070
|
context.lineTo(_this.width, _this.y(marker));
|
|
4457
|
-
context.setLineDash(LINE_DASH);
|
|
5071
|
+
context.setLineDash(LINE_DASH.map(function (v) { return v * dpr; }));
|
|
4458
5072
|
context.stroke();
|
|
4459
5073
|
});
|
|
4460
5074
|
var numBuckets = bucketValues.length - (aboveMax ? 1 : 0) - (belowMin ? 1 : 0);
|
|
@@ -4477,9 +5091,9 @@
|
|
|
4477
5091
|
.forEach(function (label, i) {
|
|
4478
5092
|
context.save();
|
|
4479
5093
|
context.translate(_this.x((i * width) / bucketValues.length +
|
|
4480
|
-
(0.5 * width) / bucketValues.length), height - 50);
|
|
5094
|
+
(0.5 * width) / bucketValues.length), height - 50 * dpr);
|
|
4481
5095
|
context.rotate(Math.PI / 4);
|
|
4482
|
-
context.font = 12 *
|
|
5096
|
+
context.font = 12 * dpr + "px Helvetica";
|
|
4483
5097
|
context.textAlign = "left";
|
|
4484
5098
|
context.textBaseline = "middle";
|
|
4485
5099
|
context.fillText(label, 0, 0);
|
|
@@ -4489,22 +5103,23 @@
|
|
|
4489
5103
|
Histogram.prototype.drawBuckets = function (bucketValues, offset) {
|
|
4490
5104
|
var _this = this;
|
|
4491
5105
|
if (offset === void 0) { offset = 0; }
|
|
4492
|
-
var canvas =
|
|
5106
|
+
var _a = this, canvas = _a.canvas, width = _a.width, height = _a.height;
|
|
4493
5107
|
var metric = this._metric;
|
|
4494
5108
|
var numMetrics = Array.isArray(metric) ? metric.length : 1;
|
|
4495
|
-
var
|
|
5109
|
+
var _b = this.opts, aboveMax = _b.aboveMax, belowMin = _b.belowMin, color = _b.color;
|
|
5110
|
+
var dpr = window.devicePixelRatio;
|
|
4496
5111
|
var context = canvas.getContext("2d");
|
|
4497
5112
|
context.fillStyle = Array.isArray(color)
|
|
4498
5113
|
? color[offset % color.length]
|
|
4499
5114
|
: color;
|
|
4500
5115
|
var numBuckets = bucketValues.length;
|
|
4501
|
-
var barWidth = (width - PADDING_AT_LEFT - PADDING_AT_RIGHT - this.markerWidth) /
|
|
5116
|
+
var barWidth = (width - PADDING_AT_LEFT * dpr - PADDING_AT_RIGHT * dpr - this.markerWidth) /
|
|
4502
5117
|
numBuckets;
|
|
4503
5118
|
barWidth *= 0.8;
|
|
4504
5119
|
bucketValues.forEach(function (value, i) {
|
|
4505
5120
|
var mappedValue = remap(value, 0, _this.maxValue, 0, 1);
|
|
4506
5121
|
var x = _this.x(((0.1 + i) * width) / numBuckets);
|
|
4507
|
-
context.fillRect(x + (offset * barWidth - (numMetrics - 1)) / numMetrics + offset, remap(mappedValue, 0, 1, height - PADDING_AT_BOTTOM, 0), barWidth / numMetrics, remap(mappedValue, 0, 1, 0, height - PADDING_AT_BOTTOM));
|
|
5122
|
+
context.fillRect(x + (offset * barWidth - (numMetrics - 1)) / numMetrics + offset, remap(mappedValue, 0, 1, height - PADDING_AT_BOTTOM * dpr, 0), barWidth / numMetrics, remap(mappedValue, 0, 1, 0, height - PADDING_AT_BOTTOM * dpr));
|
|
4508
5123
|
});
|
|
4509
5124
|
};
|
|
4510
5125
|
Histogram.prototype.getBucketValues = function (metric) {
|
|
@@ -4641,12 +5256,16 @@
|
|
|
4641
5256
|
var height = this.height;
|
|
4642
5257
|
var range = this.opts.range;
|
|
4643
5258
|
var min = range.min, max = range.max;
|
|
4644
|
-
var
|
|
4645
|
-
|
|
5259
|
+
var dpr = window.devicePixelRatio;
|
|
5260
|
+
var paddingBottom = PADDING_BOTTOM * dpr;
|
|
5261
|
+
var pxPerUnit = (height - 2 * paddingBottom) / (max - min);
|
|
5262
|
+
return Math.round(height - (value - min) * pxPerUnit) - 2 * paddingBottom;
|
|
4646
5263
|
};
|
|
4647
5264
|
LineChartRenderer.prototype.drawBackground = function () {
|
|
4648
5265
|
var _this = this;
|
|
4649
5266
|
var _a = this, context = _a.context, width = _a.width, height = _a.height, opts = _a.opts, t = _a.t;
|
|
5267
|
+
var dpr = window.devicePixelRatio;
|
|
5268
|
+
var paddingBottom = PADDING_BOTTOM * dpr;
|
|
4650
5269
|
// draw background and lines
|
|
4651
5270
|
context.fillStyle = this.opts.background;
|
|
4652
5271
|
context.fillRect(0, 0, width, height);
|
|
@@ -4654,27 +5273,27 @@
|
|
|
4654
5273
|
var markers = extractRoundNumbers(range);
|
|
4655
5274
|
var textMaxWidth = 0;
|
|
4656
5275
|
// write values on vertical axis
|
|
4657
|
-
context.font = 14 *
|
|
5276
|
+
context.font = 14 * dpr + "px Helvetica";
|
|
4658
5277
|
context.fillStyle = "#000";
|
|
4659
5278
|
context.textBaseline = "middle";
|
|
4660
5279
|
markers.forEach(function (marker) {
|
|
4661
|
-
if (_this.y(marker) < 10 || _this.y(marker) + 10 > height)
|
|
5280
|
+
if (_this.y(marker) < 10 * dpr || _this.y(marker) + 10 * dpr > height)
|
|
4662
5281
|
return;
|
|
4663
5282
|
var width = context.measureText(marker.toLocaleString()).width;
|
|
4664
5283
|
if (width > textMaxWidth)
|
|
4665
5284
|
textMaxWidth = width;
|
|
4666
|
-
context.fillText(marker.toLocaleString(), 5, _this.y(marker));
|
|
5285
|
+
context.fillText(marker.toLocaleString(), 5 * dpr, _this.y(marker));
|
|
4667
5286
|
});
|
|
4668
5287
|
// draw horizontal lines for vertical axis
|
|
4669
5288
|
context.save();
|
|
4670
5289
|
context.strokeStyle = "#999";
|
|
4671
5290
|
markers.forEach(function (marker) {
|
|
4672
|
-
if (_this.y(marker) >= height -
|
|
5291
|
+
if (_this.y(marker) >= height - paddingBottom)
|
|
4673
5292
|
return;
|
|
4674
5293
|
context.beginPath();
|
|
4675
|
-
context.moveTo(textMaxWidth + 10, _this.y(marker));
|
|
5294
|
+
context.moveTo(textMaxWidth + 10 * dpr, _this.y(marker));
|
|
4676
5295
|
context.lineTo(_this.x(Math.max(width, _this.environment.time)), _this.y(marker));
|
|
4677
|
-
context.setLineDash(lineDash);
|
|
5296
|
+
context.setLineDash(lineDash.map(function (v) { return v * dpr; }));
|
|
4678
5297
|
context.stroke();
|
|
4679
5298
|
});
|
|
4680
5299
|
context.restore();
|
|
@@ -4691,12 +5310,12 @@
|
|
|
4691
5310
|
_this.x(marker) - width / 2 < textMaxWidth) {
|
|
4692
5311
|
return;
|
|
4693
5312
|
}
|
|
4694
|
-
context.font = 11 *
|
|
4695
|
-
context.fillText(marker.toLocaleString(), _this.x(marker), height -
|
|
5313
|
+
context.font = 11 * dpr + "px Helvetica";
|
|
5314
|
+
context.fillText(marker.toLocaleString(), _this.x(marker), height - paddingBottom);
|
|
4696
5315
|
context.strokeStyle = "black";
|
|
4697
5316
|
context.lineWidth = 1;
|
|
4698
5317
|
context.beginPath();
|
|
4699
|
-
context.moveTo(_this.x(marker), height - 4);
|
|
5318
|
+
context.moveTo(_this.x(marker), height - 4 * dpr);
|
|
4700
5319
|
context.lineTo(_this.x(marker), height);
|
|
4701
5320
|
context.stroke();
|
|
4702
5321
|
});
|
|
@@ -5044,7 +5663,8 @@
|
|
|
5044
5663
|
*/
|
|
5045
5664
|
Heatmap.prototype.x = function (value) {
|
|
5046
5665
|
var width = this.width;
|
|
5047
|
-
|
|
5666
|
+
var dpr = window.devicePixelRatio;
|
|
5667
|
+
return remap(value, this.getMin("x"), this.getMax("x"), PADDING_AT_LEFT$1 * dpr, width);
|
|
5048
5668
|
};
|
|
5049
5669
|
/**
|
|
5050
5670
|
* Map a value (on the range y-min to y-max) onto canvas space to draw it along the y-axis.
|
|
@@ -5052,7 +5672,8 @@
|
|
|
5052
5672
|
*/
|
|
5053
5673
|
Heatmap.prototype.y = function (value) {
|
|
5054
5674
|
var height = this.height;
|
|
5055
|
-
|
|
5675
|
+
var dpr = window.devicePixelRatio;
|
|
5676
|
+
return remap(value, this.getMin("y"), this.getMax("y"), height - PADDING_AT_BOTTOM$1 * dpr, 0);
|
|
5056
5677
|
};
|
|
5057
5678
|
/** @hidden */
|
|
5058
5679
|
Heatmap.prototype.getKey = function (axis) {
|
|
@@ -5095,52 +5716,55 @@
|
|
|
5095
5716
|
Heatmap.prototype.drawMarkers = function () {
|
|
5096
5717
|
var _a = this, context = _a.context, width = _a.width, height = _a.height;
|
|
5097
5718
|
var _b = this.opts, from = _b.from, to = _b.to;
|
|
5719
|
+
var dpr = window.devicePixelRatio;
|
|
5720
|
+
var padLeft = PADDING_AT_LEFT$1 * dpr;
|
|
5721
|
+
var padBottom = PADDING_AT_BOTTOM$1 * dpr;
|
|
5098
5722
|
context.strokeStyle = "black";
|
|
5099
5723
|
context.lineWidth = 1;
|
|
5100
|
-
context.moveTo(
|
|
5101
|
-
context.lineTo(
|
|
5102
|
-
context.lineTo(width, height -
|
|
5724
|
+
context.moveTo(padLeft - 1, 0);
|
|
5725
|
+
context.lineTo(padLeft - 1, height - padBottom + 1);
|
|
5726
|
+
context.lineTo(width, height - padBottom + 1);
|
|
5103
5727
|
context.stroke();
|
|
5104
5728
|
context.lineWidth = 0;
|
|
5105
|
-
var gradient = context.createLinearGradient(10, 0,
|
|
5729
|
+
var gradient = context.createLinearGradient(10 * dpr, 0, padLeft - 10 * dpr, 0);
|
|
5106
5730
|
gradient.addColorStop(0, from);
|
|
5107
5731
|
gradient.addColorStop(1, to);
|
|
5108
5732
|
context.fillStyle = gradient;
|
|
5109
|
-
context.fillRect(10, height -
|
|
5733
|
+
context.fillRect(10 * dpr, height - padBottom + 20 * dpr, padLeft - 24 * dpr, 20 * dpr);
|
|
5110
5734
|
context.fillStyle = "black";
|
|
5111
5735
|
var step = (this.getMax("x") - this.getMin("x")) / this.getBuckets("x");
|
|
5112
5736
|
var originalStep = step;
|
|
5113
|
-
while (Math.abs(this.x(step) - this.x(0)) < 35)
|
|
5737
|
+
while (Math.abs(this.x(step) - this.x(0)) < 35 * dpr)
|
|
5114
5738
|
step *= 2;
|
|
5115
5739
|
for (var marker = this.getMin("x"); marker <= this.getMax("x"); marker += originalStep) {
|
|
5116
|
-
if (this.x(marker) + 10 > width)
|
|
5740
|
+
if (this.x(marker) + 10 * dpr > width)
|
|
5117
5741
|
continue;
|
|
5118
|
-
context.moveTo(this.x(marker), height -
|
|
5119
|
-
context.lineTo(this.x(marker), height -
|
|
5742
|
+
context.moveTo(this.x(marker), height - padBottom);
|
|
5743
|
+
context.lineTo(this.x(marker), height - padBottom + 10 * dpr);
|
|
5120
5744
|
context.stroke();
|
|
5121
5745
|
if (Math.abs(((marker - this.getMin("x")) / step) % 1) < 0.001 ||
|
|
5122
5746
|
Math.abs((((marker - this.getMin("x")) / step) % 1) - 1) < 0.001) {
|
|
5123
|
-
context.font = 12 *
|
|
5747
|
+
context.font = 12 * dpr + "px Helvetica";
|
|
5124
5748
|
context.textAlign = "center";
|
|
5125
|
-
context.fillText(marker.toLocaleString(), this.x(marker), height -
|
|
5749
|
+
context.fillText(marker.toLocaleString(), this.x(marker), height - padBottom + 24 * dpr);
|
|
5126
5750
|
}
|
|
5127
5751
|
}
|
|
5128
5752
|
step = (this.getMax("y") - this.getMin("y")) / this.getBuckets("y");
|
|
5129
5753
|
originalStep = step;
|
|
5130
|
-
while (Math.abs(this.y(step) - this.y(0)) < 20)
|
|
5754
|
+
while (Math.abs(this.y(step) - this.y(0)) < 20 * dpr)
|
|
5131
5755
|
step *= 2;
|
|
5132
5756
|
for (var marker = this.getMin("y"); marker <= this.getMax("y"); marker += originalStep) {
|
|
5133
|
-
if (this.y(marker) - 10 < 0)
|
|
5757
|
+
if (this.y(marker) - 10 * dpr < 0)
|
|
5134
5758
|
continue;
|
|
5135
|
-
context.moveTo(
|
|
5136
|
-
context.lineTo(
|
|
5759
|
+
context.moveTo(padLeft, this.y(marker));
|
|
5760
|
+
context.lineTo(padLeft - 10 * dpr, this.y(marker));
|
|
5137
5761
|
context.stroke();
|
|
5138
5762
|
if (Math.abs(((marker - this.getMin("y")) / step) % 1) < 0.001 ||
|
|
5139
5763
|
Math.abs((((marker - this.getMin("y")) / step) % 1) - 1) < 0.001) {
|
|
5140
|
-
context.font = 12 *
|
|
5764
|
+
context.font = 12 * dpr + "px Helvetica";
|
|
5141
5765
|
context.textAlign = "right";
|
|
5142
5766
|
context.textBaseline = "middle";
|
|
5143
|
-
context.fillText(marker.toLocaleString(),
|
|
5767
|
+
context.fillText(marker.toLocaleString(), padLeft - 14 * dpr, this.y(marker));
|
|
5144
5768
|
}
|
|
5145
5769
|
}
|
|
5146
5770
|
};
|
|
@@ -5148,6 +5772,8 @@
|
|
|
5148
5772
|
Heatmap.prototype.updateScale = function () {
|
|
5149
5773
|
var _a = this, context = _a.context, environment = _a.environment, height = _a.height;
|
|
5150
5774
|
var scale = this.opts.scale;
|
|
5775
|
+
var dpr = window.devicePixelRatio;
|
|
5776
|
+
var padLeft = PADDING_AT_LEFT$1 * dpr;
|
|
5151
5777
|
var max = scale === "relative" ? this.localMax : this.opts.max;
|
|
5152
5778
|
if (max === undefined) {
|
|
5153
5779
|
if (!this.lastUpdatedScale) {
|
|
@@ -5156,13 +5782,13 @@
|
|
|
5156
5782
|
max = environment.getAgents().length;
|
|
5157
5783
|
}
|
|
5158
5784
|
if (!this.lastUpdatedScale || +new Date() - +this.lastUpdatedScale > 250) {
|
|
5159
|
-
context.clearRect(0, height - 20,
|
|
5785
|
+
context.clearRect(0, height - 20 * dpr, padLeft, 20 * dpr);
|
|
5160
5786
|
context.fillStyle = "black";
|
|
5161
|
-
context.font = 12 *
|
|
5787
|
+
context.font = 12 * dpr + "px Helvetica";
|
|
5162
5788
|
context.textAlign = "center";
|
|
5163
5789
|
context.textBaseline = "bottom";
|
|
5164
|
-
context.fillText("0", 10, height - 5);
|
|
5165
|
-
context.fillText(max.toString(),
|
|
5790
|
+
context.fillText("0", 10 * dpr, height - 5 * dpr);
|
|
5791
|
+
context.fillText(max.toString(), padLeft - 16 * dpr, height - 5 * dpr);
|
|
5166
5792
|
this.lastUpdatedScale = new Date();
|
|
5167
5793
|
}
|
|
5168
5794
|
};
|
|
@@ -5170,6 +5796,9 @@
|
|
|
5170
5796
|
Heatmap.prototype.drawRectangles = function () {
|
|
5171
5797
|
var _a = this, canvas = _a.canvas, environment = _a.environment, width = _a.width, height = _a.height;
|
|
5172
5798
|
var _b = this.opts, scale = _b.scale, from = _b.from, to = _b.to;
|
|
5799
|
+
var dpr = window.devicePixelRatio;
|
|
5800
|
+
var padLeft = PADDING_AT_LEFT$1 * dpr;
|
|
5801
|
+
var padBottom = PADDING_AT_BOTTOM$1 * dpr;
|
|
5173
5802
|
var context = canvas.getContext("2d");
|
|
5174
5803
|
var xBuckets = this.getBuckets("x");
|
|
5175
5804
|
var yBuckets = this.getBuckets("y");
|
|
@@ -5178,9 +5807,9 @@
|
|
|
5178
5807
|
max = environment.getAgents().length;
|
|
5179
5808
|
// clear background by drawing background rectangle
|
|
5180
5809
|
context.fillStyle = from;
|
|
5181
|
-
context.fillRect(
|
|
5182
|
-
var w = width / xBuckets;
|
|
5183
|
-
var h = height / yBuckets;
|
|
5810
|
+
context.fillRect(padLeft, 0, width - padLeft, height - padBottom);
|
|
5811
|
+
var w = (width - padLeft) / xBuckets;
|
|
5812
|
+
var h = (height - padBottom) / yBuckets;
|
|
5184
5813
|
for (var row = 0; row < yBuckets; row++) {
|
|
5185
5814
|
for (var column = 0; column < xBuckets; column++) {
|
|
5186
5815
|
var index = row * xBuckets + column;
|
|
@@ -5188,7 +5817,7 @@
|
|
|
5188
5817
|
var a = clamp(remap(this.buckets[index], 0, max, 0, 1), 0, 1);
|
|
5189
5818
|
context.fillStyle = to;
|
|
5190
5819
|
context.globalAlpha = a;
|
|
5191
|
-
context.fillRect(this.x(remap(column, 0, xBuckets, this.getMin("x"), this.getMax("x"))), this.y(remap(row, -1, yBuckets - 1, this.getMin("y"), this.getMax("y"))),
|
|
5820
|
+
context.fillRect(this.x(remap(column, 0, xBuckets, this.getMin("x"), this.getMax("x"))), this.y(remap(row, -1, yBuckets - 1, this.getMin("y"), this.getMax("y"))), w, h);
|
|
5192
5821
|
}
|
|
5193
5822
|
}
|
|
5194
5823
|
context.globalAlpha = 1;
|
|
@@ -5244,7 +5873,7 @@
|
|
|
5244
5873
|
/**
|
|
5245
5874
|
* The current version of the Flocc library.
|
|
5246
5875
|
*/
|
|
5247
|
-
var version = "0.5.
|
|
5876
|
+
var version = "0.5.23";
|
|
5248
5877
|
|
|
5249
5878
|
exports.ASCIIRenderer = ASCIIRenderer;
|
|
5250
5879
|
exports.Agent = Agent;
|