@qe-mcp/server-ena 0.1.1
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/.claude/launch.json +11 -0
- package/.gitlab-ci.yml +88 -0
- package/README.md +232 -0
- package/hooks/ena-privacy-gate.mjs +29 -0
- package/package.json +27 -0
- package/qeviz-init.js +11 -0
- package/qeviz-widget.html +50 -0
- package/qeviz.umd.js +3511 -0
- package/server.js +1234 -0
- package/skill/ena-analysis/SKILL.md +82 -0
- package/test-mcp.mjs +189 -0
- package/test-render.mjs +93 -0
package/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# .gitlab-ci.yml
|
|
2
|
+
#
|
|
3
|
+
# CI pipeline for ena-mcp-wasm.
|
|
4
|
+
#
|
|
5
|
+
# - test : smoke-test the MCP server on server.js / package.json changes
|
|
6
|
+
# - trigger-cranqe: inherited from cranqe CI template; fires on main (dev build)
|
|
7
|
+
# or a vX.Y.Z tag (release build) to publish @qe-libs/ena-mcp-wasm
|
|
8
|
+
|
|
9
|
+
include:
|
|
10
|
+
- project: 'epistemic-analytics/qe-packages/cranqe'
|
|
11
|
+
ref: main
|
|
12
|
+
file: '/ci-templates/trigger-cranqe.yml'
|
|
13
|
+
|
|
14
|
+
stages:
|
|
15
|
+
- test
|
|
16
|
+
|
|
17
|
+
# ── Smoke test ─────────────────────────────────────────────────────────────────
|
|
18
|
+
# Starts the MCP server, sends an initialize request, and confirms it responds
|
|
19
|
+
# with a valid JSON-RPC result listing the expected tools.
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
stage: test
|
|
23
|
+
image: node:20-alpine
|
|
24
|
+
rules:
|
|
25
|
+
- changes:
|
|
26
|
+
- server.js
|
|
27
|
+
- package.json
|
|
28
|
+
- package-lock.json
|
|
29
|
+
script:
|
|
30
|
+
- npm ci
|
|
31
|
+
- |
|
|
32
|
+
RESPONSE=$(echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}' \
|
|
33
|
+
| timeout 10 node server.js 2>/dev/null | head -1)
|
|
34
|
+
echo "Response: ${RESPONSE}"
|
|
35
|
+
echo "${RESPONSE}" | node -e "
|
|
36
|
+
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
37
|
+
if (!r.result?.serverInfo) { console.error('Missing serverInfo'); process.exit(1); }
|
|
38
|
+
console.log('OK: server responded as', r.result.serverInfo.name, r.result.serverInfo.version);
|
|
39
|
+
"
|
|
40
|
+
- |
|
|
41
|
+
TOOLS=$(printf '%s\n%s\n' \
|
|
42
|
+
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}' \
|
|
43
|
+
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
|
|
44
|
+
| timeout 10 node server.js 2>/dev/null | tail -1)
|
|
45
|
+
echo "Tools response: ${TOOLS}"
|
|
46
|
+
echo "${TOOLS}" | node -e "
|
|
47
|
+
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
48
|
+
const names = (r.result?.tools ?? []).map(t => t.name);
|
|
49
|
+
const expected = ['ena_profile','ena_inspect','ena_fit','ena_accumulate','ena_compare_groups','ena_plot'];
|
|
50
|
+
const missing = expected.filter(n => !names.includes(n));
|
|
51
|
+
if (missing.length) { console.error('Missing tools:', missing.join(', ')); process.exit(1); }
|
|
52
|
+
console.log('OK: tools present —', names.join(', '));
|
|
53
|
+
"
|
|
54
|
+
|
|
55
|
+
# ── cranqe deployment ──────────────────────────────────────────────────────────
|
|
56
|
+
# verify-tag-version is overridden to read from package.json (not R's DESCRIPTION).
|
|
57
|
+
# trigger-cranqe and trigger-cranqe-release are inherited from the template.
|
|
58
|
+
|
|
59
|
+
verify-tag-version:
|
|
60
|
+
script:
|
|
61
|
+
- |
|
|
62
|
+
TAG_VERSION="${CI_COMMIT_TAG#v}"
|
|
63
|
+
PKG_VERSION=$(node -e "console.log(require('./package.json').version)")
|
|
64
|
+
if [ "${TAG_VERSION}" != "${PKG_VERSION}" ]; then
|
|
65
|
+
echo "-----------------------------------------------------------"
|
|
66
|
+
echo " Tag/package.json mismatch — release blocked"
|
|
67
|
+
echo "-----------------------------------------------------------"
|
|
68
|
+
echo " Tag: ${CI_COMMIT_TAG} (version ${TAG_VERSION})"
|
|
69
|
+
echo " package.json: ${PKG_VERSION}"
|
|
70
|
+
echo ""
|
|
71
|
+
echo " Fix: update package.json version to ${TAG_VERSION}"
|
|
72
|
+
echo " then commit, push, and re-tag."
|
|
73
|
+
echo "-----------------------------------------------------------"
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
echo "-----------------------------------------------------------"
|
|
77
|
+
echo " OK: ${CI_COMMIT_TAG} matches package.json ${PKG_VERSION}"
|
|
78
|
+
echo "-----------------------------------------------------------"
|
|
79
|
+
|
|
80
|
+
trigger-cranqe:
|
|
81
|
+
rules:
|
|
82
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
83
|
+
changes:
|
|
84
|
+
- server.js
|
|
85
|
+
- package.json
|
|
86
|
+
- package-lock.json
|
|
87
|
+
- hooks/**/*
|
|
88
|
+
- README.md
|
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# @qe-mcp/server-ena
|
|
2
|
+
|
|
3
|
+
MCP server for [Epistemic Network Analysis (ENA)](https://www.epistemicnetwork.org/), powered by a WebAssembly backend. Zero Python dependency — runs entirely in Node.js.
|
|
4
|
+
|
|
5
|
+
Designed for use with [Claude Desktop](https://claude.ai/download) and [Claude Code](https://claude.ai/code).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What it does
|
|
10
|
+
|
|
11
|
+
Gives Claude a set of ENA tools that run **locally on your machine**. The WASM model computation never leaves your computer. Only results (node positions, plots, summary statistics) are returned to Claude.
|
|
12
|
+
|
|
13
|
+
| Tool | Description |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `ena_profile` | Privacy-safe column metadata — no raw data transmitted |
|
|
16
|
+
| `ena_inspect` | Triage a dataset: is ENA appropriate? Returns suggested parameters |
|
|
17
|
+
| `ena_fit` | Full ENA pipeline → model + inline PNG plot |
|
|
18
|
+
| `ena_compare_groups` | Mean-rotation group comparison → model + inline PNG plot |
|
|
19
|
+
| `ena_plot` | Any view from a cached model (all / group / unit / comparison / subtraction) |
|
|
20
|
+
| `ena_accumulate` | Accumulation step only, no rotation or projection |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- **Node.js** 18 or later
|
|
27
|
+
- **Claude Desktop** or **Claude Code**
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### Option A — npx (no install needed)
|
|
34
|
+
|
|
35
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac):
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"ena": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["@qe-mcp/server-ena"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then add the registry to your `~/.npmrc` once:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
echo "@qe-mcp:registry=https://gitlab.com/api/v4/projects/22522458/packages/npm/" >> ~/.npmrc
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Restart Claude Desktop.
|
|
55
|
+
|
|
56
|
+
### Option B — global install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
echo "@qe-mcp:registry=https://gitlab.com/api/v4/projects/22522458/packages/npm/" >> ~/.npmrc
|
|
60
|
+
npm install -g @qe-mcp/server-ena
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then in `claude_desktop_config.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"ena": {
|
|
69
|
+
"command": "server-ena"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Option C — local clone (development)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone <repo-url> server-ena
|
|
79
|
+
cd server-ena
|
|
80
|
+
npm install
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"ena": {
|
|
87
|
+
"command": "node",
|
|
88
|
+
"args": ["/absolute/path/to/server-ena/server.js"]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Verify
|
|
95
|
+
|
|
96
|
+
In Claude Desktop, open a new conversation and ask:
|
|
97
|
+
|
|
98
|
+
> "What ENA tools do you have available?"
|
|
99
|
+
|
|
100
|
+
Claude should list `ena_profile`, `ena_inspect`, `ena_fit`, `ena_compare_groups`, `ena_plot`, and `ena_accumulate`.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Skill (claude.ai / Claude Desktop / Claude Science)
|
|
105
|
+
|
|
106
|
+
The [`skill/ena-analysis/`](skill/ena-analysis/) folder contains a companion skill that teaches
|
|
107
|
+
Claude the full ENA workflow — privacy gate, triage, fitting, and plot
|
|
108
|
+
exploration — so it drives the connector correctly without per-conversation
|
|
109
|
+
prompting.
|
|
110
|
+
|
|
111
|
+
To install:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
cd skill && zip -r ena-analysis.zip ena-analysis
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then upload `ena-analysis.zip` under **Settings → Customize → Skills**
|
|
118
|
+
(claude.ai / Claude Desktop) or **Customize → Skills** (Claude Science).
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Privacy model
|
|
123
|
+
|
|
124
|
+
**The WASM computation always runs locally.** What reaches Claude depends on how you provide data:
|
|
125
|
+
|
|
126
|
+
| Method | What Claude sees |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `data_path` (file path) | Nothing from the file — only model results (node positions, unit labels, plots) |
|
|
129
|
+
| `data_csv` (inline CSV) | The full raw CSV, as a tool call argument |
|
|
130
|
+
|
|
131
|
+
For research data, always use `data_path`. Use `data_csv` only for small toy or demo datasets.
|
|
132
|
+
|
|
133
|
+
Unit labels (the values in your unit column, e.g. participant IDs) are included in model results regardless of input method. If those are sensitive, be aware they will appear in the conversation.
|
|
134
|
+
|
|
135
|
+
### Privacy gate hook
|
|
136
|
+
|
|
137
|
+
To technically enforce the `data_path` preference and block large inline CSVs, install the privacy gate hook. This intercepts tool calls before they execute and rejects any `data_csv` payload with more than 50 rows.
|
|
138
|
+
|
|
139
|
+
**Step 1 — Copy the hook script:**
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
mkdir -p ~/.claude/hooks
|
|
143
|
+
cp hooks/ena-privacy-gate.mjs ~/.claude/hooks/
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Step 2 — Add to `~/.claude/settings.json`:**
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"hooks": {
|
|
151
|
+
"PreToolUse": [
|
|
152
|
+
{
|
|
153
|
+
"matcher": "mcp__(ena-wasm|ena|enajs)__(ena_inspect|ena_fit|ena_accumulate|ena_compare_groups)",
|
|
154
|
+
"hooks": [
|
|
155
|
+
{
|
|
156
|
+
"type": "command",
|
|
157
|
+
"command": "node /Users/YOUR_USERNAME/.claude/hooks/ena-privacy-gate.mjs"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Replace `YOUR_USERNAME` with your macOS username. If you already have a `hooks` block in `settings.json`, add the new entry to the existing `PreToolUse` array.
|
|
167
|
+
|
|
168
|
+
The hook takes effect immediately — no restart needed.
|
|
169
|
+
|
|
170
|
+
**Threshold:** ≤ 50 rows of `data_csv` are allowed (toy/demo use). Larger payloads are blocked with a message instructing Claude to ask for a file path instead.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Usage
|
|
175
|
+
|
|
176
|
+
### Basic analysis
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
"Analyze /path/to/data.csv"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Claude will call `ena_inspect` first, confirm ENA is appropriate, then call `ena_fit` and return a network plot.
|
|
183
|
+
|
|
184
|
+
### Sensitive data
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
"I have sensitive data at /path/to/data.csv — what can you tell me about its structure?"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Claude will call `ena_profile` (column metadata only, no raw data transmitted), identify appropriate parameters, then run the model with `data_path`.
|
|
191
|
+
|
|
192
|
+
### Group comparison
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
"Compare First vs Second half using data at /path/to/data.csv, unit column UserName, conversation column GameHalf"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Claude will call `ena_compare_groups` and return a comparison plot. Follow up with:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
"Show me the subtraction network"
|
|
202
|
+
"Show me the First half mean network"
|
|
203
|
+
"Show me unit steven_z"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
These use `ena_plot` with the cached `model_id` — no refitting.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Output
|
|
211
|
+
|
|
212
|
+
Each fitting or plotting call returns:
|
|
213
|
+
|
|
214
|
+
- **Inline PNG** — rendered via jsdom + qeviz + resvg, displayed directly in Claude's response
|
|
215
|
+
- **`interactive_plot`** — path to a local HTML file with the interactive qeviz visualization, openable in any browser
|
|
216
|
+
|
|
217
|
+
The HTML file persists at `/tmp/ena-viz/` until the next system restart.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Testing
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
node test-mcp.mjs \
|
|
225
|
+
/path/to/data.csv \
|
|
226
|
+
UnitColumn ConversationColumn \
|
|
227
|
+
Code1 Code2 Code3 Code4
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
This drives the server over stdin/stdout (same transport Claude uses) and saves all PNG outputs to `/tmp/ena-test-N.png`.
|
|
231
|
+
|
|
232
|
+
To test group comparison, edit `test-mcp.mjs` and set `GROUP_COL`, `GROUP1`, `GROUP2` at the top of the file.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ena-privacy-gate.mjs — PreToolUse hook for ENA MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Allows data_csv only for small toy/demo datasets (≤ MAX_ROWS rows).
|
|
5
|
+
* Blocks larger payloads and instructs Claude to use data_path instead,
|
|
6
|
+
* keeping research-scale data off Anthropic's servers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const MAX_ROWS = 50;
|
|
10
|
+
|
|
11
|
+
let raw = '';
|
|
12
|
+
process.stdin.on('data', chunk => { raw += chunk; });
|
|
13
|
+
process.stdin.on('end', () => {
|
|
14
|
+
let call;
|
|
15
|
+
try { call = JSON.parse(raw); } catch { process.exit(0); }
|
|
16
|
+
|
|
17
|
+
const csv = call.tool_input?.data_csv;
|
|
18
|
+
if (!csv) process.exit(0); // data_path used — allow
|
|
19
|
+
|
|
20
|
+
const rows = csv.trim().split('\n').length - 1; // subtract header row
|
|
21
|
+
if (rows <= MAX_ROWS) process.exit(0); // toy/demo data — allow
|
|
22
|
+
|
|
23
|
+
process.stderr.write(
|
|
24
|
+
`[ENA privacy gate] Blocked: data_csv contains ${rows} rows (limit: ${MAX_ROWS}).\n` +
|
|
25
|
+
`Raw data at this scale should not travel through the conversation.\n` +
|
|
26
|
+
`Ask the user to provide the file path and use data_path instead.`
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qe-mcp/server-ena",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server for Epistemic Network Analysis — WASM backend, zero Python dependency",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"server-ena": "./server.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node server.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.4.0",
|
|
15
|
+
"@qe-libs/qeviz": "^0.1.0",
|
|
16
|
+
"@qe-libs/rena-wasm": "^0.1.0",
|
|
17
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
18
|
+
"jsdom": "^25.0.1"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"ena",
|
|
23
|
+
"epistemic-network-analysis",
|
|
24
|
+
"wasm"
|
|
25
|
+
],
|
|
26
|
+
"license": "GPL-3.0-only"
|
|
27
|
+
}
|
package/qeviz-init.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
const vizData = {"directed":false,"updated":1781562967182,"id_col":"ENA_UNIT","node_id_col":"code","x_col":"x","y_col":"y","nodes":{"data":[{"code":"Questioning","x":-0.11103679945900916,"y":-0.25939267648599906},{"code":"Hypothesis","x":0.5528730944586759,"y":-6.217776977504038},{"code":"Evidence","x":-0.9369277688750262,"y":3.5436625297313116},{"code":"Reasoning","x":-0.13826394729139724,"y":-0.4698652306368787},{"code":"Reflection","x":-0.47365076416546176,"y":0.7961638952960035},{"code":"Planning","x":3.444750013738042,"y":1.4489822276896318},{"code":"Monitoring","x":-0.7768540871390985,"y":2.59597273140536}],"types":{"code":"character","x":"numeric","y":"numeric"}},"edges":{"data":[{"ENA_UNIT":"S01","Condition":"Treatment","Questioning.Hypothesis":0.31426968052735443,"Questioning.Evidence":0.15713484026367722,"Hypothesis.Evidence":0.23570226039551584,"Questioning.Reasoning":0.31426968052735443,"Hypothesis.Reasoning":0.31426968052735443,"Evidence.Reasoning":0.23570226039551584,"Questioning.Reflection":0.15713484026367722,"Hypothesis.Reflection":0.15713484026367722,"Evidence.Reflection":0.15713484026367722,"Reasoning.Reflection":0.15713484026367722,"Questioning.Planning":0.23570226039551584,"Hypothesis.Planning":0.23570226039551584,"Evidence.Planning":0.23570226039551584,"Reasoning.Planning":0.31426968052735443,"Reflection.Planning":0.15713484026367722,"Questioning.Monitoring":0.15713484026367722,"Hypothesis.Monitoring":0.15713484026367722,"Evidence.Monitoring":0.15713484026367722,"Reasoning.Monitoring":0.23570226039551584,"Reflection.Monitoring":0.15713484026367722,"Planning.Monitoring":0.15713484026367722},{"ENA_UNIT":"S02","Condition":"Treatment","Questioning.Hypothesis":0.1889822365046136,"Questioning.Evidence":0.1889822365046136,"Hypothesis.Evidence":0.2519763153394848,"Questioning.Reasoning":0.2519763153394848,"Hypothesis.Reasoning":0.2519763153394848,"Evidence.Reasoning":0.1889822365046136,"Questioning.Reflection":0.1889822365046136,"Hypothesis.Reflection":0.1889822365046136,"Evidence.Reflection":0.1889822365046136,"Reasoning.Reflection":0.1889822365046136,"Questioning.Planning":0.2519763153394848,"Hypothesis.Planning":0.2519763153394848,"Evidence.Planning":0.2519763153394848,"Reasoning.Planning":0.1889822365046136,"Reflection.Planning":0.2519763153394848,"Questioning.Monitoring":0.1889822365046136,"Hypothesis.Monitoring":0.2519763153394848,"Evidence.Monitoring":0.1889822365046136,"Reasoning.Monitoring":0.1889822365046136,"Reflection.Monitoring":0.2519763153394848,"Planning.Monitoring":0.1889822365046136},{"ENA_UNIT":"S03","Condition":"Control","Questioning.Hypothesis":0.2279211529192759,"Questioning.Evidence":0.2279211529192759,"Hypothesis.Evidence":0.2279211529192759,"Questioning.Reasoning":0.3418817293789138,"Hypothesis.Reasoning":0.3418817293789138,"Evidence.Reasoning":0.2279211529192759,"Questioning.Reflection":0.11396057645963795,"Hypothesis.Reflection":0.2279211529192759,"Evidence.Reflection":0.2279211529192759,"Reasoning.Reflection":0.2279211529192759,"Questioning.Planning":0,"Hypothesis.Planning":0,"Evidence.Planning":0,"Reasoning.Planning":0,"Reflection.Planning":0,"Questioning.Monitoring":0.3418817293789138,"Hypothesis.Monitoring":0.2279211529192759,"Evidence.Monitoring":0.2279211529192759,"Reasoning.Monitoring":0.3418817293789138,"Reflection.Monitoring":0.2279211529192759,"Planning.Monitoring":0},{"ENA_UNIT":"S04","Condition":"Control","Questioning.Hypothesis":0.2544566789039913,"Questioning.Evidence":0.2544566789039913,"Hypothesis.Evidence":0.2544566789039913,"Questioning.Reasoning":0.33927557187198837,"Hypothesis.Reasoning":0.16963778593599418,"Evidence.Reasoning":0.2544566789039913,"Questioning.Reflection":0.2544566789039913,"Hypothesis.Reflection":0.2544566789039913,"Evidence.Reflection":0.2544566789039913,"Reasoning.Reflection":0.2544566789039913,"Questioning.Planning":0,"Hypothesis.Planning":0,"Evidence.Planning":0,"Reasoning.Planning":0,"Reflection.Planning":0,"Questioning.Monitoring":0.16963778593599418,"Hypothesis.Monitoring":0.2544566789039913,"Evidence.Monitoring":0.2544566789039913,"Reasoning.Monitoring":0.33927557187198837,"Reflection.Monitoring":0.2544566789039913,"Planning.Monitoring":0},{"ENA_UNIT":"S05","Condition":"Treatment","Questioning.Hypothesis":0.3481553119113957,"Questioning.Evidence":0.26111648393354675,"Hypothesis.Evidence":0.17407765595569785,"Questioning.Reasoning":0.26111648393354675,"Hypothesis.Reasoning":0.3481553119113957,"Evidence.Reasoning":0.26111648393354675,"Questioning.Reflection":0.17407765595569785,"Hypothesis.Reflection":0.17407765595569785,"Evidence.Reflection":0.17407765595569785,"Reasoning.Reflection":0.17407765595569785,"Questioning.Planning":0.26111648393354675,"Hypothesis.Planning":0.3481553119113957,"Evidence.Planning":0.26111648393354675,"Reasoning.Planning":0.26111648393354675,"Reflection.Planning":0.17407765595569785,"Questioning.Monitoring":0.08703882797784893,"Hypothesis.Monitoring":0.08703882797784893,"Evidence.Monitoring":0.08703882797784893,"Reasoning.Monitoring":0.08703882797784893,"Reflection.Monitoring":0.08703882797784893,"Planning.Monitoring":0.08703882797784893},{"ENA_UNIT":"S06","Condition":"Treatment","Questioning.Hypothesis":0.25098232205526344,"Questioning.Evidence":0.18823674154144757,"Hypothesis.Evidence":0.25098232205526344,"Questioning.Reasoning":0.25098232205526344,"Hypothesis.Reasoning":0.25098232205526344,"Evidence.Reasoning":0.18823674154144757,"Questioning.Reflection":0.25098232205526344,"Hypothesis.Reflection":0.18823674154144757,"Evidence.Reflection":0.18823674154144757,"Reasoning.Reflection":0.12549116102763172,"Questioning.Planning":0.25098232205526344,"Hypothesis.Planning":0.25098232205526344,"Evidence.Planning":0.25098232205526344,"Reasoning.Planning":0.18823674154144757,"Reflection.Planning":0.18823674154144757,"Questioning.Monitoring":0.18823674154144757,"Hypothesis.Monitoring":0.18823674154144757,"Evidence.Monitoring":0.18823674154144757,"Reasoning.Monitoring":0.25098232205526344,"Reflection.Monitoring":0.18823674154144757,"Planning.Monitoring":0.25098232205526344},{"ENA_UNIT":"S07","Condition":"Control","Questioning.Hypothesis":0.24433888871261045,"Questioning.Evidence":0.24433888871261045,"Hypothesis.Evidence":0.24433888871261045,"Questioning.Reasoning":0.36650833306891567,"Hypothesis.Reasoning":0.12216944435630522,"Evidence.Reasoning":0.24433888871261045,"Questioning.Reflection":0.36650833306891567,"Hypothesis.Reflection":0.24433888871261045,"Evidence.Reflection":0.24433888871261045,"Reasoning.Reflection":0.24433888871261045,"Questioning.Planning":0,"Hypothesis.Planning":0,"Evidence.Planning":0,"Reasoning.Planning":0,"Reflection.Planning":0,"Questioning.Monitoring":0.24433888871261045,"Hypothesis.Monitoring":0.24433888871261045,"Evidence.Monitoring":0.24433888871261045,"Reasoning.Monitoring":0.24433888871261045,"Reflection.Monitoring":0.24433888871261045,"Planning.Monitoring":0},{"ENA_UNIT":"S08","Condition":"Control","Questioning.Hypothesis":0.33927557187198837,"Questioning.Evidence":0.2544566789039913,"Hypothesis.Evidence":0.16963778593599418,"Questioning.Reasoning":0.16963778593599418,"Hypothesis.Reasoning":0.33927557187198837,"Evidence.Reasoning":0.2544566789039913,"Questioning.Reflection":0.2544566789039913,"Hypothesis.Reflection":0.2544566789039913,"Evidence.Reflection":0.2544566789039913,"Reasoning.Reflection":0.2544566789039913,"Questioning.Planning":0,"Hypothesis.Planning":0,"Evidence.Planning":0,"Reasoning.Planning":0,"Reflection.Planning":0,"Questioning.Monitoring":0.2544566789039913,"Hypothesis.Monitoring":0.2544566789039913,"Evidence.Monitoring":0.2544566789039913,"Reasoning.Monitoring":0.2544566789039913,"Reflection.Monitoring":0.2544566789039913,"Planning.Monitoring":0}],"types":{"ENA_UNIT":"character","Condition":"character","Questioning.Hypothesis":"numeric","Questioning.Evidence":"numeric","Hypothesis.Evidence":"numeric","Questioning.Reasoning":"numeric","Hypothesis.Reasoning":"numeric","Evidence.Reasoning":"numeric","Questioning.Reflection":"numeric","Hypothesis.Reflection":"numeric","Evidence.Reflection":"numeric","Reasoning.Reflection":"numeric","Questioning.Planning":"numeric","Hypothesis.Planning":"numeric","Evidence.Planning":"numeric","Reasoning.Planning":"numeric","Reflection.Planning":"numeric","Questioning.Monitoring":"numeric","Hypothesis.Monitoring":"numeric","Evidence.Monitoring":"numeric","Reasoning.Monitoring":"numeric","Reflection.Monitoring":"numeric","Planning.Monitoring":"numeric"}},"points":{"data":[{"ENA_UNIT":"S01","x":0.3037841360667453,"y":-0.04117562721391022,"Condition":"Treatment"},{"ENA_UNIT":"S02","x":0.27230258220332104,"y":0.13321009166541833,"Condition":"Treatment"},{"ENA_UNIT":"S03","x":-0.30139827112328216,"y":-0.05483859029708646,"Condition":"Control"},{"ENA_UNIT":"S04","x":-0.3216504093955554,"y":0.06212687811064836,"Condition":"Control"},{"ENA_UNIT":"S05","x":0.391013361304719,"y":-0.21096963035525595,"Condition":"Treatment"},{"ENA_UNIT":"S06","x":0.2773550326890605,"y":0.11893516590374067,"Condition":"Treatment"},{"ENA_UNIT":"S07","x":-0.32398918147829914,"y":0.10289864233005344,"Condition":"Control"},{"ENA_UNIT":"S08","x":-0.2974172502667091,"y":-0.11018693014360789,"Condition":"Control"}],"types":{"ENA_UNIT":"character","x":"numeric","y":"numeric","Condition":"character"}},"group_col":"Condition"};
|
|
3
|
+
document.getElementById('vis-cmp').setModelData(vizData);
|
|
4
|
+
document.getElementById('vis-treat').setModelData(vizData);
|
|
5
|
+
document.getElementById('vis-ctrl').setModelData(vizData);
|
|
6
|
+
function sw(id, btn) {
|
|
7
|
+
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('on'));
|
|
8
|
+
document.querySelectorAll('.panel').forEach(p=>p.classList.remove('on'));
|
|
9
|
+
btn.classList.add('on');
|
|
10
|
+
document.getElementById('p-'+id).classList.add('on');
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
3
|
+
body{font-family:system-ui,sans-serif;background:transparent}
|
|
4
|
+
.tabs{display:flex;gap:4px;padding:10px 10px 0}
|
|
5
|
+
.tab{padding:5px 14px;border-radius:6px 6px 0 0;font-size:12px;font-weight:500;cursor:pointer;
|
|
6
|
+
background:#e8e8ec;color:#555;border:none;border-bottom:2px solid transparent}
|
|
7
|
+
.tab.on{background:#fff;color:#1a1a2e;border-bottom:2px solid #fff;border:1px solid #ddd;border-bottom:2px solid #fff}
|
|
8
|
+
.panels{background:#fff;border:1px solid #ddd;border-radius:0 6px 6px 6px;margin:0 10px 10px;overflow:hidden}
|
|
9
|
+
.panel{display:none;height:520px}
|
|
10
|
+
.panel.on{display:block}
|
|
11
|
+
qe-visual{display:block;width:100%;height:100%}
|
|
12
|
+
qe-graph{display:block;width:100%;height:100%}
|
|
13
|
+
</style>
|
|
14
|
+
|
|
15
|
+
<div class="tabs">
|
|
16
|
+
<button class="tab on" onclick="sw('cmp',this)">Comparison</button>
|
|
17
|
+
<button class="tab" onclick="sw('treat',this)">Treatment mean</button>
|
|
18
|
+
<button class="tab" onclick="sw('ctrl',this)">Control mean</button>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="panels">
|
|
21
|
+
<div class="panel on" id="p-cmp">
|
|
22
|
+
<qe-visual id="vis-cmp">
|
|
23
|
+
<qe-graph width="100%" height="520" label-nodes="on">
|
|
24
|
+
<qe-edges group="Treatment" also="Control"></qe-edges>
|
|
25
|
+
<qe-points label="auto"></qe-points>
|
|
26
|
+
<qe-means confidence></qe-means>
|
|
27
|
+
</qe-graph>
|
|
28
|
+
</qe-visual>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="panel" id="p-treat">
|
|
31
|
+
<qe-visual id="vis-treat">
|
|
32
|
+
<qe-graph width="100%" height="520" label-nodes="on">
|
|
33
|
+
<qe-edges group="Treatment"></qe-edges>
|
|
34
|
+
<qe-points label="auto"></qe-points>
|
|
35
|
+
<qe-means confidence></qe-means>
|
|
36
|
+
</qe-graph>
|
|
37
|
+
</qe-visual>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="panel" id="p-ctrl">
|
|
40
|
+
<qe-visual id="vis-ctrl">
|
|
41
|
+
<qe-graph width="100%" height="520" label-nodes="on">
|
|
42
|
+
<qe-edges group="Control"></qe-edges>
|
|
43
|
+
<qe-points label="auto"></qe-points>
|
|
44
|
+
<qe-means confidence></qe-means>
|
|
45
|
+
</qe-graph>
|
|
46
|
+
</qe-visual>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<script src="qeviz.umd.js"></script>
|
|
50
|
+
<script src="qeviz-init.js"></script>
|