@ggterm/core 0.2.7 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli-plot.js CHANGED
@@ -652,7 +652,7 @@ function isCategoricalField(data, field) {
652
652
  for (const row of data) {
653
653
  const value = row[field];
654
654
  if (value !== null && value !== undefined) {
655
- if (typeof value === "string" && isNaN(Number(value))) {
655
+ if (typeof value === "string") {
656
656
  return true;
657
657
  }
658
658
  }
@@ -1049,6 +1049,44 @@ function getPointColor(row, aes, colorScale) {
1049
1049
  }
1050
1050
  return DEFAULT_POINT_COLOR;
1051
1051
  }
1052
+ function parseColorToRgba(color, fallback = { r: 128, g: 128, b: 128, a: 1 }) {
1053
+ if (!color)
1054
+ return fallback;
1055
+ if (typeof color === "object" && color !== null && "r" in color) {
1056
+ return color;
1057
+ }
1058
+ if (typeof color === "string") {
1059
+ if (color.startsWith("#")) {
1060
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
1061
+ if (result) {
1062
+ return {
1063
+ r: parseInt(result[1], 16),
1064
+ g: parseInt(result[2], 16),
1065
+ b: parseInt(result[3], 16),
1066
+ a: 1
1067
+ };
1068
+ }
1069
+ }
1070
+ const namedColors = {
1071
+ red: { r: 255, g: 0, b: 0, a: 1 },
1072
+ blue: { r: 0, g: 0, b: 255, a: 1 },
1073
+ green: { r: 0, g: 128, b: 0, a: 1 },
1074
+ black: { r: 0, g: 0, b: 0, a: 1 },
1075
+ white: { r: 255, g: 255, b: 255, a: 1 },
1076
+ gray: { r: 128, g: 128, b: 128, a: 1 },
1077
+ grey: { r: 128, g: 128, b: 128, a: 1 },
1078
+ yellow: { r: 255, g: 255, b: 0, a: 1 },
1079
+ orange: { r: 255, g: 165, b: 0, a: 1 },
1080
+ purple: { r: 128, g: 0, b: 128, a: 1 },
1081
+ cyan: { r: 0, g: 255, b: 255, a: 1 },
1082
+ magenta: { r: 255, g: 0, b: 255, a: 1 }
1083
+ };
1084
+ const named = namedColors[color.toLowerCase()];
1085
+ if (named)
1086
+ return named;
1087
+ }
1088
+ return fallback;
1089
+ }
1052
1090
  function renderGeomPoint(data, geom, aes, scales, canvas) {
1053
1091
  const defaultShape = getPointShape(geom.params.shape);
1054
1092
  const positionType = getPositionType(geom.position);
@@ -1447,7 +1485,7 @@ function renderGeomHLine(_data, geom, _aes, scales, canvas) {
1447
1485
  if (yintercept === undefined)
1448
1486
  return;
1449
1487
  const cy = Math.round(scales.y.map(yintercept));
1450
- const color = geom.params.color ?? { r: 128, g: 128, b: 128, a: 1 };
1488
+ const color = parseColorToRgba(geom.params.color);
1451
1489
  const startX = Math.round(scales.x.range[0]);
1452
1490
  const endX = Math.round(scales.x.range[1]);
1453
1491
  canvas.drawHLine(startX, cy, endX - startX + 1, "─", color);
@@ -1457,7 +1495,7 @@ function renderGeomVLine(_data, geom, _aes, scales, canvas) {
1457
1495
  if (xintercept === undefined)
1458
1496
  return;
1459
1497
  const cx = Math.round(scales.x.map(xintercept));
1460
- const color = geom.params.color ?? { r: 128, g: 128, b: 128, a: 1 };
1498
+ const color = parseColorToRgba(geom.params.color);
1461
1499
  const startY = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
1462
1500
  const endY = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
1463
1501
  canvas.drawVLine(cx, startY, endY - startY + 1, "│", color);
@@ -1768,6 +1806,120 @@ function renderGeomViolin(data, geom, _aes, scales, canvas) {
1768
1806
  }
1769
1807
  }
1770
1808
  }
1809
+ function renderGeomRidgeline(data, geom, aes, scales, canvas) {
1810
+ const scaleFactor = geom.params.scale ?? 0.9;
1811
+ const alpha = geom.params.alpha ?? 0.8;
1812
+ const showOutline = geom.params.outline ?? true;
1813
+ const fixedFill = geom.params.fill;
1814
+ const fixedColor = geom.params.color;
1815
+ const plotLeft = Math.round(scales.x.range[0]);
1816
+ const plotRight = Math.round(scales.x.range[1]);
1817
+ const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
1818
+ const plotBottom = Math.round(Math.max(scales.y.range[0], scales.y.range[1]));
1819
+ const groups = new Map;
1820
+ const groupKeys = [];
1821
+ for (const row of data) {
1822
+ const key = String(row.y ?? "default");
1823
+ if (!groups.has(key)) {
1824
+ groups.set(key, { data: [], index: Number(row.yIndex) ?? groupKeys.length });
1825
+ groupKeys.push(key);
1826
+ }
1827
+ groups.get(key).data.push(row);
1828
+ }
1829
+ const numGroups = groupKeys.length;
1830
+ if (numGroups === 0)
1831
+ return;
1832
+ const plotHeight = Math.abs(plotBottom - plotTop);
1833
+ const ridgeBaseHeight = plotHeight / numGroups;
1834
+ const defaultColors = [
1835
+ { r: 79, g: 169, b: 238, a: 1 },
1836
+ { r: 238, g: 136, b: 102, a: 1 },
1837
+ { r: 102, g: 204, b: 153, a: 1 },
1838
+ { r: 204, g: 102, b: 204, a: 1 },
1839
+ { r: 255, g: 200, b: 87, a: 1 },
1840
+ { r: 138, g: 201, b: 222, a: 1 },
1841
+ { r: 255, g: 153, b: 153, a: 1 },
1842
+ { r: 170, g: 170, b: 170, a: 1 }
1843
+ ];
1844
+ let parsedFillColor = null;
1845
+ if (fixedFill) {
1846
+ parsedFillColor = parseColorToRgba(fixedFill);
1847
+ }
1848
+ const sortedKeys = [...groupKeys].sort((a, b) => {
1849
+ const idxA = groups.get(a)?.index ?? 0;
1850
+ const idxB = groups.get(b)?.index ?? 0;
1851
+ return idxB - idxA;
1852
+ });
1853
+ for (const groupKey of sortedKeys) {
1854
+ const group = groups.get(groupKey);
1855
+ if (!group)
1856
+ continue;
1857
+ const groupIndex = group.index;
1858
+ const groupData = group.data;
1859
+ const sorted = [...groupData].sort((a, b) => {
1860
+ const ax = Number(a.x) || 0;
1861
+ const bx = Number(b.x) || 0;
1862
+ return ax - bx;
1863
+ });
1864
+ if (sorted.length < 2)
1865
+ continue;
1866
+ const baseline = plotTop + (groupIndex + 0.5) * ridgeBaseHeight;
1867
+ let fillColor;
1868
+ if (parsedFillColor) {
1869
+ fillColor = parsedFillColor;
1870
+ } else if ((aes.fill || aes.color) && scales.color) {
1871
+ const mappedColor = scales.color.map(groupKey);
1872
+ if (typeof mappedColor === "object" && "r" in mappedColor) {
1873
+ fillColor = mappedColor;
1874
+ } else {
1875
+ fillColor = defaultColors[groupIndex % defaultColors.length];
1876
+ }
1877
+ } else {
1878
+ fillColor = defaultColors[groupIndex % defaultColors.length];
1879
+ }
1880
+ const alphaFillColor = {
1881
+ r: Math.round(fillColor.r * alpha + 255 * (1 - alpha) * 0.1),
1882
+ g: Math.round(fillColor.g * alpha + 255 * (1 - alpha) * 0.1),
1883
+ b: Math.round(fillColor.b * alpha + 255 * (1 - alpha) * 0.1),
1884
+ a: 1
1885
+ };
1886
+ const maxRidgeHeight = ridgeBaseHeight * scaleFactor * 1.5;
1887
+ const outlinePoints = [];
1888
+ for (const row of sorted) {
1889
+ const xVal = Number(row.x);
1890
+ const scaled = Number(row.scaled) || 0;
1891
+ const px = Math.round(scales.x.map(xVal));
1892
+ const ridgeHeight = scaled * maxRidgeHeight;
1893
+ const py = Math.round(baseline - ridgeHeight);
1894
+ if (px < plotLeft || px > plotRight)
1895
+ continue;
1896
+ const fillTop = Math.max(plotTop, py);
1897
+ const fillBottom = Math.min(plotBottom, Math.round(baseline));
1898
+ for (let y = fillTop;y <= fillBottom; y++) {
1899
+ canvas.drawChar(px, y, "█", alphaFillColor);
1900
+ }
1901
+ if (showOutline) {
1902
+ outlinePoints.push({ x: px, y: Math.max(plotTop, py) });
1903
+ }
1904
+ }
1905
+ if (showOutline && outlinePoints.length > 1) {
1906
+ const outlineColor = fixedColor ? parseColorToRgba(fixedColor) : { r: Math.min(255, fillColor.r + 40), g: Math.min(255, fillColor.g + 40), b: Math.min(255, fillColor.b + 40), a: 1 };
1907
+ for (let i = 0;i < outlinePoints.length - 1; i++) {
1908
+ const p1 = outlinePoints[i];
1909
+ const p2 = outlinePoints[i + 1];
1910
+ if (Math.abs(p2.x - p1.x) <= 1) {
1911
+ if (p1.y >= plotTop && p1.y <= plotBottom) {
1912
+ canvas.drawChar(p1.x, p1.y, "▄", outlineColor);
1913
+ }
1914
+ }
1915
+ }
1916
+ const last = outlinePoints[outlinePoints.length - 1];
1917
+ if (last.y >= plotTop && last.y <= plotBottom) {
1918
+ canvas.drawChar(last.x, last.y, "▄", outlineColor);
1919
+ }
1920
+ }
1921
+ }
1922
+ }
1771
1923
  function renderGeomTile(data, geom, aes, scales, canvas) {
1772
1924
  const alpha = geom.params.alpha ?? 1;
1773
1925
  const plotLeft = Math.round(scales.x.range[0]);
@@ -2016,7 +2168,7 @@ function renderGeomAbline(_data, geom, _aes, scales, canvas) {
2016
2168
  const intercept = geom.params.intercept ?? 0;
2017
2169
  const linetype = geom.params.linetype ?? "solid";
2018
2170
  const lineChar = linetype === "dotted" ? "·" : linetype === "dashed" ? "╌" : "─";
2019
- const color = geom.params.color ?? { r: 128, g: 128, b: 128, a: 1 };
2171
+ const color = parseColorToRgba(geom.params.color);
2020
2172
  const plotLeft = Math.round(scales.x.range[0]);
2021
2173
  const plotRight = Math.round(scales.x.range[1]);
2022
2174
  const plotTop = Math.round(Math.min(scales.y.range[0], scales.y.range[1]));
@@ -2206,6 +2358,10 @@ function renderGeom(data, geom, aes, scales, canvas, coordType) {
2206
2358
  case "violin":
2207
2359
  renderGeomViolin(data, geom, aes, scales, canvas);
2208
2360
  break;
2361
+ case "ridgeline":
2362
+ case "joy":
2363
+ renderGeomRidgeline(data, geom, aes, scales, canvas);
2364
+ break;
2209
2365
  case "tile":
2210
2366
  case "raster":
2211
2367
  case "bin2d":
@@ -3314,6 +3470,52 @@ function stat_ydensity(params = {}) {
3314
3470
  }
3315
3471
  };
3316
3472
  }
3473
+ function stat_xdensity(params = {}) {
3474
+ return {
3475
+ type: "xdensity",
3476
+ compute(data, aes) {
3477
+ const groups = new Map;
3478
+ const groupOrder = [];
3479
+ for (const row of data) {
3480
+ const groupKey = String(row[aes.y] ?? "default");
3481
+ const xVal = row[aes.x];
3482
+ if (xVal === null || xVal === undefined)
3483
+ continue;
3484
+ const numX = Number(xVal);
3485
+ if (isNaN(numX))
3486
+ continue;
3487
+ if (!groups.has(groupKey)) {
3488
+ groups.set(groupKey, []);
3489
+ groupOrder.push(groupKey);
3490
+ }
3491
+ groups.get(groupKey).push(numX);
3492
+ }
3493
+ const result = [];
3494
+ let groupIndex = 0;
3495
+ for (const groupKey of groupOrder) {
3496
+ const xValues = groups.get(groupKey);
3497
+ if (xValues.length < 2) {
3498
+ groupIndex++;
3499
+ continue;
3500
+ }
3501
+ const tempData = xValues.map((v) => ({ x: v }));
3502
+ const densityResult = computeDensity(tempData, "x", params);
3503
+ for (const d of densityResult) {
3504
+ result.push({
3505
+ x: d.x,
3506
+ y: groupKey,
3507
+ yIndex: groupIndex,
3508
+ density: d.density,
3509
+ scaled: d.scaled,
3510
+ height: d.scaled
3511
+ });
3512
+ }
3513
+ groupIndex++;
3514
+ }
3515
+ return result;
3516
+ }
3517
+ };
3518
+ }
3317
3519
 
3318
3520
  // src/stats/smooth.ts
3319
3521
  function linearRegression(xs, ys) {
@@ -3791,6 +3993,124 @@ function stat_qq_line(params = {}) {
3791
3993
  };
3792
3994
  }
3793
3995
 
3996
+ // src/stats/density2d.ts
3997
+ function gaussian2d(dx, dy, hx, hy) {
3998
+ const ux = dx / hx;
3999
+ const uy = dy / hy;
4000
+ return Math.exp(-0.5 * (ux * ux + uy * uy)) / (2 * Math.PI * hx * hy);
4001
+ }
4002
+ function scottBandwidth2d(values) {
4003
+ const n = values.length;
4004
+ if (n < 2)
4005
+ return 1;
4006
+ const mean = values.reduce((a, b) => a + b, 0) / n;
4007
+ const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / (n - 1);
4008
+ const std = Math.sqrt(variance);
4009
+ return std * Math.pow(n, -1 / 6);
4010
+ }
4011
+ function computeDensity2d(data, xField, yField, params = {}) {
4012
+ const points = [];
4013
+ const xValues = [];
4014
+ const yValues = [];
4015
+ for (const row of data) {
4016
+ const xVal = row[xField];
4017
+ const yVal = row[yField];
4018
+ if (xVal === null || xVal === undefined || yVal === null || yVal === undefined)
4019
+ continue;
4020
+ const x = Number(xVal);
4021
+ const y = Number(yVal);
4022
+ if (isNaN(x) || isNaN(y))
4023
+ continue;
4024
+ points.push({ x, y });
4025
+ xValues.push(x);
4026
+ yValues.push(y);
4027
+ }
4028
+ if (points.length < 3) {
4029
+ return [];
4030
+ }
4031
+ const n = params.n ?? 50;
4032
+ const nx = params.nx ?? n;
4033
+ const ny = params.ny ?? n;
4034
+ const adjust = params.adjust ?? 1;
4035
+ let hx, hy;
4036
+ if (Array.isArray(params.h)) {
4037
+ hx = params.h[0] * adjust;
4038
+ hy = params.h[1] * adjust;
4039
+ } else if (params.h !== undefined) {
4040
+ hx = params.h * adjust;
4041
+ hy = params.h * adjust;
4042
+ } else {
4043
+ hx = scottBandwidth2d(xValues) * adjust;
4044
+ hy = scottBandwidth2d(yValues) * adjust;
4045
+ }
4046
+ if (hx <= 0)
4047
+ hx = 1;
4048
+ if (hy <= 0)
4049
+ hy = 1;
4050
+ const xMin = Math.min(...xValues);
4051
+ const xMax = Math.max(...xValues);
4052
+ const yMin = Math.min(...yValues);
4053
+ const yMax = Math.max(...yValues);
4054
+ const xPad = 3 * hx;
4055
+ const yPad = 3 * hy;
4056
+ const gridXMin = xMin - xPad;
4057
+ const gridXMax = xMax + xPad;
4058
+ const gridYMin = yMin - yPad;
4059
+ const gridYMax = yMax + yPad;
4060
+ const xStep = (gridXMax - gridXMin) / (nx - 1);
4061
+ const yStep = (gridYMax - gridYMin) / (ny - 1);
4062
+ const results = [];
4063
+ const nPoints = points.length;
4064
+ for (let i = 0;i < nx; i++) {
4065
+ const gx = gridXMin + i * xStep;
4066
+ for (let j = 0;j < ny; j++) {
4067
+ const gy = gridYMin + j * yStep;
4068
+ let density = 0;
4069
+ for (const pt of points) {
4070
+ density += gaussian2d(gx - pt.x, gy - pt.y, hx, hy);
4071
+ }
4072
+ density /= nPoints;
4073
+ results.push({
4074
+ x: gx,
4075
+ y: gy,
4076
+ z: density,
4077
+ density,
4078
+ level: density
4079
+ });
4080
+ }
4081
+ }
4082
+ return results;
4083
+ }
4084
+ function stat_density_2d(params = {}) {
4085
+ return {
4086
+ type: "density_2d",
4087
+ compute(data, aes) {
4088
+ if (aes.color) {
4089
+ const groups = new Map;
4090
+ for (const row of data) {
4091
+ const group = String(row[aes.color] ?? "default");
4092
+ if (!groups.has(group)) {
4093
+ groups.set(group, []);
4094
+ }
4095
+ groups.get(group).push(row);
4096
+ }
4097
+ const result = [];
4098
+ for (const [group, groupData] of groups) {
4099
+ const density = computeDensity2d(groupData, aes.x, aes.y, params);
4100
+ for (const d of density) {
4101
+ result.push({
4102
+ ...d,
4103
+ [aes.color]: group
4104
+ });
4105
+ }
4106
+ }
4107
+ return result;
4108
+ }
4109
+ return computeDensity2d(data, aes.x, aes.y, params);
4110
+ }
4111
+ };
4112
+ }
4113
+
3794
4114
  // src/facets/index.ts
3795
4115
  function as_labeller(labels) {
3796
4116
  return (value) => labels[value] ?? value;
@@ -4068,6 +4388,14 @@ function applyStatTransform(data, geom, aes) {
4068
4388
  adjust: geom.params.adjust
4069
4389
  });
4070
4390
  return ydensityStat.compute(data, aes);
4391
+ } else if (geom.stat === "xdensity") {
4392
+ const xdensityStat = stat_xdensity({
4393
+ bw: geom.params.bw,
4394
+ kernel: geom.params.kernel,
4395
+ n: geom.params.n,
4396
+ adjust: geom.params.adjust
4397
+ });
4398
+ return xdensityStat.compute(data, aes);
4071
4399
  } else if (geom.stat === "smooth") {
4072
4400
  const smoothStat = stat_smooth({
4073
4401
  method: geom.params.method,
@@ -4109,6 +4437,15 @@ function applyStatTransform(data, geom, aes) {
4109
4437
  drop: geom.params.drop
4110
4438
  });
4111
4439
  return bin2dStat.compute(data, aes);
4440
+ } else if (geom.stat === "density_2d") {
4441
+ const density2dStat = stat_density_2d({
4442
+ h: geom.params.bandwidth,
4443
+ n: geom.params.n,
4444
+ nx: geom.params.nx,
4445
+ ny: geom.params.ny,
4446
+ adjust: geom.params.adjust
4447
+ });
4448
+ return density2dStat.compute(data, aes);
4112
4449
  }
4113
4450
  return data;
4114
4451
  }
@@ -4177,6 +4514,14 @@ function renderToCanvas(spec, options) {
4177
4514
  scaleData = applyStatTransform(spec.data, geom, spec.aes);
4178
4515
  scaleAes = { ...spec.aes, x: "x", y: "y", fill: "fill" };
4179
4516
  break;
4517
+ } else if (geom.stat === "density_2d") {
4518
+ scaleData = applyStatTransform(spec.data, geom, spec.aes);
4519
+ scaleAes = { ...spec.aes, x: "x", y: "y" };
4520
+ break;
4521
+ } else if (geom.stat === "xdensity") {
4522
+ scaleData = applyStatTransform(spec.data, geom, spec.aes);
4523
+ scaleAes = { ...spec.aes, x: "x", y: "y" };
4524
+ break;
4180
4525
  }
4181
4526
  }
4182
4527
  scaleData = applyCoordTransform(scaleData, scaleAes, spec.coord);
@@ -4213,6 +4558,10 @@ function renderToCanvas(spec, options) {
4213
4558
  geomAes = { ...spec.aes, x: "x", y: "y", xend: "xend", yend: "yend" };
4214
4559
  } else if (geom.stat === "bin2d") {
4215
4560
  geomAes = { ...spec.aes, x: "x", y: "y", fill: "fill" };
4561
+ } else if (geom.stat === "density_2d") {
4562
+ geomAes = { ...spec.aes, x: "x", y: "y" };
4563
+ } else if (geom.stat === "xdensity") {
4564
+ geomAes = { ...spec.aes, x: "x", y: "y" };
4216
4565
  }
4217
4566
  geomData = applyCoordTransform(geomData, geomAes, spec.coord);
4218
4567
  }
@@ -5135,8 +5484,32 @@ function geom_qq_line(options = {}) {
5135
5484
  };
5136
5485
  }
