@graphty/graph-io 0.3.47 → 0.3.49

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.
Files changed (770) hide show
  1. package/dist/chunks/{approximations-wWjCRzKj.js → approximations-Bzn3L-je.js} +2 -2
  2. package/dist/chunks/{approximations-wWjCRzKj.js.map → approximations-Bzn3L-je.js.map} +1 -1
  3. package/dist/chunks/bends-BGXjv_U4.js +129 -0
  4. package/dist/chunks/bends-BGXjv_U4.js.map +1 -0
  5. package/dist/chunks/{block-DiGq58c3.js → block-iptrlMBb.js} +947 -134
  6. package/dist/chunks/block-iptrlMBb.js.map +1 -0
  7. package/dist/chunks/choose-CczZJaX_.js +717 -0
  8. package/dist/chunks/choose-CczZJaX_.js.map +1 -0
  9. package/dist/chunks/codes-BE2xir2k.js +157 -0
  10. package/dist/chunks/codes-BE2xir2k.js.map +1 -0
  11. package/dist/chunks/{constants-BKwqelYD.js → constants-CKkYLPtt.js} +2 -2
  12. package/dist/chunks/{constants-BKwqelYD.js.map → constants-CKkYLPtt.js.map} +1 -1
  13. package/dist/chunks/context-C3p0RQxR.js +664 -0
  14. package/dist/chunks/context-C3p0RQxR.js.map +1 -0
  15. package/dist/chunks/{convert-BcQ8_jMo.js → convert-Ct2zelbQ.js} +7 -2
  16. package/dist/chunks/convert-Ct2zelbQ.js.map +1 -0
  17. package/dist/chunks/credentials-DXHx7nwR.js +349 -0
  18. package/dist/chunks/credentials-DXHx7nwR.js.map +1 -0
  19. package/dist/chunks/cx-export-Bt6NoqTQ.js +259 -0
  20. package/dist/chunks/cx-export-Bt6NoqTQ.js.map +1 -0
  21. package/dist/chunks/descriptor-B28U25x4.js +60 -0
  22. package/dist/chunks/descriptor-B28U25x4.js.map +1 -0
  23. package/dist/chunks/descriptor-C-YDhtsn.js +48 -0
  24. package/dist/chunks/descriptor-C-YDhtsn.js.map +1 -0
  25. package/dist/chunks/descriptor-DLA5kINx.js +142 -0
  26. package/dist/chunks/descriptor-DLA5kINx.js.map +1 -0
  27. package/dist/chunks/{emit-CwU8cUmG.js → emit-BwHQkZle.js} +1690 -103
  28. package/dist/chunks/emit-BwHQkZle.js.map +1 -0
  29. package/dist/chunks/{escape-CgzxiYPg.js → escape-CuCIzNMB.js} +12 -12
  30. package/dist/chunks/{escape-CgzxiYPg.js.map → escape-CuCIzNMB.js.map} +1 -1
  31. package/dist/chunks/{export-BvqAXCKh.js → export-Cha4dRFz.js} +48 -1780
  32. package/dist/chunks/export-Cha4dRFz.js.map +1 -0
  33. package/dist/chunks/{exporter--Mk9gXCJ.js → exporter-BP6otMO6.js} +6 -4
  34. package/dist/chunks/{exporter--Mk9gXCJ.js.map → exporter-BP6otMO6.js.map} +1 -1
  35. package/dist/chunks/{exporter-CrDWIyiq.js → exporter-D2CETOMK.js} +1222 -154
  36. package/dist/chunks/exporter-D2CETOMK.js.map +1 -0
  37. package/dist/chunks/{exporter-DndFlGSa.js → exporter-D9yMtocA.js} +7 -6
  38. package/dist/chunks/{exporter-DndFlGSa.js.map → exporter-D9yMtocA.js.map} +1 -1
  39. package/dist/chunks/exporter-_jrBTnos.js +1009 -0
  40. package/dist/chunks/exporter-_jrBTnos.js.map +1 -0
  41. package/dist/chunks/{exporter-CJgfhF-l.js → exporter-nDfxIK5D.js} +9 -7
  42. package/dist/chunks/{exporter-CJgfhF-l.js.map → exporter-nDfxIK5D.js.map} +1 -1
  43. package/dist/chunks/format-BXiAb-1h.js +408 -0
  44. package/dist/chunks/format-BXiAb-1h.js.map +1 -0
  45. package/dist/chunks/{generic-Dq1ntt9q.js → generic-ymWCETAJ.js} +387 -31
  46. package/dist/chunks/generic-ymWCETAJ.js.map +1 -0
  47. package/dist/chunks/{importer-CMmeMWlC.js → importer-B92x1xY5.js} +15 -263
  48. package/dist/chunks/importer-B92x1xY5.js.map +1 -0
  49. package/dist/chunks/{importer-DiZRmOQG.js → importer-CT9mJ1nn.js} +328 -90
  50. package/dist/chunks/importer-CT9mJ1nn.js.map +1 -0
  51. package/dist/chunks/{importer-COTK9Tsl.js → importer-DaLTjbds.js} +116 -1099
  52. package/dist/chunks/importer-DaLTjbds.js.map +1 -0
  53. package/dist/chunks/{importer-iFYPEKaJ.js → importer-GREKMdIf.js} +28 -16
  54. package/dist/chunks/importer-GREKMdIf.js.map +1 -0
  55. package/dist/chunks/{importer-BSvfrX_h.js → importer-PqaaKKnY.js} +23 -1016
  56. package/dist/chunks/importer-PqaaKKnY.js.map +1 -0
  57. package/dist/chunks/{importer-yh_aBCU6.js → importer-Vn5QvmmJ.js} +66 -12
  58. package/dist/chunks/importer-Vn5QvmmJ.js.map +1 -0
  59. package/dist/chunks/{importer-e6LGpqHB.js → importer-vRq4h3j5.js} +1470 -354
  60. package/dist/chunks/importer-vRq4h3j5.js.map +1 -0
  61. package/dist/chunks/input-CAxt_aV-.js +1397 -0
  62. package/dist/chunks/input-CAxt_aV-.js.map +1 -0
  63. package/dist/chunks/{json-elements-DJZRZ6jd.js → json-elements-Cq76WjnZ.js} +4 -4
  64. package/dist/chunks/{json-elements-DJZRZ6jd.js.map → json-elements-Cq76WjnZ.js.map} +1 -1
  65. package/dist/chunks/{lexicon-CT69Kd87.js → lexicon-Clf4QIyl.js} +299 -77
  66. package/dist/chunks/lexicon-Clf4QIyl.js.map +1 -0
  67. package/dist/chunks/{ontology-export-G4MqRdux.js → ontology-export-OMo91mb0.js} +4 -3
  68. package/dist/chunks/{ontology-export-G4MqRdux.js.map → ontology-export-OMo91mb0.js.map} +1 -1
  69. package/dist/chunks/{records-CiaWZrFH.js → records-NJiYvekn.js} +2 -2
  70. package/dist/chunks/{records-CiaWZrFH.js.map → records-NJiYvekn.js.map} +1 -1
  71. package/dist/chunks/{format-BpQBx8MZ.js → report-CwMfYa9J.js} +70 -127
  72. package/dist/chunks/report-CwMfYa9J.js.map +1 -0
  73. package/dist/chunks/round-trip-Zm3_ivgq.js +79 -0
  74. package/dist/chunks/round-trip-Zm3_ivgq.js.map +1 -0
  75. package/dist/chunks/sigv4-EokFic1V.js +247 -0
  76. package/dist/chunks/sigv4-EokFic1V.js.map +1 -0
  77. package/dist/chunks/{styles-BQnsga7p.js → styles-CCs214_3.js} +1300 -168
  78. package/dist/chunks/styles-CCs214_3.js.map +1 -0
  79. package/dist/chunks/styles-Cm62yyQX.js +2812 -0
  80. package/dist/chunks/styles-Cm62yyQX.js.map +1 -0
  81. package/dist/chunks/styles-DPQQIig4.js +1928 -0
  82. package/dist/chunks/styles-DPQQIig4.js.map +1 -0
  83. package/dist/chunks/styles-yed-UMFdNqze.js +1213 -0
  84. package/dist/chunks/styles-yed-UMFdNqze.js.map +1 -0
  85. package/dist/chunks/{text-CzJNFOS9.js → text-ystE2bYi.js} +2 -2
  86. package/dist/chunks/{text-CzJNFOS9.js.map → text-ystE2bYi.js.map} +1 -1
  87. package/dist/csv.js +7 -5
  88. package/dist/csv.js.map +1 -1
  89. package/dist/cx.js +5 -4
  90. package/dist/cx.js.map +1 -1
  91. package/dist/cx2.js +2 -2
  92. package/dist/cys.js +1 -1
  93. package/dist/dot.js +5 -4
  94. package/dist/dot.js.map +1 -1
  95. package/dist/gexf.js +42 -10
  96. package/dist/gexf.js.map +1 -1
  97. package/dist/gml.js +850 -193
  98. package/dist/gml.js.map +1 -1
  99. package/dist/graph-io.js +1189 -220
  100. package/dist/graph-io.js.map +1 -1
  101. package/dist/graphml.js +3 -3
  102. package/dist/json.js +3 -2
  103. package/dist/neo4j.js +6 -4
  104. package/dist/neo4j.js.map +1 -1
  105. package/dist/obo.js +1 -1
  106. package/dist/pajek.js +1 -1
  107. package/dist/positions.js +45 -3
  108. package/dist/positions.js.map +1 -1
  109. package/dist/sources-cypher.d.ts +1 -0
  110. package/dist/sources-cypher.js +1351 -0
  111. package/dist/sources-cypher.js.map +1 -0
  112. package/dist/sources-ndex.d.ts +1 -0
  113. package/dist/sources-ndex.js +278 -0
  114. package/dist/sources-ndex.js.map +1 -0
  115. package/dist/sources-rest.d.ts +1 -0
  116. package/dist/sources-rest.js +1377 -0
  117. package/dist/sources-rest.js.map +1 -0
  118. package/dist/sources-sparql.d.ts +1 -0
  119. package/dist/sources-sparql.js +405 -0
  120. package/dist/sources-sparql.js.map +1 -0
  121. package/dist/src/common/report.d.ts +6 -3
  122. package/dist/src/common/report.d.ts.map +1 -1
  123. package/dist/src/common/report.js +15 -9
  124. package/dist/src/common/report.js.map +1 -1
  125. package/dist/src/formats/cx/importer.d.ts.map +1 -1
  126. package/dist/src/formats/cx/importer.js +5 -1
  127. package/dist/src/formats/cx/importer.js.map +1 -1
  128. package/dist/src/formats/cx/styles.d.ts +24 -1
  129. package/dist/src/formats/cx/styles.d.ts.map +1 -1
  130. package/dist/src/formats/cx/styles.js +118 -11
  131. package/dist/src/formats/cx/styles.js.map +1 -1
  132. package/dist/src/formats/cx2/importer.d.ts.map +1 -1
  133. package/dist/src/formats/cx2/importer.js +1 -0
  134. package/dist/src/formats/cx2/importer.js.map +1 -1
  135. package/dist/src/formats/cx2/styles.d.ts.map +1 -1
  136. package/dist/src/formats/cx2/styles.js +3 -1
  137. package/dist/src/formats/cx2/styles.js.map +1 -1
  138. package/dist/src/formats/cys/importer.d.ts.map +1 -1
  139. package/dist/src/formats/cys/importer.js +1 -0
  140. package/dist/src/formats/cys/importer.js.map +1 -1
  141. package/dist/src/formats/cys/styles.d.ts +2 -0
  142. package/dist/src/formats/cys/styles.d.ts.map +1 -1
  143. package/dist/src/formats/cys/styles.js +15 -7
  144. package/dist/src/formats/cys/styles.js.map +1 -1
  145. package/dist/src/formats/dot/drawing.d.ts +98 -0
  146. package/dist/src/formats/dot/drawing.d.ts.map +1 -0
  147. package/dist/src/formats/dot/drawing.js +734 -0
  148. package/dist/src/formats/dot/drawing.js.map +1 -0
  149. package/dist/src/formats/dot/importer.d.ts.map +1 -1
  150. package/dist/src/formats/dot/importer.js +108 -31
  151. package/dist/src/formats/dot/importer.js.map +1 -1
  152. package/dist/src/formats/dot/label-places.d.ts +34 -0
  153. package/dist/src/formats/dot/label-places.d.ts.map +1 -0
  154. package/dist/src/formats/dot/label-places.js +179 -0
  155. package/dist/src/formats/dot/label-places.js.map +1 -0
  156. package/dist/src/formats/dot/record.d.ts +50 -0
  157. package/dist/src/formats/dot/record.d.ts.map +1 -0
  158. package/dist/src/formats/dot/record.js +275 -0
  159. package/dist/src/formats/dot/record.js.map +1 -0
  160. package/dist/src/formats/dot/styles.d.ts +62 -2
  161. package/dist/src/formats/dot/styles.d.ts.map +1 -1
  162. package/dist/src/formats/dot/styles.js +1457 -120
  163. package/dist/src/formats/dot/styles.js.map +1 -1
  164. package/dist/src/formats/gexf/dialog-metrics.d.ts +15 -0
  165. package/dist/src/formats/gexf/dialog-metrics.d.ts.map +1 -0
  166. package/dist/src/formats/gexf/dialog-metrics.js +59 -0
  167. package/dist/src/formats/gexf/dialog-metrics.js.map +1 -0
  168. package/dist/src/formats/gexf/importer.d.ts +7 -0
  169. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  170. package/dist/src/formats/gexf/importer.js +41 -5
  171. package/dist/src/formats/gexf/importer.js.map +1 -1
  172. package/dist/src/formats/gexf/preview.d.ts +77 -0
  173. package/dist/src/formats/gexf/preview.d.ts.map +1 -0
  174. package/dist/src/formats/gexf/preview.js +191 -0
  175. package/dist/src/formats/gexf/preview.js.map +1 -0
  176. package/dist/src/formats/gexf/schema.d.ts.map +1 -1
  177. package/dist/src/formats/gexf/schema.js +2 -1
  178. package/dist/src/formats/gexf/schema.js.map +1 -1
  179. package/dist/src/formats/gexf/styles.d.ts +60 -16
  180. package/dist/src/formats/gexf/styles.d.ts.map +1 -1
  181. package/dist/src/formats/gexf/styles.js +883 -131
  182. package/dist/src/formats/gexf/styles.js.map +1 -1
  183. package/dist/src/formats/gml/importer.d.ts +5 -2
  184. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  185. package/dist/src/formats/gml/importer.js +258 -71
  186. package/dist/src/formats/gml/importer.js.map +1 -1
  187. package/dist/src/formats/gml/index.d.ts +1 -1
  188. package/dist/src/formats/gml/index.d.ts.map +1 -1
  189. package/dist/src/formats/gml/index.js +1 -1
  190. package/dist/src/formats/gml/index.js.map +1 -1
  191. package/dist/src/formats/gml/positions.d.ts +7 -0
  192. package/dist/src/formats/gml/positions.d.ts.map +1 -1
  193. package/dist/src/formats/gml/positions.js +29 -7
  194. package/dist/src/formats/gml/positions.js.map +1 -1
  195. package/dist/src/formats/gml/styles.d.ts.map +1 -1
  196. package/dist/src/formats/gml/styles.js +756 -103
  197. package/dist/src/formats/gml/styles.js.map +1 -1
  198. package/dist/src/formats/graphml/styles-plain.d.ts.map +1 -1
  199. package/dist/src/formats/graphml/styles-plain.js +8 -12
  200. package/dist/src/formats/graphml/styles-plain.js.map +1 -1
  201. package/dist/src/formats/graphml/styles-yed.d.ts +21 -15
  202. package/dist/src/formats/graphml/styles-yed.d.ts.map +1 -1
  203. package/dist/src/formats/graphml/styles-yed.js +749 -148
  204. package/dist/src/formats/graphml/styles-yed.js.map +1 -1
  205. package/dist/src/formats/graphml/styles-yfiles3.d.ts +1 -1
  206. package/dist/src/formats/graphml/styles-yfiles3.d.ts.map +1 -1
  207. package/dist/src/formats/graphml/styles-yfiles3.js +429 -64
  208. package/dist/src/formats/graphml/styles-yfiles3.js.map +1 -1
  209. package/dist/src/formats/graphml/yfiles.d.ts +3 -3
  210. package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
  211. package/dist/src/formats/graphml/yfiles.js +16 -6
  212. package/dist/src/formats/graphml/yfiles.js.map +1 -1
  213. package/dist/src/formats/json/dialect.d.ts +3 -2
  214. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  215. package/dist/src/formats/json/dialect.js +39 -5
  216. package/dist/src/formats/json/dialect.js.map +1 -1
  217. package/dist/src/formats/json/importer.d.ts +13 -2
  218. package/dist/src/formats/json/importer.d.ts.map +1 -1
  219. package/dist/src/formats/json/importer.js +42 -6
  220. package/dist/src/formats/json/importer.js.map +1 -1
  221. package/dist/src/formats/json/index.d.ts +1 -1
  222. package/dist/src/formats/json/index.d.ts.map +1 -1
  223. package/dist/src/formats/json/index.js +1 -1
  224. package/dist/src/formats/json/index.js.map +1 -1
  225. package/dist/src/formats/json/positions.d.ts +5 -2
  226. package/dist/src/formats/json/positions.d.ts.map +1 -1
  227. package/dist/src/formats/json/positions.js +20 -4
  228. package/dist/src/formats/json/positions.js.map +1 -1
  229. package/dist/src/formats/json/styles.d.ts +12 -1
  230. package/dist/src/formats/json/styles.d.ts.map +1 -1
  231. package/dist/src/formats/json/styles.js +1755 -136
  232. package/dist/src/formats/json/styles.js.map +1 -1
  233. package/dist/src/formats/pajek/draw.d.ts +96 -0
  234. package/dist/src/formats/pajek/draw.d.ts.map +1 -0
  235. package/dist/src/formats/pajek/draw.js +372 -0
  236. package/dist/src/formats/pajek/draw.js.map +1 -0
  237. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  238. package/dist/src/formats/pajek/exporter.js +70 -51
  239. package/dist/src/formats/pajek/exporter.js.map +1 -1
  240. package/dist/src/formats/pajek/importer.d.ts +9 -0
  241. package/dist/src/formats/pajek/importer.d.ts.map +1 -1
  242. package/dist/src/formats/pajek/importer.js +233 -102
  243. package/dist/src/formats/pajek/importer.js.map +1 -1
  244. package/dist/src/formats/pajek/index.d.ts +1 -0
  245. package/dist/src/formats/pajek/index.d.ts.map +1 -1
  246. package/dist/src/formats/pajek/index.js.map +1 -1
  247. package/dist/src/formats/pajek/styles.d.ts +15 -7
  248. package/dist/src/formats/pajek/styles.d.ts.map +1 -1
  249. package/dist/src/formats/pajek/styles.js +1279 -114
  250. package/dist/src/formats/pajek/styles.js.map +1 -1
  251. package/dist/src/formats/pajek/syntax.d.ts +10 -1
  252. package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
  253. package/dist/src/formats/pajek/syntax.js +26 -2
  254. package/dist/src/formats/pajek/syntax.js.map +1 -1
  255. package/dist/src/formats/xgmml/emit.d.ts +1 -0
  256. package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
  257. package/dist/src/formats/xgmml/emit.js +65 -5
  258. package/dist/src/formats/xgmml/emit.js.map +1 -1
  259. package/dist/src/formats/xgmml/styles.d.ts +23 -3
  260. package/dist/src/formats/xgmml/styles.d.ts.map +1 -1
  261. package/dist/src/formats/xgmml/styles.js +278 -60
  262. package/dist/src/formats/xgmml/styles.js.map +1 -1
  263. package/dist/src/index.d.ts +10 -2
  264. package/dist/src/index.d.ts.map +1 -1
  265. package/dist/src/index.js +11 -2
  266. package/dist/src/index.js.map +1 -1
  267. package/dist/src/positions/convert.d.ts.map +1 -1
  268. package/dist/src/positions/convert.js +11 -1
  269. package/dist/src/positions/convert.js.map +1 -1
  270. package/dist/src/positions/extract.d.ts.map +1 -1
  271. package/dist/src/positions/extract.js +53 -1
  272. package/dist/src/positions/extract.js.map +1 -1
  273. package/dist/src/positions/index.d.ts +1 -1
  274. package/dist/src/positions/index.d.ts.map +1 -1
  275. package/dist/src/positions/types.d.ts +59 -0
  276. package/dist/src/positions/types.d.ts.map +1 -1
  277. package/dist/src/registry.d.ts +173 -1
  278. package/dist/src/registry.d.ts.map +1 -1
  279. package/dist/src/registry.js +196 -1
  280. package/dist/src/registry.js.map +1 -1
  281. package/dist/src/sources/builtins.d.ts +11 -0
  282. package/dist/src/sources/builtins.d.ts.map +1 -0
  283. package/dist/src/sources/builtins.js +53 -0
  284. package/dist/src/sources/builtins.js.map +1 -0
  285. package/dist/src/sources/codes.d.ts +230 -0
  286. package/dist/src/sources/codes.d.ts.map +1 -0
  287. package/dist/src/sources/codes.js +58 -0
  288. package/dist/src/sources/codes.js.map +1 -0
  289. package/dist/src/sources/context.d.ts +112 -0
  290. package/dist/src/sources/context.d.ts.map +1 -0
  291. package/dist/src/sources/context.js +740 -0
  292. package/dist/src/sources/context.js.map +1 -0
  293. package/dist/src/sources/credentials.d.ts +179 -0
  294. package/dist/src/sources/credentials.d.ts.map +1 -0
  295. package/dist/src/sources/credentials.js +301 -0
  296. package/dist/src/sources/credentials.js.map +1 -0
  297. package/dist/src/sources/cypher/descriptor.d.ts +21 -0
  298. package/dist/src/sources/cypher/descriptor.d.ts.map +1 -0
  299. package/dist/src/sources/cypher/descriptor.js +124 -0
  300. package/dist/src/sources/cypher/descriptor.js.map +1 -0
  301. package/dist/src/sources/cypher/flatten.d.ts +72 -0
  302. package/dist/src/sources/cypher/flatten.d.ts.map +1 -0
  303. package/dist/src/sources/cypher/flatten.js +279 -0
  304. package/dist/src/sources/cypher/flatten.js.map +1 -0
  305. package/dist/src/sources/cypher/index.d.ts +33 -0
  306. package/dist/src/sources/cypher/index.d.ts.map +1 -0
  307. package/dist/src/sources/cypher/index.js +511 -0
  308. package/dist/src/sources/cypher/index.js.map +1 -0
  309. package/dist/src/sources/cypher/params.d.ts +40 -0
  310. package/dist/src/sources/cypher/params.d.ts.map +1 -0
  311. package/dist/src/sources/cypher/params.js +6 -0
  312. package/dist/src/sources/cypher/params.js.map +1 -0
  313. package/dist/src/sources/cypher/profiles.d.ts +44 -0
  314. package/dist/src/sources/cypher/profiles.d.ts.map +1 -0
  315. package/dist/src/sources/cypher/profiles.js +61 -0
  316. package/dist/src/sources/cypher/profiles.js.map +1 -0
  317. package/dist/src/sources/cypher/query-builder.d.ts +123 -0
  318. package/dist/src/sources/cypher/query-builder.d.ts.map +1 -0
  319. package/dist/src/sources/cypher/query-builder.js +267 -0
  320. package/dist/src/sources/cypher/query-builder.js.map +1 -0
  321. package/dist/src/sources/cypher/transports/bolt.d.ts +21 -0
  322. package/dist/src/sources/cypher/transports/bolt.d.ts.map +1 -0
  323. package/dist/src/sources/cypher/transports/bolt.js +185 -0
  324. package/dist/src/sources/cypher/transports/bolt.js.map +1 -0
  325. package/dist/src/sources/cypher/transports/neptune-analytics.d.ts +15 -0
  326. package/dist/src/sources/cypher/transports/neptune-analytics.d.ts.map +1 -0
  327. package/dist/src/sources/cypher/transports/neptune-analytics.js +38 -0
  328. package/dist/src/sources/cypher/transports/neptune-analytics.js.map +1 -0
  329. package/dist/src/sources/cypher/transports/neptune-https.d.ts +31 -0
  330. package/dist/src/sources/cypher/transports/neptune-https.d.ts.map +1 -0
  331. package/dist/src/sources/cypher/transports/neptune-https.js +104 -0
  332. package/dist/src/sources/cypher/transports/neptune-https.js.map +1 -0
  333. package/dist/src/sources/cypher/transports/query-api.d.ts +14 -0
  334. package/dist/src/sources/cypher/transports/query-api.d.ts.map +1 -0
  335. package/dist/src/sources/cypher/transports/query-api.js +80 -0
  336. package/dist/src/sources/cypher/transports/query-api.js.map +1 -0
  337. package/dist/src/sources/cypher/transports/transport.d.ts +78 -0
  338. package/dist/src/sources/cypher/transports/transport.d.ts.map +1 -0
  339. package/dist/src/sources/cypher/transports/transport.js +77 -0
  340. package/dist/src/sources/cypher/transports/transport.js.map +1 -0
  341. package/dist/src/sources/cypher/types-map.d.ts +91 -0
  342. package/dist/src/sources/cypher/types-map.d.ts.map +1 -0
  343. package/dist/src/sources/cypher/types-map.js +306 -0
  344. package/dist/src/sources/cypher/types-map.js.map +1 -0
  345. package/dist/src/sources/define.d.ts +47 -0
  346. package/dist/src/sources/define.d.ts.map +1 -0
  347. package/dist/src/sources/define.js +173 -0
  348. package/dist/src/sources/define.js.map +1 -0
  349. package/dist/src/sources/fail.d.ts +33 -0
  350. package/dist/src/sources/fail.d.ts.map +1 -0
  351. package/dist/src/sources/fail.js +49 -0
  352. package/dist/src/sources/fail.js.map +1 -0
  353. package/dist/src/sources/ids.d.ts +69 -0
  354. package/dist/src/sources/ids.d.ts.map +1 -0
  355. package/dist/src/sources/ids.js +69 -0
  356. package/dist/src/sources/ids.js.map +1 -0
  357. package/dist/src/sources/ndex/client.d.ts +31 -0
  358. package/dist/src/sources/ndex/client.d.ts.map +1 -0
  359. package/dist/src/sources/ndex/client.js +90 -0
  360. package/dist/src/sources/ndex/client.js.map +1 -0
  361. package/dist/src/sources/ndex/descriptor.d.ts +10 -0
  362. package/dist/src/sources/ndex/descriptor.d.ts.map +1 -0
  363. package/dist/src/sources/ndex/descriptor.js +55 -0
  364. package/dist/src/sources/ndex/descriptor.js.map +1 -0
  365. package/dist/src/sources/ndex/expand.d.ts +20 -0
  366. package/dist/src/sources/ndex/expand.d.ts.map +1 -0
  367. package/dist/src/sources/ndex/expand.js +87 -0
  368. package/dist/src/sources/ndex/expand.js.map +1 -0
  369. package/dist/src/sources/ndex/index.d.ts +25 -0
  370. package/dist/src/sources/ndex/index.d.ts.map +1 -0
  371. package/dist/src/sources/ndex/index.js +191 -0
  372. package/dist/src/sources/ndex/index.js.map +1 -0
  373. package/dist/src/sources/ndex/params.d.ts +17 -0
  374. package/dist/src/sources/ndex/params.d.ts.map +1 -0
  375. package/dist/src/sources/ndex/params.js +6 -0
  376. package/dist/src/sources/ndex/params.js.map +1 -0
  377. package/dist/src/sources/ndex/share-url.d.ts +40 -0
  378. package/dist/src/sources/ndex/share-url.d.ts.map +1 -0
  379. package/dist/src/sources/ndex/share-url.js +64 -0
  380. package/dist/src/sources/ndex/share-url.js.map +1 -0
  381. package/dist/src/sources/params.d.ts +31 -0
  382. package/dist/src/sources/params.d.ts.map +1 -0
  383. package/dist/src/sources/params.js +6 -0
  384. package/dist/src/sources/params.js.map +1 -0
  385. package/dist/src/sources/registry.d.ts +118 -0
  386. package/dist/src/sources/registry.d.ts.map +1 -0
  387. package/dist/src/sources/registry.js +193 -0
  388. package/dist/src/sources/registry.js.map +1 -0
  389. package/dist/src/sources/rest/descriptor.d.ts +16 -0
  390. package/dist/src/sources/rest/descriptor.d.ts.map +1 -0
  391. package/dist/src/sources/rest/descriptor.js +76 -0
  392. package/dist/src/sources/rest/descriptor.js.map +1 -0
  393. package/dist/src/sources/rest/index.d.ts +14 -0
  394. package/dist/src/sources/rest/index.d.ts.map +1 -0
  395. package/dist/src/sources/rest/index.js +13 -0
  396. package/dist/src/sources/rest/index.js.map +1 -0
  397. package/dist/src/sources/rest/mapping.d.ts +30 -0
  398. package/dist/src/sources/rest/mapping.d.ts.map +1 -0
  399. package/dist/src/sources/rest/mapping.js +362 -0
  400. package/dist/src/sources/rest/mapping.js.map +1 -0
  401. package/dist/src/sources/rest/openalex.d.ts +27 -0
  402. package/dist/src/sources/rest/openalex.d.ts.map +1 -0
  403. package/dist/src/sources/rest/openalex.js +269 -0
  404. package/dist/src/sources/rest/openalex.js.map +1 -0
  405. package/dist/src/sources/rest/paging.d.ts +53 -0
  406. package/dist/src/sources/rest/paging.d.ts.map +1 -0
  407. package/dist/src/sources/rest/paging.js +138 -0
  408. package/dist/src/sources/rest/paging.js.map +1 -0
  409. package/dist/src/sources/rest/params.d.ts +216 -0
  410. package/dist/src/sources/rest/params.d.ts.map +1 -0
  411. package/dist/src/sources/rest/params.js +7 -0
  412. package/dist/src/sources/rest/params.js.map +1 -0
  413. package/dist/src/sources/rest/records.d.ts +41 -0
  414. package/dist/src/sources/rest/records.d.ts.map +1 -0
  415. package/dist/src/sources/rest/records.js +240 -0
  416. package/dist/src/sources/rest/records.js.map +1 -0
  417. package/dist/src/sources/rest/samples/deps-dev.d.ts +10 -0
  418. package/dist/src/sources/rest/samples/deps-dev.d.ts.map +1 -0
  419. package/dist/src/sources/rest/samples/deps-dev.js +47 -0
  420. package/dist/src/sources/rest/samples/deps-dev.js.map +1 -0
  421. package/dist/src/sources/rest/samples/string.d.ts +9 -0
  422. package/dist/src/sources/rest/samples/string.d.ts.map +1 -0
  423. package/dist/src/sources/rest/samples/string.js +42 -0
  424. package/dist/src/sources/rest/samples/string.js.map +1 -0
  425. package/dist/src/sources/rest/selectors.d.ts +41 -0
  426. package/dist/src/sources/rest/selectors.d.ts.map +1 -0
  427. package/dist/src/sources/rest/selectors.js +81 -0
  428. package/dist/src/sources/rest/selectors.js.map +1 -0
  429. package/dist/src/sources/rest/source.d.ts +67 -0
  430. package/dist/src/sources/rest/source.d.ts.map +1 -0
  431. package/dist/src/sources/rest/source.js +650 -0
  432. package/dist/src/sources/rest/source.js.map +1 -0
  433. package/dist/src/sources/rest/templates.d.ts +38 -0
  434. package/dist/src/sources/rest/templates.d.ts.map +1 -0
  435. package/dist/src/sources/rest/templates.js +125 -0
  436. package/dist/src/sources/rest/templates.js.map +1 -0
  437. package/dist/src/sources/run.d.ts +93 -0
  438. package/dist/src/sources/run.d.ts.map +1 -0
  439. package/dist/src/sources/run.js +673 -0
  440. package/dist/src/sources/run.js.map +1 -0
  441. package/dist/src/sources/sha256.d.ts +12 -0
  442. package/dist/src/sources/sha256.d.ts.map +1 -0
  443. package/dist/src/sources/sha256.js +95 -0
  444. package/dist/src/sources/sha256.js.map +1 -0
  445. package/dist/src/sources/sigv4.d.ts +26 -0
  446. package/dist/src/sources/sigv4.d.ts.map +1 -0
  447. package/dist/src/sources/sigv4.js +184 -0
  448. package/dist/src/sources/sigv4.js.map +1 -0
  449. package/dist/src/sources/sparql/descriptor.d.ts +10 -0
  450. package/dist/src/sources/sparql/descriptor.d.ts.map +1 -0
  451. package/dist/src/sources/sparql/descriptor.js +64 -0
  452. package/dist/src/sources/sparql/descriptor.js.map +1 -0
  453. package/dist/src/sources/sparql/index.d.ts +28 -0
  454. package/dist/src/sources/sparql/index.d.ts.map +1 -0
  455. package/dist/src/sources/sparql/index.js +314 -0
  456. package/dist/src/sources/sparql/index.js.map +1 -0
  457. package/dist/src/sources/sparql/ntriples.d.ts +69 -0
  458. package/dist/src/sources/sparql/ntriples.d.ts.map +1 -0
  459. package/dist/src/sources/sparql/ntriples.js +180 -0
  460. package/dist/src/sources/sparql/ntriples.js.map +1 -0
  461. package/dist/src/sources/sparql/params.d.ts +27 -0
  462. package/dist/src/sources/sparql/params.d.ts.map +1 -0
  463. package/dist/src/sources/sparql/params.js +5 -0
  464. package/dist/src/sources/sparql/params.js.map +1 -0
  465. package/dist/src/sources/sparql/query.d.ts +72 -0
  466. package/dist/src/sources/sparql/query.d.ts.map +1 -0
  467. package/dist/src/sources/sparql/query.js +113 -0
  468. package/dist/src/sources/sparql/query.js.map +1 -0
  469. package/dist/src/sources/testing.d.ts +96 -0
  470. package/dist/src/sources/testing.d.ts.map +1 -0
  471. package/dist/src/sources/testing.js +138 -0
  472. package/dist/src/sources/testing.js.map +1 -0
  473. package/dist/src/sources/types.d.ts +696 -0
  474. package/dist/src/sources/types.d.ts.map +1 -0
  475. package/dist/src/sources/types.js +10 -0
  476. package/dist/src/sources/types.js.map +1 -0
  477. package/dist/src/styles/annotations.d.ts +61 -5
  478. package/dist/src/styles/annotations.d.ts.map +1 -1
  479. package/dist/src/styles/annotations.js +130 -22
  480. package/dist/src/styles/annotations.js.map +1 -1
  481. package/dist/src/styles/canon/bends.d.ts +46 -0
  482. package/dist/src/styles/canon/bends.d.ts.map +1 -1
  483. package/dist/src/styles/canon/bends.js +123 -0
  484. package/dist/src/styles/canon/bends.js.map +1 -1
  485. package/dist/src/styles/canon/color-tables.d.ts +6 -1
  486. package/dist/src/styles/canon/color-tables.d.ts.map +1 -1
  487. package/dist/src/styles/canon/color-tables.js +10 -4
  488. package/dist/src/styles/canon/color-tables.js.map +1 -1
  489. package/dist/src/styles/canon/color.d.ts +3 -1
  490. package/dist/src/styles/canon/color.d.ts.map +1 -1
  491. package/dist/src/styles/canon/color.js +103 -3
  492. package/dist/src/styles/canon/color.js.map +1 -1
  493. package/dist/src/styles/canon/enums.d.ts +2 -2
  494. package/dist/src/styles/canon/enums.d.ts.map +1 -1
  495. package/dist/src/styles/canon/enums.js +82 -7
  496. package/dist/src/styles/canon/enums.js.map +1 -1
  497. package/dist/src/styles/canon/graphviz.d.ts +12 -3
  498. package/dist/src/styles/canon/graphviz.d.ts.map +1 -1
  499. package/dist/src/styles/canon/graphviz.js +57 -9
  500. package/dist/src/styles/canon/graphviz.js.map +1 -1
  501. package/dist/src/styles/canon/number.d.ts +8 -0
  502. package/dist/src/styles/canon/number.d.ts.map +1 -1
  503. package/dist/src/styles/canon/number.js +18 -0
  504. package/dist/src/styles/canon/number.js.map +1 -1
  505. package/dist/src/styles/canon/text.d.ts +29 -2
  506. package/dist/src/styles/canon/text.d.ts.map +1 -1
  507. package/dist/src/styles/canon/text.js +156 -16
  508. package/dist/src/styles/canon/text.js.map +1 -1
  509. package/dist/src/styles/codes.d.ts +2 -0
  510. package/dist/src/styles/codes.d.ts.map +1 -1
  511. package/dist/src/styles/codes.js +2 -0
  512. package/dist/src/styles/codes.js.map +1 -1
  513. package/dist/src/styles/cyjs/cascade.d.ts.map +1 -1
  514. package/dist/src/styles/cyjs/cascade.js +1037 -35
  515. package/dist/src/styles/cyjs/cascade.js.map +1 -1
  516. package/dist/src/styles/cyjs/defaults-table.d.ts.map +1 -1
  517. package/dist/src/styles/cyjs/defaults-table.js +5 -0
  518. package/dist/src/styles/cyjs/defaults-table.js.map +1 -1
  519. package/dist/src/styles/cyjs/emit.d.ts.map +1 -1
  520. package/dist/src/styles/cyjs/emit.js +216 -12
  521. package/dist/src/styles/cyjs/emit.js.map +1 -1
  522. package/dist/src/styles/cyjs/face.d.ts +20 -0
  523. package/dist/src/styles/cyjs/face.d.ts.map +1 -0
  524. package/dist/src/styles/cyjs/face.js +332 -0
  525. package/dist/src/styles/cyjs/face.js.map +1 -0
  526. package/dist/src/styles/cyjs/mappers.d.ts +48 -2
  527. package/dist/src/styles/cyjs/mappers.d.ts.map +1 -1
  528. package/dist/src/styles/cyjs/mappers.js +463 -26
  529. package/dist/src/styles/cyjs/mappers.js.map +1 -1
  530. package/dist/src/styles/cyjs/routes.d.ts +53 -0
  531. package/dist/src/styles/cyjs/routes.d.ts.map +1 -0
  532. package/dist/src/styles/cyjs/routes.js +360 -0
  533. package/dist/src/styles/cyjs/routes.js.map +1 -0
  534. package/dist/src/styles/cytoscape/charts.d.ts +23 -0
  535. package/dist/src/styles/cytoscape/charts.d.ts.map +1 -0
  536. package/dist/src/styles/cytoscape/charts.js +560 -0
  537. package/dist/src/styles/cytoscape/charts.js.map +1 -0
  538. package/dist/src/styles/cytoscape/emit.d.ts.map +1 -1
  539. package/dist/src/styles/cytoscape/emit.js +204 -44
  540. package/dist/src/styles/cytoscape/emit.js.map +1 -1
  541. package/dist/src/styles/cytoscape/lexicon.d.ts +97 -3
  542. package/dist/src/styles/cytoscape/lexicon.d.ts.map +1 -1
  543. package/dist/src/styles/cytoscape/lexicon.js +413 -79
  544. package/dist/src/styles/cytoscape/lexicon.js.map +1 -1
  545. package/dist/src/styles/cytoscape/mappings.d.ts +30 -0
  546. package/dist/src/styles/cytoscape/mappings.d.ts.map +1 -1
  547. package/dist/src/styles/cytoscape/mappings.js +92 -0
  548. package/dist/src/styles/cytoscape/mappings.js.map +1 -1
  549. package/dist/src/styles/cytoscape/resolve.d.ts +46 -3
  550. package/dist/src/styles/cytoscape/resolve.d.ts.map +1 -1
  551. package/dist/src/styles/cytoscape/resolve.js +632 -22
  552. package/dist/src/styles/cytoscape/resolve.js.map +1 -1
  553. package/dist/src/styles/cytoscape/translate.d.ts.map +1 -1
  554. package/dist/src/styles/cytoscape/translate.js +10 -10
  555. package/dist/src/styles/cytoscape/translate.js.map +1 -1
  556. package/dist/src/styles/defaults.d.ts +50 -0
  557. package/dist/src/styles/defaults.d.ts.map +1 -1
  558. package/dist/src/styles/defaults.js +315 -37
  559. package/dist/src/styles/defaults.js.map +1 -1
  560. package/dist/src/styles/index.d.ts +4 -3
  561. package/dist/src/styles/index.d.ts.map +1 -1
  562. package/dist/src/styles/index.js +3 -2
  563. package/dist/src/styles/index.js.map +1 -1
  564. package/dist/src/styles/internal.d.ts +1 -0
  565. package/dist/src/styles/internal.d.ts.map +1 -1
  566. package/dist/src/styles/internal.js +1 -0
  567. package/dist/src/styles/internal.js.map +1 -1
  568. package/dist/src/styles/io/block.d.ts.map +1 -1
  569. package/dist/src/styles/io/block.js +9 -0
  570. package/dist/src/styles/io/block.js.map +1 -1
  571. package/dist/src/styles/io/generic.d.ts.map +1 -1
  572. package/dist/src/styles/io/generic.js +56 -16
  573. package/dist/src/styles/io/generic.js.map +1 -1
  574. package/dist/src/styles/io/round-trip.d.ts +56 -0
  575. package/dist/src/styles/io/round-trip.d.ts.map +1 -0
  576. package/dist/src/styles/io/round-trip.js +138 -0
  577. package/dist/src/styles/io/round-trip.js.map +1 -0
  578. package/dist/src/styles/io/writer.d.ts.map +1 -1
  579. package/dist/src/styles/io/writer.js +13 -4
  580. package/dist/src/styles/io/writer.js.map +1 -1
  581. package/dist/src/styles/notes.d.ts.map +1 -1
  582. package/dist/src/styles/notes.js +1 -0
  583. package/dist/src/styles/notes.js.map +1 -1
  584. package/dist/src/styles/plan/features.d.ts +1 -1
  585. package/dist/src/styles/plan/features.d.ts.map +1 -1
  586. package/dist/src/styles/plan/features.js +22 -2
  587. package/dist/src/styles/plan/features.js.map +1 -1
  588. package/dist/src/styles/plan/index.d.ts +1 -1
  589. package/dist/src/styles/plan/index.d.ts.map +1 -1
  590. package/dist/src/styles/plan/index.js +1 -1
  591. package/dist/src/styles/plan/index.js.map +1 -1
  592. package/dist/src/styles/plan/plan.d.ts.map +1 -1
  593. package/dist/src/styles/plan/plan.js +61 -4
  594. package/dist/src/styles/plan/plan.js.map +1 -1
  595. package/dist/src/styles/plan/resolve.d.ts +13 -1
  596. package/dist/src/styles/plan/resolve.d.ts.map +1 -1
  597. package/dist/src/styles/plan/resolve.js +53 -6
  598. package/dist/src/styles/plan/resolve.js.map +1 -1
  599. package/dist/src/styles/slots.d.ts +14 -0
  600. package/dist/src/styles/slots.d.ts.map +1 -0
  601. package/dist/src/styles/slots.js +20 -0
  602. package/dist/src/styles/slots.js.map +1 -0
  603. package/dist/src/styles/types.d.ts +638 -5
  604. package/dist/src/styles/types.d.ts.map +1 -1
  605. package/dist/src/styles/vocabulary.d.ts +3 -2
  606. package/dist/src/styles/vocabulary.d.ts.map +1 -1
  607. package/dist/src/styles/vocabulary.js +218 -1
  608. package/dist/src/styles/vocabulary.js.map +1 -1
  609. package/dist/src/types.d.ts +19 -5
  610. package/dist/src/types.d.ts.map +1 -1
  611. package/dist/src/types.js.map +1 -1
  612. package/dist/styles.js +29 -238
  613. package/dist/styles.js.map +1 -1
  614. package/dist/xgmml.js +3 -3
  615. package/docs/samples/people.json +6 -0
  616. package/package.json +53 -8
  617. package/src/common/report.ts +27 -7
  618. package/src/formats/cx/importer.ts +6 -1
  619. package/src/formats/cx/styles.ts +144 -9
  620. package/src/formats/cx2/importer.ts +1 -0
  621. package/src/formats/cx2/styles.ts +3 -1
  622. package/src/formats/cys/importer.ts +1 -0
  623. package/src/formats/cys/styles.ts +27 -5
  624. package/src/formats/dot/drawing.ts +799 -0
  625. package/src/formats/dot/importer.ts +120 -35
  626. package/src/formats/dot/label-places.ts +228 -0
  627. package/src/formats/dot/record.ts +336 -0
  628. package/src/formats/dot/styles.ts +1657 -154
  629. package/src/formats/gexf/dialog-metrics.ts +61 -0
  630. package/src/formats/gexf/importer.ts +50 -5
  631. package/src/formats/gexf/preview.ts +217 -0
  632. package/src/formats/gexf/schema.ts +2 -1
  633. package/src/formats/gexf/styles.ts +1144 -142
  634. package/src/formats/gml/importer.ts +291 -76
  635. package/src/formats/gml/index.ts +1 -1
  636. package/src/formats/gml/positions.ts +29 -7
  637. package/src/formats/gml/styles.ts +854 -111
  638. package/src/formats/graphml/styles-plain.ts +8 -23
  639. package/src/formats/graphml/styles-yed.ts +821 -159
  640. package/src/formats/graphml/styles-yfiles3.ts +485 -63
  641. package/src/formats/graphml/yfiles.ts +19 -6
  642. package/src/formats/json/dialect.ts +41 -5
  643. package/src/formats/json/importer.ts +59 -7
  644. package/src/formats/json/index.ts +1 -0
  645. package/src/formats/json/positions.ts +22 -5
  646. package/src/formats/json/styles.ts +2041 -185
  647. package/src/formats/pajek/draw.ts +458 -0
  648. package/src/formats/pajek/exporter.ts +81 -49
  649. package/src/formats/pajek/importer.ts +278 -103
  650. package/src/formats/pajek/index.ts +1 -0
  651. package/src/formats/pajek/styles.ts +1382 -133
  652. package/src/formats/pajek/syntax.ts +27 -2
  653. package/src/formats/xgmml/emit.ts +73 -5
  654. package/src/formats/xgmml/styles.ts +331 -71
  655. package/src/index.ts +94 -0
  656. package/src/positions/convert.ts +12 -1
  657. package/src/positions/extract.ts +55 -1
  658. package/src/positions/index.ts +1 -0
  659. package/src/positions/types.ts +60 -0
  660. package/src/registry.ts +326 -1
  661. package/src/sources/builtins.ts +63 -0
  662. package/src/sources/codes.ts +235 -0
  663. package/src/sources/context.ts +894 -0
  664. package/src/sources/credentials.ts +409 -0
  665. package/src/sources/cypher/descriptor.ts +131 -0
  666. package/src/sources/cypher/flatten.ts +323 -0
  667. package/src/sources/cypher/index.ts +628 -0
  668. package/src/sources/cypher/params.ts +41 -0
  669. package/src/sources/cypher/profiles.ts +78 -0
  670. package/src/sources/cypher/query-builder.ts +327 -0
  671. package/src/sources/cypher/transports/bolt.ts +228 -0
  672. package/src/sources/cypher/transports/neptune-analytics.ts +41 -0
  673. package/src/sources/cypher/transports/neptune-https.ts +122 -0
  674. package/src/sources/cypher/transports/query-api.ts +100 -0
  675. package/src/sources/cypher/transports/transport.ts +139 -0
  676. package/src/sources/cypher/types-map.ts +418 -0
  677. package/src/sources/define.ts +195 -0
  678. package/src/sources/fail.ts +70 -0
  679. package/src/sources/ids.ts +80 -0
  680. package/src/sources/ndex/client.ts +102 -0
  681. package/src/sources/ndex/descriptor.ts +64 -0
  682. package/src/sources/ndex/expand.ts +100 -0
  683. package/src/sources/ndex/index.ts +235 -0
  684. package/src/sources/ndex/params.ts +17 -0
  685. package/src/sources/ndex/share-url.ts +84 -0
  686. package/src/sources/params.ts +32 -0
  687. package/src/sources/registry.ts +325 -0
  688. package/src/sources/rest/descriptor.ts +79 -0
  689. package/src/sources/rest/index.ts +22 -0
  690. package/src/sources/rest/mapping.ts +401 -0
  691. package/src/sources/rest/openalex.ts +288 -0
  692. package/src/sources/rest/paging.ts +180 -0
  693. package/src/sources/rest/params.ts +243 -0
  694. package/src/sources/rest/records.ts +289 -0
  695. package/src/sources/rest/samples/deps-dev.ts +49 -0
  696. package/src/sources/rest/samples/string.ts +44 -0
  697. package/src/sources/rest/selectors.ts +91 -0
  698. package/src/sources/rest/source.ts +776 -0
  699. package/src/sources/rest/templates.ts +151 -0
  700. package/src/sources/run.ts +793 -0
  701. package/src/sources/sha256.ts +99 -0
  702. package/src/sources/sigv4.ts +209 -0
  703. package/src/sources/sparql/descriptor.ts +67 -0
  704. package/src/sources/sparql/index.ts +356 -0
  705. package/src/sources/sparql/ntriples.ts +219 -0
  706. package/src/sources/sparql/params.ts +28 -0
  707. package/src/sources/sparql/query.ts +162 -0
  708. package/src/sources/testing.ts +210 -0
  709. package/src/sources/types.ts +776 -0
  710. package/src/styles/annotations.ts +201 -27
  711. package/src/styles/canon/bends.ts +137 -0
  712. package/src/styles/canon/color-tables.ts +10 -4
  713. package/src/styles/canon/color.ts +105 -3
  714. package/src/styles/canon/enums.ts +99 -8
  715. package/src/styles/canon/graphviz.ts +71 -10
  716. package/src/styles/canon/number.ts +19 -0
  717. package/src/styles/canon/text.ts +187 -16
  718. package/src/styles/codes.ts +2 -0
  719. package/src/styles/cyjs/cascade.ts +1193 -38
  720. package/src/styles/cyjs/defaults-table.ts +5 -0
  721. package/src/styles/cyjs/emit.ts +247 -16
  722. package/src/styles/cyjs/face.ts +372 -0
  723. package/src/styles/cyjs/mappers.ts +498 -28
  724. package/src/styles/cyjs/routes.ts +432 -0
  725. package/src/styles/cytoscape/charts.ts +684 -0
  726. package/src/styles/cytoscape/emit.ts +220 -44
  727. package/src/styles/cytoscape/lexicon.ts +459 -80
  728. package/src/styles/cytoscape/mappings.ts +108 -0
  729. package/src/styles/cytoscape/resolve.ts +763 -23
  730. package/src/styles/cytoscape/translate.ts +11 -11
  731. package/src/styles/defaults.ts +436 -38
  732. package/src/styles/index.ts +5 -0
  733. package/src/styles/internal.ts +1 -0
  734. package/src/styles/io/block.ts +10 -0
  735. package/src/styles/io/generic.ts +79 -23
  736. package/src/styles/io/round-trip.ts +202 -0
  737. package/src/styles/io/writer.ts +14 -4
  738. package/src/styles/notes.ts +1 -0
  739. package/src/styles/plan/features.ts +23 -2
  740. package/src/styles/plan/index.ts +1 -1
  741. package/src/styles/plan/plan.ts +69 -5
  742. package/src/styles/plan/resolve.ts +61 -6
  743. package/src/styles/slots.ts +25 -0
  744. package/src/styles/types.ts +658 -4
  745. package/src/styles/vocabulary.ts +226 -3
  746. package/src/types.ts +19 -5
  747. package/dist/chunks/block-DiGq58c3.js.map +0 -1
  748. package/dist/chunks/choose--jM0yYCo.js +0 -57
  749. package/dist/chunks/choose--jM0yYCo.js.map +0 -1
  750. package/dist/chunks/convert-BcQ8_jMo.js.map +0 -1
  751. package/dist/chunks/emit-CwU8cUmG.js.map +0 -1
  752. package/dist/chunks/export-BvqAXCKh.js.map +0 -1
  753. package/dist/chunks/exporter-CrDWIyiq.js.map +0 -1
  754. package/dist/chunks/format-BpQBx8MZ.js.map +0 -1
  755. package/dist/chunks/generic-Dq1ntt9q.js.map +0 -1
  756. package/dist/chunks/importer-BSvfrX_h.js.map +0 -1
  757. package/dist/chunks/importer-CMmeMWlC.js.map +0 -1
  758. package/dist/chunks/importer-COTK9Tsl.js.map +0 -1
  759. package/dist/chunks/importer-DiZRmOQG.js.map +0 -1
  760. package/dist/chunks/importer-e6LGpqHB.js.map +0 -1
  761. package/dist/chunks/importer-iFYPEKaJ.js.map +0 -1
  762. package/dist/chunks/importer-yh_aBCU6.js.map +0 -1
  763. package/dist/chunks/lexicon-CT69Kd87.js.map +0 -1
  764. package/dist/chunks/styles-BQnsga7p.js.map +0 -1
  765. package/dist/chunks/styles-D3fRphrO.js +0 -690
  766. package/dist/chunks/styles-D3fRphrO.js.map +0 -1
  767. package/dist/chunks/styles-yed-BF5x3_Y5.js +0 -842
  768. package/dist/chunks/styles-yed-BF5x3_Y5.js.map +0 -1
  769. package/dist/chunks/writer-DMKvthQE.js +0 -448
  770. package/dist/chunks/writer-DMKvthQE.js.map +0 -1
