{"spec_id":"campbell-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// campbell-basic: Campbell Diagram\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Rotational speed sweep, 0-6000 RPM.\nconst SPEED_MAX = 6000;\nconst N = 61;\nconst speed = Array.from({ length: N }, (_, i) => (i * SPEED_MAX) / (N - 1));\n\n// Natural frequency curves: base frequency plus a mild linear drift with speed\n// (gyroscopic stiffening/softening) — some modes rise, some fall.\nconst modes = [\n  { name: \"1st Bending\", base: 35, drift: 20, color: t.palette[0] },\n  { name: \"2nd Bending\", base: 95, drift: -15, color: t.palette[1] },\n  { name: \"1st Torsional\", base: 145, drift: 10, color: t.palette[2] },\n  { name: \"2nd Torsional\", base: 230, drift: -25, color: t.palette[3] },\n  { name: \"Axial\", base: 270, drift: 5, color: t.palette[5] },\n].map((mode) => ({\n  ...mode,\n  freq: speed.map((s) => mode.base + mode.drift * (s / SPEED_MAX)),\n}));\n\n// Engine order excitation lines: straight lines from the origin, slope =\n// order / 60 (RPM -> Hz).\nconst orders = [1, 2, 3].map((order) => ({\n  name: `${order}x`,\n  freq: speed.map((s) => (order * s) / 60),\n}));\n\n// Critical speeds: RPM where an engine order line crosses a natural\n// frequency curve, found by scanning for a sign change and interpolating.\nconst criticalSpeeds = [];\norders.forEach((order) => {\n  modes.forEach((mode) => {\n    for (let i = 1; i < N; i++) {\n      const prevDiff = order.freq[i - 1] - mode.freq[i - 1];\n      const currDiff = order.freq[i] - mode.freq[i];\n      if (prevDiff === 0 || prevDiff * currDiff < 0) {\n        const frac = prevDiff === 0 ? 0 : prevDiff / (prevDiff - currDiff);\n        const s = speed[i - 1] + frac * (speed[i] - speed[i - 1]);\n        const f = order.freq[i - 1] + frac * (order.freq[i] - order.freq[i - 1]);\n        criticalSpeeds.push([s, f]);\n      }\n    }\n  });\n});\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"campbell-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  grid: { left: 90, right: 90, top: 110, bottom: 150, containLabel: true },\n  legend: {\n    bottom: 10,\n    type: \"scroll\",\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemWidth: 20,\n    itemHeight: 12,\n  },\n  xAxis: {\n    type: \"value\",\n    name: \"Rotational Speed (RPM)\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 0,\n    max: SPEED_MAX,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Natural Frequency (Hz)\",\n    nameLocation: \"middle\",\n    nameGap: 55,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 0,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    ...modes.map((mode) => ({\n      name: mode.name,\n      type: \"line\",\n      data: speed.map((s, i) => [s, mode.freq[i]]),\n      showSymbol: false,\n      smooth: 0.2,\n      lineStyle: { width: 3.5, color: mode.color },\n      itemStyle: { color: mode.color },\n    })),\n    ...orders.map((order) => ({\n      name: order.name,\n      type: \"line\",\n      data: speed.map((s, i) => [s, order.freq[i]]),\n      showSymbol: false,\n      lineStyle: { width: 2, type: \"dashed\", color: t.ink, opacity: 0.55 },\n      itemStyle: { color: t.ink },\n      endLabel: {\n        show: true,\n        formatter: \"{a}\",\n        color: t.ink,\n        fontSize: 14,\n      },\n    })),\n    {\n      name: \"Critical Speed\",\n      type: \"scatter\",\n      data: criticalSpeeds,\n      symbol: \"diamond\",\n      symbolSize: 18,\n      itemStyle: {\n        color: t.amber,\n        borderColor: t.ink,\n        borderWidth: 1.5,\n      },\n      z: 10,\n      // Light resonance-zone shading around each intersection — showcases\n      // markArea while reinforcing the diamond markers as the focal points.\n      markArea: {\n        silent: true,\n        itemStyle: { color: t.amber, opacity: 0.12 },\n        data: criticalSpeeds.map(([s, f]) => [\n          { xAxis: s - 100, yAxis: f - 12 },\n          { xAxis: s + 100, yAxis: f + 12 },\n        ]),\n      },\n    },\n  ],\n});\n"}