{"spec_id":"probability-weibull","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// probability-weibull: Weibull Probability Plot for Reliability Analysis\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-08-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: turbine blade fatigue-life test, cycles to failure (or suspension) --\n// Sorted ascending; a handful of units were pulled from test before failing\n// (right-censored / \"suspended\"), which is common in reliability testing.\nconst observations = [\n  { cycles: 38000, censored: false },\n  { cycles: 45000, censored: false },\n  { cycles: 52000, censored: false },\n  { cycles: 55000, censored: true },\n  { cycles: 58000, censored: false },\n  { cycles: 63000, censored: false },\n  { cycles: 67000, censored: false },\n  { cycles: 71000, censored: false },\n  { cycles: 76000, censored: false },\n  { cycles: 80000, censored: false },\n  { cycles: 85000, censored: false },\n  { cycles: 90000, censored: false },\n  { cycles: 96000, censored: false },\n  { cycles: 98000, censored: true },\n  { cycles: 103000, censored: false },\n  { cycles: 111000, censored: false },\n  { cycles: 120000, censored: false },\n  { cycles: 125000, censored: true },\n  { cycles: 132000, censored: false },\n  { cycles: 148000, censored: false },\n  { cycles: 160000, censored: true },\n  { cycles: 170000, censored: false },\n];\nconst n = observations.length;\n\n// --- Median-rank regression with rank adjustment for suspensions -----------\n// Johnson's rank-increment method: each suspension leaves the pool of\n// \"at-risk\" units without resolving a rank, so later failures inherit a\n// larger increment. Suspended units are drawn at the prevailing rank so the\n// censoring pattern is visible, but only failures feed the line fit.\nlet previousAdjustedRank = 0;\nconst points = observations.map((obs, i) => {\n  const reverseRank = n - i; // remaining items at/after this position\n  if (obs.censored) {\n    const cumulativeProbability = (previousAdjustedRank - 0.3) / (n + 0.4);\n    return { ...obs, cumulativeProbability };\n  }\n  const increment = (n + 1 - previousAdjustedRank) / (1 + reverseRank);\n  previousAdjustedRank += increment;\n  const cumulativeProbability = (previousAdjustedRank - 0.3) / (n + 0.4);\n  return { ...obs, cumulativeProbability };\n});\n\n// Weibull linearization: y = ln(-ln(1 - F)), plotted against ln(time).\nconst weibullY = (f) => Math.log(-Math.log(1 - f));\n\nconst failurePoints = points\n  .filter((p) => !p.censored)\n  .map((p) => [p.cycles, weibullY(p.cumulativeProbability)]);\nconst suspensionPoints = points\n  .filter((p) => p.censored)\n  .map((p) => [p.cycles, weibullY(p.cumulativeProbability)]);\n\n// --- Least-squares fit on failures only: y = beta * ln(t) + intercept ------\nconst logT = failurePoints.map((p) => Math.log(p[0]));\nconst yVals = failurePoints.map((p) => p[1]);\nconst meanLogT = logT.reduce((a, b) => a + b, 0) / logT.length;\nconst meanY = yVals.reduce((a, b) => a + b, 0) / yVals.length;\nlet covariance = 0;\nlet variance = 0;\nfor (let i = 0; i < logT.length; i++) {\n  covariance += (logT[i] - meanLogT) * (yVals[i] - meanY);\n  variance += (logT[i] - meanLogT) ** 2;\n}\nconst beta = covariance / variance; // shape parameter\nconst intercept = meanY - beta * meanLogT;\nconst eta = Math.exp(-intercept / beta); // scale parameter (characteristic life)\n\nconst allCycles = observations.map((o) => o.cycles);\nconst tMin = Math.min(...allCycles) * 0.9;\nconst tMax = Math.max(...allCycles) * 1.1;\nconst fitLine = [\n  [tMin, beta * Math.log(tMin) + intercept],\n  [tMax, beta * Math.log(tMax) + intercept],\n];\n\n// --- Y-axis: linear space in the linearized value, labeled as probability --\nconst probabilityTicks = [1, 5, 10, 20, 30, 40, 50, 63.2, 70, 80, 90, 95, 99];\nconst weibullTicks = probabilityTicks.map((p) => ({\n  p,\n  y: weibullY(p / 100),\n}));\n\n// --- X-axis: explicit, well-spaced tick positions (log10 of the cycle count,\n// since Highcharts' logarithmic axis expects tickPositions in its internal\n// linear/log space). The default tick algorithm packs a label every 10k in\n// the 100k-200k decade, crowding \"180k\"/\"190k\" together at the right edge;\n// thinning out above 100k keeps every label legibly separated.\nconst xAxisCycleTicks = [40000, 60000, 80000, 100000, 150000, 200000];\nconst xAxisTickPositions = xAxisCycleTicks.map((v) => Math.log10(v));\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: \"probability-weibull · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: `β (shape) = ${beta.toFixed(2)} · η (scale) = ${Math.round(eta).toLocaleString()} cycles — characteristic life at 63.2% cumulative probability`,\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    type: \"logarithmic\",\n    title: {\n      text: \"Cycles to Failure (log scale)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    tickPositions: xAxisTickPositions,\n    startOnTick: false,\n    endOnTick: false,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    gridLineWidth: 1,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: {\n      text: \"Cumulative Failure Probability\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    tickPositions: weibullTicks.map((wt) => wt.y),\n    gridLineColor: t.grid,\n    lineColor: t.inkSoft,\n    labels: {\n      style: { color: t.inkSoft, fontSize: \"14px\" },\n      formatter() {\n        const match = weibullTicks.find((wt) => Math.abs(wt.y - this.value) < 1e-6);\n        return match ? `${match.p}%` : \"\";\n      },\n    },\n    plotLines: [\n      {\n        value: weibullY(0.632),\n        color: t.inkSoft,\n        dashStyle: \"ShortDash\",\n        width: 1.5,\n        zIndex: 4,\n        label: {\n          text: \"63.2% · η\",\n          style: { color: t.inkSoft, fontSize: \"13px\" },\n          align: \"left\",\n          x: 6,\n        },\n      },\n    ],\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: {\n    pointFormatter() {\n      const probability = (1 - Math.exp(-Math.exp(this.y))) * 100;\n      return `Cycles: ${this.x.toLocaleString()}<br/>Probability: ${probability.toFixed(1)}%`;\n    },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: { marker: { radius: 7, lineWidth: 2 } },\n  },\n  series: [\n    {\n      type: \"scatter\",\n      name: \"Failures\",\n      data: failurePoints,\n      marker: {\n        symbol: \"circle\",\n        fillColor: t.palette[0],\n        lineColor: t.palette[0],\n        lineWidth: 0,\n      },\n      color: t.palette[0],\n    },\n    {\n      type: \"scatter\",\n      name: \"Suspensions (censored)\",\n      data: suspensionPoints,\n      marker: {\n        symbol: \"circle\",\n        fillColor: t.pageBg,\n        lineColor: t.palette[0],\n        lineWidth: 2,\n      },\n      color: t.palette[0],\n    },\n    {\n      type: \"line\",\n      name: \"Weibull fit\",\n      data: fitLine,\n      color: t.ink,\n      lineWidth: 2.5,\n      dashStyle: \"ShortDash\",\n      marker: { enabled: false },\n      enableMouseTracking: false,\n    },\n  ],\n});\n"}