@@ -14,11 +14,11 @@ import { canonNumber } from "../../styles/canon/number.js";
14
14
  import type { CanonSite } from "../../styles/canon/site.js";
15
15
  import { canonText } from "../../styles/canon/text.js";
16
16
  import { StyleApproximations } from "../../styles/io/approximations.js";
17
- import { blockStyles, parseStyleBlock, STYLE_BLOCK_NAME } from "../../styles/io/block.js";
17
+ import { STYLE_BLOCK_NAME } from "../../styles/io/block.js";
18
+ import { type ElementValues, takeFromBlock } from "../../styles/io/round-trip.js";
18
19
  import type { ExporterStyleInput } from "../../styles/io/writer.js";
19
20
  import { StyleNotes } from "../../styles/notes.js";
20
- import { styleFingerprint } from "../../styles/plan/features.js";
21
- import { plainCell, resolveStyles } from "../../styles/plan/resolve.js";
21
+ import { plainCell } from "../../styles/plan/resolve.js";
22
22
  import type {
23
23
  ImporterStyles,
24
24
  StyleColumn,
@@ -29,7 +29,9 @@ import type {
29
29
  StyleWriteCapabilities,
30
30
  } from "../../styles/types.js";
31
31
  import type { LossNote } from "../../types.js";
32
- import { NODE_COLUMNS, VIZ_NAMESPACE } from "./schema.js";
32
+ import { DIALOG_LINE_HEIGHT, dialogWidth } from "./dialog-metrics.js";
33
+ import { dependantColor, drawnLabels, edgeThickness, type GephiFont, GephiPreview } from "./preview.js";
34
+ import { NODE_COLUMNS, TEMPORAL_COLUMNS, temporalTableName, VIZ_NAMESPACE } from "./schema.js";
33
35
 
34
36
  /** The column of a node's `label` attribute. */
35
37
  const LABEL_COLUMN = NODE_COLUMNS.label;
@@ -46,6 +48,7 @@ export const GRAPHTY_XML_NAMESPACE = "https://graphty.app/ns/gexf";
46
48
  * @category Built-in formats
47
49
  */
48
50
  export const GEXF_STYLE_CAPABILITIES: StyleWriteCapabilities = Object.freeze({
51
+ tool: "gephi",
49
52
  node: Object.freeze(["label", "fill", "fillOpacity", "width", "height", "shape", "image"] as const),
50
53
  edge: Object.freeze(["color", "opacity", "width", "lineStyle"] as const),
51
54
  block: "native",
@@ -57,10 +60,20 @@ const GEPHI_NODE_FILL = "#000000";
57
60
  /** The style column marking directed edges, which Gephi draws with an arrow at the target. */
58
61
  const DIRECTED_KEY = "directed";
59
62
 
60
- /** The style column holding the source node's color of each edge without a color of its own. */
61
- const SOURCE_COLOR_KEY = "source-color";
62
- /** The style column of the width Gephi draws an edge with from its weight. */
63
- const WEIGHT_WIDTH_KEY = "weight-width";
63
+ /** The style column marking the nodes whose labels Gephi hides where they overlap (node.label.avoidOverlap). */
64
+ const AVOID_OVERLAP_KEY = "label-avoid-overlap";
65
+
66
+ /** The style column marking the edges Gephi does not draw (dropped, or merged into a parallel edge). */
67
+ const HIDDEN_KEY = "gephi-hidden";
68
+
69
+ /** The style column holding the mix of its end nodes' colors of each edge without a color of its own. */
70
+ const MIXED_COLOR_KEY = "end-colors";
71
+ /** The width property and the style column of the width Gephi draws an edge with from its weight. */
72
+ const WEIGHT_WIDTH = ["width", "weight-width"] as const;
73
+ /** Gephi's node width when a node has no `viz:size` (graphstore NodeImpl size 10, a radius). */
74
+ const GEPHI_NODE_WIDTH = 20;
75
+ /** How much thicker than its weight Gephi draws a self-loop. */
76
+ const SELF_LOOP_THICKNESS = 1.3;
64
77
 
65
78
  /** GEXF's node shapes, by the neutral shape each writes exactly. */
66
79
  const NODE_SHAPES: ReadonlyMap<string, string> = new Map([
@@ -110,26 +123,37 @@ const VIZ_COLUMNS: Readonly<Record<StyleTarget, readonly string[]>> = {
110
123
  };
111
124
 
112
125
  /**
113
- * A `viz:color` `hex` value as channels: r, g and b 0..255 and an alpha 0..1. Reads `#rgb`, `#rgba`, `#rrggbb` and
114
- * `#rrggbbaa` (alpha last), with or without the `#`.
126
+ * A `viz:color` `hex` value as channels: r, g and b 0..255 and an alpha 0..1, read the way Gephi reads it: Java's
127
+ * `Color.decode`, which parses the text as one integer (`Integer.decode`: `#` or `0x` hexadecimal, a leading `0`
128
+ * octal, otherwise decimal; a leading sign allowed) and keeps its low 24 bits, opaque. So `#rrggbb` is the color it
129
+ * names, but `#f80` is `#000f80`, `#4daf4a80` is `#af4a80` and `984ea3` is not a color at all (Gephi draws that node
130
+ * as if it had no color: `gexf-specimen-node-fills-borders`, `color-hex-short`, `color-hex-alpha`, `color-hex-nohash`).
115
131
  * @param hex - the attribute's value
116
- * @returns `[r, g, b, a]`, or null when it is not a hex color
132
+ * @returns `[r, g, b, 1]`, or null when Java cannot read it as an int
117
133
  */
118
134
  export function canonVizColor(hex: string): [number, number, number, number] | null {
119
- const text = hex.trim();
120
- if (!/^#?([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(text)) {
135
+ const m = /^([+-]?)(?:(0x|0X|#)([0-9a-fA-F]+)|(0[0-7]+)|(\d+))$/.exec(hex.trim());
136
+ if (m === null) {
121
137
  return null;
122
138
  }
123
- const c = canonColor(text.startsWith("#") ? text : `#${text}`, "css", {
124
- notes: new StyleNotes(),
125
- target: "node",
126
- property: "fill",
127
- });
128
- if (c === null) {
139
+ const [, sign, , hexDigits, octal, decimal] = m;
140
+ let v: number;
141
+ if (hexDigits !== undefined) {
142
+ v = Number.parseInt(hexDigits, 16);
143
+ } else if (octal !== undefined) {
144
+ v = Number.parseInt(octal, 8);
145
+ } else {
146
+ v = Number.parseInt(decimal, 10);
147
+ }
148
+ if (sign === "-") {
149
+ v = -v;
150
+ }
151
+ // Integer.decode accepts -2^31 .. 2^31 - 1 only
152
+ if (v < -2147483648 || v > 2147483647) {
129
153
  return null;
130
154
  }
131
- const n = Number.parseInt(c.color.slice(1), 16);
132
- return [(n >> 16) & 255, (n >> 8) & 255, n & 255, c.alpha];
155
+ const n = v & 0xffffff;
156
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255, 1];
133
157
  }
134
158
 
135
159
  /**
@@ -250,17 +274,22 @@ function readVizColor(viz: VizRead, color: string, i: number): void {
250
274
  }
251
275
 
252
276
  /**
253
- * Reads one element's viz size (a node's width and height) or thickness (an edge's width).
277
+ * Reads one element's viz size (a node's width and height) or thickness (an edge's width). A node's
278
+ * size is a radius, as Gephi and sigma draw it (Gephi's preview: a circle of radius 0.92 x size plus
279
+ * a 0.16 x size outline, an outer diameter of 2 x size), so its width and height are twice it.
254
280
  * @param viz - the target's reading
255
281
  * @param size - the viz size or thickness column
256
282
  * @param i - the element
257
283
  */
258
284
  function readVizSize(viz: VizRead, size: string, i: number): void {
259
285
  const v = canonNumber(plainCell(viz.table.value(size, i)), viz.site(i, "width"));
260
- if (v !== null) {
261
- viz.column("width")[i] = v;
286
+ // Gephi draws a node of viz:size 0 at its default size 10 (gexf-specimen-node-sizes-opacity, size-0)
287
+ if (v !== null && !(v === 0 && viz.target === "node")) {
262
288
  if (viz.target === "node") {
263
- viz.column("height")[i] = v;
289
+ viz.column("width")[i] = 2 * v;
290
+ viz.column("height")[i] = 2 * v;
291
+ } else {
292
+ viz.column("width")[i] = v;
264
293
  }
265
294
  }
266
295
  }
@@ -282,104 +311,1003 @@ function readVizShape(viz: VizRead, shape: string, i: number): boolean {
282
311
  return typeof raw === "string" && raw.toLowerCase() === "image";
283
312
  }
284
313
 
314
+ /** What Gephi draws of each edge (gephiEdges()). */
315
+ interface GephiDrawnEdges {
316
+ /** Whether Gephi draws the edge directed, with an arrow at its target. */
317
+ readonly directed: boolean[];
318
+ /** Whether Gephi does not draw the edge at all. */
319
+ readonly hidden: boolean[];
320
+ /** The weight Gephi draws the edge with: merged, then shifted. */
321
+ readonly weight: number[];
322
+ }
323
+
285
324
  /**
286
- * Whether each edge is directed.
325
+ * The edges as Gephi's importer and preview draw them (measured with the Gephi Toolkit 0.11.3:
326
+ * `gexf-specimen-edges-lines`, `-p13`, `-p15`, `gexf-specimen-arrows`, `-p6`, `-p7`):
327
+ * - an edge whose own type is against the graph's `defaultedgetype` is dropped (`undirected` in a `directed`
328
+ * graph, `directed` in an `undirected` one); a `mutual` graph, or one with no `defaultedgetype`, keeps every
329
+ * edge, an untyped or `mutual` one directed;
330
+ * - edges of one kind between the same ends (either way round for an undirected edge) are merged into the first,
331
+ * whose weight is their sum;
332
+ * - an edge's weight is its `weight` (1 when it has none), or, when an edge attribute named weight is dynamic, its
333
+ * value over the earliest interval (0 when it has none);
334
+ * - when the smallest weight drawn is 0 or less, every weight is raised by |smallest| + 1.
335
+ * An undirected edge of a directed snapshot is two halves here; both take what its first half gets.
287
336
  * @param snapshot - the graph
288
- * @returns one flag per edge
337
+ * @param gexf - whether the file is a GEXF; another format keeps its own direction, with nothing merged or dropped
338
+ * @returns per edge, what Gephi draws
289
339
  */
290
- function directedEdges(snapshot: GraphSnapshot): boolean[] {
291
- const out = new Array<boolean>(snapshot.edgeCount).fill(snapshot.directed);
292
- const col = snapshot.directed ? snapshot.edges.byRole("directed") : null;
293
- if (col !== null) {
294
- for (let e = 0; e < snapshot.edgeCount; e++) {
295
- out[e] = plainCell(col.value(e)) !== false;
340
+ function gephiEdges(snapshot: GraphSnapshot, gexf: boolean): GephiDrawnEdges {
341
+ const n = snapshot.edgeCount;
342
+ const first = firstHalf(snapshot);
343
+ // another format Gephi draws: its own direction, nothing merged or dropped
344
+ if (!gexf) {
345
+ return otherFormatEdges(snapshot);
346
+ }
347
+ const header = gexfDefaultEdgeType(snapshot);
348
+ const dirCol = snapshot.edges.byRole("directed");
349
+ const mutualCol = snapshot.edges.byRole("mutual");
350
+ const kindCol = snapshot.edges.byRole("kind");
351
+ const weightOf = gephiWeight(snapshot);
352
+ const out: GephiDrawnEdges = {
353
+ directed: new Array<boolean>(n).fill(false),
354
+ hidden: new Array<boolean>(n).fill(false),
355
+ weight: new Array<number>(n).fill(1),
356
+ };
357
+ const leaders = new Map<string, number>();
358
+ const drawnLeaders: number[] = [];
359
+ const ownDirection = gexfOwnDirection(header, dirCol, mutualCol);
360
+ for (let e = 0; e < n; e++) {
361
+ const f = first(e);
362
+ if (f !== e) {
363
+ continue;
364
+ }
365
+ // the edge's own type: graph-io records it only when the file mixes directions
366
+ const { ownUndirected, mismatched } = ownDirection(f);
367
+ if (mismatched) {
368
+ out.hidden[f] = true;
369
+ continue;
296
370
  }
371
+ // Gephi draws no arrowhead on a self-loop
372
+ const s = snapshot.edgeSource(f);
373
+ const t = snapshot.edgeTarget(f);
374
+ const directed = header !== "undirected" && s !== t && !ownUndirected;
375
+ out.directed[f] = directed;
376
+ const kind = kindCol?.isSet(f) === true ? String(kindCol.value(f)) : "";
377
+ const key = `${kind}\u0000${gephiEndsKey(s, t, directed)}`;
378
+ const leader = leaders.get(key);
379
+ if (leader === undefined) {
380
+ leaders.set(key, f);
381
+ drawnLeaders.push(f);
382
+ out.weight[f] = weightOf(f);
383
+ } else {
384
+ out.weight[leader] += weightOf(f);
385
+ out.hidden[f] = true;
386
+ }
387
+ }
388
+ // a loop, not Math.min(...): a spread of every edge overflows the call stack on a large graph
389
+ const smallest = drawnLeaders.reduce((m, f) => Math.min(m, out.weight[f]), Infinity);
390
+ const shift = smallest <= 0 ? 1 - smallest : 0;
391
+ for (let e = 0; e < n; e++) {
392
+ const f = first(e);
393
+ out.directed[e] = out.directed[f];
394
+ out.hidden[e] = out.hidden[f];
395
+ out.weight[e] = out.weight[f] + shift;
297
396
  }
298
397
  return out;
299
398
  }
300
399
 
301
400
  /**
302
- * The rules Gephi's defaults make per element: an arrow at the target of every directed edge, the source node's
303
- * color on every edge without a color of its own, and the edge's weight as the thickness of every edge without a
304
- * thickness of its own (Gephi draws GEXF and its GraphML export the same way).
401
+ * The ends of an edge as the key Gephi merges parallel edges by: in order for a directed edge, either way round
402
+ * for an undirected one.
403
+ * @param s - the source node index
404
+ * @param t - the target node index
405
+ * @param directed - whether the edge is drawn directed
406
+ * @returns the key
407
+ */
408
+ function gephiEndsKey(s: number, t: number, directed: boolean): string {
409
+ return directed ? `${String(s)}>${String(t)}` : `${String(Math.min(s, t))}-${String(Math.max(s, t))}`;
410
+ }
411
+
412
+ /**
413
+ * A GEXF edge's own type, read from the directed and mutual columns, or from the header when the file did not
414
+ * mix directions (no directed column); an edge whose own type disagrees with the header's is one Gephi never loads.
415
+ * @param header - the file's default edge type
416
+ * @param dirCol - the directed column, or null
417
+ * @param mutualCol - the mutual column, or null
418
+ * @returns per edge index, whether its own type is undirected and whether it disagrees with the header
419
+ */
420
+ function gexfOwnDirection(
421
+ header: ReturnType<typeof gexfDefaultEdgeType>,
422
+ dirCol: ReturnType<GraphSnapshot["edges"]["byRole"]>,
423
+ mutualCol: ReturnType<GraphSnapshot["edges"]["byRole"]>,
424
+ ): (f: number) => { ownUndirected: boolean; mismatched: boolean } {
425
+ const verdict = (
426
+ ownUndirected: boolean,
427
+ ownDirected: boolean,
428
+ ): { ownUndirected: boolean; mismatched: boolean } => ({
429
+ ownUndirected,
430
+ mismatched: (header === "directed" && ownUndirected) || (header === "undirected" && ownDirected),
431
+ });
432
+ return (f) => {
433
+ if (dirCol === null) {
434
+ return verdict(header === "undirected", header !== "undirected");
435
+ }
436
+ const mutual = mutualCol?.value(f) === true;
437
+ return verdict(dirCol.value(f) === false && !mutual, dirCol.value(f) === true && !mutual);
438
+ };
439
+ }
440
+
441
+ /**
442
+ * The edges of a format other than GEXF as Gephi draws them: each in its own direction, as thick as its weight (1
443
+ * for a weight of 0 or below, so the edge stays visible).
444
+ * @param snapshot - the graph
445
+ * @returns per edge, what Gephi draws
446
+ */
447
+ function otherFormatEdges(snapshot: GraphSnapshot): GephiDrawnEdges {
448
+ const first = firstHalf(snapshot);
449
+ const weightOf = gephiWeight(snapshot);
450
+ const directed = new Array<boolean>(snapshot.edgeCount).fill(snapshot.directed);
451
+ const col = snapshot.directed ? snapshot.edges.byRole("directed") : null;
452
+ const weight: number[] = [];
453
+ for (let e = 0; e < snapshot.edgeCount; e++) {
454
+ if (col !== null) {
455
+ directed[e] = plainCell(col.value(e)) !== false;
456
+ }
457
+ const w = weightOf(first(e));
458
+ weight.push(w > 0 ? w : 1);
459
+ }
460
+ return { directed, hidden: directed.map(() => false), weight };
461
+ }
462
+
463
+ /**
464
+ * The graph's `defaultedgetype` as written, lower case, or null when it wrote none.
305
465
  * @param snapshot - the graph
306
- * @param nodeFill - each node's fill, or undefined when no node has one
307
- * @param edgeColor - each edge's own color, or undefined when no edge has one
466
+ * @returns the keyword
467
+ */
468
+ function gexfDefaultEdgeType(snapshot: GraphSnapshot): string | null {
469
+ const written = (snapshot.meta.extra.gexf as { defaultedgetype?: unknown } | undefined)?.defaultedgetype;
470
+ return typeof written === "string" ? written.trim().toLowerCase() : null;
471
+ }
472
+
473
+ /**
474
+ * Each edge's weight as Gephi reads it: an edge attribute whose id or title is `weight` and is dynamic is the weight,
475
+ * at its value over the earliest interval (0 for an edge without one: its static `weight` is not read); otherwise
476
+ * the edge's `weight`, 1 for an edge with none.
477
+ * @param snapshot - the graph
478
+ * @returns the weight of an edge
479
+ */
480
+ function gephiWeight(snapshot: GraphSnapshot): (e: number) => number {
481
+ const isWeight = (v: unknown): boolean => typeof v === "string" && v.toLowerCase() === "weight";
482
+ const dynamic = snapshot.edges
483
+ .names()
484
+ .map((name) => snapshot.edges.get(name))
485
+ .find(
486
+ (c) =>
487
+ c !== null && c.meta.dynamic && [c.meta.name, c.meta.origin?.id, c.meta.origin?.title].some(isWeight),
488
+ );
489
+ const table =
490
+ dynamic === undefined || dynamic === null
491
+ ? undefined
492
+ : snapshot.extensions.get(temporalTableName("edge", dynamic.meta.name));
493
+ if (table !== undefined) {
494
+ const element = table.get(TEMPORAL_COLUMNS.element);
495
+ const start = table.get(TEMPORAL_COLUMNS.start);
496
+ const value = table.get(TEMPORAL_COLUMNS.value);
497
+ const earliest = new Map<number, { start: number; value: number }>();
498
+ for (let row = 0; element !== null && value !== null && row < table.rowCount; row++) {
499
+ const e = element.value(row) as number;
500
+ const at = start?.isSet(row) === true ? (start.value(row) as number) : -Infinity;
501
+ const v = Number(value.value(row));
502
+ const seen = earliest.get(e);
503
+ if (Number.isFinite(v) && (seen === undefined || at < seen.start)) {
504
+ earliest.set(e, { start: at, value: v });
505
+ }
506
+ }
507
+ return (e) => earliest.get(e)?.value ?? 0;
508
+ }
509
+ // the snapshot's own weights (the role column exists only when some edge has none), as their shortest decimal
510
+ const weights = explicitWeights(snapshot);
511
+ return (e) => {
512
+ const w = Number(weights.text(e) ?? 1);
513
+ return Number.isFinite(w) ? w : 1;
514
+ };
515
+ }
516
+
517
+ /** The node values Gephi's per-element defaults are worked out from, each undefined when no node has one. */
518
+ interface GephiNodeValues {
519
+ /** Each node's fill. */
520
+ readonly fill?: readonly (StyleValue | null)[];
521
+ /** Each node's fill opacity (the alpha of its color), null for opaque. */
522
+ readonly fillOpacity?: readonly (StyleValue | null)[];
523
+ /** Each node's width, twice its Gephi size. */
524
+ readonly width?: readonly (StyleValue | null)[];
525
+ }
526
+
527
+ /** The edge values Gephi's per-element defaults give way to, each undefined when no edge has one. */
528
+ interface GephiEdgeValues {
529
+ /** Each edge's own color. */
530
+ readonly color?: readonly (StyleValue | null)[];
531
+ /** Each edge's own width. */
532
+ readonly width?: readonly (StyleValue | null)[];
533
+ }
534
+
535
+ /** The rules of one target and the style columns they read. */
536
+ interface GephiTargetRules {
537
+ readonly rules: StyleRule[];
538
+ readonly columns: Map<string, StyleColumn>;
539
+ }
540
+
541
+ /**
542
+ * A node color's channels as Gephi holds them: r, g, b and alpha, 0..255.
543
+ * @param fill - the node's fill, or null for Gephi's default
544
+ * @param opacity - its opacity, or null for opaque
545
+ * @returns the channels
546
+ */
547
+ function nodeChannels(fill: StyleValue | null | undefined, opacity: StyleValue | null | undefined): number[] {
548
+ const n = Number.parseInt((typeof fill === "string" ? fill : GEPHI_NODE_FILL).slice(1), 16);
549
+ const a = typeof opacity === "number" ? Math.round(opacity * 255) : 255;
550
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255, a];
551
+ }
552
+
553
+ /**
554
+ * `#rrggbb` of three channels.
555
+ * @param c - r, g and b, 0..255
556
+ * @returns the color
557
+ */
558
+ function hexOf(c: readonly number[]): string {
559
+ return `#${c
560
+ .slice(0, 3)
561
+ .map((v) => v.toString(16).padStart(2, "0"))
562
+ .join("")}`;
563
+ }
564
+
565
+ /**
566
+ * A passthrough rule of a tool default that is itself per element, and its column, when any element takes it.
567
+ * @param out - the target's rules and columns, changed in place
568
+ * @param target - nodes or edges
569
+ * @param pair - the style property and its style column, which is also the rule's id after `<target>.tool.`
570
+ * @param column - the default per element, null where the element has its own value
571
+ * @param reason - why the values are per element
572
+ */
573
+ function perElementDefault(
574
+ out: GephiTargetRules,
575
+ target: StyleTarget,
576
+ pair: readonly [string, string],
577
+ column: (StyleValue | null)[],
578
+ reason: string,
579
+ ): void {
580
+ const [property, key] = pair;
581
+ const elements = column.filter((v) => v !== null).length;
582
+ if (elements === 0) {
583
+ return;
584
+ }
585
+ out.columns.set(key, column);
586
+ out.rules.push({
587
+ id: `${target}.tool.${key}`,
588
+ target,
589
+ role: "passthrough",
590
+ origin: "tool",
591
+ where: { has: { style: key } },
592
+ map: { [property]: { kind: "passthrough" as const, by: { style: key } } },
593
+ facts: { elements, reason },
594
+ });
595
+ }
596
+
597
+ /** What a GEXF drawing adds to gephiRules(): the file is a GEXF, drawn under these Preview settings. */
598
+ interface GephiDrawing {
599
+ /** The Preview settings, Gephi's defaults where none is given. */
600
+ readonly preview: GephiPreview;
601
+ /** Each edge's own viz color and its alpha, which `edge.color=original` draws. */
602
+ readonly original?: {
603
+ readonly color?: readonly (StyleValue | null)[];
604
+ readonly opacity?: readonly (StyleValue | null)[];
605
+ };
606
+ /** Each edge's label, drawn under `edge.label.show`. */
607
+ readonly edgeLabels?: readonly (StyleValue | null)[];
608
+ /** Whether the node labels Gephi's overlap avoidance hides are already left out (gephiOverlapHidden()). */
609
+ readonly overlapSettled?: boolean;
610
+ }
611
+
612
+ /**
613
+ * A tool rule that sets the same values on every element.
614
+ * @param out - the target's rules, changed in place
615
+ * @param target - nodes or edges
616
+ * @param id - the rule's id after `<target>.tool.`
617
+ * @param set - the values
618
+ */
619
+ function everyElement(out: GephiTargetRules, target: StyleTarget, id: string, set: Record<string, StyleValue>): void {
620
+ if (Object.keys(set).length > 0) {
621
+ out.rules.push({
622
+ id: `${target}.tool.${id}`,
623
+ target,
624
+ role: "group",
625
+ origin: "tool",
626
+ where: { all: true },
627
+ set,
628
+ });
629
+ }
630
+ }
631
+
632
+ /**
633
+ * The rules Gephi's preview defaults (DefaultPreset) make per element, as its SVG export draws every GEXF and GraphML
634
+ * file: each node outlined in a darker shade of its fill, 0.08 of its width wide, with its label 0.3 of its width
635
+ * tall; each edge in the mix of its two end nodes' colors and alphas, as thick as its weight, with an arrow at the
636
+ * target of a directed edge. An element's own value is kept (a file graphty wrote states every one). A GEXF drawing
637
+ * also merges and drops edges as Gephi's importer does (gephiEdges()) and follows the Preview settings it was drawn
638
+ * with.
639
+ * @param snapshot - the graph
640
+ * @param nodes - each node's fill, opacity and width
641
+ * @param edges - each edge's own color and width
308
642
  * @param budget - the work budget
309
- * @param edgeWidth - each edge's own width, or undefined when no edge has one
310
- * @returns the edge rules and the style columns they read
643
+ * @param gexf - the GEXF drawing, or undefined for another format Gephi draws
644
+ * @returns the node and edge rules and the style columns they read
311
645
  */
312
646
  export function gephiRules(
313
647
  snapshot: GraphSnapshot,
314
- nodeFill: readonly (StyleValue | null)[] | undefined,
315
- edgeColor: readonly (StyleValue | null)[] | undefined,
648
+ nodes: GephiNodeValues,
649
+ edges: GephiEdgeValues,
316
650
  budget: StyleReadContext["budget"],
317
- edgeWidth?: readonly (StyleValue | null)[],
318
- ): { readonly rules: StyleRule[]; readonly columns: Map<string, StyleColumn> } {
319
- const rules: StyleRule[] = [];
320
- const columns = new Map<string, StyleColumn>();
321
- const directed = directedEdges(snapshot);
322
- if (directed.some(Boolean)) {
323
- columns.set(
651
+ gexf?: GephiDrawing,
652
+ ): { readonly node: GephiTargetRules; readonly edge: GephiTargetRules } {
653
+ const preview = gexf?.preview ?? new GephiPreview();
654
+ const node: GephiTargetRules = { rules: [], columns: new Map() };
655
+ const edge: GephiTargetRules = { rules: [], columns: new Map() };
656
+ budget.spend("node", snapshot.nodeCount);
657
+ budget.spend("edge", snapshot.edgeCount);
658
+ const channels = Array.from({ length: snapshot.nodeCount }, (_, i) =>
659
+ nodeChannels(nodes.fill?.[i], nodes.fillOpacity?.[i]),
660
+ );
661
+ const labelSizes = nodeRules(node, snapshot, channels, nodes, preview);
662
+ edgeRules(edge, snapshot, channels, nodes, edges, gexf, preview);
663
+ nodeLabelRules(node, channels, labelSizes, preview, gexf?.overlapSettled === true);
664
+ labelBox(node, channels, nodes, preview);
665
+ return { node, edge };
666
+ }
667
+
668
+ /**
669
+ * A node's width, twice its Gephi size.
670
+ * @param nodes - the node values
671
+ * @param i - the node
672
+ * @returns the width
673
+ */
674
+ function nodeWidth(nodes: GephiNodeValues, i: number): number {
675
+ const w = nodes.width?.[i];
676
+ return typeof w === "number" ? w : GEPHI_NODE_WIDTH;
677
+ }
678
+
679
+ /**
680
+ * The node rules: opacity, border and label size.
681
+ * @param node - the node rules, changed in place
682
+ * @param snapshot - the graph
683
+ * @param channels - each node's color channels
684
+ * @param nodes - the node values
685
+ * @param preview - the settings
686
+ * @returns each node's label size
687
+ */
688
+ function nodeRules(
689
+ node: GephiTargetRules,
690
+ snapshot: GraphSnapshot,
691
+ channels: readonly number[][],
692
+ nodes: GephiNodeValues,
693
+ preview: GephiPreview,
694
+ ): number[] {
695
+ const nodeIndices = Array.from({ length: snapshot.nodeCount }, (_, i) => i);
696
+ // a node's alpha is drawn only under node.per.node.opacity, times node.opacity, on its fill and its border alike:
697
+ // a node not opaque is a fill of radius 0.84 s and a separate ring out to s, both at that alpha
698
+ const perNode = preview.on("node.per.node.opacity");
699
+ const alpha = channels.map((c) => ((perNode ? c[3] : 255) / 255) * (preview.num("node.opacity") / 100));
700
+ const translucent = alpha.map((a) => (a < 1 ? a : null));
701
+ perElementDefault(node, "node", ["fillOpacity", "preview-opacity"], translucent, "NODE_OPACITY");
702
+ perElementDefault(node, "node", ["borderOpacity", "preview-border-opacity"], translucent, "NODE_OPACITY");
703
+ // NodeRenderer: the border is DependantColor DARKER by default, half of each channel (+-1 from Gephi's float math)
704
+ const borderMode = preview.text("node.border.color");
705
+ perElementDefault(
706
+ node,
707
+ "node",
708
+ ["borderColor", "border-shade"],
709
+ channels.map((c) => hexOf(dependantColor(borderMode, c) ?? c.map((v) => Math.floor(v / 2)))),
710
+ "NODE_FILL_SHADE",
711
+ );
712
+ // A node of size s is a circle of radius 0.92 s with a 0.16 s border: 0.08 of its 2 s width, or node.border.width
713
+ // under node.border.fixed. Its label is the label font's size in proportion to the node's, round(size / 20 * s)
714
+ // (NODE_LABEL_PROPORTIONAL_SIZE; Arial 12 makes round(0.6 s)), never under 1.
715
+ const fixed = preview.on("node.border.fixed") ? preview.num("node.border.width") : null;
716
+ perElementDefault(
717
+ node,
718
+ "node",
719
+ ["borderWidth", "size-border"],
720
+ nodeIndices.map((i) => {
721
+ // a fixed border is no wider than the node's size (radius): a size-3 node takes a border of 4 as 3
722
+ return fixed === null ? 0.08 * nodeWidth(nodes, i) : Math.min(fixed, nodeWidth(nodes, i) / 2);
723
+ }),
724
+ "NODE_SIZE",
725
+ );
726
+ const font = preview.font("node");
727
+ const labelSizes = nodeIndices.map((i) =>
728
+ preview.on("node.label.proportinalSize")
729
+ ? Math.max(1, Math.round((font.size / 40) * nodeWidth(nodes, i)))
730
+ : font.size,
731
+ );
732
+ perElementDefault(node, "node", ["labelSize", "size-label"], labelSizes, "NODE_SIZE");
733
+ return labelSizes;
734
+ }
735
+
736
+ /**
737
+ * The node label rules the Preview settings make beyond the size: color, outline, font and overlap avoidance.
738
+ * @param node - the node rules, changed in place
739
+ * @param channels - each node's color channels
740
+ * @param sizes - each node's label size
741
+ * @param preview - the settings
742
+ * @param settled - whether the labels overlap avoidance hides are already left out
743
+ */
744
+ function nodeLabelRules(
745
+ node: GephiTargetRules,
746
+ channels: readonly number[][],
747
+ sizes: readonly number[],
748
+ preview: GephiPreview,
749
+ settled: boolean,
750
+ ): void {
751
+ const color = preview.text("node.label.color");
752
+ if (color !== "#000000" && color !== "original") {
753
+ perElementDefault(
754
+ node,
755
+ "node",
756
+ ["labelColor", "preview-label-color"],
757
+ channels.map((c) => hexOf(dependantColor(color, c) ?? [0, 0, 0])),
758
+ "NODE_LABEL_COLOR",
759
+ );
760
+ }
761
+ labelOutline(node, "node", channels, sizes, preview);
762
+ const font = preview.font("node");
763
+ everyElement(node, "node", "preview-label", fontValues(font, preview.on("node.label.customFont")));
764
+ // ponytail: where a node has no stored position Gephi's own grid (gephiOverlapHidden()) cannot be run; avoiding
765
+ // overlap, larger first, is that test without the grid's rounding. A rule only while it is on: graphty-element
766
+ // has no value that turns a lower rule's overlap avoidance off again.
767
+ if (preview.on("node.label.avoidOverlap") && !settled) {
768
+ node.columns.set(
769
+ AVOID_OVERLAP_KEY,
770
+ sizes.map(() => true),
771
+ );
772
+ node.rules.push({
773
+ id: "node.tool.label-avoid-overlap",
774
+ target: "node",
775
+ role: "group",
776
+ origin: "tool",
777
+ where: { has: { style: AVOID_OVERLAP_KEY } },
778
+ set: { labelAvoidOverlap: true },
779
+ });
780
+ }
781
+ }
782
+
783
+ /**
784
+ * Gephi's alpha for a Preview opacity in percent: Java's `Color` keeps it as one of 256 steps, cut down to a whole
785
+ * step (`labels-p10`'s 30 is drawn 76 / 255, not 77).
786
+ * @param percent - the opacity, 0 to 100
787
+ * @returns the alpha, 0 to 1
788
+ */
789
+ function gephiAlpha(percent: number): number {
790
+ // ponytail: 1e-9 keeps a step that floating point lands a hair under (0.4 x 255) on its step
791
+ return Math.floor((Math.min(Math.max(percent, 0), 100) / 100) * 255 + 1e-9) / 255;
792
+ }
793
+
794
+ /**
795
+ * The label box of `node.label.box`: Gephi strokes an unfilled box round the label, 0.08 of the node's size wide, in
796
+ * the color `node.label.box.color` derives from the node's and at `node.label.box.opacity` (`gexf-specimen-labels-p11`,
797
+ * `-p12`, `-p13`, `-p20`, `-p21`). The box is the text's own bounds -- its advance width and its font's ascent plus
798
+ * descent, with no leading -- grown by the stroke's width, so the stroke's inner edge lies on the text's bounds
799
+ * (`labels-p11`: a 15 px "Label" is 40.47 x 17.46 inside a 2.0 stroke): no padding, a line height of 1.
800
+ * @param node - the node rules, changed in place
801
+ * @param channels - each node's color channels
802
+ * @param nodes - the node values
803
+ * @param preview - the settings
804
+ */
805
+ function labelBox(
806
+ node: GephiTargetRules,
807
+ channels: readonly number[][],
808
+ nodes: GephiNodeValues,
809
+ preview: GephiPreview,
810
+ ): void {
811
+ if (!preview.on("node.label.box")) {
812
+ return;
813
+ }
814
+ const mode = preview.text("node.label.box.color");
815
+ perElementDefault(
816
+ node,
817
+ "node",
818
+ ["labelBorderColor", "preview-box-color"],
819
+ channels.map((c) => hexOf(dependantColor(mode, c) ?? c)),
820
+ "LABEL_BOX",
821
+ );
822
+ perElementDefault(
823
+ node,
824
+ "node",
825
+ ["labelBorderWidth", "preview-box-width"],
826
+ channels.map((_, i) => 0.04 * nodeWidth(nodes, i)),
827
+ "LABEL_BOX",
828
+ );
829
+ everyElement(node, "node", "preview-box", {
830
+ labelBorderOpacity: gephiAlpha(preview.num("node.label.box.opacity")),
831
+ labelPadding: 0,
832
+ labelLineHeight: 1,
833
+ });
834
+ }
835
+
836
+ /**
837
+ * The label font values a custom font sets.
838
+ * @param font - the font
839
+ * @param custom - whether the font is custom
840
+ * @returns the values
841
+ */
842
+ function fontValues(font: GephiFont, custom: boolean): Record<string, StyleValue> {
843
+ if (!custom) {
844
+ return {};
845
+ }
846
+ return {
847
+ labelFont: font.family,
848
+ ...(font.bold ? { labelWeight: "bold" } : {}),
849
+ ...(font.italic ? { labelItalic: true } : {}),
850
+ };
851
+ }
852
+
853
+ /**
854
+ * The label outline of `<target>.label.outline.size` and `.color`: a stroke size / 32 of the label's size wide, round
855
+ * each letter, in the color its mode derives from the element's color and at `.outline.opacity` (Gephi's default 80;
856
+ * `gexf-specimen-labels-p9`, `-p10`, `-p17` .. `-p19`, `edge-labels-p5`, `-p6`).
857
+ * @param out - the rules, changed in place
858
+ * @param target - nodes or edges
859
+ * @param colors - each element's color channels
860
+ * @param sizes - each element's label size
861
+ * @param preview - the settings
862
+ */
863
+ function labelOutline(
864
+ out: GephiTargetRules,
865
+ target: StyleTarget,
866
+ colors: readonly number[][],
867
+ sizes: readonly number[],
868
+ preview: GephiPreview,
869
+ ): void {
870
+ const size = preview.num(`${target}.label.outline.size`);
871
+ if (size <= 0) {
872
+ return;
873
+ }
874
+ const mode = preview.text(`${target}.label.outline.color`);
875
+ perElementDefault(
876
+ out,
877
+ target,
878
+ ["labelOutlineWidth", "preview-outline-width"],
879
+ sizes.map((s) => (size / 32) * s),
880
+ "LABEL_OUTLINE",
881
+ );
882
+ perElementDefault(
883
+ out,
884
+ target,
885
+ ["labelOutlineColor", "preview-outline-color"],
886
+ colors.map((c) => hexOf(dependantColor(mode, c) ?? [255, 255, 255])),
887
+ "LABEL_OUTLINE",
888
+ );
889
+ everyElement(out, target, "preview-outline", {
890
+ labelOutlineOpacity: gephiAlpha(preview.num(`${target}.label.outline.opacity`)),
891
+ });
892
+ }
893
+
894
+ /**
895
+ * The node labels Gephi's label overlap avoidance hides (`node.label.avoidOverlap`, Gephi 0.11
896
+ * NodeLabelRenderer.preProcess): largest font first, each label's box -- its Dialog advance width by its line height,
897
+ * centered on its node -- marks the cells of a `node.label.overlapGridSize` grid over the nodes' bounds, and a label
898
+ * any of whose cells an earlier one marked is hidden. A label's font size is 12 x 0.05 x its node's size (proportional
899
+ * size) or the font's, measured in the float size of the last label of the same whole size (Gephi's font cache),
900
+ * its labels taken largest node first.
901
+ * Decided where every node has a stored position; elsewhere graphty-element lays the graph out, so null.
902
+ * @param snapshot - the graph
903
+ * @param labels - each node's label as drawn
904
+ * @param widths - each node's width (twice its size), or undefined for Gephi's default
905
+ * @param preview - the settings
906
+ * @returns whether each label is hidden, or null
907
+ */
908
+ function gephiOverlapHidden(
909
+ snapshot: GraphSnapshot,
910
+ labels: readonly (StyleValue | null)[],
911
+ widths: readonly (StyleValue | null)[] | undefined,
912
+ preview: GephiPreview,
913
+ ): boolean[] | null {
914
+ const centers = gephiCenters(snapshot);
915
+ if (centers.includes(null)) {
916
+ return null;
917
+ }
918
+ const f = Math.fround;
919
+ const grid = preview.num("node.label.overlapGridSize");
920
+ const proportional = preview.on("node.label.proportinalSize");
921
+ const base = f(preview.font("node").size * preview.num("node.label.scale"));
922
+ const nodeSizes = centers.map((_, i) => {
923
+ const w = widths?.[i];
924
+ return f((typeof w === "number" ? w : GEPHI_NODE_WIDTH) / 2);
925
+ });
926
+ const sizes = nodeSizes.map((nodeSize) =>
927
+ proportional ? f(f(base * nodeSize) * f(0.05)) : f(base * f(1 + f(nodeSize * f(0.00001)))),
928
+ );
929
+ // Gephi's label items come largest node first (file order among equals; measured on six corpus files)
930
+ const items = sizes.map((_, i) => i).sort((a, b) => nodeSizes[b] - nodeSizes[a]);
931
+ // Font.deriveFont(size).getSize() rounds; the cache keeps the last font of each whole size
932
+ const cached = new Map<number, number>();
933
+ items.forEach((i) => cached.set(Math.round(sizes[i]), sizes[i]));
934
+ // NodeItem Y is the file's y turned over
935
+ const at = centers.map((c) => [c?.[0] ?? 0, -(c?.[1] ?? 0)] as const);
936
+ // loops, not Math.min(...): a spread of every node overflows the call stack on a large graph
937
+ let [loX, loY, hiX, hiY] = [Infinity, Infinity, -Infinity, -Infinity];
938
+ for (const [x, y] of at) {
939
+ [loX, loY, hiX, hiY] = [Math.min(loX, x), Math.min(loY, y), Math.max(hiX, x), Math.max(hiY, y)];
940
+ }
941
+ const [minX, minY] = [loX - grid, loY - grid];
942
+ const cols = Math.ceil((hiX + grid - minX) / grid);
943
+ const rows = Math.ceil((hiY + grid - minY) / grid);
944
+ const taken = new Set<number>();
945
+ const hidden = labels.map(() => false);
946
+ const order = [...items].sort((a, b) => sizes[b] - sizes[a]);
947
+ for (const i of order) {
948
+ const label = labels[i];
949
+ if (typeof label !== "string" || label.trim() === "") {
950
+ continue;
951
+ }
952
+ const size = cached.get(Math.round(sizes[i])) ?? sizes[i];
953
+ const half = [(dialogWidth(label) * size) / 2, (DIALOG_LINE_HEIGHT * size) / 2] as const;
954
+ const cells = gridCells(
955
+ { min: [minX, minY], size: grid, cols, rows },
956
+ [at[i][0] - half[0], at[i][1] - half[1]],
957
+ [at[i][0] + half[0], at[i][1] + half[1]],
958
+ );
959
+ if (cells.some((k) => taken.has(k))) {
960
+ hidden[i] = true;
961
+ } else {
962
+ cells.forEach((k) => taken.add(k));
963
+ }
964
+ }
965
+ return hidden;
966
+ }
967
+
968
+ /**
969
+ * The cells of a grid a box covers, as Gephi's label grid counts them (each corner's cell truncated, kept on the grid).
970
+ * @param grid - the grid: its lowest corner, cell size and cell counts
971
+ * @param grid.min - its lowest x and y
972
+ * @param grid.size - its cell size
973
+ * @param grid.cols - its columns
974
+ * @param grid.rows - its rows
975
+ * @param lo - the box's lowest x and y
976
+ * @param hi - the box's highest x and y
977
+ * @returns the cell indices, row by row
978
+ */
979
+ function gridCells(
980
+ grid: {
981
+ readonly min: readonly [number, number];
982
+ readonly size: number;
983
+ readonly cols: number;
984
+ readonly rows: number;
985
+ },
986
+ lo: readonly [number, number],
987
+ hi: readonly [number, number],
988
+ ): number[] {
989
+ const cell = (v: number, axis: 0 | 1): number =>
990
+ Math.min((axis === 0 ? grid.cols : grid.rows) - 1, Math.max(0, Math.trunc((v - grid.min[axis]) / grid.size)));
991
+ const cells: number[] = [];
992
+ for (let r = cell(lo[1], 1); r <= cell(hi[1], 1); r++) {
993
+ for (let c = cell(lo[0], 0); c <= cell(hi[0], 0); c++) {
994
+ cells.push(r * grid.cols + c);
995
+ }
996
+ }
997
+ return cells;
998
+ }
999
+
1000
+ /** Gephi's `edge.arc-curveness`: a curved edge is an arc of radius its nodes' distance over it (code only, 1.2). */
1001
+ const GEPHI_ARC_CURVENESS = 1.2;
1002
+
1003
+ /**
1004
+ * Each node's stored center, its viz:position; null without one (graphty-element lays such a node out, where Gephi
1005
+ * would put it at (0, 0)).
1006
+ * @param snapshot - the graph
1007
+ * @returns x and y by node
1008
+ */
1009
+ function gephiCenters(snapshot: GraphSnapshot): ([number, number] | null)[] {
1010
+ const position = snapshot.nodes.byRole("position");
1011
+ return Array.from({ length: snapshot.nodeCount }, (_, i) => {
1012
+ const v = position?.isSet(i) === true ? (position.value(i) as ArrayLike<number>) : null;
1013
+ return v === null ? null : [v[0] || 0, v[1] || 0];
1014
+ });
1015
+ }
1016
+
1017
+ /**
1018
+ * The curved edges Gephi draws no line for: its arc (radius distance / 1.2) is cut back at each end by a chord as long
1019
+ * as that end's node radius (its size), plus `edge.radius`, plus at the target of a directed edge its arrowhead
1020
+ * (`arrow.size` x its thickness); where the two cuts meet or pass each other, or a cut is longer than the arc, the
1021
+ * nodes swallow the edge and no line is drawn (Gephi 0.11 EdgeRenderer.CurvedEdgeRenderer.Helper,
1022
+ * computeTruncateAngle; gexf-cns-wos: 24 edges between overlapping nodes). Its arrowhead is still drawn, unless its
1023
+ * nodes are at one place (ArrowRenderer).
1024
+ * @param snapshot - the graph
1025
+ * @param nodes - the node values (their widths)
1026
+ * @param directed - whether each edge is drawn directed
1027
+ * @param thickness - each edge's drawn thickness
1028
+ * @param preview - the settings
1029
+ * @returns for each edge, "line" when only its line is swallowed, "all" when its nodes are at one place, else null
1030
+ */
1031
+ function swallowedEdges(
1032
+ snapshot: GraphSnapshot,
1033
+ nodes: GephiNodeValues,
1034
+ directed: readonly boolean[],
1035
+ thickness: readonly number[],
1036
+ preview: GephiPreview,
1037
+ ): ("line" | "all" | null)[] {
1038
+ const centers = gephiCenters(snapshot);
1039
+ const edgeRadius = preview.num("edge.radius");
1040
+ const arrowSize = preview.num("arrow.size");
1041
+ const arc = 2 * Math.asin(Math.min(1, GEPHI_ARC_CURVENESS / 2));
1042
+ return Array.from({ length: snapshot.edgeCount }, (_, e) => {
1043
+ const [a, b] = [snapshot.edgeSource(e), snapshot.edgeTarget(e)];
1044
+ const [pa, pb] = [centers[a], centers[b]];
1045
+ if (a === b || pa === null || pb === null) {
1046
+ return null;
1047
+ }
1048
+ const d = Math.hypot(pb[0] - pa[0], pb[1] - pa[1]);
1049
+ if (d === 0) {
1050
+ return "all";
1051
+ }
1052
+ const r = d / GEPHI_ARC_CURVENESS;
1053
+ // the angle a cut of chord length c takes off the arc; past the arc's length, more than the whole arc
1054
+ const cut = (c: number): number =>
1055
+ c >= r * arc ? Infinity : 2 * Math.atan2(c / 2, Math.sqrt(r * r - (c / 2) * (c / 2)));
1056
+ const source = edgeRadius + nodeWidth(nodes, a) / 2;
1057
+ const target = edgeRadius + nodeWidth(nodes, b) / 2 + (directed[e] ? arrowSize * thickness[e] : 0);
1058
+ return cut(source) + cut(target) >= arc ? "line" : null;
1059
+ });
1060
+ }
1061
+
1062
+ /** How many points a Gephi self-loop's circle is drawn through. */
1063
+ const SELF_LOOP_STEPS = 96;
1064
+
1065
+ /**
1066
+ * Gephi's self-loops as bends round their node: a circle up and to the right of it, centered `r` right of and `r`
1067
+ * above the node's center, of radius `r` = half the node's size (a radius) plus a third of the loop's stroke, which is
1068
+ * 1.3 times the edge's weight (gephi PreviewPlugin EdgeRenderer, SelfLoopEdgeRenderer; measured on the GEXF specimen
1069
+ * sheets and gexf-graphology-rio: r = 0.5435 x the drawn radius + 0.33 x the stroke). The circle runs from the side
1070
+ * facing the node all the way round; its part inside the node is not drawn.
1071
+ * @param snapshot - the graph
1072
+ * @param thickness - each edge's drawn thickness
1073
+ * @param hidden - whether each edge is not drawn
1074
+ * @returns each loop's bends, as fractions of its node's half size; null for any other edge
1075
+ */
1076
+ function selfLoopBends(
1077
+ snapshot: GraphSnapshot,
1078
+ thickness: readonly number[],
1079
+ hidden: readonly boolean[],
1080
+ ): (StyleValue | null)[] {
1081
+ const sizes = vizColumn(snapshot, "node", "size");
1082
+ const sizeOf = (n: number): number => {
1083
+ const v = sizes === null ? null : Number(plainCell(snapshot.nodes.value(sizes, n)));
1084
+ return v !== null && Number.isFinite(v) && v > 0 ? v : GEPHI_NODE_WIDTH / 2;
1085
+ };
1086
+ const fraction = (v: number): string => String(Math.round(v * 1e4) / 1e4);
1087
+ return Array.from({ length: snapshot.edgeCount }, (_, e) => {
1088
+ const n = snapshot.edgeSource(e);
1089
+ if (n !== snapshot.edgeTarget(e) || hidden[e]) {
1090
+ return null;
1091
+ }
1092
+ const size = sizeOf(n);
1093
+ const r = (0.5 * size + 0.33 * SELF_LOOP_THICKNESS * thickness[e]) / size;
1094
+ const points: string[] = [];
1095
+ for (let k = 0; k <= SELF_LOOP_STEPS; k++) {
1096
+ // from the point facing the node's center (225 degrees), clockwise all the way round
1097
+ const a = (5 * Math.PI) / 4 - (2 * Math.PI * k) / SELF_LOOP_STEPS;
1098
+ points.push(`${fraction(r + r * Math.cos(a))},${fraction(r + r * Math.sin(a))}`);
1099
+ }
1100
+ return points.join(" ");
1101
+ });
1102
+ }
1103
+
1104
+ /**
1105
+ * The edge rules: arrows, thickness, the edges not drawn, color and opacity, curves and labels.
1106
+ * @param edge - the edge rules, changed in place
1107
+ * @param snapshot - the graph
1108
+ * @param channels - each node's color channels
1109
+ * @param nodes - the node values (their widths)
1110
+ * @param edges - each edge's own color and width
1111
+ * @param gexf - the GEXF drawing, or undefined
1112
+ * @param preview - the settings
1113
+ */
1114
+ function edgeRules(
1115
+ edge: GephiTargetRules,
1116
+ snapshot: GraphSnapshot,
1117
+ channels: readonly number[][],
1118
+ nodes: GephiNodeValues,
1119
+ edges: GephiEdgeValues,
1120
+ gexf: GephiDrawing | undefined,
1121
+ preview: GephiPreview,
1122
+ ): void {
1123
+ const drawn = gephiEdges(snapshot, gexf !== undefined);
1124
+ const arrowSize = preview.num("arrow.size");
1125
+ if (drawn.directed.some(Boolean) && arrowSize > 0) {
1126
+ edge.columns.set(
324
1127
  DIRECTED_KEY,
325
- directed.map((d) => (d ? true : null)),
1128
+ drawn.directed.map((d) => (d ? true : null)),
326
1129
  );
327
- rules.push({
1130
+ // arrow.size scales the arrowhead from its default 3 (gexf-specimen-arrows-p2); 0 draws none (-p3)
1131
+ edge.rules.push({
328
1132
  id: "edge.tool.arrow",
329
1133
  target: "edge",
330
1134
  role: "group",
331
1135
  origin: "tool",
332
1136
  where: { has: { style: DIRECTED_KEY } },
333
- set: { targetArrow: "triangle" },
1137
+ set: { targetArrow: "triangle", ...(arrowSize === 3 ? {} : { arrowSize: arrowSize / 3 }) },
334
1138
  });
335
1139
  }
336
- // Gephi draws an edge without a color in its source node's color. graphty-element cannot bind an edge's color
337
- // to its source node's drawn color yet, so the color is frozen here; replace this column with that binding once
338
- // the element has it (tracking issue "graphty-element: bind an edge's color to its source node's drawn color").
339
- // An undirected edge in a directed graph is one edge in the file and two halves here: Gephi draws it once, in
340
- // the color of the source the file names, so the second half takes the first half's source.
341
- const first = firstHalf(snapshot);
342
- const sourceColor = withoutOwn(
343
- edgeColor,
344
- snapshot.edgeCount,
345
- (e) => nodeFill?.[snapshot.edgeSource(first(e))] ?? GEPHI_NODE_FILL,
1140
+ // Gephi draws an edge as thick as its weight (gephiEdges(): merged and shifted); a width of its own is the file's
1141
+ const hidden = preview.on("edge.show") ? drawn.hidden : drawn.hidden.map(() => true);
1142
+ const thickness = edgeThickness(
1143
+ drawn.weight,
1144
+ hidden.map((h) => !h),
1145
+ preview,
346
1146
  );
347
- const uncolored = sourceColor.filter((v) => v !== null).length;
348
- budget.spend("edge", snapshot.edgeCount);
349
- // Gephi draws an edge as thick as its weight (1 for an edge with none); a width of its own is the file's
350
- // The weights come from the snapshot's own weights (the role column exists only when some edge has none), read
351
- // as the shortest decimal of their dtype. A weight of 0 or below is drawn 1 wide, so the edge stays visible.
352
- const weights = explicitWeights(snapshot);
353
- const weightWidth = withoutOwn(edgeWidth, snapshot.edgeCount, (e) => {
354
- const w = Number(weights.text(first(e)) ?? 1);
355
- return Number.isFinite(w) && w > 0 ? w : 1;
1147
+ perElementDefault(
1148
+ edge,
1149
+ "edge",
1150
+ WEIGHT_WIDTH,
1151
+ // Gephi's self-loop is 1.3 times as thick (measured: weight 1 draws 1.3, 3.25 draws 4.225)
1152
+ withoutOwn(edges.width, snapshot.edgeCount, (e) =>
1153
+ snapshot.edgeSource(e) === snapshot.edgeTarget(e) ? SELF_LOOP_THICKNESS * thickness[e] : thickness[e],
1154
+ ),
1155
+ "EDGE_WEIGHT",
1156
+ );
1157
+ perElementDefault(edge, "edge", ["bends", "self-loop"], selfLoopBends(snapshot, thickness, hidden), "NODE_SIZE");
1158
+ // a curved edge its nodes (and its arrowhead) swallow draws no line; its arrowhead is still drawn
1159
+ const swallowed = preview.on("edge.curved")
1160
+ ? swallowedEdges(snapshot, nodes, drawn.directed, thickness, preview)
1161
+ : hidden.map(() => null);
1162
+ // the edges Gephi does not draw: one of another direction than the graph's, one merged into a parallel edge, or a
1163
+ // swallowed one with no arrowhead to keep
1164
+ hidden.forEach((h, e) => {
1165
+ const arrow = drawn.directed[e] && arrowSize > 0 && swallowed[e] === "line";
1166
+ hidden[e] = h || (swallowed[e] !== null && !arrow);
356
1167
  });
357
- const unsized = weightWidth.filter((v) => v !== null).length;
358
- if (unsized > 0) {
359
- columns.set(WEIGHT_WIDTH_KEY, weightWidth);
360
- rules.push({
361
- id: "edge.tool.weight-width",
1168
+ if (hidden.some(Boolean)) {
1169
+ edge.columns.set(
1170
+ HIDDEN_KEY,
1171
+ hidden.map((h) => (h ? true : null)),
1172
+ );
1173
+ edge.rules.push({
1174
+ id: "edge.tool.gephi-hidden",
362
1175
  target: "edge",
363
- role: "passthrough",
1176
+ role: "group",
364
1177
  origin: "tool",
365
- where: { has: { style: WEIGHT_WIDTH_KEY } },
366
- map: { width: { kind: "passthrough", by: { style: WEIGHT_WIDTH_KEY } } },
367
- facts: { elements: unsized, reason: "EDGE_WEIGHT" },
1178
+ where: { has: { style: HIDDEN_KEY } },
1179
+ set: { visible: false },
368
1180
  });
369
1181
  }
370
- if (uncolored > 0) {
371
- columns.set(SOURCE_COLOR_KEY, sourceColor);
372
- rules.push({
373
- id: "edge.tool.source-color",
374
- target: "edge",
375
- role: "passthrough",
376
- origin: "tool",
377
- where: { has: { style: SOURCE_COLOR_KEY } },
378
- map: { color: { kind: "passthrough", by: { style: SOURCE_COLOR_KEY } } },
379
- facts: { elements: uncolored, reason: "SOURCE_NODE_COLOR" },
380
- });
1182
+ const colors = edgeColors(snapshot, channels, gexf, preview);
1183
+ perElementDefault(
1184
+ edge,
1185
+ "edge",
1186
+ ["color", MIXED_COLOR_KEY],
1187
+ withoutOwn(edges.color, snapshot.edgeCount, (e) => hexOf(colors[e])),
1188
+ "END_NODE_COLORS",
1189
+ );
1190
+ perElementDefault(
1191
+ edge,
1192
+ "edge",
1193
+ ["opacity", "end-opacity"],
1194
+ withoutOwn(edges.color, snapshot.edgeCount, (e) => colors[e][3] / 255).map((v, e) => {
1195
+ // a swallowed edge Gephi draws only the arrowhead of draws no line
1196
+ if (swallowed[e] !== null && !hidden[e]) {
1197
+ return 0;
1198
+ }
1199
+ return v === 1 ? null : v;
1200
+ }),
1201
+ "END_NODE_COLORS",
1202
+ );
1203
+ everyElement(edge, "edge", "preview-curve", preview.on("edge.curved") ? {} : { curved: false });
1204
+ // Gephi's `edge.radius` leaves that much empty between each node and its edges' ends, arrowheads too
1205
+ const nodeGap = preview.num("edge.radius");
1206
+ everyElement(edge, "edge", "preview-radius", nodeGap > 0 ? { nodeGap } : {});
1207
+ edgeLabelRules(edge, colors, gexf, preview);
1208
+ }
1209
+
1210
+ /**
1211
+ * Each edge's color and alpha (0..255) under `edge.color` and `edge.opacity`: MIXED (the default) is each channel,
1212
+ * alpha too, the truncated mean of the two end nodes'; `source` and `target` are that node's; `original` is the edge's
1213
+ * own viz color, black without one; `#rrggbb` is that color, opaque (`gexf-specimen-edges-lines-p3` .. `-p7`).
1214
+ * graphty-element cannot bind an edge's color to its nodes' drawn colors, so the color is frozen here.
1215
+ * @param snapshot - the graph
1216
+ * @param channels - each node's color channels
1217
+ * @param gexf - the GEXF drawing, or undefined
1218
+ * @param preview - the settings
1219
+ * @returns each edge's r, g, b and alpha
1220
+ */
1221
+ function edgeColors(
1222
+ snapshot: GraphSnapshot,
1223
+ channels: readonly number[][],
1224
+ gexf: GephiDrawing | undefined,
1225
+ preview: GephiPreview,
1226
+ ): number[][] {
1227
+ const mode = preview.text("edge.color");
1228
+ const factor = preview.num("edge.opacity") / 100;
1229
+ const own = (e: number): number[] => {
1230
+ const c = gexf?.original?.color?.[e];
1231
+ const a = gexf?.original?.opacity?.[e];
1232
+ return nodeChannels(typeof c === "string" ? c : GEPHI_NODE_FILL, typeof a === "number" ? a : null);
1233
+ };
1234
+ const custom = dependantColor(mode, [0, 0, 0]);
1235
+ return Array.from({ length: snapshot.edgeCount }, (_, e) => {
1236
+ const s = channels[snapshot.edgeSource(e)];
1237
+ const t = channels[snapshot.edgeTarget(e)];
1238
+ let c: number[];
1239
+ if (mode === "source") {
1240
+ c = [...s];
1241
+ } else if (mode === "target") {
1242
+ c = [...t];
1243
+ } else if (mode === "original") {
1244
+ c = own(e);
1245
+ } else if (mode.startsWith("#") && custom !== null) {
1246
+ c = [...custom, 255];
1247
+ } else {
1248
+ c = s.map((v, k) => Math.floor((v + t[k]) / 2));
1249
+ }
1250
+ return factor === 1 ? c : [c[0], c[1], c[2], c[3] * factor];
1251
+ });
1252
+ }
1253
+
1254
+ /**
1255
+ * The edge label rules of `edge.label.show`: the font's size (not in proportion to anything), color, outline and
1256
+ * font (`gexf-specimen-edge-labels-p2` .. `-p15`).
1257
+ * @param edge - the edge rules, changed in place
1258
+ * @param colors - each edge's color and alpha
1259
+ * @param gexf - the GEXF drawing, or undefined
1260
+ * @param preview - the settings
1261
+ */
1262
+ function edgeLabelRules(
1263
+ edge: GephiTargetRules,
1264
+ colors: readonly number[][],
1265
+ gexf: GephiDrawing | undefined,
1266
+ preview: GephiPreview,
1267
+ ): void {
1268
+ if (gexf?.edgeLabels === undefined || !preview.on("edge.label.show")) {
1269
+ return;
1270
+ }
1271
+ const font = preview.font("edge");
1272
+ const color = preview.text("edge.label.color");
1273
+ if (color !== "original") {
1274
+ perElementDefault(
1275
+ edge,
1276
+ "edge",
1277
+ ["labelColor", "preview-label-color"],
1278
+ colors.map((c) => hexOf(dependantColor(color, c) ?? [0, 0, 0])),
1279
+ "EDGE_LABEL_COLOR",
1280
+ );
381
1281
  }
382
- return { rules, columns };
1282
+ labelOutline(
1283
+ edge,
1284
+ "edge",
1285
+ colors,
1286
+ colors.map(() => font.size),
1287
+ preview,
1288
+ );
1289
+ // Gephi centers an edge label on its line's middle (gexf-specimen-edge-labels-p2: 0.04 of the font off it)
1290
+ everyElement(edge, "edge", "preview-label", {
1291
+ labelSize: font.size,
1292
+ labelPosition: "center",
1293
+ ...fontValues(font, preview.on("edge.label.customFont")),
1294
+ });
1295
+ }
1296
+
1297
+ /**
1298
+ * Preview's "mixed" edge color: the mean of two colors, each channel rounded down (`EdgeColor.java` lines 89-93).
1299
+ * @param a - one end's color, `#rrggbb`
1300
+ * @param b - the other's
1301
+ * @returns the mixed color
1302
+ */
1303
+ function gephiMix(a: string, b: string): string {
1304
+ const x = Number.parseInt(a.slice(1), 16);
1305
+ const y = Number.parseInt(b.slice(1), 16);
1306
+ let out = 0;
1307
+ for (const shift of [16, 8, 0]) {
1308
+ out |= Math.floor((((x >> shift) & 255) + ((y >> shift) & 255)) / 2) << shift;
1309
+ }
1310
+ return `#${out.toString(16).padStart(6, "0")}`;
383
1311
  }
384
1312
 
385
1313
  /**
@@ -415,9 +1343,10 @@ function withoutOwn(
415
1343
  }
416
1344
 
417
1345
  /**
418
- * The styles of a GEXF file: each node's label and each element's viz values as Gephi draws them, Gephi's defaults beneath
419
- * (black size-10 discs), an arrow at the target of every directed edge, and the source node's color on every edge
420
- * without a color of its own; plus a `graphty.styles` block the file holds in `<meta>`.
1346
+ * The styles of a GEXF file as Gephi's preview draws it: each node's label, fill and size, Gephi's defaults beneath
1347
+ * (black size-10 discs, curved edges) and its per-element defaults (gephiRules()); the viz values Gephi does not draw
1348
+ * are left out (withoutUndrawn()); plus a `graphty.styles` block the file holds in `<meta>`, whose file keeps every
1349
+ * value.
421
1350
  * @param snapshot - the graph the import built
422
1351
  * @param context - the work budget and the block text the import kept
423
1352
  * @returns the styles
@@ -426,18 +1355,47 @@ export function gexfStyles(snapshot: GraphSnapshot, context: StyleReadContext):
426
1355
  const notes = new StyleNotes();
427
1356
  const node = vizValues(snapshot, "node", notes, context);
428
1357
  const edge = vizValues(snapshot, "edge", notes, context);
429
- const block = typeof context.kept === "string" ? context.kept : null;
430
- if (block !== null) {
1358
+ const { block, preview } = keptOf(context.kept);
1359
+ const nodeOpacity = node.fillOpacity;
1360
+ const original = { color: edge.color, opacity: edge.opacity };
1361
+ if (block === null) {
1362
+ withoutUndrawn(node, edge);
1363
+ } else {
431
1364
  exactFromBlock(snapshot, block, node, edge);
432
1365
  }
433
- const { rules, columns } = gephiRules(snapshot, node.fill, edge.color, context.budget, edge.width);
1366
+ const labels = drawnLabels(node.label, preview, "node", block !== null);
1367
+ const overlapHidden =
1368
+ block === null && labels !== undefined && preview.on("node.label.avoidOverlap")
1369
+ ? gephiOverlapHidden(snapshot, labels, node.width, preview)
1370
+ : null;
1371
+ overlapHidden?.forEach((hide, i) => {
1372
+ if (hide && labels !== undefined) {
1373
+ labels[i] = null;
1374
+ }
1375
+ });
1376
+ if (labels === undefined) {
1377
+ delete node.label;
1378
+ } else {
1379
+ node.label = labels;
1380
+ }
1381
+ const edgeLabels = drawnLabels(edgeLabelValues(snapshot), preview, "edge", block !== null);
1382
+ if (edgeLabels !== undefined) {
1383
+ edge.label = edgeLabels;
1384
+ }
1385
+ const rules = gephiRules(
1386
+ snapshot,
1387
+ { fill: node.fill, fillOpacity: nodeOpacity, width: node.width },
1388
+ { color: edge.color, width: edge.width },
1389
+ context.budget,
1390
+ { preview, original, edgeLabels, overlapSettled: overlapHidden !== null },
1391
+ );
434
1392
  return {
435
1393
  canonical: true,
436
1394
  dialect: "gexf",
437
1395
  tool: "gephi",
438
1396
  units: "relative",
439
- node: { values: node },
440
- edge: { values: edge, rules, columns },
1397
+ node: { values: node, ...rules.node },
1398
+ edge: { values: edge, ...rules.edge },
441
1399
  notes: notes.list(),
442
1400
  opacityInColor: true,
443
1401
  block,
@@ -448,6 +1406,61 @@ export function gexfStyles(snapshot: GraphSnapshot, context: StyleReadContext):
448
1406
  };
449
1407
  }
450
1408
 
1409
+ /**
1410
+ * What the import kept for the styles: the `graphty.styles` block, and the Preview settings of the `gephiPreview`
1411
+ * option.
1412
+ * @param kept - the kept value: the block's text, or `{ block, gephiPreview }`
1413
+ * @returns the block (null without one) and the Preview settings
1414
+ */
1415
+ function keptOf(kept: unknown): { readonly block: string | null; readonly preview: GephiPreview } {
1416
+ if (typeof kept === "string") {
1417
+ return { block: kept, preview: new GephiPreview() };
1418
+ }
1419
+ if (typeof kept === "object" && kept !== null) {
1420
+ const { block, gephiPreview } = kept as { block?: unknown; gephiPreview?: Record<string, string> };
1421
+ return { block: typeof block === "string" ? block : null, preview: new GephiPreview(gephiPreview) };
1422
+ }
1423
+ return { block: null, preview: new GephiPreview() };
1424
+ }
1425
+
1426
+ /**
1427
+ * Each edge's `label` attribute.
1428
+ * @param snapshot - the graph
1429
+ * @returns the labels, or undefined when no edge has one
1430
+ */
1431
+ function edgeLabelValues(snapshot: GraphSnapshot): (StyleValue | null)[] | undefined {
1432
+ const col = snapshot.edges.byRole("label");
1433
+ if (col === null) {
1434
+ return undefined;
1435
+ }
1436
+ return Array.from({ length: snapshot.edgeCount }, (_, e) => {
1437
+ const v = plainCell(col.value(e));
1438
+ return typeof v === "string" && v.length > 0 ? v : null;
1439
+ });
1440
+ }
1441
+
1442
+ /**
1443
+ * Leaves out the viz values Gephi does not draw, for a file graphty did not write: its preview draws every node an
1444
+ * opaque disc (a node's alpha only tints its edges) and every edge a solid line in its end nodes' mixed color, as
1445
+ * thick as its weight (graphstore keeps no edge thickness or shape, and EdgeColor MIXED ignores an edge's color).
1446
+ * Every `viz:shape` is drawn a disc, an `image` shape too: Gephi Toolkit 0.11.3 draws no picture
1447
+ * (`gexf-specimen-images-charts`: a data: URI and a web URL both drawn as the node's colored disc).
1448
+ * @param node - the node values, changed in place
1449
+ * @param edge - the edge values, changed in place
1450
+ */
1451
+ function withoutUndrawn(
1452
+ node: Record<string, (StyleValue | null)[]>,
1453
+ edge: Record<string, (StyleValue | null)[]>,
1454
+ ): void {
1455
+ delete node.shape;
1456
+ delete node.image;
1457
+ delete node.fillOpacity;
1458
+ delete edge.color;
1459
+ delete edge.opacity;
1460
+ delete edge.width;
1461
+ delete edge.lineStyle;
1462
+ }
1463
+
451
1464
  /**
452
1465
  * For a file graphty wrote: where a viz value is the nearest GEXF value of what the `graphty.styles` block gives (a
453
1466
  * hexagon written as a disc, a width and height written as one size), the block's exact value is the truth, so a
@@ -464,49 +1477,33 @@ function exactFromBlock(
464
1477
  node: Record<string, (StyleValue | null)[]>,
465
1478
  edge: Record<string, (StyleValue | null)[]>,
466
1479
  ): void {
467
- const parsed = parseStyleBlock(text, styleFingerprint(snapshot));
468
- const ex = parsed.ok ? blockStyles(parsed.block, snapshot, "gexf") : null;
469
- if (ex === null) {
470
- return;
471
- }
472
- const exact = (
473
- values: Record<string, (StyleValue | null)[]>,
474
- target: StyleTarget,
475
- property: string,
476
- written: (v: StyleValue, i: number) => StyleValue | null,
477
- ): void => {
478
- const native = values[property];
479
- if (native === undefined) {
480
- return;
481
- }
482
- const want = resolveStyles(ex, target, property as never, snapshot);
483
- for (let i = 0; i < native.length; i++) {
484
- const w = want[i];
485
- // a size is written from the width and the height together: one the block leaves unset reads back too
486
- const writable = w !== null || property === "width" || property === "height";
487
- if (writable && native[i] !== w && native[i] !== null && native[i] === written(w as StyleValue, i)) {
488
- native[i] = w;
489
- }
490
- }
491
- };
492
- const width = resolveStyles(ex, "node", "width", snapshot);
493
- const height = resolveStyles(ex, "node", "height", snapshot);
494
- const size = (i: number): StyleValue | null => {
495
- const w = width[i];
496
- const h = height[i];
497
- return typeof w === "number" && typeof h === "number" ? Math.max(w, h) : (w ?? h);
498
- };
499
- const read = new Map([
1480
+ const shapeRead = new Map([
500
1481
  ["disc", "ellipse"],
501
1482
  ["square", "rectangle"],
502
1483
  ]);
503
- exact(node, "node", "shape", (v) => {
504
- const written = gexfNodeShape(String(v)).value;
505
- return read.get(written) ?? written;
506
- });
507
- exact(edge, "edge", "lineStyle", (v) => gexfEdgeShape(String(v)).value);
508
- exact(node, "node", "width", (_v, i) => size(i));
509
- exact(node, "node", "height", (_v, i) => size(i));
1484
+ const size = (want: ElementValues): StyleValue | null => {
1485
+ const w = want("width");
1486
+ const h = want("height");
1487
+ return typeof w === "number" && typeof h === "number" ? Math.max(w, h) : (w ?? h);
1488
+ };
1489
+ // what the viz element written for an element's block values reads back as
1490
+ const written: Readonly<Record<string, (want: ElementValues) => StyleValue | null>> = {
1491
+ shape: (want) => {
1492
+ const { value } = gexfNodeShape(String(want("shape")));
1493
+ return shapeRead.get(value) ?? value;
1494
+ },
1495
+ width: size,
1496
+ height: size,
1497
+ lineStyle: (want) => gexfEdgeShape(String(want("lineStyle"))).value,
1498
+ };
1499
+ takeFromBlock(snapshot, text, "gexf", (target) => ({
1500
+ values: target === "node" ? node : edge,
1501
+ properties: target === "node" ? ["shape", "width", "height"] : ["lineStyle"],
1502
+ readBack: (_i, want) => (property) => written[property](want),
1503
+ // a size is written from the width and the height together: one the block leaves unset reads back too
1504
+ take: (property, wanted, native) =>
1505
+ native !== null && (wanted !== null || property === "width" || property === "height"),
1506
+ }));
510
1507
  }
511
1508
 
512
1509
  // ============================================================ writing
@@ -575,14 +1572,19 @@ export function gexfStyledExport(
575
1572
  const s = nodeShapes[i];
576
1573
  return (
577
1574
  colorElement(fill[i], fillOpacity[i], indent) +
578
- (size === null ? "" : `${indent}<viz:size value="${String(size)}"/>\n`) +
1575
+ (size === null ? "" : `${indent}<viz:size value="${String(size / 2)}"/>\n`) +
579
1576
  shapeElement(s, s === "image" ? String(image[i]) : null, indent)
580
1577
  );
581
1578
  },
582
1579
  edge(e, indent) {
583
1580
  const w = thickness[e];
1581
+ // an edge in the mixed color of its nodes is left without one: Gephi, and graph-io, draw it so anyway
1582
+ const c = color[e];
1583
+ const end = (i: number): string => String(fill[i] ?? GEPHI_NODE_FILL);
1584
+ const mixed =
1585
+ typeof c === "string" && c === gephiMix(end(snapshot.edgeSource(e)), end(snapshot.edgeTarget(e)));
584
1586
  return (
585
- colorElement(color[e], opacity[e], indent) +
1587
+ (mixed && (opacity[e] ?? 1) === 1 ? "" : colorElement(c, opacity[e], indent)) +
586
1588
  (typeof w === "number" ? `${indent}<viz:thickness value="${String(w)}"/>\n` : "") +
587
1589
  shapeElement(edgeShapes[e], null, indent)
588
1590
  );