{"spec_id":"andrews-curves","library":"makie","language":"julia","code":"# anyplot.ai\n# andrews-curves: Andrews Curves for Multivariate Data\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 81/100 | Created: 2026-09-02\n\nusing CairoMakie\nusing RDatasets\nusing DataFrames\nusing Statistics\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens -------------------------------------------------------\nTHEME = get(ENV, \"ANYPLOT_THEME\", \"light\")\nPAGE_BG = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nINK = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nINK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\nIMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — 4-cylinder\n    colorant\"#C475FD\",  # 2 — 6-cylinder\n    colorant\"#4467A3\",  # 3 — 8-cylinder\n]\n\n# --- Data -----------------------------------------------------------------\n# Motor Trend car specs: five performance/weight measurements per car,\n# grouped by cylinder count to reveal how engine class separates in shape.\ncars = RDatasets.dataset(\"datasets\", \"mtcars\")\nfeatures = [:MPG, :Disp, :HP, :WT, :QSec]\nX = Matrix{Float64}(cars[:, features])\n\n# Normalize each variable to unit scale so no single measurement dominates\nmeans = mean(X; dims = 1)\nstds = std(X; dims = 1)\nX_scaled = (X .- means) ./ stds\n\ncylinder_groups = [4, 6, 8]\ngroup_labels = [\"4-cylinder\", \"6-cylinder\", \"8-cylinder\"]\ngroup_idx = [findfirst(==(c), cylinder_groups) for c in cars.Cyl]\n\n# --- Andrews curve transform ------------------------------------------------\n# f(t) = x1/sqrt(2) + x2*sin(t) + x3*cos(t) + x4*sin(2t) + x5*cos(2t) + ...\nt = collect(range(-pi, pi; length = 200))\nn_features = length(features)\nbasis = zeros(length(t), n_features)\nbasis[:, 1] .= 1 / sqrt(2)\nfor j in 2:n_features\n    m = j - 1\n    freq = ceil(Int, m / 2)\n    basis[:, j] = isodd(m) ? sin.(freq .* t) : cos.(freq .* t)\nend\ncurves = X_scaled * basis'  # (n_cars, length(t))\n\n# Outlier detection: the car whose normalized feature vector sits farthest\n# from the group centroid produces the most visually distinct curve.\ndistances = vec(sqrt.(sum(X_scaled .^ 2; dims = 2)))\noutlier_idx = argmax(distances)\noutlier_model = cars.Model[outlier_idx]\noutlier_t = t[argmax(abs.(curves[outlier_idx, :]))]\noutlier_y = curves[outlier_idx, argmax(abs.(curves[outlier_idx, :]))]\n\n# --- Plot -------------------------------------------------------------------\nfig = Figure(size = (1600, 900), fontsize = 14, backgroundcolor = PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title = \"andrews-curves · julia · makie · anyplot.ai\",\n    titlesize = 20,\n    titlecolor = INK,\n    xlabel = \"t (radians)\",\n    ylabel = \"f(t)\",\n    xlabelsize = 14,\n    ylabelsize = 14,\n    xlabelcolor = INK,\n    ylabelcolor = INK,\n    xticklabelsize = 12,\n    yticklabelsize = 12,\n    xticklabelcolor = INK_SOFT,\n    yticklabelcolor = INK_SOFT,\n    xtickcolor = INK_SOFT,\n    ytickcolor = INK_SOFT,\n    backgroundcolor = PAGE_BG,\n    topspinevisible = false,\n    rightspinevisible = false,\n    leftspinecolor = INK_SOFT,\n    bottomspinecolor = INK_SOFT,\n    xgridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xminorgridvisible = false,\n    yminorgridvisible = false,\n    xticks = (\n        [-pi, -pi / 2, 0, pi / 2, pi],\n        [\"-π\", \"-π/2\", \"0\", \"π/2\", \"π\"],\n    ),\n)\n\nfor i in 1:size(curves, 1)\n    i == outlier_idx && continue\n    lines!(\n        ax, t, curves[i, :];\n        color = IMPRINT_PALETTE[group_idx[i]],\n        linewidth = 3.0,\n        alpha = 0.4,\n    )\nend\n\n# Draw the outlier last with a soft ink halo underneath so it reads as a\n# distinct focal curve against the dense overlapping cluster.\nlines!(ax, t, curves[outlier_idx, :]; color = (INK, 0.5), linewidth = 6.0)\nlines!(\n    ax, t, curves[outlier_idx, :];\n    color = IMPRINT_PALETTE[group_idx[outlier_idx]],\n    linewidth = 3.0,\n    alpha = 1.0,\n)\nscatter!(\n    ax, [outlier_t], [outlier_y];\n    color = IMPRINT_PALETTE[group_idx[outlier_idx]],\n    strokecolor = INK,\n    strokewidth = 1.5,\n    markersize = 14,\n)\ntext!(\n    ax, outlier_t, outlier_y;\n    text = \"Outlier: $(outlier_model)\",\n    color = INK,\n    fontsize = 13,\n    align = (:left, :bottom),\n    offset = (8, 8),\n)\n\n# Legend proxies — one representative line per cylinder class\nfor (idx, label) in enumerate(group_labels)\n    lines!(ax, [NaN], [NaN]; color = IMPRINT_PALETTE[idx], linewidth = 4, label = label)\nend\naxislegend(\n    ax, \"Cylinders\";\n    position = :rt,\n    backgroundcolor = ELEVATED_BG,\n    framecolor = INK_SOFT,\n    labelcolor = INK_SOFT,\n    titlecolor = INK_SOFT,\n)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}