The workhorse figure of experimental work: data as markers with error bars, a model as a smooth line. In Plotly those are just two traces with the right mode.

Step 1 — Data as markers, model as line

The mode property is the key: measured points are markers (never connected — data are points), the model is lines:

const x = [1, 2, 3, 4, 5, 6, 7, 8];
const y = [2.1, 3.9, 8.3, 15.8, 31.0, 44.2, 58.9, 71.5];

const model = { x: [], y: [] };
for (let t = 1; t <= 8; t += 0.05) {
  model.x.push(t);
  model.y.push(1.15 * t * t);
}

const data = [
  { x, y, mode: 'markers', type: 'scatter', name: 'measured' },
  { x: model.x, y: model.y, mode: 'lines', type: 'scatter', name: 'model' }
];

Plotly.newPlot(chart, data);

Note the model is evaluated on a fine grid (t += 0.05) so the curve is smooth — never draw the model through only the data points.

Step 2 — Add error bars

Uncertainties attach directly to the data trace:

const yerr = [0.4, 0.5, 0.9, 1.4, 2.2, 2.7, 3.1, 3.6];

const data = [
  {
    x, y, mode: 'markers', type: 'scatter', name: 'measured',
    error_y: { type: 'data', array: yerr, visible: true, width: 3 }
  },
  { x: model.x, y: model.y, mode: 'lines', type: 'scatter', name: 'model' }
];

Plotly.newPlot(chart, data);

type: 'data' means “these are my actual per-point uncertainties” — there are also 'percent' and 'constant' modes, but real error bars from real numbers is almost always what a paper needs.

Step 3 — Axis titles and ranges

Plotly.newPlot(chart, data, {
  xaxis: { title: { text: 'Concentration (mM)' } },
  yaxis: { title: { text: 'Rate (1/s)' }, rangemode: 'tozero' },
  font: { family: 'Times New Roman', size: 14 },
  legend: { x: 0.02, y: 0.98 }
});

rangemode: 'tozero' keeps the y-axis anchored at zero — starting a bar-or-rate axis at an arbitrary value is how figures accidentally exaggerate effects.

Step 4 — Export or insert

Run, then Export / Insert — a vector PDF for LaTeX or straight into the paper with a \label. The interactivity (hover values, zoom) is your tool while iterating; the exported figure is the static, typeset result.

Tip: For log-scale data, add type: 'log' to the axis instead of transforming your numbers — the tick labels stay in real units, which is what readers need to check your values.