@ironm00n/pyret-lang 0.0.4 → 0.0.6

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.
@@ -1,6 +1,7 @@
1
1
  ({
2
2
  requires: [
3
3
  { 'import-type': 'builtin', 'name': 'image-lib' },
4
+ { "import-type": "builtin", 'name': "charts-util" },
4
5
  ],
5
6
  nativeRequires: [
6
7
  'pyret-base/js/js-numbers',
@@ -19,7 +20,7 @@
19
20
  'plot': "tany",
20
21
  }
21
22
  },
22
- theModule: function (RUNTIME, NAMESPACE, uri, IMAGELIB, jsnums, vega, canvasLib) {
23
+ theModule: function (RUNTIME, NAMESPACE, uri, IMAGELIB, CHARTSUTILLIB, jsnums, vega, canvasLib) {
23
24
  'use strict';
24
25
 
25
26
 
@@ -59,6 +60,7 @@
59
60
  const cases = RUNTIME.ffi.cases;
60
61
 
61
62
  var IMAGE = get(IMAGELIB, "internal");
63
+ var CHARTSUTIL = get(CHARTSUTILLIB, "values");
62
64
 
63
65
  const ann = function(name, pred) {
64
66
  return RUNTIME.makePrimitiveAnn(name, pred);
@@ -207,6 +209,28 @@
207
209
 
208
210
  //////////////////////////////////////////////////////////////////////////////
209
211
 
212
+ const chartFontConfig = {
213
+ signals: [
214
+ { name: 'fontSizeScale', value: 14 }
215
+ ],
216
+ mark: {
217
+ fontSize: { signal: 'fontSizeScale' }
218
+ },
219
+ text: {
220
+ fontSize: { signal: 'fontSizeScale' }
221
+ },
222
+ title: {
223
+ fontSize: { signal: '1.2 * fontSizeScale' },
224
+ },
225
+ axis: {
226
+ labelFontSize: { signal: 'fontSizeScale' },
227
+ titleFontSize: { signal: 'fontSizeScale' },
228
+ },
229
+ legend: {
230
+ labelFontSize: { signal: 'fontSizeScale' },
231
+ }
232
+ };
233
+
210
234
  function pieChart(globalOptions, rawData) {
211
235
  /*
212
236
  Note: Most of the complexity here is due to supporting the "collapsed" wedge of values,
@@ -457,6 +481,7 @@
457
481
  marks,
458
482
  legends,
459
483
  onExit: defaultImageReturn,
484
+ config: chartFontConfig,
460
485
  };
461
486
  }
462
487
 
@@ -902,6 +927,9 @@
902
927
  const background = getColorOrDefault(globalOptions['backgroundColor'], 'transparent');
903
928
  const axesConfig = dimensions[horizontal ? 'horizontal' : 'vertical']
904
929
  const axis = get_axis(rawData);
930
+ const xAxisType = globalOptions['x-axis-type'];
931
+ const yAxisType = globalOptions['y-axis-type'];
932
+ const bandwidth = toFixnum(get(rawData, 'bandwidth') || 1);
905
933
 
906
934
  const data = [];
907
935
 
@@ -934,14 +962,15 @@
934
962
  name: 'primary',
935
963
  type: 'band',
936
964
  range: axesConfig.primary.range,
937
- domain: { data: 'table', field: 'label' }
965
+ domain: { data: 'table', field: 'label' },
966
+ padding: 1 - bandwidth,
938
967
  },
939
968
  {
940
969
  name: 'secondary',
941
- type: 'linear',
942
970
  range: axesConfig.secondary.range,
943
971
  nice: true, zero: true,
944
- domain: axis ? { signal: 'extent(domain("secondaryLabels"))' } : { data: 'table', field: 'value' }
972
+ domain: axis ? { signal: 'extent(domain("secondaryLabels"))' } : { data: 'table', field: 'value' },
973
+ ...(axesConfig.secondary.name === 'x' ? xAxisType : yAxisType)
945
974
  },
946
975
  {
947
976
  name: 'color',
@@ -1015,6 +1044,7 @@
1015
1044
  axes,
1016
1045
  marks,
1017
1046
  onExit: defaultImageReturn,
1047
+ config: chartFontConfig,
1018
1048
  };
1019
1049
  }
1020
1050
 
@@ -1034,6 +1064,9 @@
1034
1064
  const stackType = get(rawData, 'is-stacked');
1035
1065
  const isStacked = stackType !== 'none';
1036
1066
  const isNotFullStacked = (stackType !== 'relative') && (stackType !== 'percent');
1067
+ const bandwidth = toFixnum(get(rawData, 'bandwidth') || 1);
1068
+ const xAxisType = globalOptions['x-axis-type'];
1069
+ const yAxisType = globalOptions['y-axis-type'];
1037
1070
 
1038
1071
  const data = [];
1039
1072
 
@@ -1114,13 +1147,13 @@
1114
1147
  type: 'band',
1115
1148
  range: axesConfig.primary.range,
1116
1149
  domain: { data: 'table', field: 'label' },
1117
- padding: 0.2
1150
+ padding: 1 - bandwidth
1118
1151
  };
1119
1152
  const secondaryScale = {
1120
1153
  name: 'secondary',
1121
- type: 'linear',
1122
1154
  range: axesConfig.secondary.range,
1123
- nice: true, zero: true,
1155
+ ...(axesConfig.secondary.name === 'x' ? xAxisType : yAxisType),
1156
+ nice: true, zero: false,
1124
1157
  domain: (axis && isNotFullStacked) ? { signal: 'extent(domain("secondaryLabels"))' } : { data: 'table', field: 'value1' }
1125
1158
  };
1126
1159
  const scales = [
@@ -1300,6 +1333,7 @@
1300
1333
  marks,
1301
1334
  legends,
1302
1335
  onExit: defaultImageReturn,
1336
+ config: chartFontConfig,
1303
1337
  }
1304
1338
  }
1305
1339
 
@@ -1317,6 +1351,8 @@
1317
1351
  const background = getColorOrDefault(globalOptions['backgroundColor'], 'transparent');
1318
1352
  const min = getNumOrDefault(globalOptions['min'], undefined);
1319
1353
  const max = getNumOrDefault(globalOptions['max'], undefined);
1354
+ const xAxisType = globalOptions['x-axis-type'];
1355
+ const yAxisType = globalOptions['y-axis-type'];
1320
1356
 
1321
1357
  const data = [
1322
1358
  {
@@ -1403,8 +1439,8 @@
1403
1439
  },
1404
1440
  update: {
1405
1441
  [PC]: { scale: 'primary', field: 'label', offset: { scale: 'primary', band: 0.5 } },
1406
- [S]: { scale: 'secondary', field: 'lowWhisker' },
1407
- [S2]: { scale: 'secondary', field: 'highWhisker' },
1442
+ [S]: { scale: 'secondary', field: showOutliers ? 'lowWhisker' : 'minVal' },
1443
+ [S2]: { scale: 'secondary', field: showOutliers ? 'highWhisker' : 'maxVal' },
1408
1444
  tooltip: { signal: tooltip }
1409
1445
  },
1410
1446
  }
@@ -1421,7 +1457,7 @@
1421
1457
  update: {
1422
1458
  [PC]: { scale: 'primary', field: 'label', offset: { scale: 'primary', band: 0.5 } },
1423
1459
  [axesConfig.primary.range]: { scale: 'primary', band: 0.25 },
1424
- [S]: { scale: 'secondary', field: 'lowWhisker' },
1460
+ [S]: { scale: 'secondary', field: showOutliers ? 'lowWhisker' : 'minVal' },
1425
1461
  tooltip: { signal: tooltip }
1426
1462
  }
1427
1463
  }
@@ -1438,7 +1474,7 @@
1438
1474
  update: {
1439
1475
  [PC]: { scale: 'primary', field: 'label', offset: { scale: 'primary', band: 0.5 } },
1440
1476
  [axesConfig.primary.range]: { scale: 'primary', band: 0.25 },
1441
- [S]: { scale: 'secondary', field: 'highWhisker' },
1477
+ [S]: { scale: 'secondary', field: showOutliers ? 'highWhisker' : 'maxVal' },
1442
1478
  tooltip: { signal: tooltip }
1443
1479
  }
1444
1480
  }
@@ -1534,7 +1570,7 @@
1534
1570
  },
1535
1571
  {
1536
1572
  name: 'secondary',
1537
- type: 'linear',
1573
+ ...(axesConfig.secondary.name === 'x' ? xAxisType : yAxisType),
1538
1574
  range: axesConfig.secondary.range,
1539
1575
  nice: true, zero: false,
1540
1576
  domain: [{ signal: 'minValue' },{ signal: 'maxValue' }],
@@ -1572,6 +1608,7 @@
1572
1608
  axes,
1573
1609
  marks,
1574
1610
  onExit: defaultImageReturn,
1611
+ config: chartFontConfig,
1575
1612
  };
1576
1613
  }
1577
1614
 
@@ -1585,6 +1622,7 @@
1585
1622
  const width = globalOptions['width'];
1586
1623
  const height = globalOptions['height'];
1587
1624
  const background = getColorOrDefault(globalOptions['backgroundColor'], 'transparent');
1625
+ const yAxisType = globalOptions['y-axis-type'];
1588
1626
 
1589
1627
  const data = [
1590
1628
  {
@@ -1714,12 +1752,14 @@
1714
1752
  name: 'binScale',
1715
1753
  type: 'linear',
1716
1754
  range: 'width',
1717
- domain: { data: 'rawTable', field: 'bin1' }
1755
+ zero: false,
1756
+ domain: { data: 'rawTable', fields: ['bin0', 'bin1'] }
1718
1757
  },
1719
1758
  {
1720
1759
  name: 'countScale',
1721
- type: 'linear',
1722
1760
  range: 'height',
1761
+ ...yAxisType,
1762
+ zero: true,
1723
1763
  domain: { data: 'rawTable', field: 'y1' }
1724
1764
  }
1725
1765
  ];
@@ -1751,6 +1791,7 @@
1751
1791
  axes,
1752
1792
  marks,
1753
1793
  onExit: defaultImageReturn,
1794
+ config: chartFontConfig,
1754
1795
  };
1755
1796
  }
1756
1797
 
@@ -1768,6 +1809,9 @@
1768
1809
  const height = globalOptions['height'];
1769
1810
  const xAxisLabel = globalOptions['x-axis'];
1770
1811
  const yAxisLabel = globalOptions['y-axis'];
1812
+ const xMinValue = getNumOrDefault(globalOptions['x-min'], undefined);
1813
+ const xMaxValue = getNumOrDefault(globalOptions['x-max'], undefined);
1814
+ const yAxisType = globalOptions['y-axis-type'];
1771
1815
  const background = getColorOrDefault(globalOptions['backgroundColor'], 'transparent');
1772
1816
 
1773
1817
  const data = [
@@ -1819,18 +1863,23 @@
1819
1863
  { name: 'binSize', update: 'invert("binScale", dotSize)' },
1820
1864
  { name: 'actualDotSize', update: 'scale("dotScale", 0) - scale("dotScale", 1)' },
1821
1865
  { name: 'headspace', value: '0.25' },
1822
- { name: 'wrapMaxY', update: 'floor(domain("dotScale")[1] * (1 - headspace))' }
1866
+ { name: 'wrapMaxY', update: 'floor(domain("dotScale")[1] * (1 - headspace))' },
1867
+ { name: 'xMinValue', value: xMinValue },
1868
+ { name: 'xMaxValue', value: xMaxValue },
1823
1869
  ];
1824
1870
  const scales = [
1825
1871
  {
1826
1872
  name: 'binScale',
1827
1873
  type: 'linear',
1874
+ zero: false,
1828
1875
  range: { signal: '[0, width - dotSize / 2]' },
1829
- domain: { data: 'rawTable', field: 'value' }
1876
+ domain: { data: 'rawTable', field: 'value' },
1877
+ domainMin: { signal: 'xMinValue' }, domainMax: { signal: 'xMaxValue' },
1830
1878
  },
1831
1879
  {
1832
1880
  name: 'dotScale',
1833
- type: 'linear',
1881
+ ...yAxisType,
1882
+ zero: true,
1834
1883
  range: { signal: '[height, dotSize / 2]' },
1835
1884
  domain: { signal: '[0, floor(height / dotSize)]' }
1836
1885
  }
@@ -1911,6 +1960,7 @@
1911
1960
  axes,
1912
1961
  marks,
1913
1962
  onExit: defaultImageReturn,
1963
+ config: chartFontConfig,
1914
1964
  };
1915
1965
  }
1916
1966
 
@@ -1926,6 +1976,7 @@
1926
1976
  const height = globalOptions['height'];
1927
1977
  const xAxisLabel = globalOptions['x-axis'];
1928
1978
  const yAxisLabel = globalOptions['y-axis'];
1979
+ const yAxisType = globalOptions['y-axis-type'];
1929
1980
  const background = getColorOrDefault(globalOptions['backgroundColor'], 'transparent');
1930
1981
 
1931
1982
 
@@ -1958,8 +2009,8 @@
1958
2009
  },
1959
2010
  {
1960
2011
  name: 'secondary',
1961
- type: 'linear',
1962
2012
  range: 'height',
2013
+ ...yAxisType,
1963
2014
  nice: true, zero: true,
1964
2015
  domain: { data: 'bars', field: 'count' }
1965
2016
  }
@@ -2028,6 +2079,7 @@
2028
2079
  axes,
2029
2080
  marks,
2030
2081
  onExit: defaultImageReturn,
2082
+ config: chartFontConfig,
2031
2083
  };
2032
2084
  }
2033
2085
 
@@ -2107,6 +2159,8 @@
2107
2159
  function scatterPlot(globalOptions, rawData, config) {
2108
2160
  const xAxisLabel = globalOptions['x-axis'];
2109
2161
  const yAxisLabel = globalOptions['y-axis'];
2162
+ const xAxisType = globalOptions['x-axis-type'];
2163
+ const yAxisType = globalOptions['y-axis-type'];
2110
2164
  const prefix = config.prefix || ''
2111
2165
  const defaultColor = config.defaultColor || default_colors[0];
2112
2166
  const color = getColorOrDefault(get(rawData, 'color'), defaultColor);
@@ -2122,43 +2176,57 @@
2122
2176
  const trendlineWidth = toFixnum(get(rawData, 'trendlineWidth'));
2123
2177
  const trendlineOpacity = toFixnum(get(rawData, 'trendlineOpacity'));
2124
2178
  const trendlineDegree = toFixnum(get(rawData, 'trendlineDegree'));
2125
- const xMinValue = getNumOrDefault(globalOptions['x-min'], undefined);
2126
- const xMaxValue = getNumOrDefault(globalOptions['x-max'], undefined);
2127
2179
  const yMinValue = getNumOrDefault(globalOptions['y-min'], undefined);
2128
2180
  const yMaxValue = getNumOrDefault(globalOptions['y-max'], undefined);
2181
+ const imageScaleFactorX = autosizeImage ? '-datum.imageWidth' : -pointSize;
2182
+ const imageScaleFactorY = autosizeImage ? '-datum.imageHeight' : -pointSize;
2129
2183
 
2130
2184
  const points = RUNTIME.ffi.toArray(get(rawData, 'ps'));
2131
-
2185
+ const pointValues = points.map((p) => ({
2186
+ label: get(p, 'label'),
2187
+ x: toFixnum(get(p, 'x')),
2188
+ y: toFixnum(get(p, 'y')),
2189
+ image: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2190
+ none: () => undefined,
2191
+ some: (opaqueImg) => imageToCanvas(opaqueImg.val)
2192
+ }),
2193
+ imageWidth: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2194
+ none: () => undefined,
2195
+ some: (opaqueImg) => opaqueImg.val.getWidth()
2196
+ }),
2197
+ imageHeight: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2198
+ none: () => undefined,
2199
+ some: (opaqueImg) => opaqueImg.val.getHeight()
2200
+ }),
2201
+ imageOffsetX: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2202
+ none: () => undefined,
2203
+ some: (opaqueImg) => opaqueImg.val.getPinholeX() / opaqueImg.val.getWidth()
2204
+ }),
2205
+ imageOffsetY: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2206
+ none: () => undefined,
2207
+ some: (opaqueImg) => opaqueImg.val.getPinholeY() / opaqueImg.val.getHeight()
2208
+ }),
2209
+ }));
2132
2210
  const data = [
2133
2211
  {
2134
2212
  name: `${prefix}rawTable`,
2135
- values: points.map((p) => ({
2136
- label: get(p, 'label'),
2137
- x: toFixnum(get(p, 'x')),
2138
- y: toFixnum(get(p, 'y')),
2139
- image: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2140
- none: () => undefined,
2141
- some: (opaqueImg) => imageToCanvas(opaqueImg.val)
2142
- }),
2143
- imageWidth: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2144
- none: () => undefined,
2145
- some: (opaqueImg) => opaqueImg.val.getWidth()
2146
- }),
2147
- imageHeight: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2148
- none: () => undefined,
2149
- some: (opaqueImg) => opaqueImg.val.getHeight()
2150
- }),
2151
- imageOffsetX: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2152
- none: () => undefined,
2153
- some: (opaqueImg) => opaqueImg.val.getPinholeX() / opaqueImg.val.getWidth()
2154
- }),
2155
- imageOffsetY: cases(RUNTIME.ffi.isOption, 'Option', get(p, 'image'), {
2156
- none: () => undefined,
2157
- some: (opaqueImg) => opaqueImg.val.getPinholeY() / opaqueImg.val.getHeight()
2158
- }),
2159
- })),
2213
+ values: pointValues,
2160
2214
  transform: []
2161
2215
  },
2216
+ {
2217
+ name: `${prefix}tableMarkExtents`,
2218
+ source: `${prefix}rawTable`,
2219
+ transform: [
2220
+ { type: 'formula', as: 'left',
2221
+ expr: `datum.x + (isValid(datum.image) ? datum.imageOffsetX * ${imageScaleFactorX}: ${-pointSize / 2}) / ${prefix}rawDomainUnitInPx` },
2222
+ { type: 'formula', as: 'right',
2223
+ expr: `datum.x + (isValid(datum.image) ? (datum.imageOffsetX * ${imageScaleFactorY} - datum.imageWidth) : ${pointSize}) / ${prefix}rawDomainUnitInPx` },
2224
+ { type: 'formula', as: 'top',
2225
+ expr: `datum.y + (isValid(datum.image) ? datum.imageOffsetY * ${imageScaleFactorY} : ${-pointSize / 2}) / ${prefix}rawRangeUnitInPx` },
2226
+ { type: 'formula', as: 'bot',
2227
+ expr: `datum.y + (isValid(datum.image) ? (datum.imageOffsetY * ${imageScaleFactorY} - datum.imageHeight) : ${pointSize}) / ${prefix}rawRangeUnitInPx` },
2228
+ ],
2229
+ },
2162
2230
  {
2163
2231
  name: `${prefix}table`,
2164
2232
  source: `${prefix}rawTable`,
@@ -2171,22 +2239,87 @@
2171
2239
  },
2172
2240
  ];
2173
2241
 
2174
- const domain = computeDomain(data[0].values.map((v) => v.x));
2175
-
2242
+ // these are measured in pixels
2243
+ let imageOverhangX, imageOverhangY;
2244
+ const imageWidths = pointValues.map((v) => v.imageWidth).filter((v) => v !== undefined);
2245
+ const imageHeights = pointValues.map((v) => v.imageHeight).filter((v) => v !== undefined);
2246
+ if (imageWidths.length === 0 || autosizeImage) {
2247
+ imageOverhangX = pointSize;
2248
+ imageOverhangY = pointSize;
2249
+ } else {
2250
+ let temp;
2251
+ temp = computeDomain(imageWidths);
2252
+ imageOverhangX = temp[1] - temp[0];
2253
+ temp = computeDomain(imageHeights);
2254
+ imageOverhangY = temp[1] - temp[0];
2255
+ }
2256
+ let windowWidth = toFixnum(globalOptions['width']);
2257
+ let windowHeight = toFixnum(globalOptions['height']);
2258
+
2259
+ const rawDomain = computeDomain(pointValues.map((v) => v.x));
2260
+ const rawRange = computeDomain(pointValues.map((v) => v.y));
2261
+ const reducedWidth = windowWidth - imageOverhangX;
2262
+ const reducedHeight = windowHeight - imageOverhangY;
2263
+ const rawDomainUnitInPx = reducedWidth / (rawDomain[1] - rawDomain[0]);
2264
+ const rawRangeUnitInPx = reducedHeight / (rawRange[1] - rawRange[0]);
2265
+ const domain = computeDomain(pointValues.map((v) => {
2266
+ let left, right;
2267
+ if (!autosizeImage || !v.image) {
2268
+ let halfPointSize = pointSize / 2;
2269
+ let inDomainHalfPointSize = halfPointSize / rawDomainUnitInPx;
2270
+ return [v.x - inDomainHalfPointSize, v.x + inDomainHalfPointSize];
2271
+ }
2272
+ left = v.x - v.imageOffsetX * v.imageWidth / rawDomainUnitInPx;
2273
+ right = left + v.imageWidth / rawDomainUnitInPx;
2274
+ return [left, right];
2275
+ }).flat());
2276
+ // console.log("Raw domain", computeDomain(pointValues.map((v) => v.x)), "Point size", pointSize);
2277
+ // console.log({reducedWidth, imageOverhangX, rawDomainUnitInPx});
2278
+ // console.log("Extended domain", domain);
2279
+
2280
+ const range = computeDomain(pointValues.map((v) => {
2281
+ let top, bot;
2282
+ if (!autosizeImage || !v.image) {
2283
+ let halfPointSize = pointSize / 2;
2284
+ let inDomainHalfPointSize = halfPointSize / rawDomainUnitInPx;
2285
+ return [v.y - inDomainHalfPointSize, v.y + inDomainHalfPointSize];
2286
+ }
2287
+ top = v.y + v.imageOffsetY * v.imageHeight / rawDomainUnitInPx;
2288
+ bot = top - v.imageHeight / rawDomainUnitInPx;
2289
+ return [top, bot];
2290
+ }).flat());
2291
+ // console.log("Raw range", computeDomain(pointValues.map((v) => v.y)), "Point size", pointSize);
2292
+ // console.log("Extended range", range);
2293
+
2294
+ function unionExtents(...names) {
2295
+ const names0 = names.map((s) => `${s}[0]`);
2296
+ const names1 = names.map((s) => `${s}[1]`);
2297
+ return `[min(${names0.join(',')}), max(${names1.join(',')})]`
2298
+ }
2176
2299
  const signals = [
2177
- { name: `${prefix}extentX`, update: `extent(pluck(data("${prefix}rawTable"), "x"))` },
2178
- { name: `${prefix}extentY`, update: `extent(pluck(data("${prefix}rawTable"), "y"))` },
2300
+ { name: `${prefix}reducedWidth`, update: `width - ${imageOverhangX}` },
2301
+ { name: `${prefix}reducedHeight`, update: `height - ${imageOverhangY}` },
2302
+ { name: `${prefix}rawDomainUnitInPx`, update: `${prefix}reducedWidth / ${rawDomain[1] - rawDomain[0]}` },
2303
+ { name: `${prefix}rawRangeUnitInPx`, update: `${prefix}reducedHeight / ${rawRange[1] - rawRange[0]}` },
2304
+ { name: `${prefix}extentLeft`, update: `extent(pluck(data("${prefix}tableMarkExtents"), "left"))` },
2305
+ { name: `${prefix}extentRight`, update: `extent(pluck(data("${prefix}tableMarkExtents"), "right"))` },
2306
+ { name: `${prefix}extentTop`, update: `extent(pluck(data("${prefix}tableMarkExtents"), "top"))` },
2307
+ { name: `${prefix}extentBot`, update: `extent(pluck(data("${prefix}tableMarkExtents"), "bot"))` },
2308
+ { name: `${prefix}extentX`, update: unionExtents(`${prefix}extentLeft`, `${prefix}extentRight`) },
2309
+ { name: `${prefix}extentY`, update: unionExtents(`${prefix}extentTop`, `${prefix}extentBot`) },
2179
2310
  ];
2180
2311
  const scales = [
2181
2312
  { name: `${prefix}xscale`,
2182
- type: 'linear',
2313
+ ...xAxisType,
2183
2314
  domain: { signal: `${prefix}extentX` },
2184
2315
  range: 'width',
2316
+ zero: false,
2185
2317
  nice: true },
2186
2318
  { name: `${prefix}yscale`,
2187
- type: 'linear',
2319
+ ...yAxisType,
2188
2320
  domain: { signal: `${prefix}extentY` },
2189
2321
  range: 'height',
2322
+ zero: false,
2190
2323
  nice: true }
2191
2324
  ];
2192
2325
  const marks = [];
@@ -2290,8 +2423,6 @@
2290
2423
  signal: `{ title: "${legend}", Label: datum.label, x: datum.x, y: datum.y }` },
2291
2424
  { signal: `{ title: "${legend}", x: datum.x, y: datum.y }` },
2292
2425
  ];
2293
- const imageScaleFactorX = autosizeImage ? '-datum.imageWidth' : -pointSize;
2294
- const imageScaleFactorY = autosizeImage ? '-datum.imageHeight' : -pointSize;
2295
2426
  marks.push({
2296
2427
  type: 'image',
2297
2428
  from: { data: `${prefix}images` },
@@ -2447,6 +2578,8 @@
2447
2578
  function intervalPlot(globalOptions, rawData, config) {
2448
2579
  const xAxisLabel = globalOptions['x-axis'];
2449
2580
  const yAxisLabel = globalOptions['y-axis'];
2581
+ const xAxisType = globalOptions['x-axis-type'];
2582
+ const yAxisType = globalOptions['y-axis-type'];
2450
2583
  const legend = get(rawData, 'legend') || config.legend;
2451
2584
  const prefix = config.prefix || ''
2452
2585
  const defaultColor = config.defaultColor || default_colors[0];
@@ -2480,7 +2613,7 @@
2480
2613
  }),
2481
2614
  })),
2482
2615
  transform: [
2483
- { type: 'formula', as: 'yprime', expr: 'datum.y + datum.delta' }
2616
+ { type: 'formula', as: 'yprime', expr: 'datum.y - datum.delta' }
2484
2617
  ]
2485
2618
  },
2486
2619
  {
@@ -2516,15 +2649,17 @@
2516
2649
 
2517
2650
  const scales = [
2518
2651
  { name: `${prefix}xscale`,
2519
- type: 'linear',
2652
+ ...xAxisType,
2520
2653
  domain: { signal: `${prefix}extentX` },
2521
2654
  range: 'width',
2522
- nice: false },
2655
+ nice: false,
2656
+ zero: false },
2523
2657
  { name: `${prefix}yscale`,
2524
- type: 'linear',
2658
+ ...yAxisType,
2525
2659
  domain: { signal: `${prefix}extentY` },
2526
2660
  range: 'height',
2527
- nice: false }
2661
+ nice: false,
2662
+ zero: false }
2528
2663
  ];
2529
2664
  const tooltip = {
2530
2665
  signal: `{ title: datum.label, x: datum.x, y: datum.y, ŷ: datum.yprime, 'y - ŷ': datum.delta }`
@@ -2663,6 +2798,8 @@
2663
2798
  const domain = config.domain;
2664
2799
  const xMinValue = domain[0];
2665
2800
  const xMaxValue = domain[1];
2801
+ const xAxisType = globalOptions['x-axis-type'];
2802
+ const yAxisType = globalOptions['y-axis-type'];
2666
2803
 
2667
2804
  const fraction = (xMaxValue - xMinValue) / (numSamples - 1);
2668
2805
 
@@ -2676,10 +2813,11 @@
2676
2813
  // from the surrounding chart context
2677
2814
  {
2678
2815
  name: `${prefix}yscale`,
2679
- type: 'linear',
2816
+ ...yAxisType,
2680
2817
  domain: { signal: `${prefix}extentY` },
2681
2818
  range: 'height',
2682
- nice: true
2819
+ nice: true,
2820
+ zero: false
2683
2821
  }
2684
2822
  ];
2685
2823
  const tooltip = {
@@ -2775,6 +2913,8 @@
2775
2913
  const numSamples = toFixnum(globalOptions['num-samples']);
2776
2914
  const xAxisLabel = globalOptions['x-axis'];
2777
2915
  const yAxisLabel = globalOptions['y-axis'];
2916
+ const xAxisType = globalOptions['x-axis-type'];
2917
+ const yAxisType = globalOptions['y-axis-type'];
2778
2918
  const width = toFixnum(globalOptions['width']);
2779
2919
  const height = toFixnum(globalOptions['height']);
2780
2920
  const background = getColorOrDefault(globalOptions['backgroundColor'], 'transparent');
@@ -2795,10 +2935,10 @@
2795
2935
  { name: 'yscaleSignal', update: unionScaleSignal(scales.filter((s) => s.name.endsWith('yscale'))) }
2796
2936
  );
2797
2937
  scales.push(
2798
- { name: 'xscale', domain: { signal: 'xscaleSignal' }, range: 'width',
2799
- domainMin: { signal: 'xMinValue' }, domainMax: { signal: 'xMaxValue' } },
2800
- { name: 'yscale', domain: { signal: 'yscaleSignal' }, range: 'height',
2801
- domainMin: { signal: 'yMinValue' }, domainMax: { signal: 'yMaxValue' } }
2938
+ { name: 'xscale', domain: { signal: 'xscaleSignal' }, range: 'width', ...xAxisType,
2939
+ domainMin: { signal: 'xMinValue' }, domainMax: { signal: 'xMaxValue' }, zero: false },
2940
+ { name: 'yscale', domain: { signal: 'yscaleSignal' }, range: 'height', ...yAxisType,
2941
+ domainMin: { signal: 'yMinValue' }, domainMax: { signal: 'yMaxValue' }, zero: false }
2802
2942
  );
2803
2943
 
2804
2944
  // NOTE: For the axes, we're going to want to put the bar lines at the zeros
@@ -2995,7 +3135,9 @@
2995
3135
  marks,
2996
3136
  legends,
2997
3137
  config: {
3138
+ ...chartFontConfig,
2998
3139
  legend: {
3140
+ ...chartFontConfig.legend,
2999
3141
  orient: 'bottom',
3000
3142
  layout: { bottom: { anchor: 'middle' } },
3001
3143
  }
@@ -3131,10 +3273,16 @@
3131
3273
  }
3132
3274
 
3133
3275
  function renderStaticImage(processed, globalOptions, rawData) {
3276
+ function isImageOrCanvas(v) {
3277
+ if (!v) return false;
3278
+ if (IMAGE.isImage(v)) return true;
3279
+ return canvasLib && canvasLib.Canvas && v instanceof canvasLib.Canvas;
3280
+ }
3281
+ const isVegaString = isTrue(globalOptions['vega']);
3134
3282
  return RUNTIME.pauseStack(restarter => {
3135
3283
  try {
3136
- if (canvasLib && canvasLib.Canvas) {
3137
- console.log(JSON.stringify(processed, (k, v) => (v && (IMAGE.isImage(v) || (v instanceof canvasLib.Canvas))) ? v.ariaText : v, 2));
3284
+ if (isVegaString) {
3285
+ return restarter.resume(JSON.stringify(processed, (k, v) => isImageOrCanvas(v) ? v.ariaText : v, 2));
3138
3286
  }
3139
3287
  const width = toFixnum(globalOptions['width']);
3140
3288
  const height = toFixnum(globalOptions['height']);
@@ -3159,15 +3307,20 @@
3159
3307
  let height = toFixnum(globalOptions['height']);
3160
3308
  const vegaTooltipHandler = new vegaTooltip.Handler({
3161
3309
  formatTooltip: (value, valueToHtml, maxDepth, baseURL) => {
3162
- if (typeof value === 'object') {
3310
+ let ans;
3311
+ if (typeof value === 'object' && value.title instanceof Array) {
3163
3312
  const { title, ...rest } = value;
3164
3313
  if (title instanceof Array) {
3165
3314
  const titleStr = `<h2>${title.map(valueToHtml).join('<br/>')}</h2>`;
3166
3315
  const restStr = vegaTooltip.formatValue(rest, valueToHtml, maxDepth, baseURL);
3167
- return titleStr + restStr;
3316
+ ans = titleStr + restStr;
3168
3317
  }
3318
+ } else {
3319
+ ans = vegaTooltip.formatValue(value, valueToHtml, maxDepth, baseURL);
3169
3320
  }
3170
- return vegaTooltip.formatValue(value, valueToHtml, maxDepth, baseURL);
3321
+ ans = ans.replaceAll("<table>", "<table class=\"pyret-row\">");
3322
+ ans = ans.replaceAll("<td class=\"value\">", "<td class=\"value replTextOutput\"> ");
3323
+ return ans;
3171
3324
  }
3172
3325
  });
3173
3326
  const view = new vega.View(vega.parse(processed), {
@@ -3232,10 +3385,21 @@
3232
3385
  }
3233
3386
 
3234
3387
 
3388
+ function configScale(val) {
3389
+ return cases(get(CHARTSUTIL, "is-AxisType"), "AxisType", val, {
3390
+ 'at-linear': () => ({ type: 'linear' }),
3391
+ 'at-power': (pow) => ({ type: 'pow', exponent: toFixnum(pow) }),
3392
+ 'at-log': (base) => ({ type: 'log', base: toFixnum(base) }),
3393
+ 'at-symlog': (base) => ({ type: 'symlog', constant: toFixnum(base) })
3394
+ });
3395
+ }
3235
3396
  function pyretObjToObj(globalOptions) {
3236
3397
  const ret = {};
3237
3398
  for (const field of RUNTIME.getFields(globalOptions)) {
3238
3399
  ret[field] = get(globalOptions, field);
3400
+ if (get(CHARTSUTIL, 'is-AxisType').app(ret[field])) {
3401
+ ret[field] = configScale(ret[field]);
3402
+ }
3239
3403
  }
3240
3404
  return ret;
3241
3405
  }