@jspsych-contrib/plugin-trail-making 0.1.0 → 0.2.0
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/index.browser.js +19 -6
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +2 -2
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +18 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.spec.ts +7 -0
- package/src/index.ts +19 -4
package/dist/index.browser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var jsPsychTrailMaking = (function (jspsych) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var version = "0.
|
|
4
|
+
var version = "0.2.0";
|
|
5
5
|
|
|
6
6
|
const info = {
|
|
7
7
|
name: "trail-making",
|
|
@@ -117,6 +117,13 @@ var jsPsychTrailMaking = (function (jspsych) {
|
|
|
117
117
|
type: jspsych.ParameterType.HTML_STRING,
|
|
118
118
|
default: null
|
|
119
119
|
},
|
|
120
|
+
/**
|
|
121
|
+
* Duration in milliseconds to wait after the last circle is clicked before ending the trial.
|
|
122
|
+
*/
|
|
123
|
+
end_delay: {
|
|
124
|
+
type: jspsych.ParameterType.INT,
|
|
125
|
+
default: 500
|
|
126
|
+
},
|
|
120
127
|
/**
|
|
121
128
|
* Random seed for reproducible target layouts. If null, uses random seed.
|
|
122
129
|
*/
|
|
@@ -194,6 +201,9 @@ var jsPsychTrailMaking = (function (jspsych) {
|
|
|
194
201
|
this.drawCanvas(ctx, targets, [], trial);
|
|
195
202
|
const handleClick = (e) => {
|
|
196
203
|
if (isShowingError) return;
|
|
204
|
+
if (e instanceof TouchEvent) {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
}
|
|
197
207
|
const rect = canvas.getBoundingClientRect();
|
|
198
208
|
let clientX, clientY;
|
|
199
209
|
if (e instanceof TouchEvent) {
|
|
@@ -230,7 +240,9 @@ var jsPsychTrailMaking = (function (jspsych) {
|
|
|
230
240
|
const visitedLabels = sequence.slice(0, currentTargetIndex);
|
|
231
241
|
this.drawCanvas(ctx, targets, visitedLabels, trial);
|
|
232
242
|
if (currentTargetIndex >= sequence.length) {
|
|
233
|
-
this.
|
|
243
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
244
|
+
this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
|
|
245
|
+
}, trial.end_delay);
|
|
234
246
|
}
|
|
235
247
|
} else {
|
|
236
248
|
clickEvent.correct = false;
|
|
@@ -238,7 +250,8 @@ var jsPsychTrailMaking = (function (jspsych) {
|
|
|
238
250
|
numErrors++;
|
|
239
251
|
isShowingError = true;
|
|
240
252
|
const visitedLabels = sequence.slice(0, currentTargetIndex);
|
|
241
|
-
|
|
253
|
+
const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
|
|
254
|
+
this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
|
|
242
255
|
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
243
256
|
isShowingError = false;
|
|
244
257
|
this.drawCanvas(ctx, targets, visitedLabels, trial);
|
|
@@ -337,7 +350,7 @@ var jsPsychTrailMaking = (function (jspsych) {
|
|
|
337
350
|
}
|
|
338
351
|
return null;
|
|
339
352
|
}
|
|
340
|
-
drawCanvas(ctx, targets, visitedLabels, trial,
|
|
353
|
+
drawCanvas(ctx, targets, visitedLabels, trial, errorLabel = null) {
|
|
341
354
|
const width = trial.canvas_width;
|
|
342
355
|
const height = trial.canvas_height;
|
|
343
356
|
ctx.fillStyle = "#f5f5f5";
|
|
@@ -360,7 +373,7 @@ var jsPsychTrailMaking = (function (jspsych) {
|
|
|
360
373
|
}
|
|
361
374
|
for (const target of targets) {
|
|
362
375
|
const isVisited = visitedLabels.includes(target.label);
|
|
363
|
-
if (
|
|
376
|
+
if (errorLabel !== null && target.label === errorLabel) {
|
|
364
377
|
ctx.fillStyle = trial.error_color;
|
|
365
378
|
} else if (isVisited) {
|
|
366
379
|
ctx.fillStyle = trial.visited_color;
|
|
@@ -406,4 +419,4 @@ var jsPsychTrailMaking = (function (jspsych) {
|
|
|
406
419
|
return TrailMakingPlugin;
|
|
407
420
|
|
|
408
421
|
})(jsPsychModule);
|
|
409
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.
|
|
422
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.2.0/dist/index.browser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var jsPsychTrailMaking=function(i){"use strict";var
|
|
2
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.
|
|
1
|
+
var jsPsychTrailMaking=function(i){"use strict";var C="0.2.0",y=Math.pow;const w={name:"trail-making",version:C,parameters:{test_type:{type:i.ParameterType.STRING,default:"A"},num_targets:{type:i.ParameterType.INT,default:25},canvas_width:{type:i.ParameterType.INT,default:600},canvas_height:{type:i.ParameterType.INT,default:600},target_radius:{type:i.ParameterType.INT,default:25},min_separation:{type:i.ParameterType.INT,default:80},target_color:{type:i.ParameterType.STRING,default:"#ffffff"},target_border_color:{type:i.ParameterType.STRING,default:"#000000"},visited_color:{type:i.ParameterType.STRING,default:"#90EE90"},line_color:{type:i.ParameterType.STRING,default:"#000000"},line_width:{type:i.ParameterType.INT,default:2},error_color:{type:i.ParameterType.STRING,default:"#FF6B6B"},error_duration:{type:i.ParameterType.INT,default:500},targets:{type:i.ParameterType.COMPLEX,default:null},prompt:{type:i.ParameterType.HTML_STRING,default:null},end_delay:{type:i.ParameterType.INT,default:500},seed:{type:i.ParameterType.INT,default:null}},data:{test_type:{type:i.ParameterType.STRING},targets:{type:i.ParameterType.COMPLEX,array:!0},clicks:{type:i.ParameterType.COMPLEX,array:!0},completion_time:{type:i.ParameterType.INT},num_errors:{type:i.ParameterType.INT},total_path_distance:{type:i.ParameterType.FLOAT},inter_click_times:{type:i.ParameterType.INT,array:!0}}};class M{constructor(t){this.jsPsych=t}trial(t,n){const a=n.targets?n.targets:this.generateTargets(n),r=this.getCorrectSequence(n.test_type,a);let l=0,s=null,e=null;const o=[],d=[];let T=0,u=!1;const f=document.createElement("div");if(f.style.cssText="display: flex; flex-direction: column; align-items: center;",n.prompt){const h=document.createElement("div");h.innerHTML=n.prompt,h.style.marginBottom="10px",f.appendChild(h)}const c=document.createElement("canvas");c.width=n.canvas_width,c.height=n.canvas_height,c.style.cssText="border: 1px solid #ccc; cursor: pointer; touch-action: none;",f.appendChild(c),t.appendChild(f);const p=c.getContext("2d");this.drawCanvas(p,a,[],n);const g=h=>{if(u)return;h instanceof TouchEvent&&h.preventDefault();const x=c.getBoundingClientRect();let b,I;h instanceof TouchEvent?(b=h.changedTouches[0].clientX,I=h.changedTouches[0].clientY):(b=h.clientX,I=h.clientY);const S=b-x.left,N=I-x.top,_=performance.now();s===null&&(s=_);const m=this.findClickedTarget(S,N,a,n.target_radius),k=r[l],P={target_index:m,label:m!==null?a[m].label:null,time:Math.round(_-s),x:Math.round(S),y:Math.round(N),correct:!1};if(m!==null&&a[m].label===k){P.correct=!0,e!==null&&d.push(Math.round(_-e)),e=_,l++,o.push(P);const v=r.slice(0,l);this.drawCanvas(p,a,v,n),l>=r.length&&this.jsPsych.pluginAPI.setTimeout(()=>{this.endTrial(a,o,s,_,T,d,n)},n.end_delay)}else{P.correct=!1,o.push(P),T++,u=!0;const v=r.slice(0,l),E=m!==null?a[m].label:null;this.drawCanvas(p,a,v,n,E),this.jsPsych.pluginAPI.setTimeout(()=>{u=!1,this.drawCanvas(p,a,v,n)},n.error_duration)}};c.addEventListener("click",g),c.addEventListener("touchend",g)}generateTargets(t){var n;const a=this.generateLabels(t.test_type,t.num_targets),r=[],l=t.target_radius+10,s=1e3;let e=(n=t.seed)!=null?n:Math.floor(Math.random()*1e6);const o=()=>(e=e*1103515245+12345&2147483647,e/2147483647);for(const d of a){let T=!1,u=0;for(;!T&&u<s;){const f=l+o()*(t.canvas_width-2*l),c=l+o()*(t.canvas_height-2*l);let p=!0;for(const g of r)if(Math.sqrt(y(f-g.x,2)+y(c-g.y,2))<t.min_separation){p=!1;break}p&&(r.push({x:Math.round(f),y:Math.round(c),label:d}),T=!0),u++}if(!T){const f=l+o()*(t.canvas_width-2*l),c=l+o()*(t.canvas_height-2*l);r.push({x:Math.round(f),y:Math.round(c),label:d})}}return r}generateLabels(t,n){const a=[];if(t==="A")for(let r=1;r<=n;r++)a.push(r.toString());else if(t==="B"){const r=[],l=[],s=Math.floor(n/2);for(let e=1;e<=s;e++)r.push(e.toString());for(let e=0;e<s;e++)l.push(String.fromCharCode(65+e));for(let e=0;e<s;e++)a.push(r[e]),a.push(l[e]);n%2===1&&a.push((s+1).toString())}return a}getCorrectSequence(t,n){if(t==="A")return n.map(a=>a.label).sort((a,r)=>parseInt(a)-parseInt(r));{const a=n.filter(e=>/^\d+$/.test(e.label)).map(e=>e.label).sort((e,o)=>parseInt(e)-parseInt(o)),r=n.filter(e=>/^[A-Z]$/.test(e.label)).map(e=>e.label).sort(),l=[],s=Math.max(a.length,r.length);for(let e=0;e<s;e++)e<a.length&&l.push(a[e]),e<r.length&&l.push(r[e]);return l}}findClickedTarget(t,n,a,r){const l=r+5;for(let s=0;s<a.length;s++)if(Math.sqrt(y(t-a[s].x,2)+y(n-a[s].y,2))<=l)return s;return null}drawCanvas(t,n,a,r,l=null){const s=r.canvas_width,e=r.canvas_height;if(t.fillStyle="#f5f5f5",t.fillRect(0,0,s,e),a.length>1){t.strokeStyle=r.line_color,t.lineWidth=r.line_width,t.beginPath();for(let o=0;o<a.length;o++){const d=n.find(T=>T.label===a[o]);d&&(o===0?t.moveTo(d.x,d.y):t.lineTo(d.x,d.y))}t.stroke()}for(const o of n){const d=a.includes(o.label);l!==null&&o.label===l?t.fillStyle=r.error_color:d?t.fillStyle=r.visited_color:t.fillStyle=r.target_color,t.beginPath(),t.arc(o.x,o.y,r.target_radius,0,Math.PI*2),t.fill(),t.strokeStyle=r.target_border_color,t.lineWidth=2,t.stroke(),t.fillStyle="#000000",t.font=`bold ${Math.round(r.target_radius*.8)}px Arial`,t.textAlign="center",t.textBaseline="middle",t.fillText(o.label,o.x,o.y)}}endTrial(t,n,a,r,l,s,e){const o=n.filter(u=>u.correct);let d=0;for(let u=1;u<o.length;u++){const f=t.find(p=>p.label===o[u-1].label),c=t.find(p=>p.label===o[u].label);f&&c&&(d+=Math.sqrt(y(c.x-f.x,2)+y(c.y-f.y,2)))}const T={test_type:e.test_type,targets:t,clicks:n,completion_time:Math.round(r-a),num_errors:l,total_path_distance:Math.round(d*100)/100,inter_click_times:s};this.jsPsych.finishTrial(T)}}return M.info=w,M}(jsPsychModule);
|
|
2
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.2.0/dist/index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["version","m","info","ParameterType","TrailMakingPlugin","jsPsych","display_element","trial","targets","sequence","currentTargetIndex","startTime","lastClickTime","clicks","interClickTimes","numErrors","isShowingError","container","promptDiv","canvas","ctx","handleClick","e","rect","clientX","clientY","x","y","now","clickedTargetIndex","expectedLabel","clickEvent","visitedLabels","_a","labels","padding","maxAttempts","seed","random","label","placed","attempts","validPosition","target","__pow","type","numTargets","i","numbers","letters","numPairs","t","a","b","maxLen","radius","hitRadius","showError","width","height","isVisited","endTime","correctClicks","c","totalPathDistance","prev","curr","trial_data"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["version","info","ParameterType","TrailMakingPlugin","jsPsych","display_element","trial","targets","sequence","currentTargetIndex","startTime","lastClickTime","clicks","interClickTimes","numErrors","isShowingError","container","promptDiv","canvas","ctx","handleClick","e","rect","clientX","clientY","x","y","now","clickedTargetIndex","expectedLabel","clickEvent","visitedLabels","errorLabel","_a","labels","padding","maxAttempts","seed","random","label","placed","attempts","validPosition","target","__pow","type","numTargets","i","numbers","letters","numPairs","t","a","b","maxLen","radius","hitRadius","width","height","isVisited","endTime","correctClicks","c","totalPathDistance","prev","curr","trial_data"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jspsych = require('jspsych');
|
|
4
4
|
|
|
5
|
-
var version = "0.
|
|
5
|
+
var version = "0.2.0";
|
|
6
6
|
|
|
7
7
|
const info = {
|
|
8
8
|
name: "trail-making",
|
|
@@ -118,6 +118,13 @@ const info = {
|
|
|
118
118
|
type: jspsych.ParameterType.HTML_STRING,
|
|
119
119
|
default: null
|
|
120
120
|
},
|
|
121
|
+
/**
|
|
122
|
+
* Duration in milliseconds to wait after the last circle is clicked before ending the trial.
|
|
123
|
+
*/
|
|
124
|
+
end_delay: {
|
|
125
|
+
type: jspsych.ParameterType.INT,
|
|
126
|
+
default: 500
|
|
127
|
+
},
|
|
121
128
|
/**
|
|
122
129
|
* Random seed for reproducible target layouts. If null, uses random seed.
|
|
123
130
|
*/
|
|
@@ -195,6 +202,9 @@ class TrailMakingPlugin {
|
|
|
195
202
|
this.drawCanvas(ctx, targets, [], trial);
|
|
196
203
|
const handleClick = (e) => {
|
|
197
204
|
if (isShowingError) return;
|
|
205
|
+
if (e instanceof TouchEvent) {
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
}
|
|
198
208
|
const rect = canvas.getBoundingClientRect();
|
|
199
209
|
let clientX, clientY;
|
|
200
210
|
if (e instanceof TouchEvent) {
|
|
@@ -231,7 +241,9 @@ class TrailMakingPlugin {
|
|
|
231
241
|
const visitedLabels = sequence.slice(0, currentTargetIndex);
|
|
232
242
|
this.drawCanvas(ctx, targets, visitedLabels, trial);
|
|
233
243
|
if (currentTargetIndex >= sequence.length) {
|
|
234
|
-
this.
|
|
244
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
245
|
+
this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
|
|
246
|
+
}, trial.end_delay);
|
|
235
247
|
}
|
|
236
248
|
} else {
|
|
237
249
|
clickEvent.correct = false;
|
|
@@ -239,7 +251,8 @@ class TrailMakingPlugin {
|
|
|
239
251
|
numErrors++;
|
|
240
252
|
isShowingError = true;
|
|
241
253
|
const visitedLabels = sequence.slice(0, currentTargetIndex);
|
|
242
|
-
|
|
254
|
+
const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
|
|
255
|
+
this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
|
|
243
256
|
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
244
257
|
isShowingError = false;
|
|
245
258
|
this.drawCanvas(ctx, targets, visitedLabels, trial);
|
|
@@ -338,7 +351,7 @@ class TrailMakingPlugin {
|
|
|
338
351
|
}
|
|
339
352
|
return null;
|
|
340
353
|
}
|
|
341
|
-
drawCanvas(ctx, targets, visitedLabels, trial,
|
|
354
|
+
drawCanvas(ctx, targets, visitedLabels, trial, errorLabel = null) {
|
|
342
355
|
const width = trial.canvas_width;
|
|
343
356
|
const height = trial.canvas_height;
|
|
344
357
|
ctx.fillStyle = "#f5f5f5";
|
|
@@ -361,7 +374,7 @@ class TrailMakingPlugin {
|
|
|
361
374
|
}
|
|
362
375
|
for (const target of targets) {
|
|
363
376
|
const isVisited = visitedLabels.includes(target.label);
|
|
364
|
-
if (
|
|
377
|
+
if (errorLabel !== null && target.label === errorLabel) {
|
|
365
378
|
ctx.fillStyle = trial.error_color;
|
|
366
379
|
} else if (isVisited) {
|
|
367
380
|
ctx.fillStyle = trial.visited_color;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -114,6 +114,13 @@ declare const info: {
|
|
|
114
114
|
readonly type: ParameterType.HTML_STRING;
|
|
115
115
|
readonly default: any;
|
|
116
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Duration in milliseconds to wait after the last circle is clicked before ending the trial.
|
|
119
|
+
*/
|
|
120
|
+
readonly end_delay: {
|
|
121
|
+
readonly type: ParameterType.INT;
|
|
122
|
+
readonly default: 500;
|
|
123
|
+
};
|
|
117
124
|
/**
|
|
118
125
|
* Random seed for reproducible target layouts. If null, uses random seed.
|
|
119
126
|
*/
|
|
@@ -286,6 +293,13 @@ declare class TrailMakingPlugin implements JsPsychPlugin<Info> {
|
|
|
286
293
|
readonly type: ParameterType.HTML_STRING;
|
|
287
294
|
readonly default: any;
|
|
288
295
|
};
|
|
296
|
+
/**
|
|
297
|
+
* Duration in milliseconds to wait after the last circle is clicked before ending the trial.
|
|
298
|
+
*/
|
|
299
|
+
readonly end_delay: {
|
|
300
|
+
readonly type: ParameterType.INT;
|
|
301
|
+
readonly default: 500;
|
|
302
|
+
};
|
|
289
303
|
/**
|
|
290
304
|
* Random seed for reproducible target layouts. If null, uses random seed.
|
|
291
305
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParameterType } from 'jspsych';
|
|
2
2
|
|
|
3
|
-
var version = "0.
|
|
3
|
+
var version = "0.2.0";
|
|
4
4
|
|
|
5
5
|
const info = {
|
|
6
6
|
name: "trail-making",
|
|
@@ -116,6 +116,13 @@ const info = {
|
|
|
116
116
|
type: ParameterType.HTML_STRING,
|
|
117
117
|
default: null
|
|
118
118
|
},
|
|
119
|
+
/**
|
|
120
|
+
* Duration in milliseconds to wait after the last circle is clicked before ending the trial.
|
|
121
|
+
*/
|
|
122
|
+
end_delay: {
|
|
123
|
+
type: ParameterType.INT,
|
|
124
|
+
default: 500
|
|
125
|
+
},
|
|
119
126
|
/**
|
|
120
127
|
* Random seed for reproducible target layouts. If null, uses random seed.
|
|
121
128
|
*/
|
|
@@ -193,6 +200,9 @@ class TrailMakingPlugin {
|
|
|
193
200
|
this.drawCanvas(ctx, targets, [], trial);
|
|
194
201
|
const handleClick = (e) => {
|
|
195
202
|
if (isShowingError) return;
|
|
203
|
+
if (e instanceof TouchEvent) {
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
}
|
|
196
206
|
const rect = canvas.getBoundingClientRect();
|
|
197
207
|
let clientX, clientY;
|
|
198
208
|
if (e instanceof TouchEvent) {
|
|
@@ -229,7 +239,9 @@ class TrailMakingPlugin {
|
|
|
229
239
|
const visitedLabels = sequence.slice(0, currentTargetIndex);
|
|
230
240
|
this.drawCanvas(ctx, targets, visitedLabels, trial);
|
|
231
241
|
if (currentTargetIndex >= sequence.length) {
|
|
232
|
-
this.
|
|
242
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
243
|
+
this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
|
|
244
|
+
}, trial.end_delay);
|
|
233
245
|
}
|
|
234
246
|
} else {
|
|
235
247
|
clickEvent.correct = false;
|
|
@@ -237,7 +249,8 @@ class TrailMakingPlugin {
|
|
|
237
249
|
numErrors++;
|
|
238
250
|
isShowingError = true;
|
|
239
251
|
const visitedLabels = sequence.slice(0, currentTargetIndex);
|
|
240
|
-
|
|
252
|
+
const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
|
|
253
|
+
this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
|
|
241
254
|
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
242
255
|
isShowingError = false;
|
|
243
256
|
this.drawCanvas(ctx, targets, visitedLabels, trial);
|
|
@@ -336,7 +349,7 @@ class TrailMakingPlugin {
|
|
|
336
349
|
}
|
|
337
350
|
return null;
|
|
338
351
|
}
|
|
339
|
-
drawCanvas(ctx, targets, visitedLabels, trial,
|
|
352
|
+
drawCanvas(ctx, targets, visitedLabels, trial, errorLabel = null) {
|
|
340
353
|
const width = trial.canvas_width;
|
|
341
354
|
const height = trial.canvas_height;
|
|
342
355
|
ctx.fillStyle = "#f5f5f5";
|
|
@@ -359,7 +372,7 @@ class TrailMakingPlugin {
|
|
|
359
372
|
}
|
|
360
373
|
for (const target of targets) {
|
|
361
374
|
const isVisited = visitedLabels.includes(target.label);
|
|
362
|
-
if (
|
|
375
|
+
if (errorLabel !== null && target.label === errorLabel) {
|
|
363
376
|
ctx.fillStyle = trial.error_color;
|
|
364
377
|
} else if (isVisited) {
|
|
365
378
|
ctx.fillStyle = trial.visited_color;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych-contrib/plugin-trail-making",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
package/src/index.spec.ts
CHANGED
|
@@ -208,6 +208,7 @@ describe("trail-making plugin", () => {
|
|
|
208
208
|
canvas.dispatchEvent(clickEvent);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
jest.advanceTimersByTime(500);
|
|
211
212
|
await expectFinished();
|
|
212
213
|
|
|
213
214
|
const data = getData().values()[0];
|
|
@@ -280,6 +281,7 @@ describe("trail-making plugin", () => {
|
|
|
280
281
|
})
|
|
281
282
|
);
|
|
282
283
|
|
|
284
|
+
jest.advanceTimersByTime(500);
|
|
283
285
|
await expectFinished();
|
|
284
286
|
|
|
285
287
|
const data = getData().values()[0];
|
|
@@ -336,6 +338,7 @@ describe("trail-making plugin", () => {
|
|
|
336
338
|
jest.advanceTimersByTime(300);
|
|
337
339
|
canvas.dispatchEvent(new MouseEvent("click", { clientX: 300, clientY: 300, bubbles: true }));
|
|
338
340
|
|
|
341
|
+
jest.advanceTimersByTime(500);
|
|
339
342
|
await expectFinished();
|
|
340
343
|
|
|
341
344
|
const data = getData().values()[0];
|
|
@@ -378,6 +381,7 @@ describe("trail-making plugin", () => {
|
|
|
378
381
|
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 0, bubbles: true }));
|
|
379
382
|
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
|
|
380
383
|
|
|
384
|
+
jest.advanceTimersByTime(500);
|
|
381
385
|
await expectFinished();
|
|
382
386
|
|
|
383
387
|
const data = getData().values()[0];
|
|
@@ -414,6 +418,7 @@ describe("trail-making plugin", () => {
|
|
|
414
418
|
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
|
|
415
419
|
canvas.dispatchEvent(new MouseEvent("click", { clientX: 200, clientY: 200, bubbles: true }));
|
|
416
420
|
|
|
421
|
+
jest.advanceTimersByTime(500);
|
|
417
422
|
await expectFinished();
|
|
418
423
|
|
|
419
424
|
const data = getData().values()[0];
|
|
@@ -453,6 +458,7 @@ describe("trail-making plugin", () => {
|
|
|
453
458
|
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
|
|
454
459
|
canvas.dispatchEvent(new MouseEvent("click", { clientX: 200, clientY: 200, bubbles: true }));
|
|
455
460
|
|
|
461
|
+
jest.advanceTimersByTime(500);
|
|
456
462
|
await expectFinished();
|
|
457
463
|
|
|
458
464
|
const data = getData().values()[0];
|
|
@@ -516,6 +522,7 @@ describe("trail-making plugin", () => {
|
|
|
516
522
|
canvas.dispatchEvent(createTouchEvent(100, 100));
|
|
517
523
|
canvas.dispatchEvent(createTouchEvent(200, 200));
|
|
518
524
|
|
|
525
|
+
jest.advanceTimersByTime(500);
|
|
519
526
|
await expectFinished();
|
|
520
527
|
});
|
|
521
528
|
});
|
package/src/index.ts
CHANGED
|
@@ -116,6 +116,13 @@ const info = <const>{
|
|
|
116
116
|
type: ParameterType.HTML_STRING,
|
|
117
117
|
default: null,
|
|
118
118
|
},
|
|
119
|
+
/**
|
|
120
|
+
* Duration in milliseconds to wait after the last circle is clicked before ending the trial.
|
|
121
|
+
*/
|
|
122
|
+
end_delay: {
|
|
123
|
+
type: ParameterType.INT,
|
|
124
|
+
default: 500,
|
|
125
|
+
},
|
|
119
126
|
/**
|
|
120
127
|
* Random seed for reproducible target layouts. If null, uses random seed.
|
|
121
128
|
*/
|
|
@@ -238,6 +245,11 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
|
|
|
238
245
|
const handleClick = (e: MouseEvent | TouchEvent) => {
|
|
239
246
|
if (isShowingError) return;
|
|
240
247
|
|
|
248
|
+
// Prevent the synthetic click event that browsers fire after touchend
|
|
249
|
+
if (e instanceof TouchEvent) {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
}
|
|
252
|
+
|
|
241
253
|
const rect = canvas.getBoundingClientRect();
|
|
242
254
|
let clientX: number, clientY: number;
|
|
243
255
|
|
|
@@ -289,7 +301,9 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
|
|
|
289
301
|
|
|
290
302
|
// Check if complete
|
|
291
303
|
if (currentTargetIndex >= sequence.length) {
|
|
292
|
-
this.
|
|
304
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
305
|
+
this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
|
|
306
|
+
}, trial.end_delay);
|
|
293
307
|
}
|
|
294
308
|
} else {
|
|
295
309
|
// Error
|
|
@@ -300,7 +314,8 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
|
|
|
300
314
|
// Show error feedback
|
|
301
315
|
isShowingError = true;
|
|
302
316
|
const visitedLabels = sequence.slice(0, currentTargetIndex);
|
|
303
|
-
|
|
317
|
+
const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
|
|
318
|
+
this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
|
|
304
319
|
|
|
305
320
|
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
306
321
|
isShowingError = false;
|
|
@@ -445,7 +460,7 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
|
|
|
445
460
|
targets: Target[],
|
|
446
461
|
visitedLabels: string[],
|
|
447
462
|
trial: TrialType<Info>,
|
|
448
|
-
|
|
463
|
+
errorLabel: string | null = null
|
|
449
464
|
) {
|
|
450
465
|
const width = trial.canvas_width;
|
|
451
466
|
const height = trial.canvas_height;
|
|
@@ -478,7 +493,7 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
|
|
|
478
493
|
const isVisited = visitedLabels.includes(target.label);
|
|
479
494
|
|
|
480
495
|
// Set fill color before drawing
|
|
481
|
-
if (
|
|
496
|
+
if (errorLabel !== null && target.label === errorLabel) {
|
|
482
497
|
ctx.fillStyle = trial.error_color;
|
|
483
498
|
} else if (isVisited) {
|
|
484
499
|
ctx.fillStyle = trial.visited_color;
|