Skip to content

Commit 1821af7

Browse files
committed
Improve charts to look similar to original explore notebooks
1 parent d1b9819 commit 1821af7

3 files changed

Lines changed: 122 additions & 39 deletions

File tree

domains/external-dependencies/externalDependencyCharts.py

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,10 @@ def filter_entries_below_percentage_threshold(
182182
threshold_percent: float,
183183
) -> pd.DataFrame:
184184
"""
185-
Returns only rows whose percentage share of the *original* total is strictly
186-
below threshold_percent. Used to drill down into the 'others' slice.
185+
Returns only rows whose percentage share of the *original* total is below
186+
threshold_percent. Used to drill down into the 'others' slice.
187+
Matches the grouping logic of group_small_values_into_others (< not <=)
188+
to avoid double-counting at the threshold boundary.
187189
"""
188190
result = add_percentage_column(data_frame, value_column)
189191
percent_column = value_column + "Percent"
@@ -452,9 +454,8 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
452454

453455
# ── Top external packages (Table 1 equivalent) ────────────────────────────
454456
if not overall_data.empty:
455-
top20 = overall_data.head(20)
456457
save_pie_chart_pair(
457-
source_data=top20,
458+
source_data=overall_data,
458459
value_column="numberOfExternalCallerTypes",
459460
name_column="externalPackageName",
460461
chart_name_prefix="Java_Top_external_packages_by_types",
@@ -463,7 +464,7 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
463464
verbose=verbose,
464465
)
465466
save_pie_chart_pair(
466-
source_data=top20,
467+
source_data=overall_data,
467468
value_column="numberOfExternalCallerPackages",
468469
name_column="externalPackageName",
469470
chart_name_prefix="Java_Top_external_packages_by_packages",
@@ -474,9 +475,8 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
474475

475476
# ── Second-level package grouping (Table 2 equivalent) ────────────────────
476477
if not second_level_overall_data.empty:
477-
top20_second_level = second_level_overall_data.head(20)
478478
save_pie_chart_pair(
479-
source_data=top20_second_level,
479+
source_data=second_level_overall_data,
480480
value_column="numberOfExternalCallerTypes",
481481
name_column="externalSecondLevelPackageName",
482482
chart_name_prefix="Java_Top_second_level_packages_by_types",
@@ -485,7 +485,7 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
485485
verbose=verbose,
486486
)
487487
save_pie_chart_pair(
488-
source_data=top20_second_level,
488+
source_data=second_level_overall_data,
489489
value_column="numberOfExternalCallerPackages",
490490
name_column="externalSecondLevelPackageName",
491491
chart_name_prefix="Java_Top_second_level_packages_by_packages",
@@ -496,9 +496,8 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
496496

497497
# ── Most spread external packages (Table 3 equivalent) ────────────────────
498498
if not spread_data.empty:
499-
top20_spread = spread_data.head(20)
500499
save_pie_chart_pair(
501-
source_data=top20_spread,
500+
source_data=spread_data,
502501
value_column="sumNumberOfTypes",
503502
name_column="externalPackageName",
504503
chart_name_prefix="Java_Most_spread_packages_by_types",
@@ -507,7 +506,7 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
507506
verbose=verbose,
508507
)
509508
save_pie_chart_pair(
510-
source_data=top20_spread,
509+
source_data=spread_data,
511510
value_column="sumNumberOfPackages",
512511
name_column="externalPackageName",
513512
chart_name_prefix="Java_Most_spread_packages_by_packages",
@@ -518,9 +517,8 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
518517

519518
# ── Most spread second-level packages (Table 4 equivalent) ────────────────
520519
if not second_level_spread_data.empty:
521-
top20_second_level_spread = second_level_spread_data.head(20)
522520
save_pie_chart_pair(
523-
source_data=top20_second_level_spread,
521+
source_data=second_level_spread_data,
524522
value_column="sumNumberOfTypes",
525523
name_column="externalSecondLevelPackageName",
526524
chart_name_prefix="Java_Most_spread_second_level_packages_by_types",
@@ -529,7 +527,7 @@ def generate_java_charts(queries_directory: str, report_directory: str, verbose:
529527
verbose=verbose,
530528
)
531529
save_pie_chart_pair(
532-
source_data=top20_second_level_spread,
530+
source_data=second_level_spread_data,
533531
value_column="sumNumberOfPackages",
534532
name_column="externalSecondLevelPackageName",
535533
chart_name_prefix="Java_Most_spread_second_level_packages_by_packages",
@@ -633,9 +631,8 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
633631

634632
# ── Module usage overall ───────────────────────────────────────────────────
635633
if not module_overall_data.empty:
636-
top20_modules = module_overall_data.head(20)
637634
save_pie_chart_pair(
638-
source_data=top20_modules,
635+
source_data=module_overall_data,
639636
value_column="numberOfExternalCallerElements",
640637
name_column="externalModuleName",
641638
chart_name_prefix="Typescript_Top_external_modules_by_elements",
@@ -644,7 +641,7 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
644641
verbose=verbose,
645642
)
646643
save_pie_chart_pair(
647-
source_data=top20_modules,
644+
source_data=module_overall_data,
648645
value_column="numberOfExternalCallerModules",
649646
name_column="externalModuleName",
650647
chart_name_prefix="Typescript_Top_external_modules_by_modules",
@@ -655,9 +652,8 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
655652

656653
# ── Namespace usage overall ────────────────────────────────────────────────
657654
if not namespace_overall_data.empty:
658-
top20_namespaces = namespace_overall_data.head(20)
659655
save_pie_chart_pair(
660-
source_data=top20_namespaces,
656+
source_data=namespace_overall_data,
661657
value_column="numberOfExternalCallerElements",
662658
name_column="externalNamespaceName",
663659
chart_name_prefix="Typescript_Top_external_namespaces_by_elements",
@@ -666,7 +662,7 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
666662
verbose=verbose,
667663
)
668664
save_pie_chart_pair(
669-
source_data=top20_namespaces,
665+
source_data=namespace_overall_data,
670666
value_column="numberOfExternalCallerModules",
671667
name_column="externalNamespaceName",
672668
chart_name_prefix="Typescript_Top_external_namespaces_by_modules",
@@ -677,9 +673,8 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
677673

678674
# ── Module spread ──────────────────────────────────────────────────────────
679675
if not module_spread_data.empty:
680-
top20_module_spread = module_spread_data.head(20)
681676
save_pie_chart_pair(
682-
source_data=top20_module_spread,
677+
source_data=module_spread_data,
683678
value_column="sumNumberOfUsedExternalDeclarations",
684679
name_column="externalModuleName",
685680
chart_name_prefix="Typescript_Most_spread_modules_by_declarations",
@@ -688,7 +683,7 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
688683
verbose=verbose,
689684
)
690685
save_pie_chart_pair(
691-
source_data=top20_module_spread,
686+
source_data=module_spread_data,
692687
value_column="numberOfInternalModules",
693688
name_column="externalModuleName",
694689
chart_name_prefix="Typescript_Most_spread_modules_by_modules",
@@ -699,9 +694,8 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
699694

700695
# ── Namespace spread ───────────────────────────────────────────────────────
701696
if not namespace_spread_data.empty:
702-
top20_namespace_spread = namespace_spread_data.head(20)
703697
save_pie_chart_pair(
704-
source_data=top20_namespace_spread,
698+
source_data=namespace_spread_data,
705699
value_column="sumNumberOfUsedExternalDeclarations",
706700
name_column="externalModuleNamespace",
707701
chart_name_prefix="Typescript_Most_spread_namespaces_by_declarations",
@@ -710,7 +704,7 @@ def generate_typescript_charts(queries_directory: str, report_directory: str, ve
710704
verbose=verbose,
711705
)
712706
save_pie_chart_pair(
713-
source_data=top20_namespace_spread,
707+
source_data=namespace_spread_data,
714708
value_column="numberOfInternalModules",
715709
name_column="externalModuleNamespace",
716710
chart_name_prefix="Typescript_Most_spread_namespaces_by_modules",

domains/java/javaCharts.py

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
TOP_ANNOTATION_LIMIT = 15
4545

4646
HORIZONTAL_BAR_COLOR = "steelblue"
47+
LINE_COUNT_DISTRIBUTION_MAX_ARTIFACTS = 20
48+
CYCLOMATIC_DISTRIBUTION_MAX_ARTIFACTS = 15
49+
DISTRIBUTION_CHART_COLORMAP = "nipy_spectral"
4750

4851

4952
# ── Parameters ────────────────────────────────────────────────────────────────
@@ -237,25 +240,52 @@ def generate_spread_per_dependent_chart(report_directory: str, verbose: bool) ->
237240
# ── Method metrics charts ─────────────────────────────────────────────────────
238241

239242
def generate_method_line_count_distribution_chart(report_directory: str, verbose: bool) -> None:
240-
"""Generate a histogram showing the distribution of effective method line counts."""
243+
"""Generate a normalized per-artifact line chart of effective method line count distribution."""
241244
data_frame = load_csv(report_directory, "EffectiveMethodLineCountDistribution.csv", verbose)
242245
if data_frame.empty:
243246
return
244247

245-
# Aggregate across all artifacts: sum method counts per line count
246-
distribution = data_frame.groupby("effectiveLineCount")["methods"].sum().reset_index()
247-
figure, axis = plot.subplots(figsize=(FIGURE_WIDTH, FIGURE_HEIGHT))
248-
axis.bar(
249-
distribution["effectiveLineCount"],
250-
distribution["methods"],
251-
width=1.0,
252-
color=HORIZONTAL_BAR_COLOR,
253-
edgecolor="white",
254-
linewidth=0.3,
248+
distribution = (
249+
data_frame
250+
.pivot(index="effectiveLineCount", columns="artifactName", values="methods")
251+
.fillna(0)
252+
.astype(int)
255253
)
254+
artifact_totals = distribution.sum()
255+
top_artifacts = artifact_totals.sort_values(ascending=False).index[:LINE_COUNT_DISTRIBUTION_MAX_ARTIFACTS]
256+
distribution = distribution[top_artifacts]
257+
258+
# Filter out columns with zero sum to prevent NaN/Inf after normalization.
259+
# A column can be zero if all method counts for that artifact fell outside
260+
# the effectiveLineCount > 0 filter, causing silent data loss in the chart.
261+
non_zero_columns = distribution.columns[distribution.sum(axis=0) > 0]
262+
distribution = distribution[non_zero_columns]
263+
264+
if distribution.empty:
265+
print(f"{SCRIPT_NAME}: No data for method line count distribution, skipping chart.")
266+
return
267+
268+
normalized = distribution.div(distribution.sum(axis=0), axis=1).multiply(100)
269+
270+
colormap = matplotlib.colormaps[DISTRIBUTION_CHART_COLORMAP]
271+
num_artifacts = len(normalized.columns)
272+
colors = [colormap(i / max(num_artifacts - 1, 1)) for i in range(num_artifacts)]
273+
274+
figure, axis = plot.subplots(figsize=(10, 6))
275+
for i, column in enumerate(normalized.columns):
276+
axis.plot(normalized.index, normalized[column], label=column, color=colors[i], linewidth=2)
277+
278+
x_ticks = list(range(2, 21))
279+
axis.set_xscale("log")
280+
axis.set_xlim(2, 20)
281+
axis.set_ylim(0, 20)
282+
axis.set_xticks(x_ticks)
283+
axis.set_xticklabels([str(t) for t in x_ticks])
256284
axis.set_xlabel("Effective Line Count")
257-
axis.set_ylabel("Number of Methods")
258-
axis.set_title("Effective Method Line Count Distribution")
285+
axis.set_ylabel("Percent of Methods")
286+
axis.set_title("Effective Method Line Count Distribution (Normalized)")
287+
axis.grid(True)
288+
axis.legend(bbox_to_anchor=(1.05, 1), loc="upper left", fontsize=7)
259289

260290
save_figure(figure, report_directory, "MethodMetrics_LineCountDistribution_Histogram", verbose)
261291

@@ -298,6 +328,61 @@ def generate_top_packages_by_loc_chart(report_directory: str, verbose: bool) ->
298328
save_figure(figure, report_directory, "MethodMetrics_TopPackagesLOC_Bar", verbose)
299329

300330

331+
def generate_cyclomatic_complexity_distribution_chart(report_directory: str, verbose: bool) -> None:
332+
"""Generate a normalized per-artifact line chart of cyclomatic method complexity distribution."""
333+
data_frame = load_csv(report_directory, "CyclomaticMethodComplexityDistribution.csv", verbose)
334+
if data_frame.empty:
335+
return
336+
337+
distribution = (
338+
data_frame
339+
.pivot(index="cyclomaticComplexity", columns="artifactName", values="methods")
340+
.fillna(0)
341+
.astype(int)
342+
)
343+
artifact_totals = distribution.sum()
344+
top_artifacts = artifact_totals.sort_values(ascending=False).index[:CYCLOMATIC_DISTRIBUTION_MAX_ARTIFACTS]
345+
distribution = distribution[top_artifacts]
346+
347+
# Filter out columns with zero sum to prevent NaN/Inf after normalization.
348+
# A column can be zero if no methods for that artifact were included in the
349+
# source CSV after upstream filtering such as method.effectiveLineCount > 0.
350+
non_zero_columns = distribution.columns[distribution.sum(axis=0) > 0]
351+
distribution = distribution[non_zero_columns]
352+
353+
if distribution.empty:
354+
print(f"{SCRIPT_NAME}: No data for cyclomatic complexity distribution, skipping chart.")
355+
return
356+
357+
normalized = distribution.div(distribution.sum(axis=0), axis=1).multiply(100)
358+
359+
colormap = matplotlib.colormaps[DISTRIBUTION_CHART_COLORMAP]
360+
num_artifacts = len(normalized.columns)
361+
colors = [colormap(i / max(num_artifacts - 1, 1)) for i in range(num_artifacts)]
362+
363+
figure, axis = plot.subplots(figsize=(10, 6))
364+
for i, column in enumerate(normalized.columns):
365+
axis.plot(normalized.index, normalized[column], label=column, color=colors[i], linewidth=2)
366+
367+
x_ticks = list(range(1, 12))
368+
y_ticks = [1, 2, 3, 4, 5, 7, 10, 20, 30, 40, 50, 100]
369+
axis.set_xscale("log")
370+
axis.set_yscale("log")
371+
axis.set_xlim(1, 11)
372+
axis.set_ylim(1, 100)
373+
axis.set_xticks(x_ticks)
374+
axis.set_xticklabels([str(t) for t in x_ticks])
375+
axis.set_yticks(y_ticks)
376+
axis.set_yticklabels([str(t) for t in y_ticks])
377+
axis.set_xlabel("Cyclomatic Complexity")
378+
axis.set_ylabel("Percentage of Methods")
379+
axis.set_title("Cyclomatic Complexity Distribution of Methods (Normalized)")
380+
axis.grid(True)
381+
axis.legend(bbox_to_anchor=(1.05, 1), loc="upper left", fontsize=7)
382+
383+
save_figure(figure, report_directory, "MethodMetrics_CyclomaticComplexityDistribution_Normalized", verbose)
384+
385+
301386
# ── Java code quality charts ──────────────────────────────────────────────────
302387

303388
def generate_annotation_type_distribution_chart(report_directory: str, verbose: bool) -> None:
@@ -430,6 +515,7 @@ def generate_all_charts(report_directory: str, verbose: bool) -> None:
430515
generate_spread_per_dependency_chart(report_directory, verbose)
431516
generate_spread_per_dependent_chart(report_directory, verbose)
432517
generate_method_line_count_distribution_chart(report_directory, verbose)
518+
generate_cyclomatic_complexity_distribution_chart(report_directory, verbose)
433519
generate_top_types_by_loc_chart(report_directory, verbose)
434520
generate_top_packages_by_loc_chart(report_directory, verbose)
435521
generate_annotation_type_distribution_chart(report_directory, verbose)

domains/java/javaCsv.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ execute_cypher "${JAVA_CODE_QUALITY_CYPHER_DIR}/JakartaEE_REST_Annotations.cyphe
8383
execute_cypher "${METHOD_METRICS_CYPHER_DIR}/Effective_Method_Line_Count_Distribution.cypher" \
8484
> "${FULL_REPORT_DIRECTORY}/EffectiveMethodLineCountDistribution.csv"
8585

86+
execute_cypher "${METHOD_METRICS_CYPHER_DIR}/Cyclomatic_Method_Complexity_Distribution.cypher" \
87+
> "${FULL_REPORT_DIRECTORY}/CyclomaticMethodComplexityDistribution.csv"
88+
8689
execute_cypher "${METHOD_METRICS_CYPHER_DIR}/Effective_lines_of_method_code_per_type.cypher" \
8790
> "${FULL_REPORT_DIRECTORY}/EffectiveLinesOfMethodCodePerType.csv"
8891

0 commit comments

Comments
 (0)