{"spec_id":"network-weighted","library":"makie","language":"julia","code":"# anyplot.ai\n# network-weighted: Weighted Network Graph with Edge Thickness\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 94/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — Americas\n    colorant\"#C475FD\",  # 2 — Europe\n    colorant\"#4467A3\",  # 3 — Asia\n]\n\n# --- Data: bilateral trade network, annual volume in billions USD ----------\ncountries = [\n    \"USA\", \"China\", \"Germany\", \"Japan\", \"UK\", \"France\",\n    \"India\", \"Brazil\", \"South Korea\", \"Canada\", \"Mexico\", \"Netherlands\",\n]\nregion = [1, 3, 2, 3, 2, 2, 3, 1, 3, 1, 1, 2]\nregion_names = [\"Americas\", \"Europe\", \"Asia\"]\nn = length(countries)\n\n# (source, target, weight) — weight is annual trade volume in $B, scaled to\n# realistic bilateral-trade magnitudes while preserving the relative ranking\n# (China/Canada/Mexico as USA's top partners)\nedges = [\n    (1, 2, 575), (1, 10, 520), (1, 11, 460), (1, 3, 200), (1, 4, 240), (1, 5, 175),\n    (2, 3, 265), (2, 4, 330), (2, 9, 285), (2, 12, 155),\n    (3, 6, 220), (3, 12, 185), (3, 5, 165),\n    (6, 5, 140), (5, 12, 120), (4, 9, 130),\n    (7, 2, 110), (7, 1, 95),\n    (8, 1, 80), (8, 2, 90),\n    (11, 2, 65), (10, 2, 75),\n    (12, 6, 90), (9, 1, 105),\n]\nweights = [w for (_, _, w) in edges]\nmin_weight, max_weight = extrema(weights)\nmean_weight = mean(weights)\n\nweighted_degree = zeros(Int, n)\nfor (a, b, w) in edges\n    weighted_degree[a] += w\n    weighted_degree[b] += w\nend\n\n# --- Force-directed layout, weight-scaled attraction (hand-rolled\n#     Fruchterman-Reingold — no NetworkLayout.jl, which is not installed\n#     in the CI runtime). Heavier edges pull their endpoints closer. --------\npos_x = randn(n) .* 3.0\npos_y = randn(n) .* 3.0\nk = sqrt(180.0 / n)\n\nfor iter in 0:299\n    t_step = max(1.0 * 0.97^iter, 0.005)\n    dx = zeros(n)\n    dy = zeros(n)\n\n    for i in 1:n, j in 1:n\n        if i != j\n            δx = pos_x[i] - pos_x[j]\n            δy = pos_y[i] - pos_y[j]\n            d  = max(sqrt(δx^2 + δy^2), 1e-4)\n            f  = k^2 / d\n            dx[i] += δx / d * f\n            dy[i] += δy / d * f\n        end\n    end\n\n    for (a, b, w) in edges\n        δx = pos_x[a] - pos_x[b]\n        δy = pos_y[a] - pos_y[b]\n        d  = max(sqrt(δx^2 + δy^2), 1e-4)\n        f  = (w / mean_weight) * d^2 / k\n        dx[a] -= δx / d * f\n        dy[a] -= δy / d * f\n        dx[b] += δx / d * f\n        dy[b] += δy / d * f\n    end\n\n    for i in 1:n\n        disp = sqrt(dx[i]^2 + dy[i]^2)\n        if disp > 0\n            pos_x[i] += dx[i] / disp * min(disp, t_step)\n            pos_y[i] += dy[i] / disp * min(disp, t_step)\n        end\n    end\nend\n\npos_x = 0.06 .+ 0.88 .* (pos_x .- minimum(pos_x)) ./ (maximum(pos_x) - minimum(pos_x))\npos_y = 0.10 .+ 0.82 .* (pos_y .- minimum(pos_y)) ./ (maximum(pos_y) - minimum(pos_y))\n\nnode_colors = [IMPRINT_PALETTE[region[i]] for i in 1:n]\nnode_sizes  = 20.0 .+ 40.0 .* (weighted_degree .- minimum(weighted_degree)) ./\n              (maximum(weighted_degree) - minimum(weighted_degree))\n\n# --- Plot ---------------------------------------------------------------\ntitle_text = \"Global Trade Network · network-weighted · julia · makie · anyplot.ai\"\ntitle_size = length(title_text) > 67 ? round(Int, 20 * 67 / length(title_text)) : 20\n\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = title_text,\n    titlesize          = title_size,\n    titlecolor         = INK,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinevisible   = false,\n    bottomspinevisible = false,\n    xgridvisible       = false,\n    ygridvisible       = false,\n    xticksvisible      = false,\n    yticksvisible      = false,\n    xticklabelsvisible = false,\n    yticklabelsvisible = false,\n)\n\nlimits!(ax, 0, 1, 0, 1)\n\n# Edges — linewidth encodes trade volume, the spec's primary signal. Each\n# edge is a 2-segment polyline bowed through a perpendicular-offset midpoint\n# (sign alternating by index) so near-parallel edges converging on the same\n# hub node stay visually separable instead of overlapping — still a single\n# batched linesegments! call.\nedge_points = Vector{Point2f}(undef, 4 * length(edges))\nedge_widths = Vector{Float32}(undef, 4 * length(edges))\nedge_mid    = Vector{Point2f}(undef, length(edges))\ntop_idx     = argmax(weights)\nfor (idx, (a, b, w)) in enumerate(edges)\n    p1 = Point2f(pos_x[a], pos_y[a])\n    p2 = Point2f(pos_x[b], pos_y[b])\n    edx, edy = pos_x[b] - pos_x[a], pos_y[b] - pos_y[a]\n    ed = max(sqrt(edx^2 + edy^2), 1e-4)\n    perp_x, perp_y = -edy / ed, edx / ed\n    curve_sign = isodd(idx) ? 1.0 : -1.0\n    mx, my = (pos_x[a] + pos_x[b]) / 2, (pos_y[a] + pos_y[b]) / 2\n    mid = Point2f(mx + perp_x * 0.028 * curve_sign, my + perp_y * 0.028 * curve_sign)\n    edge_mid[idx] = mid\n\n    width = 1.4 + (w - min_weight) / (max_weight - min_weight) * (9.0 - 1.4)\n    edge_points[4idx - 3] = p1\n    edge_points[4idx - 2] = mid\n    edge_points[4idx - 1] = mid\n    edge_points[4idx]     = p2\n    edge_widths[4idx - 3] = width\n    edge_widths[4idx - 2] = width\n    edge_widths[4idx - 1] = width\n    edge_widths[4idx]     = width\nend\nlinesegments!(ax, edge_points; color = (INK_SOFT, 0.4), linewidth = edge_widths)\n\n# Highlight the single strongest trade corridor as a sharper storytelling focal point\ntop_a, top_b, top_w = edges[top_idx]\ntop_width = 1.4 + (top_w - min_weight) / (max_weight - min_weight) * (9.0 - 1.4)\nlines!(\n    ax,\n    [Point2f(pos_x[top_a], pos_y[top_a]), edge_mid[top_idx], Point2f(pos_x[top_b], pos_y[top_b])];\n    color     = (IMPRINT_PALETTE[1], 0.85),\n    linewidth = top_width + 1.5,\n)\ntext!(\n    ax, [edge_mid[top_idx][1]], [edge_mid[top_idx][2]];\n    text     = [\"Top corridor: \\$$(top_w)B\"],\n    fontsize = 12,\n    font     = :bold,\n    color    = IMPRINT_PALETTE[1],\n    align    = (:center, :bottom),\n    offset   = (0.0f0, 6.0f0),\n)\n\n# Nodes — size encodes weighted degree (total trade volume), color encodes region\nscatter!(\n    ax, pos_x, pos_y;\n    color       = node_colors,\n    markersize  = node_sizes,\n    strokewidth = 2.0,\n    strokecolor = PAGE_BG,\n)\n\ntext!(\n    ax, pos_x, pos_y;\n    text     = countries,\n    align    = (:center, :top),\n    fontsize = 13,\n    color    = INK,\n    offset   = [(0.0f0, -(node_sizes[i] / 2 + 9)) for i in 1:n],\n)\n\n# Legend — region color + trade-volume line-width scale\nregion_elems = [\n    MarkerElement(color = IMPRINT_PALETTE[i], marker = :circle, markersize = 16, strokewidth = 0)\n    for i in 1:3\n]\nweight_samples = [90, 300, 550]\nweight_elems = [\n    LineElement(color = INK_SOFT, linewidth = 1.4 + (w - min_weight) / (max_weight - min_weight) * (9.0 - 1.4))\n    for w in weight_samples\n]\nweight_labels = [\"\\$$(w)B\" for w in weight_samples]\n\nLegend(\n    fig[1, 2],\n    [region_elems, weight_elems],\n    [region_names, weight_labels],\n    [\"Region\", \"Trade volume\"];\n    titlesize       = 13,\n    titlecolor      = INK,\n    labelsize       = 12,\n    labelcolor      = INK,\n    framevisible    = true,\n    framecolor      = (INK_SOFT, 0.3),\n    backgroundcolor = ELEVATED_BG,\n)\n\ncolsize!(fig.layout, 1, Relative(0.82))\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}