moderne-visualizations-misc 0.73.1__py3-none-any.whl → 0.74.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,45 +1,1567 @@
1
1
  {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 3,
6
- "metadata": {
7
- "tags": [
8
- "parameters"
9
- ]
10
- },
11
- "outputs": [],
12
- "source": [
13
- "repository_filter: list[str] = []"
14
- ]
15
- },
16
- {
17
- "cell_type": "code",
18
- "execution_count": null,
19
- "metadata": {},
20
- "outputs": [],
21
- "source": "import pandas as pd\nimport warnings\nimport plotly.graph_objects as go\nimport code_data_science.data_table as dt\n\nwarnings.simplefilter(\"ignore\")\n\ndf = dt.read_csv(\"../samples/dependency_vulnerabilities.csv\")\n\ndf[\"repositoryWithBranch\"] = df[\"repositoryPath\"] + \"/\" + df[\"repositoryBranch\"]\n\n# Filter the data frame to only include rows where repositoryWithBranch contain\n# a term in the repository_filter (case insensitive)\nif len(repository_filter) > 0:\n df = df[\n df[\"repositoryWithBranch\"].str.contains(\"|\".join(repository_filter), case=False)\n ]\n\n\ndef create_bar_plot(df_direct, df_transitive):\n if df_direct is None or df_transitive is None:\n # Create empty dataframes with the correct structure\n fix_types = [\"Patch\", \"Minor\", \"Major\", \"No fixed version\"]\n df_direct = pd.DataFrame({\n \"Type\": fix_types,\n \"Low\": [0] * 4,\n \"Moderate\": [0] * 4,\n \"High\": [0] * 4,\n \"Critical\": [0] * 4,\n })\n df_transitive = pd.DataFrame({\n \"Type\": fix_types,\n \"Low\": [0] * 4,\n \"Moderate\": [0] * 4,\n \"High\": [0] * 4,\n \"Critical\": [0] * 4,\n })\n\n fig = go.Figure()\n \n # Define colors for severity levels\n colors = {\n \"Critical\": \"#FF5B5B\",\n \"High\": \"#FABA49\",\n \"Moderate\": \"#FEE968\",\n \"Low\": \"#52BBA0\"\n }\n \n # Calculate totals for percentage calculations\n df_direct[\"Total\"] = df_direct[[\"Critical\", \"High\", \"Moderate\", \"Low\"]].sum(axis=1)\n df_transitive[\"Total\"] = df_transitive[[\"Critical\", \"High\", \"Moderate\", \"Low\"]].sum(axis=1)\n grand_total = df_direct[\"Total\"].sum() + df_transitive[\"Total\"].sum()\n \n # Add traces for direct dependencies\n for i, severity in enumerate([\"Critical\", \"High\", \"Moderate\", \"Low\"]):\n # Calculate percentages for hover\n percentages_direct = []\n for idx in range(len(df_direct)):\n count = df_direct.iloc[idx][severity]\n total_in_category = df_direct.iloc[idx][\"Total\"] + df_transitive.iloc[idx][\"Total\"]\n pct_of_category = (count / total_in_category * 100) if total_in_category > 0 else 0\n pct_of_all = (count / grand_total * 100) if grand_total > 0 else 0\n percentages_direct.append({\n \"category_pct\": round(pct_of_category, 1),\n \"total_pct\": round(pct_of_all, 1)\n })\n \n fig.add_trace(go.Bar(\n name=severity,\n x=df_direct[\"Type\"],\n y=df_direct[severity],\n marker_color=colors[severity],\n legendgroup=severity,\n offsetgroup=\"direct\",\n showlegend=True,\n customdata=percentages_direct,\n hovertemplate=(\n '<b>%{x}</b><br>' +\n f'{severity} (Direct): %{{y}}<br>' +\n 'Percent of %{x}: %{customdata.category_pct}%<br>' +\n 'Percent of all: %{customdata.total_pct}%' +\n '<extra></extra>'\n )\n ))\n \n # Add traces for transitive dependencies\n for i, severity in enumerate([\"Critical\", \"High\", \"Moderate\", \"Low\"]):\n # Calculate percentages for hover\n percentages_transitive = []\n for idx in range(len(df_transitive)):\n count = df_transitive.iloc[idx][severity]\n total_in_category = df_direct.iloc[idx][\"Total\"] + df_transitive.iloc[idx][\"Total\"]\n pct_of_category = (count / total_in_category * 100) if total_in_category > 0 else 0\n pct_of_all = (count / grand_total * 100) if grand_total > 0 else 0\n percentages_transitive.append({\n \"category_pct\": round(pct_of_category, 1),\n \"total_pct\": round(pct_of_all, 1)\n })\n \n fig.add_trace(go.Bar(\n name=f'{severity} ', # Add space to make it different from direct\n x=df_transitive[\"Type\"],\n y=df_transitive[severity],\n marker_color=colors[severity],\n marker_pattern_shape=\"/\",\n legendgroup=severity,\n offsetgroup=\"transitive\",\n showlegend=False, # Hide from legend since we'll use annotations\n customdata=percentages_transitive,\n hovertemplate=(\n '<b>%{x}</b><br>' +\n f'{severity} (Transitive): %{{y}}<br>' +\n 'Percent of %{x}: %{customdata.category_pct}%<br>' +\n 'Percent of all: %{customdata.total_pct}%' +\n '<extra></extra>'\n )\n ))\n \n # Update layout with improved legend\n fig.update_layout(\n xaxis={\"title\": \"Type of version required to fix vulnerability\"},\n yaxis={\"title\": \"Vulnerability count\"},\n barmode='group',\n bargap=0.15,\n bargroupgap=0.05,\n margin=dict(l=0, r=0, t=60, b=60),\n legend=dict(\n orientation=\"v\",\n yanchor=\"top\",\n y=0.95,\n xanchor=\"left\",\n x=1.02,\n title=dict(\n text=\"Severity Level<br><sub>Solid: Direct | Striped: Transitive</sub>\",\n font=dict(size=12)\n )\n ),\n annotations=[\n dict(\n text=\"<b>Dependency Vulnerabilities by Fix Type</b>\",\n xref=\"paper\",\n yref=\"paper\",\n x=0.5,\n y=1.05,\n showarrow=False,\n font=dict(size=14)\n )\n ]\n )\n \n return fig\n\n\n# Exit early if there are no stack traces and render a plot with a message\nif len(df) == 0:\n fig = create_bar_plot(None, None)\n fig.update_yaxes(range=[0, 10])\n fig.show(render=\"plotly_mimetype\")\nelse:\n\n def get_semver_fix(version, fixed_version):\n \"\"\"\n looks at current version and fixed version and determines if the fix is a major, minor, patch version, or no fix\n \"\"\"\n version_components = version.split(\".\")\n fixed_components = fixed_version.split(\".\")\n\n # if fixed version is empty, return \"No fixed version\"\n if fixed_version == \"\":\n return \"No fixed version\"\n\n if len(version_components) < 3:\n # fill in the missing version components with 0\n for i in range(3 - len(version_components)):\n version_components.append(\"0\")\n if len(fixed_components) < 3:\n # fill in the missing version components with 0\n for i in range(3 - len(fixed_components)):\n fixed_components.append(\"0\")\n\n elif version_components[0] != fixed_components[0]:\n return \"Major\"\n elif version_components[1] != fixed_components[1]:\n return \"Minor\"\n elif version_components[2] != fixed_components[2]:\n return \"Patch\"\n else:\n return \"Unknown\"\n\n # drop unnecessary columns except depth which we need\n df = df.drop(\n columns=[\n \"repositoryOrigin\",\n \"repositoryPath\",\n \"repositoryBranch\",\n \"repositoryWithBranch\",\n \"cve\",\n \"groupId\",\n \"artifactId\",\n \"summary\",\n ]\n )\n\n # fill NaN values with empty string\n df[\"fixedVersion\"] = df[\"fixedVersion\"].fillna(\"\")\n df[\"version\"] = df[\"version\"].fillna(\"\")\n # make sure version and fixedVersion is a string\n df[\"fixedVersion\"] = df[\"fixedVersion\"].astype(str)\n df[\"version\"] = df[\"version\"].astype(str)\n\n # add column 'semverFix' to dataframe\n df[\"semverFix\"] = df.apply(\n lambda x: get_semver_fix(x[\"version\"], x[\"fixedVersion\"]), axis=1\n )\n \n # Split into direct and transitive\n df_direct = df[df[\"depth\"] == 0]\n df_transitive = df[df[\"depth\"] != 0]\n \n # Define the order for fix types\n fix_order = [\"Patch\", \"Minor\", \"Major\", \"No fixed version\"]\n \n # Create aggregated data for direct dependencies\n direct_data = []\n for fix_type in fix_order:\n fix_df = df_direct[df_direct[\"semverFix\"] == fix_type]\n severity_counts = fix_df[\"severity\"].value_counts()\n direct_data.append({\n \"Type\": fix_type,\n \"Critical\": severity_counts.get(\"CRITICAL\", 0),\n \"High\": severity_counts.get(\"HIGH\", 0),\n \"Moderate\": severity_counts.get(\"MODERATE\", 0),\n \"Low\": severity_counts.get(\"LOW\", 0)\n })\n df_direct_plot = pd.DataFrame(direct_data)\n \n # Create aggregated data for transitive dependencies\n transitive_data = []\n for fix_type in fix_order:\n fix_df = df_transitive[df_transitive[\"semverFix\"] == fix_type]\n severity_counts = fix_df[\"severity\"].value_counts()\n transitive_data.append({\n \"Type\": fix_type,\n \"Critical\": severity_counts.get(\"CRITICAL\", 0),\n \"High\": severity_counts.get(\"HIGH\", 0),\n \"Moderate\": severity_counts.get(\"MODERATE\", 0),\n \"Low\": severity_counts.get(\"LOW\", 0)\n })\n df_transitive_plot = pd.DataFrame(transitive_data)\n\n fig = create_bar_plot(df_direct_plot, df_transitive_plot)\n\n # Show the figure\n fig.show(render=\"plotly_mimetype\")"
22
- }
23
- ],
24
- "metadata": {
25
- "kernelspec": {
26
- "display_name": "Python 3 (ipykernel)",
27
- "language": "python",
28
- "name": "python3"
29
- },
30
- "language_info": {
31
- "codemirror_mode": {
32
- "name": "ipython",
33
- "version": 3
34
- },
35
- "file_extension": ".py",
36
- "mimetype": "text/x-python",
37
- "name": "python",
38
- "nbconvert_exporter": "python",
39
- "pygments_lexer": "ipython3",
40
- "version": "3.12.2"
41
- }
42
- },
43
- "nbformat": 4,
44
- "nbformat_minor": 4
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 3,
6
+ "metadata": {
7
+ "tags": [
8
+ "parameters"
9
+ ]
10
+ },
11
+ "outputs": [],
12
+ "source": [
13
+ "repository_filter: list[str] = []"
14
+ ]
15
+ },
16
+ {
17
+ "cell_type": "code",
18
+ "execution_count": null,
19
+ "metadata": {},
20
+ "outputs": [
21
+ {
22
+ "data": {
23
+ "application/vnd.plotly.v1+json": {
24
+ "config": {
25
+ "plotlyServerURL": "https://plot.ly"
26
+ },
27
+ "data": [
28
+ {
29
+ "customdata": [
30
+ {
31
+ "category_pct": 0.1,
32
+ "total_pct": 0
33
+ },
34
+ {
35
+ "category_pct": 0,
36
+ "total_pct": 0
37
+ },
38
+ {
39
+ "category_pct": 1.2,
40
+ "total_pct": 0.1
41
+ },
42
+ {
43
+ "category_pct": 0.5,
44
+ "total_pct": 0.1
45
+ }
46
+ ],
47
+ "hovertemplate": "<b>%{x}</b><br>Low (Direct): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
48
+ "legendgroup": "Low",
49
+ "marker": {
50
+ "color": "#52BBA0"
51
+ },
52
+ "name": "Low",
53
+ "showlegend": true,
54
+ "type": "bar",
55
+ "x": [
56
+ -0.2,
57
+ 0.8,
58
+ 1.8,
59
+ 2.8
60
+ ],
61
+ "y": [
62
+ 5,
63
+ 0,
64
+ 13,
65
+ 10
66
+ ]
67
+ },
68
+ {
69
+ "customdata": [
70
+ {
71
+ "category_pct": 0.6,
72
+ "total_pct": 0.3
73
+ },
74
+ {
75
+ "category_pct": 2.3,
76
+ "total_pct": 0.6
77
+ },
78
+ {
79
+ "category_pct": 7.1,
80
+ "total_pct": 0.5
81
+ },
82
+ {
83
+ "category_pct": 2.2,
84
+ "total_pct": 0.3
85
+ }
86
+ ],
87
+ "hovertemplate": "<b>%{x}</b><br>Moderate (Direct): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
88
+ "legendgroup": "Moderate",
89
+ "marker": {
90
+ "color": "#FEE968"
91
+ },
92
+ "name": "Moderate",
93
+ "showlegend": true,
94
+ "type": "bar",
95
+ "x": [
96
+ -0.2,
97
+ 0.8,
98
+ 1.8,
99
+ 2.8
100
+ ],
101
+ "y": [
102
+ 49,
103
+ 87,
104
+ 80,
105
+ 40
106
+ ]
107
+ },
108
+ {
109
+ "customdata": [
110
+ {
111
+ "category_pct": 1,
112
+ "total_pct": 0.5
113
+ },
114
+ {
115
+ "category_pct": 3,
116
+ "total_pct": 0.8
117
+ },
118
+ {
119
+ "category_pct": 3.7,
120
+ "total_pct": 0.3
121
+ },
122
+ {
123
+ "category_pct": 0.9,
124
+ "total_pct": 0.1
125
+ }
126
+ ],
127
+ "hovertemplate": "<b>%{x}</b><br>High (Direct): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
128
+ "legendgroup": "High",
129
+ "marker": {
130
+ "color": "#FABA49"
131
+ },
132
+ "name": "High",
133
+ "showlegend": true,
134
+ "type": "bar",
135
+ "x": [
136
+ -0.2,
137
+ 0.8,
138
+ 1.8,
139
+ 2.8
140
+ ],
141
+ "y": [
142
+ 78,
143
+ 115,
144
+ 42,
145
+ 17
146
+ ]
147
+ },
148
+ {
149
+ "customdata": [
150
+ {
151
+ "category_pct": 0.3,
152
+ "total_pct": 0.2
153
+ },
154
+ {
155
+ "category_pct": 2.1,
156
+ "total_pct": 0.5
157
+ },
158
+ {
159
+ "category_pct": 4.9,
160
+ "total_pct": 0.4
161
+ },
162
+ {
163
+ "category_pct": 0.3,
164
+ "total_pct": 0
165
+ }
166
+ ],
167
+ "hovertemplate": "<b>%{x}</b><br>Critical (Direct): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
168
+ "legendgroup": "Critical",
169
+ "marker": {
170
+ "color": "#FF5B5B"
171
+ },
172
+ "name": "Critical",
173
+ "showlegend": true,
174
+ "type": "bar",
175
+ "x": [
176
+ -0.2,
177
+ 0.8,
178
+ 1.8,
179
+ 2.8
180
+ ],
181
+ "y": [
182
+ 26,
183
+ 81,
184
+ 55,
185
+ 5
186
+ ]
187
+ },
188
+ {
189
+ "customdata": [
190
+ {
191
+ "category_pct": 8,
192
+ "total_pct": 4.4
193
+ },
194
+ {
195
+ "category_pct": 6.5,
196
+ "total_pct": 1.7
197
+ },
198
+ {
199
+ "category_pct": 7.8,
200
+ "total_pct": 0.6
201
+ },
202
+ {
203
+ "category_pct": 14.6,
204
+ "total_pct": 1.8
205
+ }
206
+ ],
207
+ "hovertemplate": "<b>%{x}</b><br>Low (Transitive): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
208
+ "legendgroup": "Low",
209
+ "marker": {
210
+ "color": "#52BBA0",
211
+ "pattern": {
212
+ "shape": "/"
213
+ }
214
+ },
215
+ "name": "Low ",
216
+ "showlegend": false,
217
+ "type": "bar",
218
+ "x": [
219
+ 0.2,
220
+ 1.2,
221
+ 2.2,
222
+ 3.2
223
+ ],
224
+ "y": [
225
+ 650,
226
+ 251,
227
+ 87,
228
+ 269
229
+ ]
230
+ },
231
+ {
232
+ "customdata": [
233
+ {
234
+ "category_pct": 37.6,
235
+ "total_pct": 20.4
236
+ },
237
+ {
238
+ "category_pct": 29.4,
239
+ "total_pct": 7.6
240
+ },
241
+ {
242
+ "category_pct": 31.6,
243
+ "total_pct": 2.4
244
+ },
245
+ {
246
+ "category_pct": 43.2,
247
+ "total_pct": 5.3
248
+ }
249
+ ],
250
+ "hovertemplate": "<b>%{x}</b><br>Moderate (Transitive): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
251
+ "legendgroup": "Moderate",
252
+ "marker": {
253
+ "color": "#FEE968",
254
+ "pattern": {
255
+ "shape": "/"
256
+ }
257
+ },
258
+ "name": "Moderate ",
259
+ "showlegend": false,
260
+ "type": "bar",
261
+ "x": [
262
+ 0.2,
263
+ 1.2,
264
+ 2.2,
265
+ 3.2
266
+ ],
267
+ "y": [
268
+ 3041,
269
+ 1133,
270
+ 355,
271
+ 797
272
+ ]
273
+ },
274
+ {
275
+ "customdata": [
276
+ {
277
+ "category_pct": 42.8,
278
+ "total_pct": 23.2
279
+ },
280
+ {
281
+ "category_pct": 45,
282
+ "total_pct": 11.6
283
+ },
284
+ {
285
+ "category_pct": 29.1,
286
+ "total_pct": 2.2
287
+ },
288
+ {
289
+ "category_pct": 29.5,
290
+ "total_pct": 3.7
291
+ }
292
+ ],
293
+ "hovertemplate": "<b>%{x}</b><br>High (Transitive): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
294
+ "legendgroup": "High",
295
+ "marker": {
296
+ "color": "#FABA49",
297
+ "pattern": {
298
+ "shape": "/"
299
+ }
300
+ },
301
+ "name": "High ",
302
+ "showlegend": false,
303
+ "type": "bar",
304
+ "x": [
305
+ 0.2,
306
+ 1.2,
307
+ 2.2,
308
+ 3.2
309
+ ],
310
+ "y": [
311
+ 3467,
312
+ 1734,
313
+ 326,
314
+ 545
315
+ ]
316
+ },
317
+ {
318
+ "customdata": [
319
+ {
320
+ "category_pct": 9.6,
321
+ "total_pct": 5.2
322
+ },
323
+ {
324
+ "category_pct": 11.8,
325
+ "total_pct": 3
326
+ },
327
+ {
328
+ "category_pct": 14.6,
329
+ "total_pct": 1.1
330
+ },
331
+ {
332
+ "category_pct": 8.8,
333
+ "total_pct": 1.1
334
+ }
335
+ ],
336
+ "hovertemplate": "<b>%{x}</b><br>Critical (Transitive): %{y}<br>Percent of %{x}: %{customdata.category_pct}%<br>Percent of all: %{customdata.total_pct}%<extra></extra>",
337
+ "legendgroup": "Critical",
338
+ "marker": {
339
+ "color": "#FF5B5B",
340
+ "pattern": {
341
+ "shape": "/"
342
+ }
343
+ },
344
+ "name": "Critical ",
345
+ "showlegend": false,
346
+ "type": "bar",
347
+ "x": [
348
+ 0.2,
349
+ 1.2,
350
+ 2.2,
351
+ 3.2
352
+ ],
353
+ "y": [
354
+ 778,
355
+ 453,
356
+ 164,
357
+ 163
358
+ ]
359
+ }
360
+ ],
361
+ "layout": {
362
+ "annotations": [
363
+ {
364
+ "font": {
365
+ "size": 14
366
+ },
367
+ "showarrow": false,
368
+ "text": "<b>Dependency Vulnerabilities by Fix Type</b>",
369
+ "x": 0.5,
370
+ "xref": "paper",
371
+ "y": 1.05,
372
+ "yref": "paper"
373
+ }
374
+ ],
375
+ "bargap": 0.6,
376
+ "bargroupgap": 0.1,
377
+ "barmode": "stack",
378
+ "legend": {
379
+ "orientation": "v",
380
+ "title": {
381
+ "font": {
382
+ "size": 12
383
+ },
384
+ "text": "Severity Level<br><sub>Solid: Direct | Striped: Transitive</sub>"
385
+ },
386
+ "traceorder": "reversed",
387
+ "x": 1.02,
388
+ "xanchor": "left",
389
+ "y": 0.95,
390
+ "yanchor": "top"
391
+ },
392
+ "margin": {
393
+ "b": 60,
394
+ "l": 0,
395
+ "r": 0,
396
+ "t": 60
397
+ },
398
+ "template": {
399
+ "data": {
400
+ "bar": [
401
+ {
402
+ "error_x": {
403
+ "color": "#2a3f5f"
404
+ },
405
+ "error_y": {
406
+ "color": "#2a3f5f"
407
+ },
408
+ "marker": {
409
+ "line": {
410
+ "color": "#E5ECF6",
411
+ "width": 0.5
412
+ },
413
+ "pattern": {
414
+ "fillmode": "overlay",
415
+ "size": 10,
416
+ "solidity": 0.2
417
+ }
418
+ },
419
+ "type": "bar"
420
+ }
421
+ ],
422
+ "barpolar": [
423
+ {
424
+ "marker": {
425
+ "line": {
426
+ "color": "#E5ECF6",
427
+ "width": 0.5
428
+ },
429
+ "pattern": {
430
+ "fillmode": "overlay",
431
+ "size": 10,
432
+ "solidity": 0.2
433
+ }
434
+ },
435
+ "type": "barpolar"
436
+ }
437
+ ],
438
+ "carpet": [
439
+ {
440
+ "aaxis": {
441
+ "endlinecolor": "#2a3f5f",
442
+ "gridcolor": "white",
443
+ "linecolor": "white",
444
+ "minorgridcolor": "white",
445
+ "startlinecolor": "#2a3f5f"
446
+ },
447
+ "baxis": {
448
+ "endlinecolor": "#2a3f5f",
449
+ "gridcolor": "white",
450
+ "linecolor": "white",
451
+ "minorgridcolor": "white",
452
+ "startlinecolor": "#2a3f5f"
453
+ },
454
+ "type": "carpet"
455
+ }
456
+ ],
457
+ "choropleth": [
458
+ {
459
+ "colorbar": {
460
+ "outlinewidth": 0,
461
+ "ticks": ""
462
+ },
463
+ "type": "choropleth"
464
+ }
465
+ ],
466
+ "contour": [
467
+ {
468
+ "colorbar": {
469
+ "outlinewidth": 0,
470
+ "ticks": ""
471
+ },
472
+ "colorscale": [
473
+ [
474
+ 0,
475
+ "#0d0887"
476
+ ],
477
+ [
478
+ 0.1111111111111111,
479
+ "#46039f"
480
+ ],
481
+ [
482
+ 0.2222222222222222,
483
+ "#7201a8"
484
+ ],
485
+ [
486
+ 0.3333333333333333,
487
+ "#9c179e"
488
+ ],
489
+ [
490
+ 0.4444444444444444,
491
+ "#bd3786"
492
+ ],
493
+ [
494
+ 0.5555555555555556,
495
+ "#d8576b"
496
+ ],
497
+ [
498
+ 0.6666666666666666,
499
+ "#ed7953"
500
+ ],
501
+ [
502
+ 0.7777777777777778,
503
+ "#fb9f3a"
504
+ ],
505
+ [
506
+ 0.8888888888888888,
507
+ "#fdca26"
508
+ ],
509
+ [
510
+ 1,
511
+ "#f0f921"
512
+ ]
513
+ ],
514
+ "type": "contour"
515
+ }
516
+ ],
517
+ "contourcarpet": [
518
+ {
519
+ "colorbar": {
520
+ "outlinewidth": 0,
521
+ "ticks": ""
522
+ },
523
+ "type": "contourcarpet"
524
+ }
525
+ ],
526
+ "heatmap": [
527
+ {
528
+ "colorbar": {
529
+ "outlinewidth": 0,
530
+ "ticks": ""
531
+ },
532
+ "colorscale": [
533
+ [
534
+ 0,
535
+ "#0d0887"
536
+ ],
537
+ [
538
+ 0.1111111111111111,
539
+ "#46039f"
540
+ ],
541
+ [
542
+ 0.2222222222222222,
543
+ "#7201a8"
544
+ ],
545
+ [
546
+ 0.3333333333333333,
547
+ "#9c179e"
548
+ ],
549
+ [
550
+ 0.4444444444444444,
551
+ "#bd3786"
552
+ ],
553
+ [
554
+ 0.5555555555555556,
555
+ "#d8576b"
556
+ ],
557
+ [
558
+ 0.6666666666666666,
559
+ "#ed7953"
560
+ ],
561
+ [
562
+ 0.7777777777777778,
563
+ "#fb9f3a"
564
+ ],
565
+ [
566
+ 0.8888888888888888,
567
+ "#fdca26"
568
+ ],
569
+ [
570
+ 1,
571
+ "#f0f921"
572
+ ]
573
+ ],
574
+ "type": "heatmap"
575
+ }
576
+ ],
577
+ "heatmapgl": [
578
+ {
579
+ "colorbar": {
580
+ "outlinewidth": 0,
581
+ "ticks": ""
582
+ },
583
+ "colorscale": [
584
+ [
585
+ 0,
586
+ "#0d0887"
587
+ ],
588
+ [
589
+ 0.1111111111111111,
590
+ "#46039f"
591
+ ],
592
+ [
593
+ 0.2222222222222222,
594
+ "#7201a8"
595
+ ],
596
+ [
597
+ 0.3333333333333333,
598
+ "#9c179e"
599
+ ],
600
+ [
601
+ 0.4444444444444444,
602
+ "#bd3786"
603
+ ],
604
+ [
605
+ 0.5555555555555556,
606
+ "#d8576b"
607
+ ],
608
+ [
609
+ 0.6666666666666666,
610
+ "#ed7953"
611
+ ],
612
+ [
613
+ 0.7777777777777778,
614
+ "#fb9f3a"
615
+ ],
616
+ [
617
+ 0.8888888888888888,
618
+ "#fdca26"
619
+ ],
620
+ [
621
+ 1,
622
+ "#f0f921"
623
+ ]
624
+ ],
625
+ "type": "heatmapgl"
626
+ }
627
+ ],
628
+ "histogram": [
629
+ {
630
+ "marker": {
631
+ "pattern": {
632
+ "fillmode": "overlay",
633
+ "size": 10,
634
+ "solidity": 0.2
635
+ }
636
+ },
637
+ "type": "histogram"
638
+ }
639
+ ],
640
+ "histogram2d": [
641
+ {
642
+ "colorbar": {
643
+ "outlinewidth": 0,
644
+ "ticks": ""
645
+ },
646
+ "colorscale": [
647
+ [
648
+ 0,
649
+ "#0d0887"
650
+ ],
651
+ [
652
+ 0.1111111111111111,
653
+ "#46039f"
654
+ ],
655
+ [
656
+ 0.2222222222222222,
657
+ "#7201a8"
658
+ ],
659
+ [
660
+ 0.3333333333333333,
661
+ "#9c179e"
662
+ ],
663
+ [
664
+ 0.4444444444444444,
665
+ "#bd3786"
666
+ ],
667
+ [
668
+ 0.5555555555555556,
669
+ "#d8576b"
670
+ ],
671
+ [
672
+ 0.6666666666666666,
673
+ "#ed7953"
674
+ ],
675
+ [
676
+ 0.7777777777777778,
677
+ "#fb9f3a"
678
+ ],
679
+ [
680
+ 0.8888888888888888,
681
+ "#fdca26"
682
+ ],
683
+ [
684
+ 1,
685
+ "#f0f921"
686
+ ]
687
+ ],
688
+ "type": "histogram2d"
689
+ }
690
+ ],
691
+ "histogram2dcontour": [
692
+ {
693
+ "colorbar": {
694
+ "outlinewidth": 0,
695
+ "ticks": ""
696
+ },
697
+ "colorscale": [
698
+ [
699
+ 0,
700
+ "#0d0887"
701
+ ],
702
+ [
703
+ 0.1111111111111111,
704
+ "#46039f"
705
+ ],
706
+ [
707
+ 0.2222222222222222,
708
+ "#7201a8"
709
+ ],
710
+ [
711
+ 0.3333333333333333,
712
+ "#9c179e"
713
+ ],
714
+ [
715
+ 0.4444444444444444,
716
+ "#bd3786"
717
+ ],
718
+ [
719
+ 0.5555555555555556,
720
+ "#d8576b"
721
+ ],
722
+ [
723
+ 0.6666666666666666,
724
+ "#ed7953"
725
+ ],
726
+ [
727
+ 0.7777777777777778,
728
+ "#fb9f3a"
729
+ ],
730
+ [
731
+ 0.8888888888888888,
732
+ "#fdca26"
733
+ ],
734
+ [
735
+ 1,
736
+ "#f0f921"
737
+ ]
738
+ ],
739
+ "type": "histogram2dcontour"
740
+ }
741
+ ],
742
+ "mesh3d": [
743
+ {
744
+ "colorbar": {
745
+ "outlinewidth": 0,
746
+ "ticks": ""
747
+ },
748
+ "type": "mesh3d"
749
+ }
750
+ ],
751
+ "parcoords": [
752
+ {
753
+ "line": {
754
+ "colorbar": {
755
+ "outlinewidth": 0,
756
+ "ticks": ""
757
+ }
758
+ },
759
+ "type": "parcoords"
760
+ }
761
+ ],
762
+ "pie": [
763
+ {
764
+ "automargin": true,
765
+ "type": "pie"
766
+ }
767
+ ],
768
+ "scatter": [
769
+ {
770
+ "fillpattern": {
771
+ "fillmode": "overlay",
772
+ "size": 10,
773
+ "solidity": 0.2
774
+ },
775
+ "type": "scatter"
776
+ }
777
+ ],
778
+ "scatter3d": [
779
+ {
780
+ "line": {
781
+ "colorbar": {
782
+ "outlinewidth": 0,
783
+ "ticks": ""
784
+ }
785
+ },
786
+ "marker": {
787
+ "colorbar": {
788
+ "outlinewidth": 0,
789
+ "ticks": ""
790
+ }
791
+ },
792
+ "type": "scatter3d"
793
+ }
794
+ ],
795
+ "scattercarpet": [
796
+ {
797
+ "marker": {
798
+ "colorbar": {
799
+ "outlinewidth": 0,
800
+ "ticks": ""
801
+ }
802
+ },
803
+ "type": "scattercarpet"
804
+ }
805
+ ],
806
+ "scattergeo": [
807
+ {
808
+ "marker": {
809
+ "colorbar": {
810
+ "outlinewidth": 0,
811
+ "ticks": ""
812
+ }
813
+ },
814
+ "type": "scattergeo"
815
+ }
816
+ ],
817
+ "scattergl": [
818
+ {
819
+ "marker": {
820
+ "colorbar": {
821
+ "outlinewidth": 0,
822
+ "ticks": ""
823
+ }
824
+ },
825
+ "type": "scattergl"
826
+ }
827
+ ],
828
+ "scattermapbox": [
829
+ {
830
+ "marker": {
831
+ "colorbar": {
832
+ "outlinewidth": 0,
833
+ "ticks": ""
834
+ }
835
+ },
836
+ "type": "scattermapbox"
837
+ }
838
+ ],
839
+ "scatterpolar": [
840
+ {
841
+ "marker": {
842
+ "colorbar": {
843
+ "outlinewidth": 0,
844
+ "ticks": ""
845
+ }
846
+ },
847
+ "type": "scatterpolar"
848
+ }
849
+ ],
850
+ "scatterpolargl": [
851
+ {
852
+ "marker": {
853
+ "colorbar": {
854
+ "outlinewidth": 0,
855
+ "ticks": ""
856
+ }
857
+ },
858
+ "type": "scatterpolargl"
859
+ }
860
+ ],
861
+ "scatterternary": [
862
+ {
863
+ "marker": {
864
+ "colorbar": {
865
+ "outlinewidth": 0,
866
+ "ticks": ""
867
+ }
868
+ },
869
+ "type": "scatterternary"
870
+ }
871
+ ],
872
+ "surface": [
873
+ {
874
+ "colorbar": {
875
+ "outlinewidth": 0,
876
+ "ticks": ""
877
+ },
878
+ "colorscale": [
879
+ [
880
+ 0,
881
+ "#0d0887"
882
+ ],
883
+ [
884
+ 0.1111111111111111,
885
+ "#46039f"
886
+ ],
887
+ [
888
+ 0.2222222222222222,
889
+ "#7201a8"
890
+ ],
891
+ [
892
+ 0.3333333333333333,
893
+ "#9c179e"
894
+ ],
895
+ [
896
+ 0.4444444444444444,
897
+ "#bd3786"
898
+ ],
899
+ [
900
+ 0.5555555555555556,
901
+ "#d8576b"
902
+ ],
903
+ [
904
+ 0.6666666666666666,
905
+ "#ed7953"
906
+ ],
907
+ [
908
+ 0.7777777777777778,
909
+ "#fb9f3a"
910
+ ],
911
+ [
912
+ 0.8888888888888888,
913
+ "#fdca26"
914
+ ],
915
+ [
916
+ 1,
917
+ "#f0f921"
918
+ ]
919
+ ],
920
+ "type": "surface"
921
+ }
922
+ ],
923
+ "table": [
924
+ {
925
+ "cells": {
926
+ "fill": {
927
+ "color": "#EBF0F8"
928
+ },
929
+ "line": {
930
+ "color": "white"
931
+ }
932
+ },
933
+ "header": {
934
+ "fill": {
935
+ "color": "#C8D4E3"
936
+ },
937
+ "line": {
938
+ "color": "white"
939
+ }
940
+ },
941
+ "type": "table"
942
+ }
943
+ ]
944
+ },
945
+ "layout": {
946
+ "annotationdefaults": {
947
+ "arrowcolor": "#2a3f5f",
948
+ "arrowhead": 0,
949
+ "arrowwidth": 1
950
+ },
951
+ "autotypenumbers": "strict",
952
+ "coloraxis": {
953
+ "colorbar": {
954
+ "outlinewidth": 0,
955
+ "ticks": ""
956
+ }
957
+ },
958
+ "colorscale": {
959
+ "diverging": [
960
+ [
961
+ 0,
962
+ "#8e0152"
963
+ ],
964
+ [
965
+ 0.1,
966
+ "#c51b7d"
967
+ ],
968
+ [
969
+ 0.2,
970
+ "#de77ae"
971
+ ],
972
+ [
973
+ 0.3,
974
+ "#f1b6da"
975
+ ],
976
+ [
977
+ 0.4,
978
+ "#fde0ef"
979
+ ],
980
+ [
981
+ 0.5,
982
+ "#f7f7f7"
983
+ ],
984
+ [
985
+ 0.6,
986
+ "#e6f5d0"
987
+ ],
988
+ [
989
+ 0.7,
990
+ "#b8e186"
991
+ ],
992
+ [
993
+ 0.8,
994
+ "#7fbc41"
995
+ ],
996
+ [
997
+ 0.9,
998
+ "#4d9221"
999
+ ],
1000
+ [
1001
+ 1,
1002
+ "#276419"
1003
+ ]
1004
+ ],
1005
+ "sequential": [
1006
+ [
1007
+ 0,
1008
+ "#0d0887"
1009
+ ],
1010
+ [
1011
+ 0.1111111111111111,
1012
+ "#46039f"
1013
+ ],
1014
+ [
1015
+ 0.2222222222222222,
1016
+ "#7201a8"
1017
+ ],
1018
+ [
1019
+ 0.3333333333333333,
1020
+ "#9c179e"
1021
+ ],
1022
+ [
1023
+ 0.4444444444444444,
1024
+ "#bd3786"
1025
+ ],
1026
+ [
1027
+ 0.5555555555555556,
1028
+ "#d8576b"
1029
+ ],
1030
+ [
1031
+ 0.6666666666666666,
1032
+ "#ed7953"
1033
+ ],
1034
+ [
1035
+ 0.7777777777777778,
1036
+ "#fb9f3a"
1037
+ ],
1038
+ [
1039
+ 0.8888888888888888,
1040
+ "#fdca26"
1041
+ ],
1042
+ [
1043
+ 1,
1044
+ "#f0f921"
1045
+ ]
1046
+ ],
1047
+ "sequentialminus": [
1048
+ [
1049
+ 0,
1050
+ "#0d0887"
1051
+ ],
1052
+ [
1053
+ 0.1111111111111111,
1054
+ "#46039f"
1055
+ ],
1056
+ [
1057
+ 0.2222222222222222,
1058
+ "#7201a8"
1059
+ ],
1060
+ [
1061
+ 0.3333333333333333,
1062
+ "#9c179e"
1063
+ ],
1064
+ [
1065
+ 0.4444444444444444,
1066
+ "#bd3786"
1067
+ ],
1068
+ [
1069
+ 0.5555555555555556,
1070
+ "#d8576b"
1071
+ ],
1072
+ [
1073
+ 0.6666666666666666,
1074
+ "#ed7953"
1075
+ ],
1076
+ [
1077
+ 0.7777777777777778,
1078
+ "#fb9f3a"
1079
+ ],
1080
+ [
1081
+ 0.8888888888888888,
1082
+ "#fdca26"
1083
+ ],
1084
+ [
1085
+ 1,
1086
+ "#f0f921"
1087
+ ]
1088
+ ]
1089
+ },
1090
+ "colorway": [
1091
+ "#636efa",
1092
+ "#EF553B",
1093
+ "#00cc96",
1094
+ "#ab63fa",
1095
+ "#FFA15A",
1096
+ "#19d3f3",
1097
+ "#FF6692",
1098
+ "#B6E880",
1099
+ "#FF97FF",
1100
+ "#FECB52"
1101
+ ],
1102
+ "font": {
1103
+ "color": "#2a3f5f"
1104
+ },
1105
+ "geo": {
1106
+ "bgcolor": "white",
1107
+ "lakecolor": "white",
1108
+ "landcolor": "#E5ECF6",
1109
+ "showlakes": true,
1110
+ "showland": true,
1111
+ "subunitcolor": "white"
1112
+ },
1113
+ "hoverlabel": {
1114
+ "align": "left"
1115
+ },
1116
+ "hovermode": "closest",
1117
+ "mapbox": {
1118
+ "style": "light"
1119
+ },
1120
+ "paper_bgcolor": "white",
1121
+ "plot_bgcolor": "#E5ECF6",
1122
+ "polar": {
1123
+ "angularaxis": {
1124
+ "gridcolor": "white",
1125
+ "linecolor": "white",
1126
+ "ticks": ""
1127
+ },
1128
+ "bgcolor": "#E5ECF6",
1129
+ "radialaxis": {
1130
+ "gridcolor": "white",
1131
+ "linecolor": "white",
1132
+ "ticks": ""
1133
+ }
1134
+ },
1135
+ "scene": {
1136
+ "xaxis": {
1137
+ "backgroundcolor": "#E5ECF6",
1138
+ "gridcolor": "white",
1139
+ "gridwidth": 2,
1140
+ "linecolor": "white",
1141
+ "showbackground": true,
1142
+ "ticks": "",
1143
+ "zerolinecolor": "white"
1144
+ },
1145
+ "yaxis": {
1146
+ "backgroundcolor": "#E5ECF6",
1147
+ "gridcolor": "white",
1148
+ "gridwidth": 2,
1149
+ "linecolor": "white",
1150
+ "showbackground": true,
1151
+ "ticks": "",
1152
+ "zerolinecolor": "white"
1153
+ },
1154
+ "zaxis": {
1155
+ "backgroundcolor": "#E5ECF6",
1156
+ "gridcolor": "white",
1157
+ "gridwidth": 2,
1158
+ "linecolor": "white",
1159
+ "showbackground": true,
1160
+ "ticks": "",
1161
+ "zerolinecolor": "white"
1162
+ }
1163
+ },
1164
+ "shapedefaults": {
1165
+ "line": {
1166
+ "color": "#2a3f5f"
1167
+ }
1168
+ },
1169
+ "ternary": {
1170
+ "aaxis": {
1171
+ "gridcolor": "white",
1172
+ "linecolor": "white",
1173
+ "ticks": ""
1174
+ },
1175
+ "baxis": {
1176
+ "gridcolor": "white",
1177
+ "linecolor": "white",
1178
+ "ticks": ""
1179
+ },
1180
+ "bgcolor": "#E5ECF6",
1181
+ "caxis": {
1182
+ "gridcolor": "white",
1183
+ "linecolor": "white",
1184
+ "ticks": ""
1185
+ }
1186
+ },
1187
+ "title": {
1188
+ "x": 0.05
1189
+ },
1190
+ "xaxis": {
1191
+ "automargin": true,
1192
+ "gridcolor": "white",
1193
+ "linecolor": "white",
1194
+ "ticks": "",
1195
+ "title": {
1196
+ "standoff": 15
1197
+ },
1198
+ "zerolinecolor": "white",
1199
+ "zerolinewidth": 2
1200
+ },
1201
+ "yaxis": {
1202
+ "automargin": true,
1203
+ "gridcolor": "white",
1204
+ "linecolor": "white",
1205
+ "ticks": "",
1206
+ "title": {
1207
+ "standoff": 15
1208
+ },
1209
+ "zerolinecolor": "white",
1210
+ "zerolinewidth": 2
1211
+ }
1212
+ }
1213
+ },
1214
+ "xaxis": {
1215
+ "tickmode": "array",
1216
+ "ticktext": [
1217
+ "Patch",
1218
+ "Minor",
1219
+ "Major",
1220
+ "No fixed version"
1221
+ ],
1222
+ "tickvals": [
1223
+ 0,
1224
+ 1,
1225
+ 2,
1226
+ 3
1227
+ ],
1228
+ "title": {
1229
+ "text": "Type of version required to fix vulnerability"
1230
+ }
1231
+ },
1232
+ "yaxis": {
1233
+ "title": {
1234
+ "text": "Vulnerability count"
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+ },
1240
+ "metadata": {},
1241
+ "output_type": "display_data"
1242
+ }
1243
+ ],
1244
+ "source": [
1245
+ "import pandas as pd\n",
1246
+ "import warnings\n",
1247
+ "import plotly.graph_objects as go\n",
1248
+ "import code_data_science.data_table as dt\n",
1249
+ "\n",
1250
+ "warnings.simplefilter(\"ignore\")\n",
1251
+ "\n",
1252
+ "df = dt.read_csv(\"../samples/dependency_vulnerabilities.csv\")\n",
1253
+ "\n",
1254
+ "df[\"repositoryWithBranch\"] = df[\"repositoryPath\"] + \"/\" + df[\"repositoryBranch\"]\n",
1255
+ "\n",
1256
+ "# Filter the data frame to only include rows where repositoryWithBranch contain\n",
1257
+ "# a term in the repository_filter (case insensitive)\n",
1258
+ "if len(repository_filter) > 0:\n",
1259
+ " df = df[\n",
1260
+ " df[\"repositoryWithBranch\"].str.contains(\"|\".join(repository_filter), case=False)\n",
1261
+ " ]\n",
1262
+ "\n",
1263
+ "\n",
1264
+ "def create_bar_plot(df_direct, df_transitive):\n",
1265
+ " if df_direct is None or df_transitive is None:\n",
1266
+ " # Create empty dataframes with the correct structure\n",
1267
+ " fix_types = [\"Patch\", \"Minor\", \"Major\", \"No fixed version\"]\n",
1268
+ " df_direct = pd.DataFrame(\n",
1269
+ " {\n",
1270
+ " \"Type\": fix_types,\n",
1271
+ " \"Low\": [0] * 4,\n",
1272
+ " \"Moderate\": [0] * 4,\n",
1273
+ " \"High\": [0] * 4,\n",
1274
+ " \"Critical\": [0] * 4,\n",
1275
+ " }\n",
1276
+ " )\n",
1277
+ " df_transitive = pd.DataFrame(\n",
1278
+ " {\n",
1279
+ " \"Type\": fix_types,\n",
1280
+ " \"Low\": [0] * 4,\n",
1281
+ " \"Moderate\": [0] * 4,\n",
1282
+ " \"High\": [0] * 4,\n",
1283
+ " \"Critical\": [0] * 4,\n",
1284
+ " }\n",
1285
+ " )\n",
1286
+ "\n",
1287
+ " fig = go.Figure()\n",
1288
+ "\n",
1289
+ " # Define colors for severity levels\n",
1290
+ " colors = {\n",
1291
+ " \"Critical\": \"#FF5B5B\",\n",
1292
+ " \"High\": \"#FABA49\",\n",
1293
+ " \"Moderate\": \"#FEE968\",\n",
1294
+ " \"Low\": \"#52BBA0\",\n",
1295
+ " }\n",
1296
+ "\n",
1297
+ " # Calculate totals for percentage calculations\n",
1298
+ " df_direct[\"Total\"] = df_direct[[\"Critical\", \"High\", \"Moderate\", \"Low\"]].sum(axis=1)\n",
1299
+ " df_transitive[\"Total\"] = df_transitive[[\"Critical\", \"High\", \"Moderate\", \"Low\"]].sum(\n",
1300
+ " axis=1\n",
1301
+ " )\n",
1302
+ " grand_total = df_direct[\"Total\"].sum() + df_transitive[\"Total\"].sum()\n",
1303
+ "\n",
1304
+ " # Create custom x-axis positions for grouping\n",
1305
+ " fix_types = df_direct[\"Type\"].tolist()\n",
1306
+ " n_groups = len(fix_types)\n",
1307
+ " \n",
1308
+ " # Create x positions: each group gets positions like [0, 0.4], [1, 1.4], [2, 2.4], etc.\n",
1309
+ " x_direct = [i - 0.2 for i in range(n_groups)]\n",
1310
+ " x_transitive = [i + 0.2 for i in range(n_groups)]\n",
1311
+ " \n",
1312
+ " # Custom tick labels and positions for the main groups\n",
1313
+ " tick_positions = list(range(n_groups))\n",
1314
+ " tick_labels = fix_types\n",
1315
+ "\n",
1316
+ " # Add traces for direct dependencies\n",
1317
+ " for i, severity in enumerate([\"Low\", \"Moderate\", \"High\", \"Critical\"]):\n",
1318
+ " # Calculate percentages for hover\n",
1319
+ " percentages_direct = []\n",
1320
+ " for idx in range(len(df_direct)):\n",
1321
+ " count = df_direct.iloc[idx][severity]\n",
1322
+ " total_in_category = (\n",
1323
+ " df_direct.iloc[idx][\"Total\"] + df_transitive.iloc[idx][\"Total\"]\n",
1324
+ " )\n",
1325
+ " pct_of_category = (\n",
1326
+ " (count / total_in_category * 100) if total_in_category > 0 else 0\n",
1327
+ " )\n",
1328
+ " pct_of_all = (count / grand_total * 100) if grand_total > 0 else 0\n",
1329
+ " percentages_direct.append(\n",
1330
+ " {\n",
1331
+ " \"category_pct\": round(pct_of_category, 1),\n",
1332
+ " \"total_pct\": round(pct_of_all, 1),\n",
1333
+ " }\n",
1334
+ " )\n",
1335
+ "\n",
1336
+ " fig.add_trace(\n",
1337
+ " go.Bar(\n",
1338
+ " name=severity,\n",
1339
+ " x=x_direct,\n",
1340
+ " y=df_direct[severity],\n",
1341
+ " marker_color=colors[severity],\n",
1342
+ " legendgroup=severity,\n",
1343
+ " showlegend=True,\n",
1344
+ " width=0.35, # Make bars wider\n",
1345
+ " customdata=percentages_direct,\n",
1346
+ " hovertemplate=(\n",
1347
+ " \"<b>%{x}</b><br>\"\n",
1348
+ " + f\"{severity} (Direct): %{{y}}<br>\"\n",
1349
+ " + \"Percent of %{x}: %{customdata.category_pct}%<br>\"\n",
1350
+ " + \"Percent of all: %{customdata.total_pct}%\"\n",
1351
+ " + \"<extra></extra>\"\n",
1352
+ " ),\n",
1353
+ " )\n",
1354
+ " )\n",
1355
+ "\n",
1356
+ " # Add traces for transitive dependencies\n",
1357
+ " for i, severity in enumerate([\"Low\", \"Moderate\", \"High\", \"Critical\"]):\n",
1358
+ " # Calculate percentages for hover\n",
1359
+ " percentages_transitive = []\n",
1360
+ " for idx in range(len(df_transitive)):\n",
1361
+ " count = df_transitive.iloc[idx][severity]\n",
1362
+ " total_in_category = (\n",
1363
+ " df_direct.iloc[idx][\"Total\"] + df_transitive.iloc[idx][\"Total\"]\n",
1364
+ " )\n",
1365
+ " pct_of_category = (\n",
1366
+ " (count / total_in_category * 100) if total_in_category > 0 else 0\n",
1367
+ " )\n",
1368
+ " pct_of_all = (count / grand_total * 100) if grand_total > 0 else 0\n",
1369
+ " percentages_transitive.append(\n",
1370
+ " {\n",
1371
+ " \"category_pct\": round(pct_of_category, 1),\n",
1372
+ " \"total_pct\": round(pct_of_all, 1),\n",
1373
+ " }\n",
1374
+ " )\n",
1375
+ "\n",
1376
+ " fig.add_trace(\n",
1377
+ " go.Bar(\n",
1378
+ " name=f\"{severity} \", # Add space to make it different from direct\n",
1379
+ " x=x_transitive,\n",
1380
+ " y=df_transitive[severity],\n",
1381
+ " marker_color=colors[severity],\n",
1382
+ " marker_pattern_shape=\"/\",\n",
1383
+ " legendgroup=severity,\n",
1384
+ " showlegend=False, # Hide from legend since we'll use annotations\n",
1385
+ " width=0.35, # Make bars wider\n",
1386
+ " customdata=percentages_transitive,\n",
1387
+ " hovertemplate=(\n",
1388
+ " \"<b>%{x}</b><br>\"\n",
1389
+ " + f\"{severity} (Transitive): %{{y}}<br>\"\n",
1390
+ " + \"Percent of %{x}: %{customdata.category_pct}%<br>\"\n",
1391
+ " + \"Percent of all: %{customdata.total_pct}%\"\n",
1392
+ " + \"<extra></extra>\"\n",
1393
+ " ),\n",
1394
+ " )\n",
1395
+ " )\n",
1396
+ "\n",
1397
+ " # Update layout with improved legend\n",
1398
+ " fig.update_layout(\n",
1399
+ " xaxis={\n",
1400
+ " \"title\": \"Type of version required to fix vulnerability\",\n",
1401
+ " \"tickmode\": \"array\",\n",
1402
+ " \"tickvals\": tick_positions,\n",
1403
+ " \"ticktext\": tick_labels,\n",
1404
+ " },\n",
1405
+ " yaxis={\"title\": \"Vulnerability count\"},\n",
1406
+ " barmode=\"stack\",\n",
1407
+ " bargap=0.6, # Increase gap between groups\n",
1408
+ " bargroupgap=0.1, # Small gap within groups\n",
1409
+ " margin=dict(l=0, r=0, t=60, b=60),\n",
1410
+ " legend=dict(\n",
1411
+ " orientation=\"v\",\n",
1412
+ " yanchor=\"top\",\n",
1413
+ " y=0.95,\n",
1414
+ " xanchor=\"left\",\n",
1415
+ " x=1.02,\n",
1416
+ " title=dict(\n",
1417
+ " text=\"Severity Level<br><sub>Solid: Direct | Striped: Transitive</sub>\",\n",
1418
+ " font=dict(size=12),\n",
1419
+ " ),\n",
1420
+ " traceorder=\"reversed\", # Reverse legend order to show Critical first\n",
1421
+ " ),\n",
1422
+ " annotations=[\n",
1423
+ " dict(\n",
1424
+ " text=\"<b>Dependency Vulnerabilities by Fix Type</b>\",\n",
1425
+ " xref=\"paper\",\n",
1426
+ " yref=\"paper\",\n",
1427
+ " x=0.5,\n",
1428
+ " y=1.05,\n",
1429
+ " showarrow=False,\n",
1430
+ " font=dict(size=14),\n",
1431
+ " )\n",
1432
+ " ],\n",
1433
+ " )\n",
1434
+ "\n",
1435
+ " return fig\n",
1436
+ "\n",
1437
+ "\n",
1438
+ "# Exit early if there are no stack traces and render a plot with a message\n",
1439
+ "if len(df) == 0:\n",
1440
+ " fig = create_bar_plot(None, None)\n",
1441
+ " fig.update_yaxes(range=[0, 10])\n",
1442
+ " fig.show(render=\"plotly_mimetype\")\n",
1443
+ "else:\n",
1444
+ "\n",
1445
+ " def get_semver_fix(version, fixed_version):\n",
1446
+ " \"\"\"\n",
1447
+ " looks at current version and fixed version and determines if the fix is a major, minor, patch version, or no fix\n",
1448
+ " \"\"\"\n",
1449
+ " version_components = version.split(\".\")\n",
1450
+ " fixed_components = fixed_version.split(\".\")\n",
1451
+ "\n",
1452
+ " # if fixed version is empty, return \"No fixed version\"\n",
1453
+ " if fixed_version == \"\":\n",
1454
+ " return \"No fixed version\"\n",
1455
+ "\n",
1456
+ " if len(version_components) < 3:\n",
1457
+ " # fill in the missing version components with 0\n",
1458
+ " for i in range(3 - len(version_components)):\n",
1459
+ " version_components.append(\"0\")\n",
1460
+ " if len(fixed_components) < 3:\n",
1461
+ " # fill in the missing version components with 0\n",
1462
+ " for i in range(3 - len(fixed_components)):\n",
1463
+ " fixed_components.append(\"0\")\n",
1464
+ "\n",
1465
+ " elif version_components[0] != fixed_components[0]:\n",
1466
+ " return \"Major\"\n",
1467
+ " elif version_components[1] != fixed_components[1]:\n",
1468
+ " return \"Minor\"\n",
1469
+ " elif version_components[2] != fixed_components[2]:\n",
1470
+ " return \"Patch\"\n",
1471
+ " else:\n",
1472
+ " return \"Unknown\"\n",
1473
+ "\n",
1474
+ " # drop unnecessary columns except depth which we need\n",
1475
+ " df = df.drop(\n",
1476
+ " columns=[\n",
1477
+ " \"repositoryOrigin\",\n",
1478
+ " \"repositoryPath\",\n",
1479
+ " \"repositoryBranch\",\n",
1480
+ " \"repositoryWithBranch\",\n",
1481
+ " \"cve\",\n",
1482
+ " \"groupId\",\n",
1483
+ " \"artifactId\",\n",
1484
+ " \"summary\",\n",
1485
+ " ]\n",
1486
+ " )\n",
1487
+ "\n",
1488
+ " # fill NaN values with empty string\n",
1489
+ " df[\"fixedVersion\"] = df[\"fixedVersion\"].fillna(\"\")\n",
1490
+ " df[\"version\"] = df[\"version\"].fillna(\"\")\n",
1491
+ " # make sure version and fixedVersion is a string\n",
1492
+ " df[\"fixedVersion\"] = df[\"fixedVersion\"].astype(str)\n",
1493
+ " df[\"version\"] = df[\"version\"].astype(str)\n",
1494
+ "\n",
1495
+ " # add column 'semverFix' to dataframe\n",
1496
+ " df[\"semverFix\"] = df.apply(\n",
1497
+ " lambda x: get_semver_fix(x[\"version\"], x[\"fixedVersion\"]), axis=1\n",
1498
+ " )\n",
1499
+ "\n",
1500
+ " # Split into direct and transitive\n",
1501
+ " df_direct = df[df[\"depth\"] == 0]\n",
1502
+ " df_transitive = df[df[\"depth\"] != 0]\n",
1503
+ "\n",
1504
+ " # Define the order for fix types\n",
1505
+ " fix_order = [\"Patch\", \"Minor\", \"Major\", \"No fixed version\"]\n",
1506
+ "\n",
1507
+ " # Create aggregated data for direct dependencies\n",
1508
+ " direct_data = []\n",
1509
+ " for fix_type in fix_order:\n",
1510
+ " fix_df = df_direct[df_direct[\"semverFix\"] == fix_type]\n",
1511
+ " severity_counts = fix_df[\"severity\"].value_counts()\n",
1512
+ " direct_data.append(\n",
1513
+ " {\n",
1514
+ " \"Type\": fix_type,\n",
1515
+ " \"Critical\": severity_counts.get(\"CRITICAL\", 0),\n",
1516
+ " \"High\": severity_counts.get(\"HIGH\", 0),\n",
1517
+ " \"Moderate\": severity_counts.get(\"MODERATE\", 0),\n",
1518
+ " \"Low\": severity_counts.get(\"LOW\", 0),\n",
1519
+ " }\n",
1520
+ " )\n",
1521
+ " df_direct_plot = pd.DataFrame(direct_data)\n",
1522
+ "\n",
1523
+ " # Create aggregated data for transitive dependencies\n",
1524
+ " transitive_data = []\n",
1525
+ " for fix_type in fix_order:\n",
1526
+ " fix_df = df_transitive[df_transitive[\"semverFix\"] == fix_type]\n",
1527
+ " severity_counts = fix_df[\"severity\"].value_counts()\n",
1528
+ " transitive_data.append(\n",
1529
+ " {\n",
1530
+ " \"Type\": fix_type,\n",
1531
+ " \"Critical\": severity_counts.get(\"CRITICAL\", 0),\n",
1532
+ " \"High\": severity_counts.get(\"HIGH\", 0),\n",
1533
+ " \"Moderate\": severity_counts.get(\"MODERATE\", 0),\n",
1534
+ " \"Low\": severity_counts.get(\"LOW\", 0),\n",
1535
+ " }\n",
1536
+ " )\n",
1537
+ " df_transitive_plot = pd.DataFrame(transitive_data)\n",
1538
+ "\n",
1539
+ " fig = create_bar_plot(df_direct_plot, df_transitive_plot)\n",
1540
+ "\n",
1541
+ " # Show the figure\n",
1542
+ " fig.show(render=\"plotly_mimetype\")"
1543
+ ]
1544
+ }
1545
+ ],
1546
+ "metadata": {
1547
+ "kernelspec": {
1548
+ "display_name": ".venv",
1549
+ "language": "python",
1550
+ "name": "python3"
1551
+ },
1552
+ "language_info": {
1553
+ "codemirror_mode": {
1554
+ "name": "ipython",
1555
+ "version": 3
1556
+ },
1557
+ "file_extension": ".py",
1558
+ "mimetype": "text/x-python",
1559
+ "name": "python",
1560
+ "nbconvert_exporter": "python",
1561
+ "pygments_lexer": "ipython3",
1562
+ "version": "3.9.19"
1563
+ }
1564
+ },
1565
+ "nbformat": 4,
1566
+ "nbformat_minor": 4
45
1567
  }