{"spec_id":"silhouette-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// silhouette-basic: Silhouette Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-09\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Helpers -----------------------------------------------------------------\nlet seed = 42;\nfunction nextRandom() {\n  seed = (seed * 1664525 + 1013904223) >>> 0;\n  return seed / 4294967296;\n}\n\nfunction clusterSamples(count, meanScore, spread, misclassifiedTail) {\n  const scores = [];\n  for (let i = 0; i < count; i++) {\n    let score = meanScore + (nextRandom() - 0.5) * spread;\n    if (misclassifiedTail && i >= count - Math.round(count * 0.1)) {\n      score -= nextRandom() * 0.6;\n    }\n    scores.push(Math.max(-1, Math.min(1, score)));\n  }\n  scores.sort((a, b) => b - a);\n  return scores;\n}\n\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n// --- Data (in-memory, deterministic) ------------------------------------------\n// Silhouette coefficients for a k=3 clustering of an iris-like dataset. Within\n// each cluster, samples are sorted descending by score — the classic layout\n// popularised by sklearn.metrics.silhouette_samples plots.\nconst clusterDefs = [\n  { name: \"Cluster 0\", count: 55, meanScore: 0.72, spread: 0.26, misclassifiedTail: false },\n  { name: \"Cluster 1\", count: 50, meanScore: 0.46, spread: 0.42, misclassifiedTail: false },\n  { name: \"Cluster 2\", count: 40, meanScore: 0.27, spread: 0.5, misclassifiedTail: true },\n];\n\nconst data = [];\nconst categories = [];\nconst plotBands = [];\nlet allScores = [];\nlet cursor = 0;\n\nclusterDefs.forEach((cluster, clusterIndex) => {\n  const scores = clusterSamples(cluster.count, cluster.meanScore, cluster.spread, cluster.misclassifiedTail);\n  const color = t.palette[clusterIndex];\n  const startIndex = cursor;\n\n  scores.forEach((score) => {\n    data.push({ y: Math.round(score * 1000) / 1000, color });\n    categories.push(\"\");\n    cursor += 1;\n  });\n  allScores = allScores.concat(scores);\n\n  const clusterAvg = scores.reduce((sum, v) => sum + v, 0) / scores.length;\n  plotBands.push({\n    from: startIndex - 0.5,\n    to: cursor - 0.5,\n    color: hexToRgba(color, 0.08),\n    label: {\n      text: `${cluster.name} · avg ${clusterAvg.toFixed(2)}`,\n      align: \"right\",\n      x: -10,\n      y: 16,\n      useHTML: true,\n      style: {\n        color: t.inkSoft,\n        fontSize: \"14px\",\n        background: t.pageBg,\n        padding: \"2px 6px\",\n        borderRadius: \"3px\",\n      },\n    },\n  });\n\n  // Gap between clusters for visual separation\n  if (clusterIndex < clusterDefs.length - 1) {\n    data.push(null);\n    categories.push(\"\");\n    cursor += 1;\n  }\n});\n\nconst overallAverage = allScores.reduce((sum, v) => sum + v, 0) / allScores.length;\nconst axisMin = Math.floor(Math.min(...allScores, 0) * 10) / 10 - 0.05;\n\n// --- Chart --------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"bar\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  title: {\n    text: \"silhouette-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Iris-like dataset · k-means, k = 3 · samples sorted by silhouette score within cluster\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    categories,\n    labels: { enabled: false },\n    lineColor: t.inkSoft,\n    tickLength: 0,\n    title: { text: \"Samples (grouped by cluster)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    plotBands,\n  },\n  yAxis: {\n    min: axisMin,\n    max: 1,\n    tickInterval: 0.2,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    title: { text: \"Silhouette coefficient\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotLines: [\n      {\n        value: 0,\n        color: t.grid,\n        width: 1,\n        zIndex: 4,\n      },\n      {\n        value: overallAverage,\n        color: t.ink,\n        dashStyle: \"Dash\",\n        width: 2,\n        zIndex: 5,\n        label: {\n          text: `Overall avg ${overallAverage.toFixed(2)}`,\n          align: \"center\",\n          verticalAlign: \"top\",\n          y: 28,\n          useHTML: true,\n          style: {\n            color: t.ink,\n            fontSize: \"14px\",\n            fontWeight: \"600\",\n            background: t.pageBg,\n            padding: \"2px 6px\",\n            borderRadius: \"3px\",\n          },\n        },\n      },\n    ],\n  },\n  legend: { enabled: false },\n  plotOptions: {\n    series: { animation: false, pointPadding: 0.05, groupPadding: 0 },\n    bar: { borderWidth: 0 },\n  },\n  tooltip: {\n    pointFormat: \"Silhouette score: <b>{point.y:.2f}</b>\",\n  },\n  series: [\n    {\n      name: \"Silhouette score\",\n      data,\n    },\n  ],\n});\n"}