{"spec_id":"line-pca-variance-cumulative","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// line-pca-variance-cumulative: Cumulative Explained Variance for PCA Component Selection\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-26\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Simulated eigenvalues from PCA on a 15-sensor manufacturing quality-control\n// dataset (temperature, pressure, vibration, etc. readings on a production line).\nconst rawEigenvalues = [\n  312, 198, 137, 94, 71, 54, 42, 33, 27, 22, 18, 15, 12, 10, 8,\n];\nconst totalVariance = rawEigenvalues.reduce((sum, v) => sum + v, 0);\nconst individualRatio = rawEigenvalues.map((v) => v / totalVariance);\nconst cumulativeRatio = individualRatio.reduce((acc, v, i) => {\n  acc.push((i > 0 ? acc[i - 1] : 0) + v);\n  return acc;\n}, []);\nconst nComponents = rawEigenvalues.map((_, i) => i + 1);\n\n// Elbow detection: point of max perpendicular distance from the chord\n// connecting the first and last cumulative points (standard elbow heuristic).\nconst x1 = 0;\nconst y1 = cumulativeRatio[0];\nconst x2 = cumulativeRatio.length - 1;\nconst y2 = cumulativeRatio[cumulativeRatio.length - 1];\nconst chordLength = Math.hypot(x2 - x1, y2 - y1);\nconst distances = cumulativeRatio.map((y, i) => {\n  const x = i;\n  return Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / chordLength;\n});\nconst elbowIndex = distances.indexOf(Math.max(...distances));\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"line-pca-variance-cumulative · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    categories: nComponents.map(String),\n    title: { text: \"Number of Components\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: [\n    {\n      title: { text: \"Individual Variance Ratio\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n      labels: {\n        style: { color: t.inkSoft, fontSize: \"14px\" },\n        formatter() {\n          return Math.round(this.value * 100) + \"%\";\n        },\n      },\n      gridLineColor: t.grid,\n      max: Math.max(...individualRatio) * 1.6,\n    },\n    {\n      title: { text: \"Cumulative Explained Variance\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n      labels: {\n        style: { color: t.inkSoft, fontSize: \"14px\" },\n        formatter() {\n          return Math.round(this.value * 100) + \"%\";\n        },\n      },\n      gridLineColor: t.grid,\n      min: 0,\n      max: 1,\n      opposite: true,\n      plotLines: [\n        {\n          value: 0.9,\n          color: t.amber,\n          dashStyle: \"Dash\",\n          width: 1.5,\n          label: { text: \"90%\", style: { color: t.inkSoft, fontSize: \"13px\" }, align: \"left\", x: 4 },\n        },\n        {\n          value: 0.95,\n          color: t.amber,\n          dashStyle: \"Dash\",\n          width: 1.5,\n          label: { text: \"95%\", style: { color: t.inkSoft, fontSize: \"13px\" }, align: \"left\", x: 4 },\n        },\n      ],\n    },\n  ],\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  plotOptions: {\n    series: { animation: false },\n    column: { borderWidth: 0, opacity: 0.55 },\n  },\n  series: [\n    {\n      type: \"column\",\n      name: \"Individual variance\",\n      data: individualRatio,\n      yAxis: 0,\n      color: t.palette[0],\n    },\n    {\n      type: \"line\",\n      name: \"Cumulative variance\",\n      data: cumulativeRatio.map((v, i) =>\n        i === elbowIndex\n          ? { y: v, marker: { radius: 8, symbol: \"diamond\", fillColor: t.palette[1], lineColor: t.ink, lineWidth: 1.5 } }\n          : v\n      ),\n      yAxis: 1,\n      color: t.palette[1],\n      lineWidth: 2.5,\n      marker: { radius: 5, fillColor: t.palette[1], lineColor: t.pageBg, lineWidth: 1 },\n      dataLabels: {\n        enabled: true,\n        formatter() {\n          return this.point.index === elbowIndex ? \"Elbow\" : null;\n        },\n        style: { color: t.ink, fontSize: \"13px\", fontWeight: \"600\", textOutline: \"none\" },\n        y: -16,\n      },\n    },\n  ],\n});\n"}