5137
5486
 
5487
+ // src/geoms/ridgeline.ts
5488
+ function geom_ridgeline(options = {}) {
5489
+ return {
5490
+ type: "ridgeline",
5491
+ stat: "xdensity",
5492
+ position: "identity",
5493
+ params: {
5494
+ scale: options.scale ?? 0.9,
5495
+ alpha: options.alpha ?? 0.8,
5496
+ fill: options.fill,
5497
+ color: options.color,
5498
+ adjust: options.adjust ?? 1,
5499
+ n: options.n ?? 128,
5500
+ outline: options.outline ?? true
5501
+ }
5502
+ };
5503
+ }
5504
+ var geom_joy;
5505
+ var init_ridgeline = __esm(() => {
5506
+ geom_joy = geom_ridgeline;
5507
+ });
5508
+
5138
5509
  // src/geoms/index.ts
5139
- var init_geoms = () => {};
5510
+ var init_geoms = __esm(() => {
5511
+ init_ridgeline();
5512
+ });
5140
5513
 
5141
5514
  // src/stats/index.ts
5142
5515
  var init_stats = __esm(() => {
@@ -9645,6 +10018,7 @@ __export(exports_src, {
9645
10018
  geom_smooth: () => geom_smooth,
9646
10019
  geom_segment: () => geom_segment,
9647
10020
  geom_rug: () => geom_rug,
10021
+ geom_ridgeline: () => geom_ridgeline,
9648
10022
  geom_ribbon: () => geom_ribbon,
9649
10023
  geom_rect: () => geom_rect,
9650
10024
  geom_raster: () => geom_raster,
@@ -9656,6 +10030,7 @@ __export(exports_src, {
9656
10030
  geom_linerange: () => geom_linerange,
9657
10031
  geom_line: () => geom_line,
9658
10032
  geom_label: () => geom_label,
10033
+ geom_joy: () => geom_joy,
9659
10034
  geom_hline: () => geom_hline,
9660
10035
  geom_histogram: () => geom_histogram,
9661
10036
  geom_freqpoly: () => geom_freqpoly,
@@ -10564,6 +10939,8 @@ var GEOM_TYPES = [
10564
10939
  "freqpoly",
10565
10940
  "boxplot",
10566
10941
  "violin",
10942
+ "ridgeline",
10943
+ "joy",
10567
10944
  "area",
10568
10945
  "ribbon",
10569
10946
  "rug",
@@ -10958,7 +11335,7 @@ Error: Unknown geometry type "${geomType}"`);
10958
11335
  Available geom types:`);
10959
11336
  console.error(` Points/Lines: point, line, path, step, smooth, segment`);
10960
11337
  console.error(` Bars/Areas: bar, col, histogram, freqpoly, area, ribbon`);
10961
- console.error(` Distributions: boxplot, violin, qq, density_2d`);
11338
+ console.error(` Distributions: boxplot, violin, ridgeline, joy, qq, density_2d`);
10962
11339
  console.error(` Uncertainty: errorbar, errorbarh, crossbar, linerange, pointrange`);
10963
11340
  console.error(` 2D: tile, rect, raster, bin2d, contour, contour_filled`);
10964
11341
  console.error(` Text: text, label`);
@@ -11103,6 +11480,10 @@ If you want a univariate plot, try: histogram, bar, qq, or freqpoly`);
11103
11480
  case "violin":
11104
11481
  plot = plot.geom(geom_violin());
11105
11482
  break;
11483
+ case "ridgeline":
11484
+ case "joy":
11485
+ plot = plot.geom(geom_ridgeline());
11486
+ break;
11106
11487
  case "bar":
11107
11488
  plot = plot.geom(geom_bar());
11108
11489
  break;