{"spec_id":"sn-curve-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// sn-curve-basic: S-N Curve (Wöhler Curve)\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Fatigue test results for a steel alloy: stress amplitude (MPa) vs. cycles to\n// failure (N). Basquin's equation: sigma_a = A * N^b, plus scatter typical of\n// multiple coupon specimens tested at the same stress level.\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = lcg(42);\n\nconst basquinA = 950; // MPa, fatigue strength coefficient\nconst basquinB = -0.11; // fatigue strength exponent\n\nconst stressLevels = [520, 480, 440, 400, 360, 320, 290, 260, 240, 220];\nconst specimensPerLevel = 4;\nconst testPoints = [];\nstressLevels.forEach((stress) => {\n  const meanLogN = Math.log10(stress / basquinA) / basquinB;\n  for (let i = 0; i < specimensPerLevel; i++) {\n    const scatter = (rand() - 0.5) * 0.42; // log10(N) scatter band\n    const logN = meanLogN + scatter;\n    testPoints.push({ x: Math.pow(10, logN), y: stress });\n  }\n});\n\nconst fitCycles = [1e3, 1e7];\nconst fitLine = fitCycles.map((n) => ({ x: n, y: basquinA * Math.pow(n, basquinB) }));\n\nconst ultimateStrength = 780; // MPa\nconst yieldStrength = 620; // MPa\nconst enduranceLimit = 230; // MPa\nconst enduranceStart = 1e6; // cycles beyond which the endurance limit applies\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [\n      {\n        label: \"Test specimens\",\n        data: testPoints,\n        backgroundColor: t.palette[0],\n        borderColor: t.pageBg,\n        borderWidth: 1,\n        pointRadius: 6,\n        pointHoverRadius: 6,\n        showLine: false,\n        order: 1,\n      },\n      {\n        label: \"Basquin fit\",\n        data: fitLine,\n        type: \"line\",\n        borderColor: t.palette[1],\n        backgroundColor: t.palette[1],\n        borderWidth: 3,\n        pointRadius: 0,\n        tension: 0,\n        order: 2,\n      },\n      {\n        label: \"Ultimate strength (780 MPa)\",\n        data: [\n          { x: 1e3, y: ultimateStrength },\n          { x: 1e7, y: ultimateStrength },\n        ],\n        type: \"line\",\n        borderColor: t.palette[2],\n        backgroundColor: t.palette[2],\n        borderWidth: 2,\n        borderDash: [12, 4, 2, 4],\n        pointRadius: 0,\n        tension: 0,\n        order: 3,\n      },\n      {\n        label: \"Yield strength (620 MPa)\",\n        data: [\n          { x: 1e3, y: yieldStrength },\n          { x: 1e7, y: yieldStrength },\n        ],\n        type: \"line\",\n        borderColor: t.palette[3],\n        backgroundColor: t.palette[3],\n        borderWidth: 2,\n        borderDash: [10, 6],\n        pointRadius: 0,\n        tension: 0,\n        order: 4,\n      },\n      {\n        label: \"Endurance limit (230 MPa)\",\n        data: [\n          { x: enduranceStart, y: enduranceLimit },\n          { x: 1e7, y: enduranceLimit },\n        ],\n        type: \"line\",\n        borderColor: t.palette[5],\n        backgroundColor: t.palette[5],\n        borderWidth: 2,\n        borderDash: [3, 5],\n        pointRadius: 0,\n        tension: 0,\n        order: 5,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"sn-curve-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { bottom: 20 },\n      },\n      legend: {\n        position: \"top\",\n        align: \"end\",\n        labels: { color: t.inkSoft, font: { size: 14 }, boxWidth: 24, usePointStyle: true },\n      },\n    },\n    scales: {\n      x: {\n        type: \"logarithmic\",\n        min: 1e3,\n        max: 1e7,\n        title: { display: true, text: \"Cycles to Failure (N)\", color: t.ink, font: { size: 16 } },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: (value) => {\n            const log10 = Math.log10(value);\n            return Number.isInteger(log10) ? `10^${log10}` : null;\n          },\n        },\n        grid: { color: t.grid },\n      },\n      y: {\n        type: \"logarithmic\",\n        min: 150,\n        max: 850,\n        title: { display: true, text: \"Stress Amplitude (MPa)\", color: t.ink, font: { size: 16 } },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          callback: (value) => {\n            if ([150, 200, 300, 400, 500, 600, 700, 800].includes(value)) return value;\n            return null;\n          },\n        },\n        grid: { color: t.grid },\n      },\n    },\n  },\n});\n"}