-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFaktoranalyse.html
More file actions
870 lines (816 loc) · 57 KB
/
Copy pathFaktoranalyse.html
File metadata and controls
870 lines (816 loc) · 57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Faktoranalyse | MethodsLab</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,400&family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,600;1,9..144,300;1,9..144,400&display=swap');
:root{
--bg:#f0ead8;--paper:#f8f3e8;--ink:#18130e;--ink2:#5a4f42;--ink3:#8a7f72;
--a1:#c94a2a;--a2:#2a6e8a;--a3:#5a8a2a;--a4:#7a3a8a;--a5:#b85c00;--a6:#0e7c7b;
--grid:#ddd5c5;--panel:#e8e0ce;--border:#c8bfaa;--accent:var(--a4);--topbar-h:54px;
}
[data-theme="dark"]{
--bg:#0c0f14;--paper:#111520;--ink:#ece6d8;--ink2:#8a8070;--ink3:#857c68;
--a1:#e8614a;--a2:#4ab0d8;--a3:#80cb52;--a4:#c87ad8;--a5:#e07830;--a6:#2bc7c4;
--grid:#1e242e;--panel:#171d28;--border:#2a3040;
}
*{box-sizing:border-box;margin:0;padding:0}
html{font-size:18px}
body{background:var(--bg);color:var(--ink);font-family:'Fraunces',Georgia,serif;font-size:1rem;min-height:100vh}
#topbar{position:sticky;top:0;z-index:300;background:var(--paper);border-bottom:1px solid var(--grid);
padding:.6rem 1.2rem;display:flex;align-items:center;gap:.8rem;height:var(--topbar-h)}
#topbar h1{font-size:1.05rem;font-weight:300;letter-spacing:-.01em;flex:1}
#topbar h1 em{color:var(--accent);font-style:italic}
.tbtn{font-family:'DM Mono',monospace;font-size:.72rem;padding:.28rem .7rem;border:1.5px solid var(--border);
background:transparent;cursor:pointer;color:var(--ink2);transition:all .15s;letter-spacing:.04em}
.tbtn:hover{background:var(--ink);color:var(--bg)}
.tbtn.help{background:var(--a1);border-color:var(--a1);color:#fff}
.tbtn.help:hover{filter:brightness(1.12)}
#app{display:grid;grid-template-columns:300px 1fr;height:calc(100vh - var(--topbar-h));overflow:hidden}
#sidebar{overflow-y:scroll;height:100%;background:var(--paper);border-right:1px solid var(--grid);padding:1rem .9rem}
#sidebar::-webkit-scrollbar{width:5px}
#sidebar::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
#main{overflow-y:scroll;height:100%;padding:1.1rem 1.4rem 3rem}
.scard{background:var(--panel);border:1px solid var(--grid);padding:.75rem .85rem;margin-bottom:.7rem}
.scard-title{font-family:'DM Mono',monospace;font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;
color:var(--accent);margin-bottom:.6rem;padding-bottom:.3rem;border-bottom:1px solid var(--grid)}
.slider-row{margin-bottom:.55rem}
.slider-lbl{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:.18rem}
.slider-name{font-family:'DM Mono',monospace;font-size:.79rem;color:var(--ink)}
.slider-val{font-family:'DM Mono',monospace;font-size:.82rem;color:var(--accent);font-weight:500}
input[type=range]{width:100%;appearance:none;height:3px;background:var(--grid);border-radius:2px;outline:none;cursor:pointer}
input[type=range]::-webkit-slider-thumb{appearance:none;width:11px;height:11px;background:var(--ink);border-radius:50%;cursor:pointer}
input[type=range]::-webkit-slider-thumb:hover{background:var(--accent)}
.rot-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:.3rem;margin-top:.3rem}
.rbtn{font-family:'DM Mono',monospace;font-size:.7rem;padding:.3rem .1rem;border:1.5px solid var(--border);
background:transparent;cursor:pointer;color:var(--ink2);transition:all .15s;text-align:center}
.rbtn:hover{border-color:var(--accent);color:var(--accent)}
.rbtn.act{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--accent);font-weight:700}
/* Scenario buttons */
.scen-grid{display:grid;grid-template-columns:1fr;gap:.28rem;margin-top:.3rem}
.scen-btn{font-family:'DM Mono',monospace;font-size:.72rem;padding:.35rem .52rem;border:1.5px solid var(--border);
background:transparent;cursor:pointer;color:var(--ink2);transition:all .15s;text-align:left;line-height:1.42;width:100%}
.scen-btn:hover{border-color:var(--a6);color:var(--a6)}
.scen-btn.act{border-color:var(--a6);background:color-mix(in srgb,var(--a6) 14%,transparent);color:var(--a6);font-weight:700}
.scen-sub{font-size:.68rem;color:var(--ink3);display:block}
.scen-btn.act .scen-sub{color:color-mix(in srgb,var(--a6) 70%,var(--ink3))}
.erklaer{background:var(--panel);border-left:3px solid var(--accent);
padding:.7rem .9rem;margin-bottom:.85rem;font-family:'DM Mono',monospace;
font-size:.77rem;color:var(--ink2);line-height:1.9}
.erklaer strong{color:var(--ink)}
.erklaer em{color:var(--accent);font-style:normal}
.story-box{background:var(--panel);border:1px solid var(--grid);border-left:3px solid var(--a2);
padding:.75rem .9rem;margin-bottom:.85rem;line-height:1.6}
.story-lbl{font-family:'DM Mono',monospace;font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;
color:var(--a2);margin-bottom:.45rem}
.story-box strong{color:var(--ink)} .story-box em{font-style:italic;color:var(--ink)}
.xlink{display:inline-block;margin-top:.5rem;font-family:'DM Mono',monospace;font-size:.73rem;
padding:.3rem .7rem;border:1.5px solid var(--a4);color:var(--a4);text-decoration:none;transition:all .15s}
.xlink:hover{background:var(--a4);color:var(--bg)}
.proc-list{margin-bottom:1rem}
.proc-step{margin-bottom:.7rem;font-size:.87rem;line-height:1.75;color:var(--ink2)}
.proc-step strong{color:var(--ink)}
.proc-step .circ{margin-right:.4rem;font-size:1.7em}
.proc-jump{font-family:'DM Mono',monospace;font-size:.72rem;color:var(--a2);text-decoration:none;
border-bottom:1px dotted var(--a2);white-space:nowrap}
.proc-jump:hover{color:var(--accent);border-bottom-color:var(--accent)}
.stat-row{display:flex;justify-content:space-between;align-items:center;
padding:.25rem 0;border-bottom:1px solid var(--grid);font-family:'DM Mono',monospace;font-size:.75rem}
.stat-row:last-child{border-bottom:none}
.stat-label{color:var(--ink3)}
.stat-val{color:var(--accent);font-weight:500}
.plot-grid{display:grid;grid-template-columns:1fr 1fr;gap:.9rem;margin-bottom:.9rem}
.plot-panel{background:var(--paper);border:1px solid var(--grid);margin-bottom:.9rem}
.plot-panel:last-child{margin-bottom:0}
.plot-grid .plot-panel{margin-bottom:0}
.plot-title{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;
padding:.55rem .8rem .35rem;border-bottom:1px solid var(--grid)}
.pt-blue{color:var(--a2)} .pt-purple{color:var(--a4)} .pt-teal{color:var(--a6)} .pt-green{color:var(--a3)}
canvas{display:block;width:100%}
.plot-note{font-family:'DM Mono',monospace;font-size:.76rem;color:var(--ink2);line-height:1.75;
padding:.65rem .9rem;border-top:1px solid var(--grid)}
.plot-note strong{color:var(--ink)}
/* Reliability panel */
.reli-panel{background:var(--paper);border:1px solid var(--grid);margin-bottom:.9rem}
.reli-head{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;
color:var(--ink2);padding:.55rem .8rem .35rem;border-bottom:1px solid var(--grid)}
.reli-inner{padding:.85rem 1.2rem .9rem}
.reli-row{display:grid;grid-template-columns:108px 1fr 64px;align-items:center;gap:.9rem;margin-bottom:.75rem}
.reli-row:last-of-type{margin-bottom:0}
/* two-line label: large Greek symbol + small description */
.reli-lbl{display:flex;flex-direction:column;gap:.1rem}
.reli-sym{font-family:'Fraunces',Georgia,serif;font-style:italic;font-size:1.2rem;line-height:1.05}
.reli-sym sub{font-style:normal;font-size:.55em;vertical-align:-.1em}
.reli-lbl-name{font-family:'DM Mono',monospace;font-size:.64rem;color:var(--ink3);letter-spacing:.025em;line-height:1.2}
.reli-track{background:var(--grid);height:15px;border-radius:2px;overflow:hidden}
.reli-bar{height:100%;border-radius:2px;transition:width .38s ease}
.reli-val{font-family:'DM Mono',monospace;font-size:.87rem;font-weight:700;text-align:right}
.reli-note{font-family:'DM Mono',monospace;font-size:.75rem;color:var(--ink2);
margin-top:.65rem;line-height:1.68;padding-top:.5rem;border-top:1px solid var(--grid)}
.reli-note strong{color:var(--ink)}
.reli-note em{color:var(--ink);font-style:normal}
/* Greek letter helper — use for λ φ α ω in monospace contexts */
.gk{font-family:'Fraunces',Georgia,serif;font-style:italic;text-transform:none}
.circ{font-size:2em;font-weight:600;vertical-align:-.15em;line-height:1;display:inline-block}
/* Table */
.ltbl{width:100%;border-collapse:collapse;font-family:'DM Mono',monospace;font-size:.77rem;display:block;padding:.5rem .7rem}
.ltbl th{font-size:.7rem;letter-spacing:.06em;padding:.3rem .5rem;border-bottom:2px solid var(--grid);text-align:right;font-weight:600}
.ltbl th:first-child{text-align:left;color:var(--ink3)}
.ltbl td{padding:.28rem .5rem;border-bottom:1px solid var(--grid);text-align:right;color:var(--ink2)}
.ltbl td:first-child{text-align:left;color:var(--ink);font-family:'Fraunces',serif;font-size:.8rem}
.ltbl tr:last-child td{border-bottom:none}
.hi2{color:var(--a2)!important;font-weight:700} .hi3{color:var(--a3)!important;font-weight:700}
.hi5{color:var(--a5)!important;font-weight:700} .lo{color:var(--ink3)!important}
/* Hat/bar diacritics */
.gl-hat,.gl-bar{position:relative;display:inline-block;line-height:1}
.gl-hat::before{content:'^';position:absolute;top:-0.15em;left:0;right:0;text-align:center;font-size:.75em;line-height:1;pointer-events:none}
.gl-bar::before{content:'\2212';position:absolute;top:-0.1em;left:0;right:0;text-align:center;font-size:.95em;line-height:1;pointer-events:none}
/* Learn cards */
.sec-h{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;
color:var(--ink3);margin:1.3rem 0 .6rem;padding-bottom:.3rem;border-bottom:1px solid var(--grid)}
.lcard-grid{display:grid;grid-template-columns:1fr 1fr;gap:.7rem}
.lcard{background:var(--paper);border:1.5px solid var(--border);padding:.9rem 1rem}
.lcard-h{font-family:'DM Mono',monospace;font-size:.72rem;color:var(--accent);
letter-spacing:.07em;text-transform:uppercase;margin-bottom:.4rem;font-weight:500}
.lcard-body{font-size:.82rem;line-height:1.75;color:var(--ink2)}
.lcard-body strong{color:var(--ink)}
/* Help */
.help-overlay{display:none;position:fixed;inset:0;z-index:500;background:rgba(0,0,0,.82);overflow-y:auto;padding:2rem 1rem}
.help-overlay.open{display:block}
.help-panel{max-width:740px;margin:0 auto;background:var(--paper);border:1px solid var(--border);padding:2rem}
.help-h{font-family:'DM Mono',monospace;font-size:.78rem;letter-spacing:.1em;text-transform:uppercase;
color:var(--a1);margin-bottom:.7rem;padding-bottom:.5rem;border-bottom:1px solid var(--grid)}
.help-sh{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;
color:var(--accent);margin:1rem 0 .35rem;padding-bottom:.25rem;border-bottom:1px solid var(--grid)}
.help-p{font-family:'DM Mono',monospace;font-size:.79rem;color:var(--ink2);line-height:1.82;margin-bottom:.5rem}
.help-p strong{color:var(--ink)}
.help-ul{font-family:'DM Mono',monospace;font-size:.79rem;color:var(--ink2);line-height:1.82;padding-left:1.1rem;margin-bottom:.5rem}
.help-formula{display:block;background:var(--bg);border:1px solid var(--grid);padding:.35rem .75rem;
font-family:'DM Mono',monospace;font-size:.78rem;color:var(--a6);margin:.3rem 0 .45rem}
.help-ex{background:var(--panel);border-left:3px solid var(--accent);padding:.6rem .9rem;margin:.5rem 0;
font-family:'DM Mono',monospace;font-size:.78rem;color:var(--ink2);line-height:1.85}
.help-ex strong{color:var(--ink)}
.close-btn{float:right;font-family:'DM Mono',monospace;font-size:.72rem;padding:.28rem .7rem;
border:1.5px solid var(--border);background:transparent;cursor:pointer;color:var(--ink2)}
.close-btn:hover{background:var(--ink);color:var(--bg)}
</style>
</head>
<body>
<div id="topbar">
<h1>Explorative Faktoranalyse — <em>EFA / PAF</em></h1>
<button class="tbtn help" onclick="openHelp()">❓ Hilfe</button>
<button class="tbtn" id="theme-btn" onclick="toggleTheme()">☀ Light</button>
</div>
<div id="app">
<div id="sidebar">
<div class="scard">
<div class="scard-title">Faktorstruktur</div>
<div class="slider-row">
<div class="slider-lbl">
<span class="slider-name">Primäre Ladung <span class="gk">λ</span></span>
<span class="slider-val" id="v-lam" style="color:var(--a4)">0.70</span>
</div>
<input type="range" id="sl-lam" min="30" max="90" value="70" oninput="update()">
</div>
<div class="slider-row">
<div class="slider-lbl">
<span class="slider-name">Kreuzladung <span class="gk">λ</span><sub>x</sub></span>
<span class="slider-val" id="v-cross" style="color:var(--a5)">0.10</span>
</div>
<input type="range" id="sl-cross" min="0" max="50" value="10" oninput="update()">
</div>
<div class="slider-row">
<div class="slider-lbl">
<span class="slider-name">Faktorkorrelation <span class="gk">φ</span></span>
<span class="slider-val" id="v-phi" style="color:var(--a6)">0.00</span>
</div>
<input type="range" id="sl-phi" min="0" max="70" value="0" oninput="update()">
</div>
</div>
<div class="scard">
<div class="scard-title" style="color:var(--a6)">Szenarien</div>
<div class="scen-grid">
<button class="scen-btn act" data-lam="70" data-cross="10" data-phi="0"
onclick="setScen(this)">
<span><span class="circ">①</span> Orthogonal — <span class="gk">φ</span> = 0</span>
<span class="scen-sub">Unabhängige Faktoren — kein g — <span class="gk">ω</span><sub>h</sub> = 0</span>
</button>
<button class="scen-btn" data-lam="70" data-cross="10" data-phi="40"
onclick="setScen(this)">
<span><span class="circ">②</span> Oblique — <span class="gk">φ</span> = .40</span>
<span class="scen-sub">Moderate Faktorkorrelation — <span class="gk">ω</span><sub>h</sub> ≈ .51</span>
</button>
<button class="scen-btn" data-lam="70" data-cross="10" data-phi="70"
onclick="setScen(this)">
<span><span class="circ">③</span> Stark oblique — <span class="gk">φ</span> = .70</span>
<span class="scen-sub">Nahezu unidimensional — <span class="gk">ω</span><sub>h</sub> ≈ .75</span>
</button>
</div>
</div>
<div class="scard">
<div class="scard-title">Rotation</div>
<div class="rot-grid">
<button class="rbtn act" data-rot="none" onclick="setRot(this)">Unrotiert</button>
<button class="rbtn" data-rot="varimax" onclick="setRot(this)">Varimax</button>
<button class="rbtn" data-rot="oblique" onclick="setRot(this)">Oblique</button>
</div>
<div id="rot-hint" style="font-family:'DM Mono',monospace;font-size:.72rem;color:var(--ink3);margin-top:.55rem;line-height:1.6"></div>
</div>
<div class="scard">
<div class="scard-title">Kennwerte</div>
<div id="stats"></div>
</div>
<div class="erklaer">
<strong>Beispiel:</strong> 300 Personen bearbeiten 8 kognitive Tests. <em>V1–V4</em> = Verbale Fähigkeiten, <em>S1–S4</em> = Räumliches Denken. Die Szenarien variieren <strong><span class="gk">φ</span> (Faktorkorrelation)</strong>:<br>
<em>① <span class="gk">φ</span>=0</em>: unabhängige Faktoren, kein g → <span class="gk">ω</span><sub>h</sub> = 0<br>
<em>② <span class="gk">φ</span>=.40</em>: moderate Faktorkorrelation → <span class="gk">ω</span><sub>h</sub> ≈ .51<br>
<em>③ <span class="gk">φ</span>=.70</em>: stark oblique, fast unidimensional → <span class="gk">ω</span><sub>h</sub> ≈ .75<br>
→ <em><span class="gk">ω</span><sub>h</sub> wächst mit <span class="gk">φ</span> — direktes Maß für Unidimensionalität.</em>
</div>
</div><!-- /sidebar -->
<div id="main">
<div class="story-box">
<div class="story-lbl">📋 Beispiel — Kognitionstest mit 8 Untertests</div>
300 Personen bearbeiten einen Kognitionstest mit <strong>8 Untertests</strong>: <em>V1–V4</em> prüfen verbale Fähigkeiten, <em>S1–S4</em> räumliches Denken. Vorab ist unklar, wie viele latente Faktoren dahinterstecken und welche Items zu welchem Faktor gehören — genau das soll die <strong>explorative Faktorenanalyse (EFA)</strong> aufdecken. Die Regler links stellen das <strong>wahre</strong> Messmodell ein (in der Praxis unbekannt): Ladung <span class="gk">λ</span>, Kreuzladung <span class="gk">λ</span><sub>x</sub>, Faktorkorrelation <span class="gk">φ</span> — so lässt sich direkt beobachten, wie sich Änderungen am Modell auf Scree-Plot, Ladungen und die Reliabilität des Gesamtscores auswirken.
<div style="margin-top:.6rem">
<a class="xlink" href="KTT_Grundlagen.html" target="_blank">← KTT-Grundlagen: ein einzelner Testwert</a>
<a class="xlink" href="Messmodelle.html" target="_blank" style="margin-left:.4rem">Welches Messmodell steckt in den Ladungen? →</a>
<a class="xlink" href="Konfirmatorische_Faktoranalyse.html" target="_blank" style="margin-left:.4rem">Struktur schon aus Theorie bekannt? → Konfirmatorische Faktoranalyse</a>
<a class="xlink" href="Messinvarianz.html" target="_blank" style="margin-left:.4rem">Miss dieselbe Struktur in zwei Gruppen? → Messinvarianz</a>
</div>
</div>
<div class="sec-h" style="margin-top:0">Vorgehen — von den Items zu den Faktoren</div>
<div class="proc-list">
<div class="proc-step"><span class="circ">①</span><strong>Items & Messmodell.</strong> 8 Items (V1–V4, S1–S4), N = 300. Was hier per Regler vorgegeben wird, müsste man in der Praxis aus echten Item-Antworten schätzen.</div>
<div class="proc-step"><span class="circ">②</span><strong>Korrelationsmatrix R.</strong> Aus den Item-Antworten (bzw. hier: aus dem Messmodell) ergibt sich, wie stark je zwei Items zusammenhängen. <a class="proc-jump" href="#cv-heat">→ reduzierte Matrix R<sub>r</sub> ansehen</a></div>
<div class="proc-step"><span class="circ">③</span><strong>Faktorextraktion — Eigenwerte & Scree-Plot.</strong> Wie viele Faktoren erklären die gemeinsame Varianz? Das Kaiser-Kriterium (Eigenwert > 1) ist gebräuchlich, aber recht freizügig — robuster ist die Parallelanalyse (Vergleich mit Eigenwerten zufälliger, unkorrelierter Daten gleicher Größe). <a class="proc-jump" href="#cv-scree">→ Scree-Plot ansehen</a></div>
<div class="proc-step"><span class="circ">④</span><strong>Rotation & Ladungsmatrix.</strong> Die unrotierte Lösung wird rotiert (Varimax/Oblique), um eine interpretierbare Einfachstruktur zu erhalten — welches Item lädt auf welchen Faktor? Tabelle und Biplot zeigen dieselbe Information zweimal, einmal als Zahlen und einmal räumlich. <a class="proc-jump" href="#tbl-wrap">→ Ladungstabelle</a> · <a class="proc-jump" href="#cv-biplot">→ Biplot</a></div>
<div class="proc-step"><span class="circ">⑤</span><strong>Finale Kommunalität h² & Reliabilität.</strong> Die Ladungstabelle zeigt bereits, wie viel Varianz jedes Items durch die Faktoren gemeinsam erklärt wird. Was folgt daraus für die Reliabilität (α, ω) des Gesamtscores? <a class="proc-jump" href="#reli-panel">→ Reliabilität ansehen</a></div>
</div>
<div class="plot-panel">
<div class="plot-title pt-teal">Korrelationsmatrix — reduziert R<sub>r</sub> (finale h² auf der Diagonale)</div>
<canvas id="cv-heat" height="200" style="height:200px"></canvas>
<div class="plot-note">Jede Zelle ist die (vom Messmodell implizierte) Korrelation zwischen zwei Items — teal = positiv, orange = negativ. Die <strong>grau hinterlegte Diagonale</strong> ist kein Korrelationswert, sondern h². In der Praxis kennt man h² vorab nicht: PAF startet mit einer <strong>initialen</strong> Schätzung — meist der quadrierten multiplen Korrelation SMC, also dem R² aus der <em>gemeinsamen</em> Regression jedes Items auf <em>alle</em> p−1 übrigen Items (nicht auf ein einzelnes anderes Item) — und verbessert das iterativ, bis h² konvergiert. Dieses Tool überspringt die Schätzung komplett: Da das Populationsmodell (Λ, Φ) über die Regler bekannt ist, steht direkt die modellimplizierte ("wahre") Kommunalität auf der Diagonale.</div>
</div>
<div class="plot-panel">
<div class="plot-title pt-blue">Scree-Plot — Eigenwerte (aus R)</div>
<canvas id="cv-scree" height="320" style="height:320px"></canvas>
<div class="plot-note"><strong>Kaiser-Kriterium</strong> (gestrichelte Linie bei Eigenwert = 1): Faktoren mit Eigenwert > 1 behalten. Gebräuchlich, aber notorisch <strong>zu freizügig</strong> — es hängt kaum von der Itemanzahl oder der Stichprobengröße ab und überschätzt oft die Faktorenzahl. Robuster ist die <strong>Parallelanalyse</strong> (Horn, 1965): Man simuliert viele Datensätze aus unkorrelierten Zufallsvariablen derselben Größe (n Personen, p Items) und behält nur Faktoren, deren Eigenwert über dem 95. Perzentil der simulierten Eigenwerte liegt. Das entfernt Faktoren, die im echten Scree-Plot nur knapp über 1 liegen und tatsächlich Stichprobenrauschen sind.</div>
</div>
<div class="plot-panel">
<div class="plot-title pt-green">Faktorladungstabelle & finale Kommunalitäten (PAF)</div>
<div id="tbl-wrap"></div>
</div>
<div class="plot-panel">
<div class="plot-title pt-purple" style="display:flex;align-items:center;justify-content:space-between;gap:.6rem;flex-wrap:wrap">
<span id="biplot-title-text">Faktorladungs-Biplot (F1 vs. F2)</span>
<button class="rbtn" id="btn-geom" onclick="toggleGeom()" style="padding:.3rem .65rem;flex:none">📐 Winkel & Lot anzeigen</button>
</div>
<canvas id="cv-biplot" height="460" style="height:460px"></canvas>
<div class="plot-note" id="geom-note" style="display:none"></div>
</div>
<div class="plot-panel">
<div class="plot-title pt-green">Wie liest man Tabelle & Biplot?</div>
<div class="plot-note">Beide zeigen <strong>dieselbe Information</strong> zweimal — einmal als Zahlen, einmal räumlich. In der <strong>Tabelle</strong> stehen für jedes der 8 Items die Ladungen auf F1 und F2 sowie h² (Varianzanteil, der durch beide Faktoren gemeinsam erklärt wird) und u² = 1−h² (Rest). SSL (Summe der quadrierten Ladungen) und % Var. in der letzten Zeile zeigen, wie viel Gesamtvarianz jeder Faktor über alle 8 Items hinweg erklärt.
<br><br>Im <strong>Biplot</strong> ist jeder Punkt ein Item, seine Koordinaten sind exakt dieselben Ladungen (F1 = x, F2 = y) — das sind dieselben Zahlen wie in der Tabelle, nur als Position statt als Zahl. Nahe der F1-Achse = lädt fast nur auf F1; nahe der F2-Achse = fast nur auf F2; nahe dem <strong>Ursprung</strong> = niedrige Ladungen auf beide Faktoren (schlecht erklärtes Item, geringe Kommunalität). Der Abstand vom Ursprung wächst mit h². Nach <strong>Varimax</strong>-Rotation clustern gut trennbare Items nahe den Achsen (Einfachstruktur); Items zwischen den Achsen haben nennenswerte Kreuzladungen auf beide Faktoren. Bei <strong>Oblique</strong>-Rotation stehen die Faktorachsen im Winkel arccos(φ) zueinander statt senkrecht — die eingezeichneten Achsen selbst zeigen dann die Faktorkorrelation.
<br><br>Gezeigt werden nur <strong>V1 und S1</strong> als Vertreter ihrer Gruppe — die übrigen Items liegen (in diesem vereinfachten Modell) exakt auf denselben Positionen.</div>
</div>
<!-- Reliability add-on -->
<div class="reli-panel" id="reli-panel">
<div class="reli-head">Reliabilität des Gesamtscores — aus der EFA-Struktur abgeleitet</div>
<div class="reli-inner">
<div class="reli-row">
<div class="reli-lbl">
<div class="reli-sym" style="color:var(--a6)">α</div>
<div class="reli-lbl-name">Cronbach Alpha</div>
</div>
<div class="reli-track"><div class="reli-bar" id="bar-alpha" style="background:var(--a6);width:0%"></div></div>
<div class="reli-val" id="val-alpha" style="color:var(--a6)">—</div>
</div>
<div class="reli-row">
<div class="reli-lbl">
<div class="reli-sym" style="color:var(--a3)">ω<sub>t</sub></div>
<div class="reli-lbl-name">Omega total (McDonald)</div>
</div>
<div class="reli-track"><div class="reli-bar" id="bar-wt" style="background:var(--a3);width:0%"></div></div>
<div class="reli-val" id="val-wt" style="color:var(--a3)">—</div>
</div>
<div class="reli-row">
<div class="reli-lbl">
<div class="reli-sym" style="color:var(--a2)">ω<sub>h</sub></div>
<div class="reli-lbl-name">Omega hierarch. (McDonald)</div>
</div>
<div class="reli-track"><div class="reli-bar" id="bar-wh" style="background:var(--a2);width:0%"></div></div>
<div class="reli-val" id="val-wh" style="color:var(--a2)">—</div>
</div>
<div class="reli-note" id="reli-note"></div>
<div class="reli-note" style="margin-top:.4rem;border-top:1px solid var(--grid);padding-top:.5rem">
Hier ist die Reliabilität ein <em>Nebenprodukt</em> der Faktorstruktur (ω<sub>h</sub> wächst mit der
Faktorkorrelation φ). Die α-vs-ω-Frage selbst — wann α unterschätzt bzw. Eindimensionalität vortäuscht —
vertieft das eigene Tool:
<a href="Reliabilitaet.html" target="_blank" style="color:var(--a4);font-weight:500;text-decoration:none;border-bottom:1px solid var(--a4)">→ Reliabilität: α vs. ω</a>
</div>
</div>
</div>
<div class="sec-h">Lernkarten — Explorative Faktorenanalyse</div>
<div class="lcard-grid">
<div class="lcard">
<div class="lcard-h"><span class="circ">①</span> EFA vs. HKA/PCA</div>
<div class="lcard-body">Die <strong>Explorative Faktoranalyse (EFA)</strong> erklärt nur die <em>gemeinsame Varianz</em> der Items — sie verwendet eine <em>reduzierte</em> Korrelationsmatrix mit Kommunalitäten h² auf der Diagonalen. Die <strong>Hauptkomponentenanalyse (HKA)</strong> erklärt die <em>gesamte</em> Varianz (inkl. Fehler) und verwendet Einsen auf der Diagonalen. Dies ist ein konzeptuell wichtiger Unterschied.</div>
</div>
<div class="lcard">
<div class="lcard-h"><span class="circ">②</span> Kommunalität & Uniqueness</div>
<div class="lcard-body">Die <strong>Kommunalität h²</strong> ist der Varianzanteil eines Items, der durch die gemeinsamen Faktoren erklärt wird. Man unterscheidet zwei Zeitpunkte: die <strong>initiale</strong> Kommunalität (Startschätzung vor der Extraktion, meist die quadrierte multiple Korrelation SMC — das R² aus der gemeinsamen Regression des Items auf <em>alle</em> übrigen Items, nicht auf ein einzelnes) und die <strong>finale</strong> Kommunalität (nach der Extraktion: h² = Summe der quadrierten Ladungen über alle behaltenen Faktoren — was die Faktorlösung tatsächlich aufklärt). Die Tabelle in diesem Tool zeigt immer die finale h². Die <strong>Uniqueness u² = 1 − h²</strong> enthält Messfehler und itemspezifische Varianz.</div>
</div>
<div class="lcard">
<div class="lcard-h"><span class="circ">③</span> Eigenwerte & Scree-Plot</div>
<div class="lcard-body">Der Eigenwert eines Faktors zeigt, wie viel Varianz (in Einheiten standardisierter Items) er erklärt. Der Scree-Plot zeigt die Eigenwerte nach Größe sortiert. Das <strong>Kaiser-Kriterium</strong> (Eigenwert > 1) und der <strong>Scree-Knick (Elbow)</strong> sind die häufigsten Kriterien zur Faktoranzahl-Bestimmung.</div>
</div>
<div class="lcard">
<div class="lcard-h"><span class="circ">④</span> Rotation & Einfachstruktur</div>
<div class="lcard-body"><strong>Varimax</strong> rotiert die orthogonalen Achsen so, dass jedes Item möglichst hoch auf genau <em>einen</em> Faktor lädt (Thurstons Einfachstruktur). <strong>Oblique</strong> erlaubt Faktorkorrelation (phi > 0) — realistischer, aber schwieriger zu interpretieren. Kommunalitäten ändern sich durch Rotation nicht.</div>
</div>
<div class="lcard">
<div class="lcard-h"><span class="circ">⑤</span> Cronbachs Alpha</div>
<div class="lcard-body">Cronbachs <strong>Alpha</strong> schätzt die interne Konsistenz als Funktion der durchschnittlichen Interitemkorrelation. Ein häufiges Missverständnis: Alpha ist <strong>kein Maß für Unidimensionalität</strong>. Eine 2-faktorielle Struktur kann identisches Alpha wie eine 1-faktorielle haben — wenn die mittlere Interitemkorrelation gleich bleibt.</div>
</div>
<div class="lcard">
<div class="lcard-h"><span class="circ">⑥</span> Omega total & hierarchisch</div>
<div class="lcard-body"><strong>Omega total</strong> berücksichtigt alle Faktoren und ist eine bessere untere Schranke der Reliabilität als Alpha. <strong>Omega hierarchisch</strong> (Schmid-Leiman) misst nur den Anteil eines übergeordneten Generalfaktors — es steigt mit phi und ist 0 bei orthogonalen Faktoren (kein g). Hohe phi → mehr gemeinsame Varianz → omegas hierarchisch steigt.</div>
</div>
</div>
</div><!-- /main -->
</div><!-- /app -->
<!-- Help -->
<div class="help-overlay" id="help-ov" onclick="closeHelp(event)">
<div class="help-panel" onclick="event.stopPropagation()">
<button class="close-btn" onclick="closeHelp()">✕ Schließen</button>
<div class="help-h">❓ Hilfe — Explorative Faktoranalyse (EFA)</div>
<div class="help-ex">
<strong>Forschungsbeispiel:</strong> Ein Psychologe entwickelt einen Kognitionstest mit 8 Untertests. Die EFA soll prüfen, ob sich dahinter 2 latente Faktoren — Verbale Fähigkeit und Räumliches Denken — verbergen, ohne vorab festzulegen, welche Items zu welchem Faktor gehören.
</div>
<div class="help-sh">Parameter</div>
<ul class="help-ul">
<li><strong>Primäre Ladung (lambda):</strong> Stärke der Hauptladung jedes Items auf seinen "eigenen" Faktor. Höhere Werte = klarere Faktorstruktur, höhere Kommunalitäten.</li>
<li><strong>Kreuzladung (lambda_x):</strong> Ladung eines Items auf den anderen Faktor. Bei 0 = perfekte Einfachstruktur. Bei hohen Werten = Items sind schwieriger eindeutig einem Faktor zuzuordnen.</li>
<li><strong>Faktorkorrelation (phi):</strong> Korrelation zwischen F1 und F2 (nur relevant bei Oblique). Bei phi = 0 = orthogonale Faktoren; phi > 0 = schräge Faktoren (z.B. verbale und räumliche Fähigkeiten hängen zusammen).</li>
</ul>
<div class="help-sh">Extraktionsmethode: PAF</div>
<p class="help-p">Dieses Tool verwendet die <strong>Hauptachsenfaktorisierung (PAF)</strong> — eine echte EFA-Methode. PAF arbeitet mit einer <em>reduzierten</em> Korrelationsmatrix, bei der die Diagonale durch Kommunalitäten h² ersetzt wird — nicht durch Einsen wie bei der HKA/PCA. Dadurch wird nur die gemeinsame Varianz (Faktorvarianz) modelliert, nicht die Gesamtvarianz.</p>
<p class="help-p">In der Praxis läuft das iterativ: Man startet mit einer <strong>initialen</strong> Kommunalitätenschätzung (meist die quadrierte multiple Korrelation SMC jedes Items mit allen anderen), extrahiert Faktoren, berechnet daraus neue, <strong>finale</strong> Kommunalitäten (h² = Summe der quadrierten Ladungen) und wiederholt das, bis sich h² kaum noch ändert. Da in diesem Tool das Populationsmodell (Λ, Φ) über die Regler direkt vorgegeben wird, überspringt es diese Iteration und setzt sofort die modellimplizierte Kommunalität ein — mit echten Daten ist h² dagegen immer nur eine Schätzung, kein bekannter wahrer Wert.</p>
<p class="help-p">Der Scree-Plot zeigt die Eigenwerte der <em>vollen</em> R-Matrix (wie in der Praxis üblich, da die meisten Softwarepakete dies so ausgeben). Die eigentliche Faktorextraktion erfolgt aus der reduzierten Matrix.</p>
<div class="help-sh">Reliabilitätsindizes (Add-on)</div>
<p class="help-p">Die Reliabilitätsindizes werden <em>analytisch</em> aus der Populationsstruktur berechnet:</p>
<code class="help-formula">r_within = lambda² + lambda_x² + 2·phi·lambda·lambda_x (Korrelation innerhalb einer Gruppe)</code>
<code class="help-formula">r_between = 2·lambda·lambda_x + phi·(lambda²+lambda_x²) (Korrelation zwischen Gruppen)</code>
<code class="help-formula">sigma²_X = 8 + 24·r_within + 32·r_between (Varianz des Gesamtscores)</code>
<code class="help-formula">Alpha = (8/7)·(sigma²_X − 8) / sigma²_X</code>
<code class="help-formula">Omega_total = 1 − 8·(1 − r_within) / sigma²_X</code>
<code class="help-formula">Omega_hier = 64·(lambda+lambda_x)²·phi / sigma²_X (Schmid-Leiman)</code>
<p class="help-p"><strong>Omega hierarchisch</strong> basiert auf der Schmid-Leiman-Orthogonalisierung: Bei obliquen Faktoren mit Korrelation phi können die gemeinsamen Varianzanteile auf einen höhergeordneten Generalfaktor g zurückgeführt werden. Die g-Ladung pro Item beträgt (lambda + lambda_x)·sqrt(phi). Omega hierarchisch = 0 wenn phi = 0 (keine korrelierenden Faktoren, kein g).</p>
<div class="help-sh">Biplot & Korrelationsmatrix</div>
<p class="help-p">Der Biplot zeigt jedes Item als Punkt im F1/F2-Koordinatensystem. Items nahe (1,0) laden hoch auf F1; nahe (0,1) hoch auf F2. Nach Varimax-Rotation clustern Items nahe den Achsen (Einfachstruktur). Bei Oblique werden die Faktorachsen im Winkel arccos(phi) dargestellt.</p>
<p class="help-p">Die Korrelationsmatrix zeigt die von der Faktorstruktur <em>implizierte</em> Populationsmatrix R = Lambda·Phi·Lambda' + Psi. Teal = positive, Orange = negative Korrelation.</p>
</div>
</div>
<script>
function fixDiacritics(s){
return s
.replace(/([^\s<>])̂/g,'<span class="gl-hat">$1</span>')
.replace(/([^\s<>])̄/g,'<span class="gl-bar">$1</span>');
}
function toggleTheme(){
const dark=document.documentElement.getAttribute('data-theme')==='dark';
document.documentElement.setAttribute('data-theme',dark?'light':'dark');
document.getElementById('theme-btn').textContent=dark?'🌙 Dark':'☀ Light';
redraw();
}
function openHelp(){document.getElementById('help-ov').classList.add('open');}
function closeHelp(e){
if(!e||e.target===document.getElementById('help-ov'))
document.getElementById('help-ov').classList.remove('open');
}
// Scenario presets
function setScen(btn){
document.querySelectorAll('.scen-btn').forEach(b=>b.classList.remove('act'));
btn.classList.add('act');
document.getElementById('sl-lam').value =btn.dataset.lam;
document.getElementById('sl-cross').value=btn.dataset.cross;
document.getElementById('sl-phi').value =btn.dataset.phi;
update();
}
// Deactivate scenario button when sliders move manually
function syncScenBtns(){
const lv=document.getElementById('sl-lam').value;
const cv=document.getElementById('sl-cross').value;
const pv=document.getElementById('sl-phi').value;
document.querySelectorAll('.scen-btn').forEach(b=>{
b.classList.toggle('act',b.dataset.lam===lv&&b.dataset.cross===cv&&b.dataset.phi===pv);
});
}
let ROT='none';
let SHOW_GEOM=false;
function toggleGeom(){
SHOW_GEOM=!SHOW_GEOM;
document.getElementById('btn-geom').classList.toggle('act',SHOW_GEOM);
update();
}
function setRot(btn){
document.querySelectorAll('.rbtn').forEach(b=>b.classList.remove('act'));
btn.classList.add('act'); ROT=btn.dataset.rot;
const hints={
none:'Unrotierte Hauptachsen-Lösung. Oft laden viele Items auf den ersten Faktor.',
varimax:'Orthogonale Rotation (Achsen bleiben rechtwinklig). Maximiert Einfachstruktur — Kommunalitäten unverändert.',
oblique:'Schräge Rotation: Achsenwinkel = arccos(φ). Biplot zeigt die Pattern-Matrix (Λ) — partielle Regressionsgewichte, keine Korrelationen. Erhöhe φ um Unterschied zu Varimax zu sehen.'
};
document.getElementById('rot-hint').textContent=hints[ROT];
update();
}
// Matrix utils
function matMul(A,B){
const m=A.length,k=B.length,n=B[0].length;
return Array.from({length:m},(_,i)=>Array.from({length:n},(_,j)=>
Array.from({length:k},(_,l)=>A[i][l]*B[l][j]).reduce((a,b)=>a+b,0)));
}
function matT(A){return A[0].map((_,j)=>A.map(r=>r[j]));}
// Jacobi eigendecomposition
function jacobi(M_in){
const n=M_in.length;
let M=M_in.map(r=>[...r]);
let V=Array.from({length:n},(_,i)=>Array.from({length:n},(_,j)=>i===j?1:0));
for(let iter=0;iter<200*n*n;iter++){
let maxV=0,p=0,q=1;
for(let i=0;i<n;i++) for(let j=i+1;j<n;j++) if(Math.abs(M[i][j])>maxV){maxV=Math.abs(M[i][j]);p=i;q=j;}
if(maxV<1e-12)break;
const denom=M[p][p]-M[q][q];
const th=Math.abs(denom)<1e-14?(Math.PI/4)*Math.sign(M[p][q]):0.5*Math.atan2(2*M[p][q],denom);
const c=Math.cos(th),s=Math.sin(th);
for(let i=0;i<n;i++){
if(i!==p&&i!==q){const mip=M[i][p],miq=M[i][q];M[i][p]=M[p][i]=c*mip+s*miq;M[i][q]=M[q][i]=-s*mip+c*miq;}
}
const mpp=M[p][p],mqq=M[q][q],mpq=M[p][q];
M[p][p]=c*c*mpp+2*s*c*mpq+s*s*mqq;M[q][q]=s*s*mpp-2*s*c*mpq+c*c*mqq;M[p][q]=M[q][p]=0;
for(let i=0;i<n;i++){const vip=V[i][p],viq=V[i][q];V[i][p]=c*vip+s*viq;V[i][q]=-s*vip+c*viq;}
}
const pairs=Array.from({length:n},(_,i)=>({val:M[i][i],vec:V.map(r=>r[i])}));
pairs.sort((a,b)=>b.val-a.val);
return{values:pairs.map(p=>p.val),vectors:pairs.map(p=>p.vec)};
}
// Varimax rotation
function varimax(L_in,maxIter=500){
const p=L_in.length,m=L_in[0].length;
let L=L_in.map(r=>[...r]);
for(let iter=0;iter<maxIter;iter++){
let changed=false;
for(let j=0;j<m-1;j++) for(let k=j+1;k<m;k++){
let A=0,B=0,C=0,D=0;
for(let i=0;i<p;i++){const u=L[i][j]*L[i][j]-L[i][k]*L[i][k],v=2*L[i][j]*L[i][k];A+=u;B+=v;C+=u*u-v*v;D+=u*v;}
const num=2*(p*D-A*B),den=p*C-(A*A-B*B);
if(Math.abs(num)<1e-10&&Math.abs(den)<1e-10)continue;
const th=0.25*Math.atan2(num,den);
if(Math.abs(th)<1e-9)continue;
changed=true;
const c=Math.cos(th),s=Math.sin(th);
for(let i=0;i<p;i++){const lj=L[i][j],lk=L[i][k];L[i][j]=c*lj+s*lk;L[i][k]=-s*lj+c*lk;}
}
if(!changed)break;
}
for(let f=0;f<m;f++){const sumL=L.reduce((s,r)=>s+r[f],0);if(sumL<0)L.forEach(r=>r[f]=-r[f]);}
return L;
}
// Core: PAF (true EFA) + reliability
function computeFA(){
const lam =parseInt(document.getElementById('sl-lam').value)/100;
const cross=parseInt(document.getElementById('sl-cross').value)/100;
const phi =parseInt(document.getElementById('sl-phi').value)/100;
document.getElementById('v-lam').textContent =lam.toFixed(2);
document.getElementById('v-cross').textContent=cross.toFixed(2);
document.getElementById('v-phi').textContent =phi.toFixed(2);
syncScenBtns();
// True loadings: items 0–3 on F1, items 4–7 on F2
const Lambda=[];
for(let i=0;i<4;i++)Lambda.push([lam,cross]);
for(let i=0;i<4;i++)Lambda.push([cross,lam]);
const Phi=[[1,phi],[phi,1]];
// Communality h² = lambda_i' Phi lambda_i (same for all items)
const h2val=Math.min(lam*lam+cross*cross+2*phi*lam*cross,0.999);
const h2=Array(8).fill(h2val);
const u2=h2.map(h=>1-h);
// Implied R = Lambda Phi Lambda' + Psi (full, 1s on diagonal)
const LPLt=matMul(matMul(Lambda,Phi),matT(Lambda));
const R=LPLt.map((row,i)=>row.map((v,j)=>{
if(i===j)return 1;
return Math.max(-0.999,Math.min(0.999,v));
}));
// Scree: eigenvalues of full R (standard practice)
const{values:eigVals}=jacobi(R);
// PAF: eigendecompose REDUCED R (h² on diagonal)
const Rr=R.map((row,i)=>row.map((v,j)=>i===j?h2val:v));
const{values:eigValsRr,vectors:eigVecsRr}=jacobi(Rr);
// Unrotated PAF loadings (first 2 factors)
const unrotated=Array.from({length:8},(_,i)=>
Array.from({length:2},(_,f)=>eigVecsRr[f][i]*Math.sqrt(Math.max(0,eigValsRr[f]))));
// Rotation
let rotLoads=unrotated.map(r=>[...r]);
if(ROT==='varimax'){
rotLoads=varimax(unrotated.map(r=>[...r]));
// Canonical column order: F1 = the axis the V-group (items 0–3) loads higher on.
// Varimax's pairwise Jacobi sweep has no inherent factor order — the two columns
// it returns can land in either orientation depending on rounding/starting signs
// (verified: at phi=.40/.55/.65 it flips while neighboring phi values don't).
// Enforce the same "F1 = verbal axis" convention Oblique uses below, so switching
// rotation methods for the same scenario never flips which axis V/S load on.
const vAvg0=rotLoads.slice(0,4).reduce((s,r)=>s+Math.abs(r[0]),0)/4;
const vAvg1=rotLoads.slice(0,4).reduce((s,r)=>s+Math.abs(r[1]),0)/4;
if(vAvg1>vAvg0) rotLoads.forEach(r=>{const t=r[0];r[0]=r[1];r[1]=t;});
} else if(ROT==='oblique'){
// Oblique (true population solution):
// For a known population model with factor correlation phi, the correct oblique
// solution IS Lambda itself as the Pattern Matrix. PAF spans the same factor
// space; the oblique rotation simply recovers Lambda with phi as factor correlation.
// Pattern matrix loadings differ from structure matrix loadings when phi > 0.
rotLoads=Lambda.map(r=>[...r]);
// Sign convention: positive dominant loading per factor
for(let f=0;f<2;f++){
const sumL=rotLoads.reduce((s,r)=>s+r[f],0);
if(sumL<0) rotLoads.forEach(r=>{r[f]=-r[f];});
}
}
// Reliability (analytical from population model)
// r_within = corr between items in same group
// r_between = corr between items in different groups
const r_within =lam*lam+cross*cross+2*phi*lam*cross;
const r_between=2*lam*cross+phi*(lam*lam+cross*cross);
const sigX2=8+24*r_within+32*r_between;
const alpha =(8/7)*(sigX2-8)/sigX2;
// Omega total = 1 - sum(u²)/sigX2
const omegaT=1-8*(1-h2val)/sigX2;
// Omega hierarchical (Schmid-Leiman): g-loading = (lam+cross)*sqrt(phi)
const omegaH=64*(lam+cross)*(lam+cross)*phi/sigX2;
return{R,Rr,eigVals,eigValsRr,unrotated,rotLoads,h2,u2,phi,lam,cross,alpha,omegaT,omegaH};
}
// Canvas helper
function setupCanvas(id){
const cv=document.getElementById(id);
const dpr=window.devicePixelRatio||1;
const rect=cv.getBoundingClientRect();
cv.width=rect.width*dpr; cv.height=rect.height*dpr;
const ctx=cv.getContext('2d'); ctx.scale(dpr,dpr);
return{ctx,W:rect.width,H:rect.height};
}
function cssVar(v){return getComputedStyle(document.documentElement).getPropertyValue(v).trim();}
function col(v){return cssVar('--'+v);}
// Scree plot (blue, eigenvalues of R)
function drawScree(eigVals){
const{ctx,W,H}=setupCanvas('cv-scree');
const pad={t:28,r:20,b:52,l:56};
const pw=W-pad.l-pad.r,ph=H-pad.t-pad.b;
const n=eigVals.length;
const maxV=Math.max(...eigVals,1.2)*1.08;
const toX=i=>pad.l+(i/(n-1))*pw;
const toY=v=>pad.t+ph*(1-v/maxV);
ctx.clearRect(0,0,W,H);
[0.5,1,1.5,2,2.5,3,3.5,4,5].filter(v=>v<=maxV).forEach(v=>{
const y=toY(v);
ctx.strokeStyle=col('grid');ctx.lineWidth=1;
ctx.beginPath();ctx.moveTo(pad.l,y);ctx.lineTo(pad.l+pw,y);ctx.stroke();
ctx.fillStyle=col('ink3');ctx.font='14px DM Mono,monospace';ctx.textAlign='right';
ctx.fillText(v.toFixed(1),pad.l-7,y+5);
});
ctx.save();ctx.strokeStyle=col('a1');ctx.lineWidth=1.5;ctx.setLineDash([5,3]);
ctx.beginPath();ctx.moveTo(pad.l,toY(1));ctx.lineTo(pad.l+pw,toY(1));ctx.stroke();
ctx.setLineDash([]);ctx.fillStyle=col('a1');ctx.font='bold 13px DM Mono,monospace';ctx.textAlign='left';
ctx.fillText('Kaiser=1',pad.l+6,toY(1)-6);ctx.restore();
const bw=Math.min(pw/n*0.55,26);
eigVals.forEach((v,i)=>{
const x=toX(i),y=toY(v),barH=ph-(y-pad.t);
ctx.fillStyle=v>1?col('a2')+'bb':col('ink3')+'55';
ctx.fillRect(x-bw/2,y,bw,barH);
if(v>0.2){ctx.fillStyle=v>1?col('a2'):col('ink3');ctx.font='bold 14px DM Mono,monospace';ctx.textAlign='center';ctx.fillText(v.toFixed(2),x,y-7);}
ctx.fillStyle=col('ink3');ctx.font='14px DM Mono,monospace';ctx.textAlign='center';ctx.fillText(String(i+1),x,pad.t+ph+20);
});
ctx.strokeStyle=col('a2');ctx.lineWidth=1.5;ctx.beginPath();
eigVals.forEach((v,i)=>{const x=toX(i),y=toY(v);i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);});
ctx.stroke();
eigVals.forEach((v,i)=>{ctx.beginPath();ctx.arc(toX(i),toY(v),4,0,Math.PI*2);ctx.fillStyle=v>1?col('a2'):col('ink3');ctx.fill();});
ctx.fillStyle=col('ink2');ctx.font='bold 14px DM Mono,monospace';ctx.textAlign='center';
ctx.fillText('Faktor',pad.l+pw/2,H-6);
ctx.save();ctx.translate(16,pad.t+ph/2);ctx.rotate(-Math.PI/2);ctx.fillText('Eigenwert',0,0);ctx.restore();
}
// Biplot (purple V, orange S)
function drawBiplot(loads,phi){
const{ctx,W,H}=setupCanvas('cv-biplot');
const pad=36;const s=Math.min(W-2*pad,H-2*pad)/2;const cx=W/2,cy=H/2;
const sx=v=>cx+v*s,sy=v=>cy-v*s;
ctx.clearRect(0,0,W,H);
ctx.strokeStyle=col('border');ctx.lineWidth=1;ctx.beginPath();ctx.arc(cx,cy,s,0,Math.PI*2);ctx.stroke();
ctx.strokeStyle=col('grid');ctx.lineWidth=1;
ctx.beginPath();ctx.moveTo(pad,cy);ctx.lineTo(W-pad,cy);ctx.stroke();
ctx.beginPath();ctx.moveTo(cx,pad);ctx.lineTo(cx,H-pad);ctx.stroke();
const f1col=col('a4'),f2col=col('a5');
if(ROT==='oblique'&&phi>0.02){
ctx.strokeStyle=f1col+'aa';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(sx(-1.08),sy(0));ctx.lineTo(sx(1.08),sy(0));ctx.stroke();
const f2x=phi,f2y=Math.sqrt(Math.max(0,1-phi*phi));
ctx.strokeStyle=f2col+'aa';ctx.beginPath();ctx.moveTo(sx(-f2x*1.08),sy(-f2y*1.08));ctx.lineTo(sx(f2x*1.08),sy(f2y*1.08));ctx.stroke();
ctx.fillStyle=f2col;ctx.font='bold 12px DM Mono,monospace';ctx.textAlign='left';ctx.fillText('F2',sx(f2x*1.08)+4,sy(f2y*1.08)-2);
}else{
ctx.strokeStyle=f1col+'aa';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(sx(-1.08),sy(0));ctx.lineTo(sx(1.08),sy(0));ctx.stroke();
ctx.strokeStyle=f2col+'aa';ctx.beginPath();ctx.moveTo(sx(0),sy(-1.08));ctx.lineTo(sx(0),sy(1.08));ctx.stroke();
ctx.fillStyle=f2col;ctx.font='bold 12px DM Mono,monospace';ctx.textAlign='left';ctx.fillText('F2',sx(0)+4,sy(1.08)-2);
}
ctx.fillStyle=f1col;ctx.font='bold 12px DM Mono,monospace';ctx.textAlign='left';ctx.fillText('F1',sx(1.08)+4,sy(0)+4);
const labels=['V1','V2','V3','V4','S1','S2','S3','S4'];
const colors=[f1col,f1col,f1col,f1col,f2col,f2col,f2col,f2col];
// Nur 2 Vertreter-Items (V1, S1) — der Übersichtlichkeit halber, da innerhalb einer
// Gruppe ohnehin alle Items exakt dieselbe Ladung/Position hätten. Beide werden an ihrer
// TRUE Cartesian position geplottet: V=(S1, P2·sin(arccos(axisPhi))), was sich für
// axisPhi=0 (Unrotiert/Varimax) exakt auf V=(P1,P2) reduziert — verifiziert numerisch,
// z. B. lam=.70,cross=.10,phi=.40 → S1=.74,S2=.38, Pattern-Fußpunkt auf F1 reproduziert
// P1=.70 exakt (Struktur = Pattern·Φ).
const axisPhi=(ROT==='oblique'&&phi>0.02)?phi:0;
const alpha=Math.acos(axisPhi), sinA=Math.sin(alpha);
const structCol=col('a2'), patCol=col('a3');
function footTick(fx,fy,dx,dy,strokeCol){
const len=Math.hypot(dx,dy)||1, tx=-dy/len*6, ty=dx/len*6;
ctx.strokeStyle=strokeCol;ctx.lineWidth=1.8;
ctx.beginPath();ctx.moveTo(fx-tx,fy-ty);ctx.lineTo(fx+tx,fy+ty);ctx.stroke();
}
[0,4].forEach(i=>{
const [P1,P2]=loads[i], clr=colors[i];
const S1=P1+axisPhi*P2, S2=axisPhi*P1+P2;
const Vx=S1, Vy=P2*sinA;
const px=sx(Vx), py=sy(Vy);
ctx.strokeStyle=clr+'70';ctx.lineWidth=1.4;ctx.beginPath();ctx.moveTo(cx,cy);ctx.lineTo(px,py);ctx.stroke();
// Konstruktion (Winkelbogen + Struktur-/Pattern-Projektion) nur exemplarisch für EIN
// Item (V1, i===0) einblenden — bei zwei Items gleichzeitig würden sich die Linien bei
// engem Achsenwinkel (starkes Oblique) visuell nicht mehr trennen lassen.
if(SHOW_GEOM&&i===0){
const canvasAngle=Math.atan2(-Vy,Vx), arcR=26, angDeg=Math.abs(canvasAngle)*180/Math.PI;
if(angDeg>4){
ctx.strokeStyle=col('ink3');ctx.lineWidth=1;ctx.beginPath();
if(canvasAngle>=0) ctx.arc(cx,cy,arcR,0,canvasAngle); else ctx.arc(cx,cy,arcR,canvasAngle,0);
ctx.stroke();
const midA=canvasAngle/2;
ctx.fillStyle=col('ink3');ctx.font='11px DM Mono,monospace';ctx.textAlign='center';
ctx.fillText(Math.round(angDeg)+'°',cx+(arcR+13)*Math.cos(midA),cy+(arcR+13)*Math.sin(midA)+3);
}
const footF1x=sx(S1),footF1y=sy(0);
const footF2x=sx(S2*axisPhi),footF2y=sy(S2*sinA);
ctx.strokeStyle=structCol;ctx.lineWidth=1.5;ctx.setLineDash([2,3]);
ctx.beginPath();ctx.moveTo(px,py);ctx.lineTo(footF1x,footF1y);ctx.stroke();
ctx.beginPath();ctx.moveTo(px,py);ctx.lineTo(footF2x,footF2y);ctx.stroke();
ctx.setLineDash([]);
footTick(footF1x,footF1y,1,0,structCol);
footTick(footF2x,footF2y,axisPhi,sinA,structCol);
if(axisPhi>0.02){
const patF1x=sx(P1),patF1y=sy(0);
const patF2x=sx(P2*axisPhi),patF2y=sy(P2*sinA);
ctx.strokeStyle=patCol;ctx.lineWidth=1.5;ctx.setLineDash([6,3]);
ctx.beginPath();ctx.moveTo(px,py);ctx.lineTo(patF1x,patF1y);ctx.stroke();
ctx.beginPath();ctx.moveTo(px,py);ctx.lineTo(patF2x,patF2y);ctx.stroke();
ctx.setLineDash([]);
footTick(patF1x,patF1y,1,0,patCol);
footTick(patF2x,patF2y,axisPhi,sinA,patCol);
}
}
ctx.beginPath();ctx.arc(px,py,7,0,Math.PI*2);ctx.fillStyle=clr;ctx.fill();
ctx.strokeStyle=col('paper');ctx.lineWidth=1.8;ctx.stroke();
ctx.fillStyle=clr;ctx.font='bold 13px DM Mono,monospace';ctx.textAlign=Vx>=0?'left':'right';
ctx.fillText(labels[i],px+(Vx>=0?11:-11),py-11);
});
ctx.fillStyle=col('ink3');ctx.font='10px DM Mono,monospace';ctx.textAlign='center';
ctx.fillText('−1',sx(-1),cy+14);ctx.fillText('1',sx(1),cy+14);
ctx.textAlign='right';ctx.fillText('1',cx-4,sy(1)+4);ctx.fillText('−1',cx-4,sy(-1)+4);
}
// Heatmap (teal/orange)
function drawHeatmap(R){
const{ctx,W,H}=setupCanvas('cv-heat');
const n=R.length,pad={t:10,r:10,b:28,l:38};
const pw=W-pad.l-pad.r,ph=H-pad.t-pad.b,cw=pw/n,ch=ph/n;
const lbls=['V1','V2','V3','V4','S1','S2','S3','S4'];
const dark=document.documentElement.getAttribute('data-theme')==='dark';
ctx.clearRect(0,0,W,H);
ctx.fillStyle=col('ink3');ctx.font='bold 13px DM Mono,monospace';
lbls.forEach((lb,i)=>{
ctx.textAlign='right';ctx.fillText(lb,pad.l-6,pad.t+ch*(i+0.5)+5);
ctx.textAlign='center';ctx.fillText(lb,pad.l+cw*(i+0.5),pad.t+ph+20);
});
const baseD=[23,21,20],baseL=[232,225,210];
const posC=[43,199,196],negC=[224,120,48];
const diagC=dark?[133,124,104]:[138,127,114];
R.forEach((row,i)=>row.forEach((v,j)=>{
const isDiag=i===j;
const t=Math.min(Math.abs(v),1),base=dark?baseD:baseL,tgt=isDiag?diagC:(v>=0?posC:negC);
const r=Math.round(base[0]+(tgt[0]-base[0])*t);
const g=Math.round(base[1]+(tgt[1]-base[1])*t);
const b=Math.round(base[2]+(tgt[2]-base[2])*t);
ctx.fillStyle=`rgb(${r},${g},${b})`;ctx.fillRect(pad.l+j*cw,pad.t+i*ch,cw-1,ch-1);
ctx.fillStyle=t>0.45?'#fff':(dark?'#ece6d8':'#18130e');
ctx.font=(isDiag?'bold ':'')+'15px DM Mono,monospace';ctx.textAlign='center';
ctx.fillText(v.toFixed(2),pad.l+j*cw+cw/2,pad.t+i*ch+ch/2+5);
}));
}
// Loading table (green highlights)
function renderTable(loads,h2,u2){
const lbls=['V1','V2','V3','V4','S1','S2','S3','S4'],thr=0.35;
let html=`<table class="ltbl"><thead><tr>
<th style="text-align:left">Item</th>
<th style="color:var(--a4)">F1</th><th style="color:var(--a5)">F2</th>
<th style="color:var(--a3)">h²</th><th>u²</th>
</tr></thead><tbody>`;
loads.forEach((l,i)=>{
const c1=Math.abs(l[0])>=thr?'hi2':'lo',c2=Math.abs(l[1])>=thr?'hi5':'lo';
const hc=h2[i]>=0.35?'hi3':'';
html+=`<tr><td>${lbls[i]}</td><td class="${c1}">${l[0].toFixed(3)}</td><td class="${c2}">${l[1].toFixed(3)}</td><td class="${hc}">${h2[i].toFixed(3)}</td><td>${u2[i].toFixed(3)}</td></tr>`;
});
const ssl1=loads.reduce((s,r)=>s+r[0]*r[0],0),ssl2=loads.reduce((s,r)=>s+r[1]*r[1],0),p=loads.length;
html+=`<tr style="border-top:2px solid var(--border)">
<td style="color:var(--ink3);font-size:.73rem;font-family:'DM Mono',monospace">SSL</td>
<td class="hi2">${ssl1.toFixed(3)}</td><td class="hi5">${ssl2.toFixed(3)}</td>
<td colspan="2" style="color:var(--ink3);font-size:.73rem">Σλ²</td>
</tr><tr>
<td style="color:var(--ink3);font-size:.73rem;font-family:'DM Mono',monospace">% Var.</td>
<td class="hi2">${(ssl1/p*100).toFixed(1)}%</td><td class="hi5">${(ssl2/p*100).toFixed(1)}%</td>
<td colspan="2" style="color:var(--ink3);font-size:.73rem">SSL/p</td>
</tr>`;
html+='</tbody></table>';
document.getElementById('tbl-wrap').innerHTML=html;
}
// Reliability panel
function renderReliability(alpha,omegaT,omegaH,phi){
const pct=v=>(Math.max(0,Math.min(1,v))*100).toFixed(1)+'%';
document.getElementById('bar-alpha').style.width=pct(alpha);
document.getElementById('bar-wt').style.width=pct(omegaT);
document.getElementById('bar-wh').style.width=pct(omegaH);
document.getElementById('val-alpha').textContent=alpha.toFixed(3);
document.getElementById('val-wt').textContent=omegaT.toFixed(3);
document.getElementById('val-wh').textContent=omegaH<0.001?'0.000':omegaH.toFixed(3);
// Unidimensionality interpretation of omega_h (innerHTML for Greek)
let note='';
if(phi<0.01){
note='<strong>φ = 0:</strong> Die Faktoren F1 und F2 sind orthogonal — kein Generalfaktor g. ω<sub>h</sub> = 0: Ein Gesamtscore ist <em>nicht</em> eindimensional begründet. α und ω<sub>t</sub> messen interne Konsistenz, sagen aber nichts über Unidimensionalität.';
} else if(omegaH/omegaT>0.91){
note='<strong>Nahezu unidimensional (φ = '+phi.toFixed(2)+'):</strong> Alle drei Maße nähern sich einander an. ω<sub>h</sub> = '+omegaH.toFixed(3)+' — der größte Teil der Varianz geht auf einen übergeordneten Generalfaktor g zurück. Bei φ → 1 gilt α = ω<sub>t</sub> = ω<sub>h</sub>: echte Unidimensionalität.';
} else {
note='<strong>Oblique Faktoren (φ = '+phi.toFixed(2)+'):</strong> Es gibt einen Generalfaktor g. ω<sub>h</sub> = '+omegaH.toFixed(3)+' wächst mit φ und misst den g-Varianzanteil — direktes Maß für Unidimensionalität. α = '+alpha.toFixed(3)+' bleibt dabei nahezu konstant und erkennt die Unidimensionalitätsänderung nicht.';
}
document.getElementById('reli-note').innerHTML=note;
}
// Stats sidebar
function renderStats(eigVals,eigValsRr,h2val){
const nK=eigVals.filter(v=>v>1).length;
const v1=(eigVals[0]/8*100).toFixed(1),v12=((eigVals[0]+eigVals[1])/8*100).toFixed(1);
document.getElementById('stats').innerHTML=`
<div class="stat-row"><span class="stat-label">Faktoren (Kaiser)</span><span class="stat-val">${nK}</span></div>
<div class="stat-row"><span class="stat-label">Eigenwert F1 (R)</span><span class="stat-val">${eigVals[0].toFixed(3)}</span></div>
<div class="stat-row"><span class="stat-label">Eigenwert F2 (R)</span><span class="stat-val">${eigVals[1].toFixed(3)}</span></div>
<div class="stat-row"><span class="stat-label">EW F1 (Rr, PAF)</span><span class="stat-val">${eigValsRr[0].toFixed(3)}</span></div>
<div class="stat-row"><span class="stat-label">Var. erklärt F1+F2</span><span class="stat-val">${v12}%</span></div>
<div class="stat-row"><span class="stat-label">Finale Kommunalität h²</span><span class="stat-val">${h2val.toFixed(3)}</span></div>
`;
}
function update(){
const fa=computeFA();
// Update biplot panel title depending on rotation mode
const biplotTitles={
none:'Faktorladungs-Biplot — Unrotiert (F1 vs. F2)',
varimax:'Faktorladungs-Biplot — Varimax (F1 vs. F2)',
oblique:'Pattern-Matrix-Biplot — Oblique (Λ, Achsen bei arccosφ)'
};
document.getElementById('biplot-title-text').textContent=biplotTitles[ROT]+(SHOW_GEOM?' · Geometrie':'');
const geomNote=document.getElementById('geom-note');
if(SHOW_GEOM){
const axisPhi=(ROT==='oblique'&&fa.phi>0.02)?fa.phi:0;
const readout=([P1,P2])=>{
const S1v=P1+axisPhi*P2, S2v=axisPhi*P1+P2;
return {P1,P2,S1v,S2v};
};
const v1=readout(fa.rotLoads[0]), s1=readout(fa.rotLoads[4]);
geomNote.style.display='';
geomNote.innerHTML=`Reduziert auf <strong>2 Items</strong> (V1, S1 — stellvertretend für ihre Gruppe, da innerhalb einer Gruppe eh alle Items identisch sind). Die Konstruktion (Winkelbogen, Lot) wird nur exemplarisch für <strong>V1</strong> eingezeichnet — bei beiden Items gleichzeitig wären die Linien bei engem Achsenwinkel nicht mehr sauber zu trennen.
<br><br><strong style="color:${col('a2')}">Punktiert = Struktur (Korrelation)</strong>: Lot fällen — orthogonale Projektion auf die Achse. S₁=P₁+φ·P₂, S₂=φ·P₁+P₂.
${axisPhi>0.02?`<br><strong style="color:${col('a3')}">Gestrichelt = Pattern (Regressionsgewicht)</strong>: parallel zur jeweils anderen Achse projiziert — das sind die rohen Ladungen aus der Tabelle.
<br><br>Bei φ=${fa.phi.toFixed(2)} weichen Struktur und Pattern sichtbar voneinander ab — die Ladungstabelle zeigt nur das Pattern, nicht die Korrelation mit dem Faktor.
<br><br><strong>V1:</strong> Pattern (P₁=${v1.P1.toFixed(2)}, P₂=${v1.P2.toFixed(2)}) → Struktur (S₁=${v1.S1v.toFixed(2)}, S₂=${v1.S2v.toFixed(2)})
<br><strong>S1:</strong> Pattern (P₁=${s1.P1.toFixed(2)}, P₂=${s1.P2.toFixed(2)}) → Struktur (S₁=${s1.S1v.toFixed(2)}, S₂=${s1.S2v.toFixed(2)})`
:`<br><br>Hier sind Struktur und Pattern <strong>identisch</strong>, weil die Achsen orthogonal stehen (Unrotiert/Varimax) — schalte auf Oblique um, um den Unterschied zu sehen.
<br><br><strong>V1:</strong> P₁=${v1.P1.toFixed(2)}, P₂=${v1.P2.toFixed(2)} · <strong>S1:</strong> P₁=${s1.P1.toFixed(2)}, P₂=${s1.P2.toFixed(2)}`}`;
} else {
geomNote.style.display='none';
}
drawScree(fa.eigVals);
drawBiplot(fa.rotLoads,fa.phi);
drawHeatmap(fa.Rr);
renderTable(fa.rotLoads,fa.h2,fa.u2);
renderReliability(fa.alpha,fa.omegaT,fa.omegaH,fa.phi);
renderStats(fa.eigVals,fa.eigValsRr,fa.h2[0]);
}
function redraw(){update();}
window.addEventListener('resize',()=>requestAnimationFrame(update));
document.getElementById('rot-hint').textContent='Unrotierte Hauptachsen-Lösung. Oft laden viele Items auf den ersten Faktor.';
window.addEventListener('load',update);
</script>
</body>
</html>