udsdoc 0.66.0__py3-none-any.whl
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.
- udsdoc/__init__.py +288 -0
- udsdoc/__main__.py +7 -0
- udsdoc/_compat.py +74 -0
- udsdoc/_d3_interactive.py +324 -0
- udsdoc/_reprogramming.py +1288 -0
- udsdoc/_rich_output.py +208 -0
- udsdoc/_serialization.py +331 -0
- udsdoc/_style_presets.py +328 -0
- udsdoc/ap_parser.py +410 -0
- udsdoc/autosar_compat.py +208 -0
- udsdoc/boot_overlay.py +506 -0
- udsdoc/c_scanner.py +393 -0
- udsdoc/cantp_parser.py +493 -0
- udsdoc/cli.py +2299 -0
- udsdoc/comm_arxml_parser.py +420 -0
- udsdoc/completion.py +260 -0
- udsdoc/compliance_matrix.py +1357 -0
- udsdoc/config.py +64 -0
- udsdoc/csv_to_arxml.py +745 -0
- udsdoc/dem_parser.py +978 -0
- udsdoc/diff.py +562 -0
- udsdoc/doip_parser.py +268 -0
- udsdoc/ecu_log_analyzer.py +379 -0
- udsdoc/ecu_simulator.py +432 -0
- udsdoc/ecu_test/__init__.py +84 -0
- udsdoc/ecu_test/coverage.py +232 -0
- udsdoc/ecu_test/executor.py +296 -0
- udsdoc/ecu_test/formal_generator.py +298 -0
- udsdoc/ecu_test/fsm.py +565 -0
- udsdoc/ecu_test/generator.py +697 -0
- udsdoc/ecu_test/logger.py +532 -0
- udsdoc/ecu_test/models.py +122 -0
- udsdoc/ecu_test/transport.py +256 -0
- udsdoc/generator.py +7041 -0
- udsdoc/hardware.py +431 -0
- udsdoc/i18n.py +690 -0
- udsdoc/interactive.py +544 -0
- udsdoc/iso_versions.py +281 -0
- udsdoc/llm_integration.py +1436 -0
- udsdoc/lsp_server.py +322 -0
- udsdoc/models.py +1957 -0
- udsdoc/multi_ecu/__init__.py +59 -0
- udsdoc/multi_ecu/generator.py +2012 -0
- udsdoc/multi_ecu/inference.py +104 -0
- udsdoc/multi_ecu/loader.py +315 -0
- udsdoc/multi_ecu/models.py +406 -0
- udsdoc/multi_ecu/validator.py +207 -0
- udsdoc/multi_ecu/visualizations.py +379 -0
- udsdoc/obd_parser.py +245 -0
- udsdoc/odx/__init__.py +228 -0
- udsdoc/odx/mapper.py +1468 -0
- udsdoc/odx/models_odx.py +996 -0
- udsdoc/odx/pdx_packager.py +347 -0
- udsdoc/odx/serializers/__init__.py +85 -0
- udsdoc/odx/serializers/_shared.py +664 -0
- udsdoc/odx/serializers/odx_c.py +188 -0
- udsdoc/odx/serializers/odx_cs.py +143 -0
- udsdoc/odx/serializers/odx_d.py +935 -0
- udsdoc/odx/serializers/odx_e.py +268 -0
- udsdoc/odx/serializers/odx_f.py +260 -0
- udsdoc/odx/serializers/odx_fd.py +168 -0
- udsdoc/odx/serializers/odx_m.py +187 -0
- udsdoc/odx/serializers/odx_v.py +429 -0
- udsdoc/odx/supplement.py +342 -0
- udsdoc/odx/xsd/LICENSE +48 -0
- udsdoc/odx/xsd/odx-c.xsd +26 -0
- udsdoc/odx/xsd/odx-cs.xsd +26 -0
- udsdoc/odx/xsd/odx-d.xsd +28 -0
- udsdoc/odx/xsd/odx-e.xsd +27 -0
- udsdoc/odx/xsd/odx-f.xsd +26 -0
- udsdoc/odx/xsd/odx-fd.xsd +26 -0
- udsdoc/odx/xsd/odx-m.xsd +26 -0
- udsdoc/odx/xsd/odx-v.xsd +26 -0
- udsdoc/odx/xsd_validator.py +180 -0
- udsdoc/ollama_setup.py +513 -0
- udsdoc/parser.py +3480 -0
- udsdoc/plugin.py +110 -0
- udsdoc/py.typed +0 -0
- udsdoc/pytest_plugin.py +353 -0
- udsdoc/repo_scanner.py +1137 -0
- udsdoc/samples/boot_overlay_minimal.yaml +31 -0
- udsdoc/samples/boot_overlay_sample.yaml +103 -0
- udsdoc/samples/compliance_matrix_oem_annex.csv +103 -0
- udsdoc/samples/compliance_matrix_oem_did.csv +102 -0
- udsdoc/samples/compliance_matrix_oem_docan_ecu.csv +101 -0
- udsdoc/samples/compliance_matrix_oem_docan_standard.csv +102 -0
- udsdoc/samples/compliance_matrix_oem_dtc.csv +118 -0
- udsdoc/samples/compliance_matrix_oem_reprogramming.csv +99 -0
- udsdoc/samples/compliance_matrix_oem_rid.csv +106 -0
- udsdoc/samples/compliance_matrix_oem_sample.csv +7 -0
- udsdoc/samples/compliance_matrix_sources_sample.yaml +55 -0
- udsdoc/samples/compliance_matrix_supplier_sample.csv +5 -0
- udsdoc/samples/header_config_sample.yaml +22 -0
- udsdoc/samples/llm_config_ollama.json +9 -0
- udsdoc/samples/llm_config_openrouter.json +7 -0
- udsdoc/samples/odx_supplement_sample.yaml +87 -0
- udsdoc/samples/sample1_cantp.arxml +243 -0
- udsdoc/samples/sample1_dcm.arxml +2384 -0
- udsdoc/samples/sample1_dem.arxml +493 -0
- udsdoc/samples/sample1_did.c +127 -0
- udsdoc/samples/sample1_rid.c +104 -0
- udsdoc/samples/sample2_cantp.arxml +243 -0
- udsdoc/samples/sample2_dcm.arxml +1966 -0
- udsdoc/samples/sample2_dem.arxml +493 -0
- udsdoc/samples/sample2_did.c +110 -0
- udsdoc/samples/sample2_rid.c +104 -0
- udsdoc/samples/style_preset_demo.yaml +36 -0
- udsdoc/samples/system_redundant.yaml +76 -0
- udsdoc/samples/udsspec.cls +113 -0
- udsdoc/schema.py +286 -0
- udsdoc/templates/assets/mermaid.min.js +2029 -0
- udsdoc/templates/assets/search.js +138 -0
- udsdoc/templates/assets/tikz-uml.sty +5376 -0
- udsdoc/templates/structured_tex/root.tex.j2 +197 -0
- udsdoc/templates/structured_tex/sections/0_intro/0_short_descript/short_descript.tex.j2 +113 -0
- udsdoc/templates/structured_tex/sections/0_intro/1_about_doc/about_doc.tex.j2 +79 -0
- udsdoc/templates/structured_tex/sections/0_intro/2_context/context.tex.j2 +75 -0
- udsdoc/templates/structured_tex/sections/0_intro/intro.tex.j2 +3 -0
- udsdoc/templates/structured_tex/sections/1_low_layer/2_datalink/datalink.tex.j2 +22 -0
- udsdoc/templates/structured_tex/sections/1_low_layer/3_network/network.tex.j2 +87 -0
- udsdoc/templates/structured_tex/sections/1_low_layer/4_transport/transport.tex.j2 +146 -0
- udsdoc/templates/structured_tex/sections/1_low_layer/5_session/session.tex.j2 +127 -0
- udsdoc/templates/structured_tex/sections/1_low_layer/low_layer.tex.j2 +7 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/09_overview/overview.tex.j2 +66 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/10-2_sid10/sid10.tex.j2 +148 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/10-3_sid11/sid11.tex.j2 +130 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/10-4_sid27/sid27.tex.j2 +312 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/10-5_sid28/sid28.tex.j2 +136 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/10-6_sid29/sid29.tex.j2 +144 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/10-7_sid3E/sid3E.tex.j2 +127 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/10-8_sid85/sid85.tex.j2 +136 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/11-2_sid22/sid22.tex.j2 +137 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/11-7_sid2E/sid2E.tex.j2 +140 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/12-2_sid14/sid14.tex.j2 +122 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/12-3_sid19/sid19.tex.j2 +126 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/14-2_sid31/sid31.tex.j2 +184 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/15-2_sid34/sid34.tex.j2 +127 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/15-4_sid36/sid36.tex.j2 +131 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/15-5_sid37/sid37.tex.j2 +118 -0
- udsdoc/templates/structured_tex/sections/2_high_layer/high_layer.tex.j2 +50 -0
- udsdoc/templates/structured_tex/sections/3_bootloader/bootloader.tex.j2 +8 -0
- udsdoc/templates/structured_tex/sections/A_annex/annex.tex.j2 +249 -0
- udsdoc/templates/structured_tex/sections/C_references/references.tex.j2 +31 -0
- udsdoc/templates/structured_tex/sections/_cm_trace.tex.j2 +36 -0
- udsdoc/templates/structured_tex/sections/section.tex.j2 +10 -0
- udsdoc/templates/tikz-uml.sty +5386 -0
- udsdoc/templates/uds_spec.html.j2 +737 -0
- udsdoc/templates/uds_spec.tex.j2 +2248 -0
- udsdoc/templates/udsspec.cls +130 -0
- udsdoc/templates/web_ui.html +4548 -0
- udsdoc/tikz_svg.py +213 -0
- udsdoc/validator.py +450 -0
- udsdoc/visualizations.py +441 -0
- udsdoc/webserver.py +4732 -0
- udsdoc-0.66.0.dist-info/METADATA +347 -0
- udsdoc-0.66.0.dist-info/RECORD +160 -0
- udsdoc-0.66.0.dist-info/WHEEL +5 -0
- udsdoc-0.66.0.dist-info/entry_points.txt +6 -0
- udsdoc-0.66.0.dist-info/licenses/LICENSE +21 -0
- udsdoc-0.66.0.dist-info/top_level.txt +1 -0
udsdoc/__init__.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""udsdoc - Convert AUTOSAR DCM/CanTp/DEM arxml to UDS specification documents and ISO 22901-1 ODX/PDX."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.66.0"
|
|
4
|
+
|
|
5
|
+
# LLM features — submodule import fails fast if anthropic is missing only when
|
|
6
|
+
# its functions are actually called.
|
|
7
|
+
from udsdoc import (
|
|
8
|
+
ecu_test, # noqa: F401
|
|
9
|
+
llm_integration, # noqa: F401
|
|
10
|
+
)
|
|
11
|
+
from udsdoc.ap_parser import ApParser
|
|
12
|
+
from udsdoc.boot_overlay import (
|
|
13
|
+
BootloaderInfo,
|
|
14
|
+
BootOverlay,
|
|
15
|
+
DomainCanIds,
|
|
16
|
+
DomainMap,
|
|
17
|
+
FlashSegment,
|
|
18
|
+
empty_overlay,
|
|
19
|
+
load_overlay,
|
|
20
|
+
)
|
|
21
|
+
from udsdoc.boot_overlay import (
|
|
22
|
+
apply_to_spec as apply_boot_overlay,
|
|
23
|
+
)
|
|
24
|
+
from udsdoc.c_scanner import scan_did_variables, scan_rid_variables
|
|
25
|
+
from udsdoc.cantp_parser import CanTpParser
|
|
26
|
+
from udsdoc.comm_arxml_parser import CommArxmlParser
|
|
27
|
+
from udsdoc.compliance_matrix import (
|
|
28
|
+
ComplianceMatrixDocument,
|
|
29
|
+
ComplianceMatrixEntry,
|
|
30
|
+
ComplianceMatrixSource,
|
|
31
|
+
ComplianceMatrixTrace,
|
|
32
|
+
load_compliance_matrix_csv,
|
|
33
|
+
load_compliance_matrix_csvs,
|
|
34
|
+
load_compliance_matrix_document,
|
|
35
|
+
load_compliance_matrix_sources,
|
|
36
|
+
)
|
|
37
|
+
from udsdoc.compliance_matrix import (
|
|
38
|
+
match_to_spec as match_compliance_matrix_to_spec,
|
|
39
|
+
)
|
|
40
|
+
from udsdoc.config import GenerationConfig
|
|
41
|
+
from udsdoc.dem_parser import DemParser
|
|
42
|
+
from udsdoc.diff import DiffEntry, DiffKind, SpecDiff, SpecDiffer, diff_specs
|
|
43
|
+
from udsdoc.doip_parser import DoIpParser
|
|
44
|
+
from udsdoc.generator import TexGenerator
|
|
45
|
+
from udsdoc.i18n import SUPPORTED_LANGS, Translator
|
|
46
|
+
from udsdoc.i18n import t as translate
|
|
47
|
+
from udsdoc.interactive import InteractiveSession
|
|
48
|
+
from udsdoc.models import (
|
|
49
|
+
AccessTimingRow,
|
|
50
|
+
ApConfig,
|
|
51
|
+
ApDiagnosticService,
|
|
52
|
+
ApEvent,
|
|
53
|
+
ApField,
|
|
54
|
+
ApMethod,
|
|
55
|
+
ApServiceInterface,
|
|
56
|
+
AuthenticationConfig,
|
|
57
|
+
CanTpChannel,
|
|
58
|
+
CanTpConfig,
|
|
59
|
+
CommunicationConfig,
|
|
60
|
+
DataElement,
|
|
61
|
+
DemCombinedDtcEntry,
|
|
62
|
+
DemDebounceConfig,
|
|
63
|
+
DemDtcEntry,
|
|
64
|
+
DemEnableCondition,
|
|
65
|
+
DemEventMemoryConfig,
|
|
66
|
+
DemEventParameter,
|
|
67
|
+
DemExtendedDataClass,
|
|
68
|
+
DemFreezeFrameClass,
|
|
69
|
+
DemGeneralConfig,
|
|
70
|
+
DemIndicatorConfig,
|
|
71
|
+
DemOperationCycle,
|
|
72
|
+
DemStorageCondition,
|
|
73
|
+
DiagSession,
|
|
74
|
+
Did,
|
|
75
|
+
DoIpConfig,
|
|
76
|
+
DoIpLogicalAddress,
|
|
77
|
+
DoIpRoutingActivation,
|
|
78
|
+
DslConnection,
|
|
79
|
+
DtcGroup,
|
|
80
|
+
EcuInstance,
|
|
81
|
+
Frame,
|
|
82
|
+
FrameSignal,
|
|
83
|
+
ISignal,
|
|
84
|
+
LinkControlBaudrateEntry,
|
|
85
|
+
NetworkTopology,
|
|
86
|
+
NrcEntry,
|
|
87
|
+
ObdConfig,
|
|
88
|
+
ObdPid,
|
|
89
|
+
Pdu,
|
|
90
|
+
ProtocolRow,
|
|
91
|
+
Routine,
|
|
92
|
+
RoutineParameter,
|
|
93
|
+
SecurityLevel,
|
|
94
|
+
SubFunction,
|
|
95
|
+
UdsService,
|
|
96
|
+
UdsSpecification,
|
|
97
|
+
)
|
|
98
|
+
from udsdoc.obd_parser import ObdParser
|
|
99
|
+
from udsdoc.odx import (
|
|
100
|
+
OdxCategory,
|
|
101
|
+
OdxDocument,
|
|
102
|
+
empty_supplement,
|
|
103
|
+
load_supplement,
|
|
104
|
+
map_ecu_system_to_odx,
|
|
105
|
+
map_spec_to_odx,
|
|
106
|
+
)
|
|
107
|
+
from udsdoc.parser import ArxmlParser, ArxmlValidationError, ArxmlValidationWarning
|
|
108
|
+
from udsdoc.plugin import GeneratorPlugin, ParserPlugin, PluginRegistry, registry
|
|
109
|
+
from udsdoc.repo_scanner import (
|
|
110
|
+
EcuPartition,
|
|
111
|
+
RepoScanResult,
|
|
112
|
+
classify_arxml,
|
|
113
|
+
detect_ecu_mode,
|
|
114
|
+
load_from_repo,
|
|
115
|
+
load_system_from_repo,
|
|
116
|
+
partition_by_ecu,
|
|
117
|
+
scan_repo,
|
|
118
|
+
)
|
|
119
|
+
from udsdoc.schema import generate_json_schema, generate_pydantic_models, save_json_schema
|
|
120
|
+
from udsdoc.validator import UdsValidator, ValidationIssue, ValidationResult, validate
|
|
121
|
+
from udsdoc.visualizations import (
|
|
122
|
+
generate_all_visualizations,
|
|
123
|
+
generate_security_did_heatmap,
|
|
124
|
+
generate_session_service_heatmap,
|
|
125
|
+
generate_timing_diagram,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
__all__ = [
|
|
129
|
+
"ecu_test",
|
|
130
|
+
"AccessTimingRow",
|
|
131
|
+
"ArxmlParser",
|
|
132
|
+
"ArxmlValidationError",
|
|
133
|
+
"ArxmlValidationWarning",
|
|
134
|
+
"AuthenticationConfig",
|
|
135
|
+
"CanTpParser",
|
|
136
|
+
"DemCombinedDtcEntry",
|
|
137
|
+
"DemDebounceConfig",
|
|
138
|
+
"DemDtcEntry",
|
|
139
|
+
"DemEnableCondition",
|
|
140
|
+
"DemEventMemoryConfig",
|
|
141
|
+
"DemEventParameter",
|
|
142
|
+
"DemExtendedDataClass",
|
|
143
|
+
"DemFreezeFrameClass",
|
|
144
|
+
"DemGeneralConfig",
|
|
145
|
+
"DemIndicatorConfig",
|
|
146
|
+
"DemOperationCycle",
|
|
147
|
+
"DemParser",
|
|
148
|
+
"DemStorageCondition",
|
|
149
|
+
"DslConnection",
|
|
150
|
+
"DtcGroup",
|
|
151
|
+
"GenerationConfig",
|
|
152
|
+
"InteractiveSession",
|
|
153
|
+
"LinkControlBaudrateEntry",
|
|
154
|
+
"ProtocolRow",
|
|
155
|
+
"TexGenerator",
|
|
156
|
+
"UdsSpecification",
|
|
157
|
+
"UdsService",
|
|
158
|
+
"DiagSession",
|
|
159
|
+
"SecurityLevel",
|
|
160
|
+
"Did",
|
|
161
|
+
"DataElement",
|
|
162
|
+
"Routine",
|
|
163
|
+
"RoutineParameter",
|
|
164
|
+
"SubFunction",
|
|
165
|
+
"NrcEntry",
|
|
166
|
+
"CanTpChannel",
|
|
167
|
+
"CanTpConfig",
|
|
168
|
+
"scan_did_variables",
|
|
169
|
+
"scan_rid_variables",
|
|
170
|
+
# Validation
|
|
171
|
+
"UdsValidator",
|
|
172
|
+
"ValidationIssue",
|
|
173
|
+
"ValidationResult",
|
|
174
|
+
"validate",
|
|
175
|
+
# Diff
|
|
176
|
+
"SpecDiffer",
|
|
177
|
+
"SpecDiff",
|
|
178
|
+
"DiffEntry",
|
|
179
|
+
"DiffKind",
|
|
180
|
+
"diff_specs",
|
|
181
|
+
# Plugin system
|
|
182
|
+
"ParserPlugin",
|
|
183
|
+
"GeneratorPlugin",
|
|
184
|
+
"PluginRegistry",
|
|
185
|
+
"registry",
|
|
186
|
+
# i18n
|
|
187
|
+
"Translator",
|
|
188
|
+
"SUPPORTED_LANGS",
|
|
189
|
+
"translate",
|
|
190
|
+
# DoIP (ISO 13400)
|
|
191
|
+
"DoIpConfig",
|
|
192
|
+
"DoIpLogicalAddress",
|
|
193
|
+
"DoIpRoutingActivation",
|
|
194
|
+
"DoIpParser",
|
|
195
|
+
# OBD (ISO 15031 / WWH-OBD)
|
|
196
|
+
"ObdConfig",
|
|
197
|
+
"ObdPid",
|
|
198
|
+
"ObdParser",
|
|
199
|
+
# Communication ARXML
|
|
200
|
+
"CommunicationConfig",
|
|
201
|
+
"Frame",
|
|
202
|
+
"FrameSignal",
|
|
203
|
+
"ISignal",
|
|
204
|
+
"Pdu",
|
|
205
|
+
"EcuInstance",
|
|
206
|
+
"NetworkTopology",
|
|
207
|
+
"CommArxmlParser",
|
|
208
|
+
# Adaptive Platform
|
|
209
|
+
"ApConfig",
|
|
210
|
+
"ApServiceInterface",
|
|
211
|
+
"ApMethod",
|
|
212
|
+
"ApEvent",
|
|
213
|
+
"ApField",
|
|
214
|
+
"ApDiagnosticService",
|
|
215
|
+
"ApParser",
|
|
216
|
+
# JSON Schema / Pydantic export
|
|
217
|
+
"generate_json_schema",
|
|
218
|
+
"save_json_schema",
|
|
219
|
+
"generate_pydantic_models",
|
|
220
|
+
# ODX / PDX (ISO 22901-1)
|
|
221
|
+
"OdxCategory",
|
|
222
|
+
"OdxDocument",
|
|
223
|
+
"empty_supplement",
|
|
224
|
+
"load_supplement",
|
|
225
|
+
"map_ecu_system_to_odx",
|
|
226
|
+
"map_spec_to_odx",
|
|
227
|
+
# Bootloader-domain overlay
|
|
228
|
+
"BootOverlay",
|
|
229
|
+
"BootloaderInfo",
|
|
230
|
+
"DomainCanIds",
|
|
231
|
+
"DomainMap",
|
|
232
|
+
"FlashSegment",
|
|
233
|
+
"apply_boot_overlay",
|
|
234
|
+
"empty_overlay",
|
|
235
|
+
"load_overlay",
|
|
236
|
+
# Repository auto-discovery
|
|
237
|
+
"EcuPartition",
|
|
238
|
+
"RepoScanResult",
|
|
239
|
+
"classify_arxml",
|
|
240
|
+
"detect_ecu_mode",
|
|
241
|
+
"load_from_repo",
|
|
242
|
+
"load_system_from_repo",
|
|
243
|
+
"partition_by_ecu",
|
|
244
|
+
"scan_repo",
|
|
245
|
+
# Compliance Matrix (ASPICE SYS.2.BP4 / ISO 26262-8 §6.4.5)
|
|
246
|
+
"ComplianceMatrixDocument",
|
|
247
|
+
"ComplianceMatrixEntry",
|
|
248
|
+
"ComplianceMatrixSource",
|
|
249
|
+
"ComplianceMatrixTrace",
|
|
250
|
+
"load_compliance_matrix_csv",
|
|
251
|
+
"load_compliance_matrix_csvs",
|
|
252
|
+
"load_compliance_matrix_document",
|
|
253
|
+
"load_compliance_matrix_sources",
|
|
254
|
+
"match_compliance_matrix_to_spec",
|
|
255
|
+
# Visualizations
|
|
256
|
+
"generate_all_visualizations",
|
|
257
|
+
"generate_security_did_heatmap",
|
|
258
|
+
"generate_session_service_heatmap",
|
|
259
|
+
"generate_timing_diagram",
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
# ----------------------------------------------------------------------------
|
|
263
|
+
# Agent D — Style preset system (0.38)
|
|
264
|
+
# Generic-only presets (no OEM / vendor branding). Append below to keep
|
|
265
|
+
# merge conflicts with Agent E's reconciliation obvious.
|
|
266
|
+
# TODO (Agent E): fold these imports + names into the main import block
|
|
267
|
+
# and the literal ``__all__`` list once all agents have landed.
|
|
268
|
+
# ----------------------------------------------------------------------------
|
|
269
|
+
from udsdoc._style_presets import ( # noqa: F401, E402
|
|
270
|
+
HeaderConfig,
|
|
271
|
+
StylePreset,
|
|
272
|
+
empty_header_config,
|
|
273
|
+
get_preset,
|
|
274
|
+
list_presets,
|
|
275
|
+
load_header_config,
|
|
276
|
+
register_preset,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
__all__ += [
|
|
280
|
+
# Style preset system
|
|
281
|
+
"HeaderConfig",
|
|
282
|
+
"StylePreset",
|
|
283
|
+
"empty_header_config",
|
|
284
|
+
"get_preset",
|
|
285
|
+
"list_presets",
|
|
286
|
+
"load_header_config",
|
|
287
|
+
"register_preset",
|
|
288
|
+
]
|
udsdoc/__main__.py
ADDED
udsdoc/_compat.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Backward-compatibility helpers for the udsxml2tex → udsdoc rename.
|
|
2
|
+
|
|
3
|
+
The project was published as ``udsxml2tex`` up to v0.65.x. Environment
|
|
4
|
+
variables, the user config directory and the console script kept that
|
|
5
|
+
prefix. This module lets the renamed package keep honouring the legacy
|
|
6
|
+
spellings for a deprecation period (planned removal: v1.0).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import warnings
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import overload
|
|
15
|
+
|
|
16
|
+
_NEW_PREFIX = "UDSDOC_"
|
|
17
|
+
_LEGACY_PREFIX = "UDSXML2TEX_"
|
|
18
|
+
|
|
19
|
+
#: Legacy user config directory (``~/.udsxml2tex``), consulted as a
|
|
20
|
+
#: fallback when ``~/.udsdoc`` has no matching file.
|
|
21
|
+
LEGACY_CONFIG_DIR = ".udsxml2tex"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _warn_legacy(old: str, new: str) -> None:
|
|
25
|
+
warnings.warn(
|
|
26
|
+
f"{old} is deprecated since the rename to udsdoc; "
|
|
27
|
+
f"use {new} instead (legacy support will be removed in v1.0)",
|
|
28
|
+
DeprecationWarning,
|
|
29
|
+
stacklevel=3,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@overload
|
|
34
|
+
def env_get(name: str) -> str | None: ...
|
|
35
|
+
@overload
|
|
36
|
+
def env_get(name: str, default: str) -> str: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def env_get(name: str, default: str | None = None) -> str | None:
|
|
40
|
+
"""``os.environ.get`` honouring the legacy ``UDSXML2TEX_*`` prefix.
|
|
41
|
+
|
|
42
|
+
``name`` must be the new ``UDSDOC_*`` spelling. When it is unset but
|
|
43
|
+
the legacy spelling is present, the legacy value is returned with a
|
|
44
|
+
:class:`DeprecationWarning`.
|
|
45
|
+
"""
|
|
46
|
+
value = os.environ.get(name)
|
|
47
|
+
if value is not None:
|
|
48
|
+
return value
|
|
49
|
+
if name.startswith(_NEW_PREFIX):
|
|
50
|
+
legacy_name = _LEGACY_PREFIX + name[len(_NEW_PREFIX):]
|
|
51
|
+
legacy_value = os.environ.get(legacy_name)
|
|
52
|
+
if legacy_value is not None:
|
|
53
|
+
_warn_legacy(legacy_name, name)
|
|
54
|
+
return legacy_value
|
|
55
|
+
return default
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def user_config_candidates(*relative: str) -> list[Path]:
|
|
59
|
+
"""Paths for a user config file, new location first, legacy second.
|
|
60
|
+
|
|
61
|
+
``user_config_candidates("llm.json")`` →
|
|
62
|
+
``[~/.udsdoc/llm.json, ~/.udsxml2tex/llm.json]``.
|
|
63
|
+
"""
|
|
64
|
+
home = Path.home()
|
|
65
|
+
return [
|
|
66
|
+
home.joinpath(".udsdoc", *relative),
|
|
67
|
+
home.joinpath(LEGACY_CONFIG_DIR, *relative),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def warn_legacy_path(path: Path) -> None:
|
|
72
|
+
"""Emit the deprecation warning for a legacy config path hit."""
|
|
73
|
+
if LEGACY_CONFIG_DIR in path.parts:
|
|
74
|
+
_warn_legacy(str(path), str(path).replace(LEGACY_CONFIG_DIR, ".udsdoc"))
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""D3.js-based interactive visualizations for the HTML ZIP output.
|
|
2
|
+
|
|
3
|
+
Produces a self-contained HTML page (`interactive.html`) embedding a D3 v7
|
|
4
|
+
force-directed graph of the UDS specification's Sessions, Services, DIDs, and
|
|
5
|
+
Routines, with click-through filtering and a sidebar inspector panel.
|
|
6
|
+
|
|
7
|
+
The D3 library is loaded from a CDN (no bundling) — works offline once the
|
|
8
|
+
page has been visited once and the asset is cached, but does require initial
|
|
9
|
+
network access. To bundle D3 instead, drop `d3.v7.min.js` into the ZIP and
|
|
10
|
+
swap the <script src="..."> URL.
|
|
11
|
+
|
|
12
|
+
Public API:
|
|
13
|
+
render_interactive_page(spec, ecu_name) -> str # full HTML page
|
|
14
|
+
spec_to_d3_json(spec) -> dict # serialisable graph data
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def spec_to_d3_json(spec: Any) -> dict:
|
|
24
|
+
"""Convert a UdsSpecification into a {nodes, links} dict for D3."""
|
|
25
|
+
nodes: list[dict] = []
|
|
26
|
+
links: list[dict] = []
|
|
27
|
+
|
|
28
|
+
# Nodes
|
|
29
|
+
for s in (spec.sessions or []):
|
|
30
|
+
nodes.append({
|
|
31
|
+
"id": f"sess:{s.session_id}",
|
|
32
|
+
"label": s.short_name,
|
|
33
|
+
"type": "session",
|
|
34
|
+
"hex": f"0x{s.session_id:02X}",
|
|
35
|
+
})
|
|
36
|
+
for sec in (spec.security_levels or []):
|
|
37
|
+
nodes.append({
|
|
38
|
+
"id": f"sec:{sec.security_level}",
|
|
39
|
+
"label": sec.short_name,
|
|
40
|
+
"type": "security",
|
|
41
|
+
"hex": f"0x{sec.security_level:02X}",
|
|
42
|
+
})
|
|
43
|
+
for svc in (spec.services or []):
|
|
44
|
+
nodes.append({
|
|
45
|
+
"id": f"svc:{svc.service_id}",
|
|
46
|
+
"label": svc.short_name,
|
|
47
|
+
"type": "service",
|
|
48
|
+
"hex": f"0x{svc.service_id:02X}",
|
|
49
|
+
"sub_funcs": len(svc.sub_functions or []),
|
|
50
|
+
"nrcs": len(svc.nrc_list or []),
|
|
51
|
+
})
|
|
52
|
+
for did in (spec.dids or []):
|
|
53
|
+
nodes.append({
|
|
54
|
+
"id": f"did:{did.did_id}",
|
|
55
|
+
"label": did.short_name,
|
|
56
|
+
"type": "did",
|
|
57
|
+
"hex": f"0x{did.did_id:04X}",
|
|
58
|
+
"read": bool(did.read_access),
|
|
59
|
+
"write": bool(did.write_access),
|
|
60
|
+
})
|
|
61
|
+
for rt in (spec.routines or []):
|
|
62
|
+
nodes.append({
|
|
63
|
+
"id": f"rid:{rt.routine_id}",
|
|
64
|
+
"label": rt.short_name,
|
|
65
|
+
"type": "routine",
|
|
66
|
+
"hex": f"0x{rt.routine_id:04X}",
|
|
67
|
+
"ops": (
|
|
68
|
+
("S" if rt.start_supported else "")
|
|
69
|
+
+ ("X" if rt.stop_supported else "")
|
|
70
|
+
+ ("R" if rt.result_supported else "")
|
|
71
|
+
),
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
# Index by short name for cross-references
|
|
75
|
+
sess_by_name = {s.short_name: f"sess:{s.session_id}"
|
|
76
|
+
for s in (spec.sessions or [])}
|
|
77
|
+
sec_by_name = {s.short_name: f"sec:{s.security_level}"
|
|
78
|
+
for s in (spec.security_levels or [])}
|
|
79
|
+
|
|
80
|
+
# Service → session links
|
|
81
|
+
for svc in (spec.services or []):
|
|
82
|
+
for sname in (svc.supported_sessions or []):
|
|
83
|
+
target = sess_by_name.get(sname)
|
|
84
|
+
if target:
|
|
85
|
+
links.append({"source": f"svc:{svc.service_id}",
|
|
86
|
+
"target": target, "kind": "supports"})
|
|
87
|
+
for sname in (svc.required_security_levels or []):
|
|
88
|
+
target = sec_by_name.get(sname)
|
|
89
|
+
if target:
|
|
90
|
+
links.append({"source": f"svc:{svc.service_id}",
|
|
91
|
+
"target": target, "kind": "requires-sec"})
|
|
92
|
+
|
|
93
|
+
# DID → session / security
|
|
94
|
+
for did in (spec.dids or []):
|
|
95
|
+
for sname in (did.supported_sessions or []):
|
|
96
|
+
target = sess_by_name.get(sname)
|
|
97
|
+
if target:
|
|
98
|
+
links.append({"source": f"did:{did.did_id}",
|
|
99
|
+
"target": target, "kind": "supports"})
|
|
100
|
+
for sname in (did.required_security_levels or []):
|
|
101
|
+
target = sec_by_name.get(sname)
|
|
102
|
+
if target:
|
|
103
|
+
links.append({"source": f"did:{did.did_id}",
|
|
104
|
+
"target": target, "kind": "requires-sec"})
|
|
105
|
+
# DIDs are operated on by 0x22 (read) and 0x2E (write)
|
|
106
|
+
if did.read_access:
|
|
107
|
+
for svc in (spec.services or []):
|
|
108
|
+
if svc.service_id == 0x22:
|
|
109
|
+
links.append({"source": f"svc:{svc.service_id}",
|
|
110
|
+
"target": f"did:{did.did_id}",
|
|
111
|
+
"kind": "reads"})
|
|
112
|
+
break
|
|
113
|
+
if did.write_access:
|
|
114
|
+
for svc in (spec.services or []):
|
|
115
|
+
if svc.service_id == 0x2E:
|
|
116
|
+
links.append({"source": f"svc:{svc.service_id}",
|
|
117
|
+
"target": f"did:{did.did_id}",
|
|
118
|
+
"kind": "writes"})
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
# Routine → session / security; RoutineControl 0x31 → routines
|
|
122
|
+
for rt in (spec.routines or []):
|
|
123
|
+
for sname in (rt.supported_sessions or []):
|
|
124
|
+
target = sess_by_name.get(sname)
|
|
125
|
+
if target:
|
|
126
|
+
links.append({"source": f"rid:{rt.routine_id}",
|
|
127
|
+
"target": target, "kind": "supports"})
|
|
128
|
+
for sname in (rt.required_security_levels or []):
|
|
129
|
+
target = sec_by_name.get(sname)
|
|
130
|
+
if target:
|
|
131
|
+
links.append({"source": f"rid:{rt.routine_id}",
|
|
132
|
+
"target": target, "kind": "requires-sec"})
|
|
133
|
+
for svc in (spec.services or []):
|
|
134
|
+
if svc.service_id == 0x31:
|
|
135
|
+
links.append({"source": f"svc:{svc.service_id}",
|
|
136
|
+
"target": f"rid:{rt.routine_id}",
|
|
137
|
+
"kind": "controls"})
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
return {"nodes": nodes, "links": links}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def render_interactive_page(spec: Any, ecu_name: str = "") -> str:
|
|
144
|
+
"""Return a full self-contained HTML page with D3 force-directed graph."""
|
|
145
|
+
data = spec_to_d3_json(spec)
|
|
146
|
+
json_blob = json.dumps(data, ensure_ascii=False)
|
|
147
|
+
|
|
148
|
+
return r"""<!DOCTYPE html>
|
|
149
|
+
<html lang="en">
|
|
150
|
+
<head>
|
|
151
|
+
<meta charset="UTF-8">
|
|
152
|
+
<title>UDS Spec — Interactive Graph (""" + ecu_name + r""")</title>
|
|
153
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
154
|
+
<style>
|
|
155
|
+
* { box-sizing: border-box; }
|
|
156
|
+
body { margin: 0; font-family: Georgia, serif; background: #f4f3ef; color: #111; }
|
|
157
|
+
#container { display: flex; height: 100vh; }
|
|
158
|
+
#graph { flex: 1; background: #ffffff; }
|
|
159
|
+
#panel { width: 320px; padding: 1.2em; background: #fafafa; border-left: 1px solid #c8c8c4;
|
|
160
|
+
font-family: Arial, sans-serif; font-size: 13px; overflow-y: auto; }
|
|
161
|
+
#panel h1 { font-family: Georgia, serif; font-size: 1.2em; margin-top: 0;
|
|
162
|
+
border-bottom: 2px solid #111; padding-bottom: 0.3em; }
|
|
163
|
+
#panel h2 { font-family: Georgia, serif; font-size: 1em; margin-top: 1.2em;
|
|
164
|
+
border-bottom: 1px solid #999; padding-bottom: 0.15em; }
|
|
165
|
+
#panel .hex { font-family: 'Courier New', monospace; background: #ebebea; padding: 1px 5px; }
|
|
166
|
+
#legend span { display: inline-block; width: 12px; height: 12px; margin-right: 4px;
|
|
167
|
+
border-radius: 50%; vertical-align: middle; }
|
|
168
|
+
#legend li { margin: 3px 0; list-style: none; }
|
|
169
|
+
.toolbar { padding: 6px 12px; background: #3a3a3a; color: #ddd; font-size: 12px; font-family: Arial, sans-serif; }
|
|
170
|
+
.toolbar button { background: #555; color: #eee; border: 1px solid #444; padding: 3px 10px;
|
|
171
|
+
margin-right: 6px; cursor: pointer; font-family: inherit; }
|
|
172
|
+
.toolbar button:hover { background: #666; }
|
|
173
|
+
.toolbar input[type=text] { padding: 3px 6px; background: #444; color: #eee; border: 1px solid #555; }
|
|
174
|
+
.node { stroke: #fff; stroke-width: 1px; cursor: pointer; }
|
|
175
|
+
.node.dim { opacity: 0.15; }
|
|
176
|
+
.link { stroke: #aaa; stroke-opacity: 0.4; fill: none; }
|
|
177
|
+
.link.highlight { stroke: #cc4400; stroke-opacity: 0.9; stroke-width: 2px; }
|
|
178
|
+
.label { font-size: 9px; font-family: Arial, sans-serif; pointer-events: none; fill: #222; }
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<div id="container">
|
|
183
|
+
<div style="flex:1; display:flex; flex-direction:column;">
|
|
184
|
+
<div class="toolbar">
|
|
185
|
+
<button id="btn-reset">Reset</button>
|
|
186
|
+
<input type="text" id="search" placeholder="Search by name…" size="20">
|
|
187
|
+
<label style="margin-left:1em"><input type="checkbox" id="show-labels" checked> Labels</label>
|
|
188
|
+
<span style="margin-left:1em">""" + ecu_name + r"""</span>
|
|
189
|
+
</div>
|
|
190
|
+
<svg id="graph"></svg>
|
|
191
|
+
</div>
|
|
192
|
+
<aside id="panel">
|
|
193
|
+
<h1>UDS Spec Inspector</h1>
|
|
194
|
+
<div id="info">
|
|
195
|
+
<p>Click a node to inspect. Drag nodes to reposition. Search to highlight.</p>
|
|
196
|
+
</div>
|
|
197
|
+
<h2>Legend</h2>
|
|
198
|
+
<ul id="legend">
|
|
199
|
+
<li><span style="background:#2a6e2a"></span>Session</li>
|
|
200
|
+
<li><span style="background:#996600"></span>Security level</li>
|
|
201
|
+
<li><span style="background:#334466"></span>Service</li>
|
|
202
|
+
<li><span style="background:#aa2222"></span>DID</li>
|
|
203
|
+
<li><span style="background:#664488"></span>Routine</li>
|
|
204
|
+
</ul>
|
|
205
|
+
<h2>Statistics</h2>
|
|
206
|
+
<div id="stats"></div>
|
|
207
|
+
</aside>
|
|
208
|
+
</div>
|
|
209
|
+
<script>
|
|
210
|
+
const data = """ + json_blob + r""";
|
|
211
|
+
|
|
212
|
+
const colorMap = {
|
|
213
|
+
session: '#2a6e2a',
|
|
214
|
+
security: '#996600',
|
|
215
|
+
service: '#334466',
|
|
216
|
+
did: '#aa2222',
|
|
217
|
+
routine: '#664488',
|
|
218
|
+
};
|
|
219
|
+
const radiusMap = { session: 10, security: 10, service: 9, did: 7, routine: 7 };
|
|
220
|
+
|
|
221
|
+
const svg = d3.select('#graph');
|
|
222
|
+
let width = window.innerWidth - 320;
|
|
223
|
+
let height = window.innerHeight - 36;
|
|
224
|
+
svg.attr('viewBox', [0, 0, width, height]);
|
|
225
|
+
|
|
226
|
+
const g = svg.append('g');
|
|
227
|
+
const linkSel = g.append('g').selectAll('line').data(data.links).enter()
|
|
228
|
+
.append('line').attr('class', 'link');
|
|
229
|
+
const nodeSel = g.append('g').selectAll('circle').data(data.nodes).enter()
|
|
230
|
+
.append('circle').attr('class', 'node')
|
|
231
|
+
.attr('r', d => radiusMap[d.type] || 6)
|
|
232
|
+
.attr('fill', d => colorMap[d.type] || '#888')
|
|
233
|
+
.on('click', (e, d) => inspect(d));
|
|
234
|
+
const labelSel = g.append('g').selectAll('text').data(data.nodes).enter()
|
|
235
|
+
.append('text').attr('class', 'label')
|
|
236
|
+
.text(d => d.hex)
|
|
237
|
+
.attr('dx', 8).attr('dy', 3);
|
|
238
|
+
|
|
239
|
+
const sim = d3.forceSimulation(data.nodes)
|
|
240
|
+
.force('link', d3.forceLink(data.links).id(d => d.id).distance(60))
|
|
241
|
+
.force('charge', d3.forceManyBody().strength(-180))
|
|
242
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
243
|
+
.force('collide', d3.forceCollide().radius(d => (radiusMap[d.type] || 6) + 4))
|
|
244
|
+
.on('tick', () => {
|
|
245
|
+
linkSel.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
|
|
246
|
+
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
247
|
+
nodeSel.attr('cx', d => d.x).attr('cy', d => d.y);
|
|
248
|
+
labelSel.attr('x', d => d.x).attr('y', d => d.y);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
nodeSel.call(d3.drag()
|
|
252
|
+
.on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
253
|
+
.on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
|
|
254
|
+
.on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
svg.call(d3.zoom().scaleExtent([0.3, 4]).on('zoom', e => g.attr('transform', e.transform)));
|
|
258
|
+
|
|
259
|
+
function inspect(d) {
|
|
260
|
+
const neighbors = new Set([d.id]);
|
|
261
|
+
data.links.forEach(l => {
|
|
262
|
+
const sId = l.source.id || l.source;
|
|
263
|
+
const tId = l.target.id || l.target;
|
|
264
|
+
if (sId === d.id) neighbors.add(tId);
|
|
265
|
+
if (tId === d.id) neighbors.add(sId);
|
|
266
|
+
});
|
|
267
|
+
nodeSel.classed('dim', n => !neighbors.has(n.id));
|
|
268
|
+
linkSel.classed('highlight', l => {
|
|
269
|
+
const sId = l.source.id || l.source;
|
|
270
|
+
const tId = l.target.id || l.target;
|
|
271
|
+
return sId === d.id || tId === d.id;
|
|
272
|
+
});
|
|
273
|
+
let html = '<h2>' + escapeHtml(d.label) + '</h2>';
|
|
274
|
+
html += '<p><strong>Type:</strong> ' + d.type + '</p>';
|
|
275
|
+
html += '<p><strong>ID:</strong> <span class="hex">' + d.hex + '</span></p>';
|
|
276
|
+
if (d.sub_funcs !== undefined) html += '<p><strong>Sub-functions:</strong> ' + d.sub_funcs + '</p>';
|
|
277
|
+
if (d.nrcs !== undefined) html += '<p><strong>NRCs:</strong> ' + d.nrcs + '</p>';
|
|
278
|
+
if (d.read !== undefined) html += '<p><strong>Read:</strong> ' + (d.read ? '✓' : '—') + '</p>';
|
|
279
|
+
if (d.write !== undefined) html += '<p><strong>Write:</strong> ' + (d.write ? '✓' : '—') + '</p>';
|
|
280
|
+
if (d.ops) html += '<p><strong>Ops:</strong> ' + d.ops + '</p>';
|
|
281
|
+
document.getElementById('info').innerHTML = html;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function escapeHtml(s) {
|
|
285
|
+
return String(s).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
document.getElementById('btn-reset').onclick = () => {
|
|
289
|
+
nodeSel.classed('dim', false);
|
|
290
|
+
linkSel.classed('highlight', false);
|
|
291
|
+
document.getElementById('info').innerHTML = '<p>Click a node to inspect.</p>';
|
|
292
|
+
};
|
|
293
|
+
document.getElementById('search').oninput = e => {
|
|
294
|
+
const q = e.target.value.toLowerCase().trim();
|
|
295
|
+
if (!q) {
|
|
296
|
+
nodeSel.classed('dim', false);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
nodeSel.classed('dim', n => !(n.label.toLowerCase().includes(q) || n.hex.toLowerCase().includes(q)));
|
|
300
|
+
};
|
|
301
|
+
document.getElementById('show-labels').onchange = e => {
|
|
302
|
+
labelSel.style('display', e.target.checked ? 'block' : 'none');
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Stats
|
|
306
|
+
const counts = {};
|
|
307
|
+
data.nodes.forEach(n => counts[n.type] = (counts[n.type] || 0) + 1);
|
|
308
|
+
document.getElementById('stats').innerHTML =
|
|
309
|
+
'<ul style="padding-left:1em;list-style:disc;">' +
|
|
310
|
+
Object.entries(counts).map(([k, v]) =>
|
|
311
|
+
'<li><strong>' + k + ':</strong> ' + v + '</li>'
|
|
312
|
+
).join('') +
|
|
313
|
+
'<li><strong>links:</strong> ' + data.links.length + '</li></ul>';
|
|
314
|
+
|
|
315
|
+
window.addEventListener('resize', () => {
|
|
316
|
+
width = window.innerWidth - 320;
|
|
317
|
+
height = window.innerHeight - 36;
|
|
318
|
+
svg.attr('viewBox', [0, 0, width, height]);
|
|
319
|
+
sim.force('center', d3.forceCenter(width / 2, height / 2)).alpha(0.3).restart();
|
|
320
|
+
});
|
|
321
|
+
</script>
|
|
322
|
+
</body>
|
|
323
|
+
</html>
|
|
324
|
+
"""
|