{"spec_id":"probability-weibull","library":"d3","language":"javascript","code":"// anyplot.ai\n// probability-weibull: Weibull Probability Plot for Reliability Analysis\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-07\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\nconst margin = { top: 80, right: 100, bottom: 120, left: 130 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// Turbine blade fatigue-life data: 17 failures + 3 right-censored suspensions\n// Times approximate Weibull(β≈2, η≈4000 h) — sorted by time\nconst rawData = [\n  { time: 750,  failed: true  },\n  { time: 1180, failed: true  },\n  { time: 1500, failed: true  },\n  { time: 1790, failed: true  },\n  { time: 1850, failed: false },\n  { time: 2050, failed: true  },\n  { time: 2290, failed: true  },\n  { time: 2520, failed: true  },\n  { time: 2750, failed: true  },\n  { time: 2980, failed: true  },\n  { time: 3100, failed: false },\n  { time: 3210, failed: true  },\n  { time: 3450, failed: true  },\n  { time: 3690, failed: true  },\n  { time: 3750, failed: false },\n  { time: 3950, failed: true  },\n  { time: 4220, failed: true  },\n  { time: 4520, failed: true  },\n  { time: 4850, failed: true  },\n  { time: 5230, failed: true  },\n];\n\nconst n = rawData.length;\nconst weibullY = (F) => Math.log(-Math.log(1 - F));\n\n// Bernard's median rank: (i − 0.3) / (n + 0.4)\nlet failRank = 0;\nconst plotData = rawData.map((d) => {\n  if (d.failed) {\n    failRank++;\n    const F = (failRank - 0.3) / (n + 0.4);\n    return { time: d.time, failed: true, F, yw: weibullY(F) };\n  }\n  return { time: d.time, failed: false, F: null, yw: null };\n});\n\nconst failures = plotData.filter((d) => d.failed);\nconst censored = plotData.filter((d) => !d.failed);\n\n// OLS regression in Weibull coordinates: yw = β·ln(t) + b\nconst lnT = (d) => Math.log(d.time);\nconst nF  = failures.length;\nconst sx  = d3.sum(failures, lnT);\nconst sy  = d3.sum(failures, (d) => d.yw);\nconst sxy = d3.sum(failures, (d) => lnT(d) * d.yw);\nconst sxx = d3.sum(failures, (d) => lnT(d) * lnT(d));\nconst beta       = (nF * sxy - sx * sy) / (nF * sxx - sx * sx);\nconst bIntercept = (sy - beta * sx) / nF;\nconst eta        = Math.round(Math.exp(-bIntercept / beta));\n\n// Probability axis ticks — major (heavier) and minor (lighter) for Weibull paper texture\nconst majorProbs = [0.01, 0.1, 0.5, 0.9, 0.99];\nconst minorProbs = [0.05, 0.2];\nconst allProbs   = [0.01, 0.05, 0.1, 0.2, 0.5, 0.632, 0.9, 0.99];\nconst yMin = weibullY(0.01);\nconst yMax = weibullY(0.99);\n\n// SVG + defs (clip path + subtle drop-shadow filter for info boxes)\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst defs = svg.append(\"defs\");\ndefs.append(\"clipPath\").attr(\"id\", \"plot-clip\")\n  .append(\"rect\").attr(\"width\", iw).attr(\"height\", ih);\nconst shadowFilter = defs.append(\"filter\")\n  .attr(\"id\", \"box-shadow\").attr(\"x\", \"-20%\").attr(\"y\", \"-20%\").attr(\"width\", \"140%\").attr(\"height\", \"140%\");\nshadowFilter.append(\"feDropShadow\")\n  .attr(\"dx\", 0).attr(\"dy\", 1.5).attr(\"stdDeviation\", 2.5).attr(\"flood-color\", \"rgba(0,0,0,0.10)\");\n\nconst g     = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\nconst plotG = g.append(\"g\").attr(\"clip-path\", \"url(#plot-clip)\");\n\n// Scales\nconst x = d3.scaleLog().domain([400, 8000]).range([0, iw]);\nconst y = d3.scaleLinear().domain([yMin, yMax]).range([ih, 0]);\n\n// Horizontal grid — major ticks heavier (1.5px), minor lighter (0.75px) to mimic Weibull paper\nmajorProbs.forEach((F) => {\n  plotG.append(\"line\")\n    .attr(\"x1\", 0).attr(\"x2\", iw)\n    .attr(\"y1\", y(weibullY(F))).attr(\"y2\", y(weibullY(F)))\n    .attr(\"stroke\", t.grid).attr(\"stroke-width\", 1.5);\n});\nminorProbs.forEach((F) => {\n  plotG.append(\"line\")\n    .attr(\"x1\", 0).attr(\"x2\", iw)\n    .attr(\"y1\", y(weibullY(F))).attr(\"y2\", y(weibullY(F)))\n    .attr(\"stroke\", t.grid).attr(\"stroke-width\", 0.75);\n});\n// Vertical grid at log-decade ticks\n[500, 1000, 2000, 4000, 8000].forEach((v) => {\n  plotG.append(\"line\")\n    .attr(\"x1\", x(v)).attr(\"x2\", x(v)).attr(\"y1\", 0).attr(\"y2\", ih)\n    .attr(\"stroke\", t.grid).attr(\"stroke-width\", 1);\n});\n\n// 63.2% characteristic life reference line (amber dashed)\nconst y632 = y(weibullY(0.632));\nplotG.append(\"line\")\n  .attr(\"x1\", 0).attr(\"x2\", iw).attr(\"y1\", y632).attr(\"y2\", y632)\n  .attr(\"stroke\", t.amber).attr(\"stroke-width\", 2).attr(\"stroke-dasharray\", \"8 4\");\n\nif (eta >= 400 && eta <= 8000) {\n  plotG.append(\"line\")\n    .attr(\"x1\", x(eta)).attr(\"x2\", x(eta)).attr(\"y1\", y632).attr(\"y2\", ih)\n    .attr(\"stroke\", t.amber).attr(\"stroke-width\", 1.5).attr(\"stroke-dasharray\", \"5 3\");\n}\n\n// Fitted Weibull line using d3.line() generator\nconst lineGen = d3.line().x((d) => x(d.tx)).y((d) => y(d.yw));\nconst fitData = [400, 8000].map((tx) => ({ tx, yw: beta * Math.log(tx) + bIntercept }));\nplotG.append(\"path\")\n  .datum(fitData).attr(\"d\", lineGen)\n  .attr(\"stroke\", t.palette[2]).attr(\"stroke-width\", 2.5).attr(\"fill\", \"none\");\n\n// Failure points — filled green circles\nplotG.selectAll(\".fail\")\n  .data(failures).join(\"circle\").attr(\"class\", \"fail\")\n  .attr(\"cx\", (d) => x(d.time)).attr(\"cy\", (d) => y(d.yw))\n  .attr(\"r\", 7)\n  .attr(\"fill\", t.palette[0]).attr(\"stroke\", t.pageBg).attr(\"stroke-width\", 1.5);\n\n// Y-axis: idiomatic d3.axisLeft with Weibull probability tick formatting\nconst yAxis = d3.axisLeft(y)\n  .tickValues(allProbs.map(weibullY))\n  .tickFormat((yw) => {\n    const F = 1 - Math.exp(-Math.exp(yw));\n    if (Math.abs(F - 0.632) < 0.002) return \"63.2%\";\n    return `${Math.round(F * 100)}%`;\n  })\n  .tickSize(6);\nconst yAxisG = g.append(\"g\").call(yAxis);\nyAxisG.selectAll(\".tick text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\").attr(\"x\", -10);\nyAxisG.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\nyAxisG.select(\".domain\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5);\n\n// X-axis: idiomatic d3.axisBottom with custom log tick values\nconst xAxis = d3.axisBottom(x)\n  .tickValues([500, 1000, 2000, 4000, 8000])\n  .tickFormat((v) => v >= 1000 ? `${v / 1000}k` : `${v}`)\n  .tickSize(6);\nconst xAxisG = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(xAxis);\nxAxisG.selectAll(\".tick text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\nxAxisG.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\nxAxisG.select(\".domain\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5);\n\n// Censored suspensions: hollow + crosshair markers below x-axis\ncensored.forEach((d) => {\n  const cx = x(d.time);\n  const cy = ih + 38;\n  g.append(\"circle\").attr(\"cx\", cx).attr(\"cy\", cy).attr(\"r\", 7)\n    .attr(\"fill\", \"none\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 2);\n  g.append(\"line\").attr(\"x1\", cx - 4).attr(\"x2\", cx + 4).attr(\"y1\", cy).attr(\"y2\", cy)\n    .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5);\n  g.append(\"line\").attr(\"x1\", cx).attr(\"x2\", cx).attr(\"y1\", cy - 4).attr(\"y2\", cy + 4)\n    .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5);\n});\n\n// Amber annotations at 15px for clear readability\ng.append(\"text\").attr(\"x\", iw - 8).attr(\"y\", y632 - 10)\n  .attr(\"text-anchor\", \"end\").attr(\"fill\", t.amber)\n  .style(\"font-size\", \"15px\").style(\"font-weight\", \"500\")\n  .text(\"63.2% — Characteristic Life\");\n\nif (eta >= 400 && eta <= 8000) {\n  g.append(\"text\").attr(\"x\", x(eta) + 8).attr(\"y\", ih - 20)\n    .attr(\"fill\", t.amber).style(\"font-size\", \"15px\").style(\"font-weight\", \"500\")\n    .text(`η = ${eta.toLocaleString()} h`);\n}\n\n// Parameter box — generous padding, amber accent border, drop shadow\nconst pX = 16, pY = 16;\ng.append(\"rect\").attr(\"x\", pX).attr(\"y\", pY).attr(\"width\", 204).attr(\"height\", 84)\n  .attr(\"fill\", t.elevatedBg).attr(\"rx\", 5)\n  .attr(\"stroke\", t.amber).attr(\"stroke-width\", 1.5).attr(\"stroke-opacity\", 0.55)\n  .attr(\"filter\", \"url(#box-shadow)\");\ng.append(\"text\").attr(\"x\", pX + 16).attr(\"y\", pY + 30)\n  .attr(\"fill\", t.ink).style(\"font-size\", \"15px\").style(\"font-weight\", \"600\")\n  .text(`β = ${beta.toFixed(2)}  (shape)`);\ng.append(\"text\").attr(\"x\", pX + 16).attr(\"y\", pY + 60)\n  .attr(\"fill\", t.ink).style(\"font-size\", \"15px\").style(\"font-weight\", \"600\")\n  .text(`η = ${eta.toLocaleString()} h  (scale)`);\n\n// Legend — generous padding, neutral border, drop shadow\nconst lx = iw - 172, ly = 16;\ng.append(\"rect\").attr(\"x\", lx).attr(\"y\", ly).attr(\"width\", 169).attr(\"height\", 114)\n  .attr(\"fill\", t.elevatedBg).attr(\"rx\", 5)\n  .attr(\"stroke\", t.grid).attr(\"stroke-width\", 1.5)\n  .attr(\"filter\", \"url(#box-shadow)\");\n// Failure row\ng.append(\"circle\").attr(\"cx\", lx + 18).attr(\"cy\", ly + 25).attr(\"r\", 6)\n  .attr(\"fill\", t.palette[0]).attr(\"stroke\", t.pageBg).attr(\"stroke-width\", 1.5);\ng.append(\"text\").attr(\"x\", lx + 34).attr(\"y\", ly + 25)\n  .attr(\"dominant-baseline\", \"middle\").attr(\"fill\", t.ink).style(\"font-size\", \"14px\").text(\"Failure\");\n// Suspended row\ng.append(\"circle\").attr(\"cx\", lx + 18).attr(\"cy\", ly + 57).attr(\"r\", 6)\n  .attr(\"fill\", \"none\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 2);\ng.append(\"line\").attr(\"x1\", lx + 14).attr(\"x2\", lx + 22).attr(\"y1\", ly + 57).attr(\"y2\", ly + 57)\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5);\ng.append(\"line\").attr(\"x1\", lx + 18).attr(\"x2\", lx + 18).attr(\"y1\", ly + 53).attr(\"y2\", ly + 61)\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5);\ng.append(\"text\").attr(\"x\", lx + 34).attr(\"y\", ly + 57)\n  .attr(\"dominant-baseline\", \"middle\").attr(\"fill\", t.ink).style(\"font-size\", \"14px\").text(\"Suspended\");\n// Weibull fit row\ng.append(\"line\").attr(\"x1\", lx + 10).attr(\"x2\", lx + 26).attr(\"y1\", ly + 89).attr(\"y2\", ly + 89)\n  .attr(\"stroke\", t.palette[2]).attr(\"stroke-width\", 2.5);\ng.append(\"text\").attr(\"x\", lx + 34).attr(\"y\", ly + 89)\n  .attr(\"dominant-baseline\", \"middle\").attr(\"fill\", t.ink).style(\"font-size\", \"14px\").text(\"Weibull fit\");\n\n// Axis labels\ng.append(\"text\").attr(\"x\", iw / 2).attr(\"y\", ih + 80)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\").style(\"font-weight\", \"500\")\n  .text(\"Time to Failure (hours)\");\nsvg.append(\"text\")\n  .attr(\"transform\", `translate(32,${margin.top + ih / 2}) rotate(-90)`)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\").style(\"font-weight\", \"500\")\n  .text(\"Cumulative Failure Probability F(t)\");\n\n// Title\nsvg.append(\"text\").attr(\"x\", width / 2).attr(\"y\", 48)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\").style(\"font-weight\", \"600\")\n  .text(\"probability-weibull · javascript · d3 · anyplot.ai\");\n"}