From 6b87468a7dafa6c25f10949a3124565c53b2c965 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 13 Mar 2023 13:01:30 -0400 Subject: [PATCH] TypeScript declarations! (#1320) * remove typescript machinery * checkpoint typescript declarations * restore mocha and eslint for typescript * tweak package.json * port tests to TypeScript * fix plural inconsistency * more better types * allow empty interfaces, for now * fix plural test * remove jsdoc annotations * fix test names, again * export all types * remove build * more better types * add missing exports * RenderFunction, strict * more better types * more better transform types * more better types * more better types * still more better types * quantize, quantile scale options * nullish markish * more better types * even more better types * avoid name conflicts with types * still more better types * unstrict * allow cased color scheme names * more better types * more documentation; ScaleDefaults * turn off data type checking * fix remaining TypeScript errors; enable tsc * auto types * stricter lint * better line, area options, and more * more documentation * linearRegression[XY] dense interval * stricter percentile * better bar, rect intervals * more percentiles * more interval types * tree, valueof, column * style, className * comment re. omit interval * autocomplete stack order * z for stack, map, select --- .eslintrc.json | 1 + .github/workflows/node.js.yml | 2 +- .gitignore | 3 - README.md | 444 ---- bundle.js | 2 +- package.json | 34 +- scripts/readme-to-jsdoc.ts | 80 - src/channel.d.ts | 84 + src/channel.js | 10 +- src/color.d.ts | 55 + src/context.d.ts | 6 + src/context.js | 6 +- src/curve.d.ts | 37 + src/{curve.ts => curve.js} | 43 +- src/defined.js | 29 + src/defined.ts | 30 - src/dimensions.d.ts | 14 + src/dimensions.js | 2 +- src/facet.d.ts | 16 + src/facet.js | 6 +- src/format.d.ts | 34 + src/format.js | 44 + src/format.ts | 53 - src/index.d.ts | 56 + src/inset.d.ts | 11 + src/interpolate.d.ts | 9 + src/interval.d.ts | 38 + src/legends.d.ts | 48 + src/legends.js | 7 +- src/legends/ramp.js | 4 +- src/legends/swatches.js | 4 +- src/mark.d.ts | 101 + src/mark.js | 5 +- src/marker.d.ts | 12 + src/{marks => }/marker.js | 13 +- src/marks/area.d.ts | 34 + src/marks/area.js | 7 +- src/marks/arrow.d.ts | 21 + src/marks/arrow.js | 1 - src/marks/auto.d.ts | 28 + src/marks/auto.js | 1 - src/marks/axis.d.ts | 70 + src/marks/axis.js | 5 - src/marks/bar.d.ts | 33 + src/marks/bar.js | 2 - src/marks/box.d.ts | 13 + src/marks/box.js | 2 - src/marks/cell.d.ts | 18 + src/marks/cell.js | 3 - src/marks/contour.d.ts | 18 + src/marks/contour.js | 9 +- src/marks/delaunay.d.ts | 20 + src/marks/delaunay.js | 13 +- src/marks/density.d.ts | 15 + src/marks/density.js | 5 +- src/marks/dot.d.ts | 35 + src/marks/dot.js | 7 +- src/marks/frame.d.ts | 12 + src/marks/frame.js | 1 - src/marks/geo.d.ts | 16 + src/marks/hexgrid.d.ts | 9 + src/marks/hexgrid.js | 3 +- src/marks/image.d.ts | 18 + src/marks/image.js | 1 - src/marks/line.d.ts | 30 + src/marks/line.js | 11 +- src/marks/linearRegression.d.ts | 25 + src/marks/linearRegression.js | 2 - src/marks/link.d.ts | 17 + src/marks/link.js | 9 +- src/marks/raster.d.ts | 54 + src/marks/raster.js | 5 - src/marks/rect.d.ts | 33 + src/marks/rect.js | 3 - src/marks/rule.d.ts | 30 + src/marks/rule.js | 2 - src/marks/text.d.ts | 53 + src/marks/text.js | 3 - src/marks/tick.d.ts | 21 + src/marks/tick.js | 2 - src/marks/tree.d.ts | 15 + src/marks/tree.js | 2 - src/marks/vector.d.ts | 31 + src/marks/vector.js | 8 +- src/math.js | 1 + src/math.ts | 1 - src/memoize.js | 10 + src/memoize.ts | 11 - src/options.d.ts | 8 + src/options.js | 7 +- src/plot.d.ts | 279 +++ src/plot.js | 38 +- src/projection.d.ts | 32 + src/projection.js | 4 +- src/reducer.d.ts | 40 + src/scales.d.ts | 175 ++ src/scales.js | 83 +- src/scales/diverging.js | 30 +- src/scales/ordinal.js | 14 +- src/scales/quantitative.js | 38 +- src/scales/temporal.js | 14 +- src/symbol.d.ts | 19 + src/{symbols.js => symbol.js} | 0 src/transforms/basic.d.ts | 47 + src/transforms/basic.js | 6 - src/transforms/bin.d.ts | 30 + src/transforms/bin.js | 7 +- src/transforms/centroid.d.ts | 10 + src/transforms/centroid.js | 2 - src/transforms/dodge.d.ts | 20 + src/transforms/dodge.js | 6 +- src/transforms/group.d.ts | 18 + src/transforms/group.js | 12 +- src/transforms/hexbin.d.ts | 8 + src/transforms/hexbin.js | 7 +- src/transforms/map.d.ts | 22 + src/transforms/map.js | 3 - src/transforms/normalize.d.ts | 33 + src/transforms/normalize.js | 3 - src/transforms/select.d.ts | 28 + src/transforms/select.js | 7 - src/transforms/stack.d.ts | 52 + src/transforms/stack.js | 6 - src/transforms/tree.d.ts | 15 + src/transforms/tree.js | 2 - src/transforms/window.d.ts | 40 + src/types/isoformat.d.ts | 6 - src/{warnings.ts => warnings.js} | 2 +- .../legends-test.ts => legend-test.js} | 2 +- test/plot.js | 2 +- .../{aapl-bollinger.js => aapl-bollinger.ts} | 6 +- ...apl-candlestick.js => aapl-candlestick.ts} | 4 +- ...change-volume.js => aapl-change-volume.ts} | 4 +- ...close-untyped.js => aapl-close-untyped.ts} | 4 +- test/plots/{aapl-close.js => aapl-close.ts} | 14 +- ...{aapl-fancy-axis.js => aapl-fancy-axis.ts} | 2 +- .../{aapl-monthly.js => aapl-monthly.ts} | 4 +- ...apl-volume-rect.js => aapl-volume-rect.ts} | 4 +- test/plots/{aapl-volume.js => aapl-volume.ts} | 4 +- ...nscombe-quartet.js => anscombe-quartet.ts} | 4 +- test/plots/{armadillo.js => armadillo.ts} | 4 +- test/plots/{aspectRatio.js => aspectRatio.ts} | 0 ...bins-colors.js => athletes-bins-colors.ts} | 4 +- ...tes-birthdays.js => athletes-birthdays.ts} | 4 +- ...ng-height.js => athletes-boxing-height.ts} | 12 +- ...s => athletes-height-weight-bin-stroke.ts} | 4 +- ...t-bin.js => athletes-height-weight-bin.ts} | 4 +- ...t-sex.js => athletes-height-weight-sex.ts} | 4 +- ...ort.js => athletes-height-weight-sport.ts} | 4 +- ...ht-weight.js => athletes-height-weight.ts} | 4 +- ...nationality.js => athletes-nationality.ts} | 4 +- ...{athletes-sample.js => athletes-sample.ts} | 4 +- ...s-sex-weight.js => athletes-sex-weight.ts} | 4 +- .../{athletes-sort.js => athletes-sort.ts} | 8 +- ...tes-sport-sex.js => athletes-sport-sex.ts} | 4 +- ...ort-weight.js => athletes-sport-weight.ts} | 4 +- ...ative.js => athletes-weight-cumulative.ts} | 4 +- ...{athletes-weight.js => athletes-weight.ts} | 4 +- test/plots/{autoplot.js => autoplot.ts} | 94 +- .../{availability.js => availability.ts} | 4 +- test/plots/{axis-labels.js => axis-labels.ts} | 0 ...t-status-race.js => ballot-status-race.ts} | 18 +- test/plots/{band-clip.js => band-clip.ts} | 2 +- test/plots/{beagle.js => beagle.ts} | 4 +- .../{becker-barley.js => becker-barley.ts} | 4 +- test/plots/{bigint.js => bigint.ts} | 0 test/plots/{bin-1m.js => bin-1m.ts} | 0 test/plots/{bin-strings.js => bin-strings.ts} | 2 +- .../{bin-timestamps.js => bin-timestamps.ts} | 2 +- .../{bounding-boxes.js => bounding-boxes.ts} | 4 +- test/plots/{boxplot.js => boxplot.ts} | 2 +- ...ain-direction.js => caltrain-direction.ts} | 4 +- test/plots/{caltrain.js => caltrain.ts} | 4 +- test/plots/{cars-dodge.js => cars-dodge.ts} | 4 +- test/plots/{cars-hexbin.js => cars-hexbin.ts} | 4 +- test/plots/{cars-jitter.js => cars-jitter.ts} | 4 +- test/plots/{cars-mpg.js => cars-mpg.ts} | 4 +- .../{cars-parcoords.js => cars-parcoords.ts} | 8 +- test/plots/{clamp.js => clamp.ts} | 4 +- ...ed-histogram.js => collapsed-histogram.ts} | 2 +- ...ntry-centroids.js => country-centroids.ts} | 4 +- ...aths.js => covid-ihme-projected-deaths.ts} | 4 +- ...mean-war-arrow.js => crimean-war-arrow.ts} | 4 +- ...rimean-war-line.js => crimean-war-line.ts} | 4 +- ...verlapped.js => crimean-war-overlapped.ts} | 4 +- ...-war-stacked.js => crimean-war-stacked.ts} | 4 +- ...5-comfort.js => d3-survey-2015-comfort.ts} | 4 +- ...rvey-2015-why.js => d3-survey-2015-why.ts} | 4 +- .../{d3-survey-2015.js => d3-survey-2015.ts} | 0 .../{darker-dodge.js => darker-dodge.ts} | 4 +- test/plots/{decathlon.js => decathlon.ts} | 4 +- ...iamonds-boxplot.js => diamonds-boxplot.ts} | 4 +- ...e-dots.js => diamonds-carat-price-dots.ts} | 4 +- ...carat-price.js => diamonds-carat-price.ts} | 4 +- ...sampling.js => diamonds-carat-sampling.ts} | 4 +- ...tation-links.js => documentation-links.ts} | 4 +- test/plots/{dodge-rule.js => dodge-rule.ts} | 2 +- ...ge-text-radius.js => dodge-text-radius.ts} | 2 +- test/plots/{dodge-tick.js => dodge-tick.ts} | 2 +- test/plots/{dot-sort.js => dot-sort.ts} | 4 +- ...nloads-ordinal.js => downloads-ordinal.ts} | 4 +- test/plots/{downloads.js => downloads.ts} | 4 +- test/plots/{driving.js => driving.ts} | 4 +- ...ricity-demand.js => electricity-demand.ts} | 2 +- test/plots/{empty-facet.js => empty-facet.ts} | 2 +- .../{empty-legend.js => empty-legend.ts} | 2 +- test/plots/{empty-x.js => empty-x.ts} | 2 +- test/plots/{empty.js => empty.ts} | 2 +- ...rgy-production.js => energy-production.ts} | 4 +- ...l-density-1d.js => faithful-density-1d.ts} | 4 +- ...aithful-density.js => faithful-density.ts} | 4 +- .../{federal-funds.js => federal-funds.ts} | 0 ...{figcaption-html.js => figcaption-html.ts} | 4 +- test/plots/{figcaption.js => figcaption.ts} | 4 +- .../{first-ladies.js => first-ladies.ts} | 4 +- .../{flare-cluster.js => flare-cluster.ts} | 4 +- .../{flare-indent.js => flare-indent.ts} | 4 +- test/plots/{flare-tree.js => flare-tree.ts} | 4 +- ...tball-coverage.js => football-coverage.ts} | 4 +- test/plots/{frame.js => frame.ts} | 0 ...ruit-sales-date.js => fruit-sales-date.ts} | 4 +- test/plots/{fruit-sales.js => fruit-sales.ts} | 4 +- ...unction-contour.js => function-contour.ts} | 2 +- test/plots/{geo-link.js => geo-link.ts} | 0 ...ly-moving.js => gistemp-anomaly-moving.ts} | 4 +- ...nsform.js => gistemp-anomaly-transform.ts} | 4 +- ...{gistemp-anomaly.js => gistemp-anomaly.ts} | 4 +- ...idgeline.js => google-trends-ridgeline.ts} | 4 +- test/plots/{graticule.js => graticule.ts} | 2 +- test/plots/{greek-gods.js => greek-gods.ts} | 2 +- ...choropleth-dx.js => grid-choropleth-dx.ts} | 8 +- ...{grid-choropleth.js => grid-choropleth.ts} | 8 +- .../{grouped-rects.js => grouped-rects.ts} | 2 +- ...-stripes.js => hadcrut-warming-stripes.ts} | 4 +- test/plots/{heatmap.js => heatmap.ts} | 0 .../{hexbin-oranges.js => hexbin-oranges.ts} | 4 +- test/plots/{hexbin-r.js => hexbin-r.ts} | 4 +- .../{hexbin-symbol.js => hexbin-symbol.ts} | 4 +- test/plots/{hexbin-text.js => hexbin-text.ts} | 4 +- .../{hexbin-z-null.js => hexbin-z-null.ts} | 4 +- test/plots/{hexbin-z.js => hexbin-z.ts} | 4 +- test/plots/{hexbin.js => hexbin.ts} | 4 +- ...ordinal.js => high-cardinality-ordinal.ts} | 2 +- test/plots/{href-fill.js => href-fill.ts} | 2 +- test/plots/{ibm-trading.js => ibm-trading.ts} | 4 +- .../{identity-scale.js => identity-scale.ts} | 2 +- ...{image-rendering.js => image-rendering.ts} | 0 test/plots/index.html | 2 +- test/plots/index.js | 309 --- test/plots/index.ts | 308 +++ ...hare.js => industry-unemployment-share.ts} | 4 +- ...eam.js => industry-unemployment-stream.ts} | 4 +- ...rack.js => industry-unemployment-track.ts} | 4 +- ...employment.js => industry-unemployment.ts} | 4 +- .../{infinity-log.js => infinity-log.ts} | 2 +- ...nteger-interval.js => integer-interval.ts} | 2 +- ...day-histogram.js => intraday-histogram.ts} | 4 +- ...earning-poverty.js => learning-poverty.ts} | 4 +- .../{legend-color.js => legend-color.ts} | 4 +- .../{legend-opacity.js => legend-opacity.ts} | 0 .../{legend-symbol.js => legend-symbol.ts} | 0 ...equency-bar.js => letter-frequency-bar.ts} | 4 +- ...ncy-cloud.js => letter-frequency-cloud.ts} | 4 +- ...y-column.js => letter-frequency-column.ts} | 4 +- ...equency-dot.js => letter-frequency-dot.ts} | 4 +- ...llipop.js => letter-frequency-lollipop.ts} | 4 +- ...ncy-wheel.js => letter-frequency-wheel.ts} | 4 +- ...or-projections.js => libor-projections.ts} | 2 +- .../{likert-survey.js => likert-survey.ts} | 11 +- ...sion-cars.js => linear-regression-cars.ts} | 4 +- ...-mtcars.js => linear-regression-mtcars.ts} | 4 +- ...guins.js => linear-regression-penguins.ts} | 4 +- .../{log-degenerate.js => log-degenerate.ts} | 2 +- test/plots/{long-labels.js => long-labels.ts} | 0 .../{markov-chain.js => markov-chain.ts} | 4 +- ...y-change.js => metro-inequality-change.ts} | 4 +- ...etro-inequality.js => metro-inequality.ts} | 4 +- ...ght.js => metro-unemployment-highlight.ts} | 4 +- ...t-index.js => metro-unemployment-index.ts} | 4 +- ...moving.js => metro-unemployment-moving.ts} | 4 +- ...ize.js => metro-unemployment-normalize.ts} | 4 +- ...ine.js => metro-unemployment-ridgeline.ts} | 4 +- ...t-slope.js => metro-unemployment-slope.ts} | 4 +- ...stroke.js => metro-unemployment-stroke.ts} | 4 +- ...-unemployment.js => metro-unemployment.ts} | 4 +- ...y-dick-faceted.js => moby-dick-faceted.ts} | 2 +- ...uency.js => moby-dick-letter-frequency.ts} | 2 +- ...ter-pairs.js => moby-dick-letter-pairs.ts} | 2 +- ...sition.js => moby-dick-letter-position.ts} | 2 +- ...=> moby-dick-letter-relative-frequency.ts} | 2 +- test/plots/{moby-dick.js => moby-dick.ts} | 2 +- .../{morley-boxplot.js => morley-boxplot.ts} | 4 +- ...-by-genre.js => movies-profit-by-genre.ts} | 4 +- ...-by-genre.js => movies-rating-by-genre.ts} | 4 +- ...ation-table.js => multiplication-table.ts} | 2 +- .../{music-revenue.js => music-revenue.ts} | 12 +- .../{npm-versions.js => npm-versions.ts} | 2 +- test/plots/{ordinal-bar.js => ordinal-bar.ts} | 2 +- ...guin-annotated.js => penguin-annotated.ts} | 4 +- ...ulmen-array.js => penguin-culmen-array.ts} | 4 +- ...esh.js => penguin-culmen-delaunay-mesh.ts} | 4 +- ....js => penguin-culmen-delaunay-species.ts} | 4 +- ...delaunay.js => penguin-culmen-delaunay.ts} | 4 +- ...-facet.js => penguin-culmen-mark-facet.ts} | 4 +- ...n-voronoi.js => penguin-culmen-voronoi.ts} | 4 +- .../{penguin-culmen.js => penguin-culmen.ts} | 4 +- ...ensity-fill.js => penguin-density-fill.ts} | 4 +- ...guin-density-z.js => penguin-density-z.ts} | 4 +- ...{penguin-density.js => penguin-density.ts} | 4 +- ...odge-hexbin.js => penguin-dodge-hexbin.ts} | 4 +- ...ge-voronoi.js => penguin-dodge-voronoi.ts} | 4 +- .../{penguin-dodge.js => penguin-dodge.ts} | 4 +- ...ated-x.js => penguin-facet-annotated-x.ts} | 4 +- ...nnotated.js => penguin-facet-annotated.ts} | 4 +- ...ity.js => penguin-facet-dodge-identity.ts} | 4 +- ...sland.js => penguin-facet-dodge-island.ts} | 4 +- ...ymbol.js => penguin-facet-dodge-symbol.ts} | 4 +- ...-facet-dodge.js => penguin-facet-dodge.ts} | 4 +- ...it.js => penguin-hexbin-color-explicit.ts} | 2 +- ...d-unknown.js => penguin-island-unknown.ts} | 4 +- ...species.js => penguin-mass-sex-species.ts} | 4 +- ...enguin-mass-sex.js => penguin-mass-sex.ts} | 4 +- ...ass-species.js => penguin-mass-species.ts} | 4 +- .../{penguin-mass.js => penguin-mass.ts} | 4 +- ...unknown.js => penguin-quantile-unknown.ts} | 4 +- ....js => penguin-sex-mass-culmen-species.ts} | 4 +- test/plots/{penguin-sex.js => penguin-sex.ts} | 4 +- ...ize-symbols.js => penguin-size-symbols.ts} | 4 +- ...heysson.js => penguin-species-cheysson.ts} | 4 +- ...radient.js => penguin-species-gradient.ts} | 4 +- ...cies-group.js => penguin-species-group.ts} | 4 +- ....js => penguin-species-island-relative.ts} | 4 +- ...d-sex.js => penguin-species-island-sex.ts} | 4 +- ...es-island.js => penguin-species-island.ts} | 4 +- ...in-voronoi-1d.js => penguin-voronoi-1d.ts} | 4 +- test/plots/{polylinear.js => polylinear.ts} | 4 +- ...-latitude.js => population-by-latitude.ts} | 6 +- ...ongitude.js => population-by-longitude.ts} | 6 +- ...eed-edges.js => projection-bleed-edges.ts} | 4 +- ...d-edges2.js => projection-bleed-edges2.ts} | 4 +- ...rame.js => projection-clip-angle-frame.ts} | 4 +- ...clip-angle.js => projection-clip-angle.ts} | 4 +- ...erghaus.js => projection-clip-berghaus.ts} | 4 +- ...rctica.js => projection-fit-antarctica.ts} | 4 +- ...in1953.js => projection-fit-bertin1953.ts} | 4 +- ...n-fit-conic.js => projection-fit-conic.ts} | 4 +- ...identity.js => projection-fit-identity.ts} | 2 +- ...-albers.js => projection-fit-us-albers.ts} | 4 +- ...-albers.js => projection-height-albers.ts} | 4 +- ...th.js => projection-height-equal-earth.ts} | 4 +- ...metry.js => projection-height-geometry.ts} | 4 +- ...cator.js => projection-height-mercator.ts} | 4 +- ...c.js => projection-height-orthographic.ts} | 4 +- .../{random-bins-xy.js => random-bins-xy.ts} | 2 +- test/plots/{random-bins.js => random-bins.ts} | 2 +- ...{random-quantile.js => random-quantile.ts} | 2 +- test/plots/{random-walk.js => random-walk.ts} | 4 +- test/plots/{raster-ca55.js => raster-ca55.ts} | 8 +- ...{raster-penguins.js => raster-penguins.ts} | 2 +- .../{raster-vapor.js => raster-vapor.ts} | 0 .../{raster-walmart.js => raster-walmart.ts} | 6 +- test/plots/{rect-band.js => rect-band.ts} | 2 +- ...ty.js => seattle-precipitation-density.ts} | 4 +- ...-rule.js => seattle-precipitation-rule.ts} | 4 +- ...on-sum.js => seattle-precipitation-sum.ts} | 4 +- ...de.js => seattle-temperature-amplitude.ts} | 4 +- ...re-band.js => seattle-temperature-band.ts} | 4 +- ...re-cell.js => seattle-temperature-cell.ts} | 4 +- ...{sf-covid-deaths.js => sf-covid-deaths.ts} | 4 +- ...nd-area.js => sf-temperature-band-area.ts} | 4 +- ...erature-band.js => sf-temperature-band.ts} | 4 +- .../{shorthand-area.js => shorthand-area.ts} | 2 +- ...{shorthand-areaY.js => shorthand-areaY.ts} | 2 +- .../{shorthand-barY.js => shorthand-barY.ts} | 2 +- ...hand-binRectY.js => shorthand-binRectY.ts} | 2 +- .../{shorthand-boxX.js => shorthand-boxX.ts} | 2 +- .../{shorthand-cell.js => shorthand-cell.ts} | 2 +- ...{shorthand-cellX.js => shorthand-cellX.ts} | 2 +- .../{shorthand-dot.js => shorthand-dot.ts} | 2 +- .../{shorthand-dotX.js => shorthand-dotX.ts} | 2 +- ...nd-groupBarY.js => shorthand-groupBarY.ts} | 2 +- .../{shorthand-line.js => shorthand-line.ts} | 2 +- ...{shorthand-lineY.js => shorthand-lineY.ts} | 2 +- ...{shorthand-rectY.js => shorthand-rectY.ts} | 2 +- ...{shorthand-ruleX.js => shorthand-ruleX.ts} | 2 +- .../{shorthand-text.js => shorthand-text.ts} | 2 +- ...{shorthand-textX.js => shorthand-textX.ts} | 2 +- ...{shorthand-tickX.js => shorthand-tickX.ts} | 2 +- ...horthand-vector.js => shorthand-vector.ts} | 2 +- ...rthand-vectorX.js => shorthand-vectorX.ts} | 2 +- ...tings-dots.js => simpsons-ratings-dots.ts} | 4 +- ...impsons-ratings.js => simpsons-ratings.ts} | 4 +- .../{simpsons-views.js => simpsons-views.ts} | 4 +- ...ingle-value-bar.js => single-value-bar.ts} | 2 +- ...ingle-value-bin.js => single-value-bin.ts} | 2 +- ...tware-versions.js => software-versions.ts} | 6 +- test/plots/{sparse-cell.js => sparse-cell.ts} | 4 +- test/plots/{stacked-bar.js => stacked-bar.ts} | 2 +- .../{stacked-rect.js => stacked-rect.ts} | 2 +- ...rgazers-binned.js => stargazers-binned.ts} | 4 +- ...ly-group.js => stargazers-hourly-group.ts} | 4 +- ...rgazers-hourly.js => stargazers-hourly.ts} | 4 +- test/plots/{stargazers.js => stargazers.ts} | 4 +- .../{stocks-index.js => stocks-index.ts} | 2 +- .../{text-overflow.js => text-overflow.ts} | 8 +- ...-just-to-say.js => this-is-just-to-say.ts} | 2 +- ...{traffic-horizon.js => traffic-horizon.ts} | 4 +- ...-covid-drop.js => travelers-covid-drop.ts} | 4 +- ...er-year.js => travelers-year-over-year.ts} | 4 +- ...erence.js => uniform-random-difference.ts} | 2 +- ...ntyped-date-bin.js => untyped-date-bin.ts} | 4 +- ...t.js => us-congress-age-color-explicit.ts} | 4 +- ...ge-gender.js => us-congress-age-gender.ts} | 4 +- ....js => us-congress-age-symbol-explicit.ts} | 4 +- ...{us-congress-age.js => us-congress-age.ts} | 4 +- ...-choropleth.js => us-county-choropleth.ts} | 6 +- ...s-county-spikes.js => us-county-spikes.ts} | 4 +- ...ots.js => us-population-state-age-dots.ts} | 4 +- ...tate-age.js => us-population-state-age.ts} | 4 +- ...s.js => us-president-favorability-dots.ts} | 4 +- test/plots/us-president-gallery.js | 24 - test/plots/us-president-gallery.ts | 24 + ...20.js => us-presidential-election-2020.ts} | 4 +- ...s => us-presidential-election-map-2020.ts} | 8 +- ...16.js => us-presidential-forecast-2016.ts} | 4 +- ...{us-retail-sales.js => us-retail-sales.ts} | 7 +- ...oronoi.js => us-state-capitals-voronoi.ts} | 6 +- ...state-capitals.js => us-state-capitals.ts} | 6 +- ...hange.js => us-state-population-change.ts} | 4 +- .../{vector-field.js => vector-field.ts} | 2 +- .../{vector-frame.js => vector-frame.ts} | 2 +- test/plots/{volcano.js => volcano.ts} | 6 +- ...almarts-decades.js => walmarts-decades.ts} | 6 +- ...ted.js => walmarts-density-unprojected.ts} | 4 +- ...almarts-density.js => walmarts-density.ts} | 6 +- test/plots/{walmarts.js => walmarts.ts} | 6 +- ...h-britain-bar.js => wealth-britain-bar.ts} | 4 +- ...t.js => wealth-britain-proportion-plot.ts} | 4 +- test/plots/{word-cloud.js => word-cloud.ts} | 2 +- ...-moby-dick.js => word-length-moby-dick.ts} | 2 +- ...requests-dot.js => yearly-requests-dot.ts} | 2 +- ...quests-line.js => yearly-requests-line.ts} | 2 +- ...{yearly-requests.js => yearly-requests.ts} | 2 +- tsconfig.json | 18 +- vite.config.js | 26 +- yarn.lock | 1977 +---------------- 446 files changed, 3385 insertions(+), 3842 deletions(-) delete mode 100755 scripts/readme-to-jsdoc.ts create mode 100644 src/channel.d.ts create mode 100644 src/color.d.ts create mode 100644 src/context.d.ts create mode 100644 src/curve.d.ts rename src/{curve.ts => curve.js} (64%) create mode 100644 src/defined.js delete mode 100644 src/defined.ts create mode 100644 src/dimensions.d.ts create mode 100644 src/facet.d.ts create mode 100644 src/format.d.ts create mode 100644 src/format.js delete mode 100644 src/format.ts create mode 100644 src/index.d.ts create mode 100644 src/inset.d.ts create mode 100644 src/interpolate.d.ts create mode 100644 src/interval.d.ts create mode 100644 src/legends.d.ts create mode 100644 src/mark.d.ts create mode 100644 src/marker.d.ts rename src/{marks => }/marker.js (88%) create mode 100644 src/marks/area.d.ts create mode 100644 src/marks/arrow.d.ts create mode 100644 src/marks/auto.d.ts create mode 100644 src/marks/axis.d.ts create mode 100644 src/marks/bar.d.ts create mode 100644 src/marks/box.d.ts create mode 100644 src/marks/cell.d.ts create mode 100644 src/marks/contour.d.ts create mode 100644 src/marks/delaunay.d.ts create mode 100644 src/marks/density.d.ts create mode 100644 src/marks/dot.d.ts create mode 100644 src/marks/frame.d.ts create mode 100644 src/marks/geo.d.ts create mode 100644 src/marks/hexgrid.d.ts create mode 100644 src/marks/image.d.ts create mode 100644 src/marks/line.d.ts create mode 100644 src/marks/linearRegression.d.ts create mode 100644 src/marks/link.d.ts create mode 100644 src/marks/raster.d.ts create mode 100644 src/marks/rect.d.ts create mode 100644 src/marks/rule.d.ts create mode 100644 src/marks/text.d.ts create mode 100644 src/marks/tick.d.ts create mode 100644 src/marks/tree.d.ts create mode 100644 src/marks/vector.d.ts create mode 100644 src/math.js delete mode 100644 src/math.ts create mode 100644 src/memoize.js delete mode 100644 src/memoize.ts create mode 100644 src/options.d.ts create mode 100644 src/plot.d.ts create mode 100644 src/projection.d.ts create mode 100644 src/reducer.d.ts create mode 100644 src/scales.d.ts create mode 100644 src/symbol.d.ts rename src/{symbols.js => symbol.js} (100%) create mode 100644 src/transforms/basic.d.ts create mode 100644 src/transforms/bin.d.ts create mode 100644 src/transforms/centroid.d.ts create mode 100644 src/transforms/dodge.d.ts create mode 100644 src/transforms/group.d.ts create mode 100644 src/transforms/hexbin.d.ts create mode 100644 src/transforms/map.d.ts create mode 100644 src/transforms/normalize.d.ts create mode 100644 src/transforms/select.d.ts create mode 100644 src/transforms/stack.d.ts create mode 100644 src/transforms/tree.d.ts create mode 100644 src/transforms/window.d.ts delete mode 100644 src/types/isoformat.d.ts rename src/{warnings.ts => warnings.js} (78%) rename test/{legends/legends-test.ts => legend-test.js} (97%) rename test/plots/{aapl-bollinger.js => aapl-bollinger.ts} (92%) rename test/plots/{aapl-candlestick.js => aapl-candlestick.ts} (83%) rename test/plots/{aapl-change-volume.js => aapl-change-volume.ts} (80%) rename test/plots/{aapl-close-untyped.js => aapl-close-untyped.ts} (73%) rename test/plots/{aapl-close.js => aapl-close.ts} (74%) rename test/plots/{aapl-fancy-axis.js => aapl-fancy-axis.ts} (82%) rename test/plots/{aapl-monthly.js => aapl-monthly.ts} (84%) rename test/plots/{aapl-volume-rect.js => aapl-volume-rect.ts} (77%) rename test/plots/{aapl-volume.js => aapl-volume.ts} (77%) rename test/plots/{anscombe-quartet.js => anscombe-quartet.ts} (71%) rename test/plots/{armadillo.js => armadillo.ts} (83%) rename test/plots/{aspectRatio.js => aspectRatio.ts} (100%) rename test/plots/{athletes-bins-colors.js => athletes-bins-colors.ts} (63%) rename test/plots/{athletes-birthdays.js => athletes-birthdays.ts} (80%) rename test/plots/{athletes-boxing-height.js => athletes-boxing-height.ts} (73%) rename test/plots/{athletes-height-weight-bin-stroke.js => athletes-height-weight-bin-stroke.ts} (79%) rename test/plots/{athletes-height-weight-bin.js => athletes-height-weight-bin.ts} (71%) rename test/plots/{athletes-height-weight-sex.js => athletes-height-weight-sex.ts} (72%) rename test/plots/{athletes-height-weight-sport.js => athletes-height-weight-sport.ts} (65%) rename test/plots/{athletes-height-weight.js => athletes-height-weight.ts} (61%) rename test/plots/{athletes-nationality.js => athletes-nationality.ts} (71%) rename test/plots/{athletes-sample.js => athletes-sample.ts} (86%) rename test/plots/{athletes-sex-weight.js => athletes-sex-weight.ts} (73%) rename test/plots/{athletes-sort.js => athletes-sort.ts} (73%) rename test/plots/{athletes-sport-sex.js => athletes-sport-sex.ts} (77%) rename test/plots/{athletes-sport-weight.js => athletes-sport-weight.ts} (75%) rename test/plots/{athletes-weight-cumulative.js => athletes-weight-cumulative.ts} (63%) rename test/plots/{athletes-weight.js => athletes-weight.ts} (60%) rename test/plots/{autoplot.js => autoplot.ts} (64%) rename test/plots/{availability.js => availability.ts} (82%) rename test/plots/{axis-labels.js => axis-labels.ts} (100%) rename test/plots/{ballot-status-race.js => ballot-status-race.ts} (83%) rename test/plots/{band-clip.js => band-clip.ts} (96%) rename test/plots/{beagle.js => beagle.ts} (87%) rename test/plots/{becker-barley.js => becker-barley.ts} (84%) rename test/plots/{bigint.js => bigint.ts} (100%) rename test/plots/{bin-1m.js => bin-1m.ts} (100%) rename test/plots/{bin-strings.js => bin-strings.ts} (77%) rename test/plots/{bin-timestamps.js => bin-timestamps.ts} (88%) rename test/plots/{bounding-boxes.js => bounding-boxes.ts} (90%) rename test/plots/{boxplot.js => boxplot.ts} (74%) rename test/plots/{caltrain-direction.js => caltrain-direction.ts} (83%) rename test/plots/{caltrain.js => caltrain.ts} (93%) rename test/plots/{cars-dodge.js => cars-dodge.ts} (68%) rename test/plots/{cars-hexbin.js => cars-hexbin.ts} (77%) rename test/plots/{cars-jitter.js => cars-jitter.ts} (85%) rename test/plots/{cars-mpg.js => cars-mpg.ts} (87%) rename test/plots/{cars-parcoords.js => cars-parcoords.ts} (79%) rename test/plots/{clamp.js => clamp.ts} (78%) rename test/plots/{collapsed-histogram.js => collapsed-histogram.ts} (68%) rename test/plots/{country-centroids.js => country-centroids.ts} (84%) rename test/plots/{covid-ihme-projected-deaths.js => covid-ihme-projected-deaths.ts} (90%) rename test/plots/{crimean-war-arrow.js => crimean-war-arrow.ts} (78%) rename test/plots/{crimean-war-line.js => crimean-war-line.ts} (78%) rename test/plots/{crimean-war-overlapped.js => crimean-war-overlapped.ts} (79%) rename test/plots/{crimean-war-stacked.js => crimean-war-stacked.ts} (79%) rename test/plots/{d3-survey-2015-comfort.js => d3-survey-2015-comfort.ts} (86%) rename test/plots/{d3-survey-2015-why.js => d3-survey-2015-why.ts} (83%) rename test/plots/{d3-survey-2015.js => d3-survey-2015.ts} (100%) rename test/plots/{darker-dodge.js => darker-dodge.ts} (85%) rename test/plots/{decathlon.js => decathlon.ts} (71%) rename test/plots/{diamonds-boxplot.js => diamonds-boxplot.ts} (60%) rename test/plots/{diamonds-carat-price-dots.js => diamonds-carat-price-dots.ts} (77%) rename test/plots/{diamonds-carat-price.js => diamonds-carat-price.ts} (72%) rename test/plots/{diamonds-carat-sampling.js => diamonds-carat-sampling.ts} (87%) rename test/plots/{documentation-links.js => documentation-links.ts} (83%) rename test/plots/{dodge-rule.js => dodge-rule.ts} (73%) rename test/plots/{dodge-text-radius.js => dodge-text-radius.ts} (91%) rename test/plots/{dodge-tick.js => dodge-tick.ts} (73%) rename test/plots/{dot-sort.js => dot-sort.ts} (86%) rename test/plots/{downloads-ordinal.js => downloads-ordinal.ts} (79%) rename test/plots/{downloads.js => downloads.ts} (71%) rename test/plots/{driving.js => driving.ts} (82%) rename test/plots/{electricity-demand.js => electricity-demand.ts} (90%) rename test/plots/{empty-facet.js => empty-facet.ts} (88%) rename test/plots/{empty-legend.js => empty-legend.ts} (81%) rename test/plots/{empty-x.js => empty-x.ts} (83%) rename test/plots/{empty.js => empty.ts} (90%) rename test/plots/{energy-production.js => energy-production.ts} (93%) rename test/plots/{faithful-density-1d.js => faithful-density-1d.ts} (78%) rename test/plots/{faithful-density.js => faithful-density.ts} (78%) rename test/plots/{federal-funds.js => federal-funds.ts} (100%) rename test/plots/{figcaption-html.js => figcaption-html.ts} (80%) rename test/plots/{figcaption.js => figcaption.ts} (79%) rename test/plots/{first-ladies.js => first-ladies.ts} (83%) rename test/plots/{flare-cluster.js => flare-cluster.ts} (68%) rename test/plots/{flare-indent.js => flare-indent.ts} (82%) rename test/plots/{flare-tree.js => flare-tree.ts} (71%) rename test/plots/{football-coverage.js => football-coverage.ts} (77%) rename test/plots/{frame.js => frame.ts} (100%) rename test/plots/{fruit-sales-date.js => fruit-sales-date.ts} (75%) rename test/plots/{fruit-sales.js => fruit-sales.ts} (74%) rename test/plots/{function-contour.js => function-contour.ts} (97%) rename test/plots/{geo-link.js => geo-link.ts} (100%) rename test/plots/{gistemp-anomaly-moving.js => gistemp-anomaly-moving.ts} (79%) rename test/plots/{gistemp-anomaly-transform.js => gistemp-anomaly-transform.ts} (80%) rename test/plots/{gistemp-anomaly.js => gistemp-anomaly.ts} (75%) rename test/plots/{google-trends-ridgeline.js => google-trends-ridgeline.ts} (82%) rename test/plots/{graticule.js => graticule.ts} (86%) rename test/plots/{greek-gods.js => greek-gods.ts} (91%) rename test/plots/{grid-choropleth-dx.js => grid-choropleth-dx.ts} (80%) rename test/plots/{grid-choropleth.js => grid-choropleth.ts} (78%) rename test/plots/{grouped-rects.js => grouped-rects.ts} (81%) rename test/plots/{hadcrut-warming-stripes.js => hadcrut-warming-stripes.ts} (88%) rename test/plots/{heatmap.js => heatmap.ts} (100%) rename test/plots/{hexbin-oranges.js => hexbin-oranges.ts} (73%) rename test/plots/{hexbin-r.js => hexbin-r.ts} (82%) rename test/plots/{hexbin-symbol.js => hexbin-symbol.ts} (71%) rename test/plots/{hexbin-text.js => hexbin-text.ts} (81%) rename test/plots/{hexbin-z-null.js => hexbin-z-null.ts} (78%) rename test/plots/{hexbin-z.js => hexbin-z.ts} (75%) rename test/plots/{hexbin.js => hexbin.ts} (71%) rename test/plots/{high-cardinality-ordinal.js => high-cardinality-ordinal.ts} (77%) rename test/plots/{href-fill.js => href-fill.ts} (86%) rename test/plots/{ibm-trading.js => ibm-trading.ts} (86%) rename test/plots/{identity-scale.js => identity-scale.ts} (92%) rename test/plots/{image-rendering.js => image-rendering.ts} (100%) delete mode 100644 test/plots/index.js create mode 100644 test/plots/index.ts rename test/plots/{industry-unemployment-share.js => industry-unemployment-share.ts} (74%) rename test/plots/{industry-unemployment-stream.js => industry-unemployment-stream.ts} (71%) rename test/plots/{industry-unemployment-track.js => industry-unemployment-track.ts} (88%) rename test/plots/{industry-unemployment.js => industry-unemployment.ts} (73%) rename test/plots/{infinity-log.js => infinity-log.ts} (78%) rename test/plots/{integer-interval.js => integer-interval.ts} (85%) rename test/plots/{intraday-histogram.js => intraday-histogram.ts} (63%) rename test/plots/{learning-poverty.js => learning-poverty.ts} (88%) rename test/plots/{legend-color.js => legend-color.ts} (99%) rename test/plots/{legend-opacity.js => legend-opacity.ts} (100%) rename test/plots/{legend-symbol.js => legend-symbol.ts} (100%) rename test/plots/{letter-frequency-bar.js => letter-frequency-bar.ts} (85%) rename test/plots/{letter-frequency-cloud.js => letter-frequency-cloud.ts} (78%) rename test/plots/{letter-frequency-column.js => letter-frequency-column.ts} (72%) rename test/plots/{letter-frequency-dot.js => letter-frequency-dot.ts} (58%) rename test/plots/{letter-frequency-lollipop.js => letter-frequency-lollipop.ts} (71%) rename test/plots/{letter-frequency-wheel.js => letter-frequency-wheel.ts} (86%) rename test/plots/{libor-projections.js => libor-projections.ts} (88%) rename test/plots/{likert-survey.js => likert-survey.ts} (85%) rename test/plots/{linear-regression-cars.js => linear-regression-cars.ts} (71%) rename test/plots/{linear-regression-mtcars.js => linear-regression-mtcars.ts} (72%) rename test/plots/{linear-regression-penguins.js => linear-regression-penguins.ts} (77%) rename test/plots/{log-degenerate.js => log-degenerate.ts} (79%) rename test/plots/{long-labels.js => long-labels.ts} (100%) rename test/plots/{markov-chain.js => markov-chain.ts} (94%) rename test/plots/{metro-inequality-change.js => metro-inequality-change.ts} (86%) rename test/plots/{metro-inequality.js => metro-inequality.ts} (73%) rename test/plots/{metro-unemployment-highlight.js => metro-unemployment-highlight.ts} (78%) rename test/plots/{metro-unemployment-index.js => metro-unemployment-index.ts} (53%) rename test/plots/{metro-unemployment-moving.js => metro-unemployment-moving.ts} (61%) rename test/plots/{metro-unemployment-normalize.js => metro-unemployment-normalize.ts} (72%) rename test/plots/{metro-unemployment-ridgeline.js => metro-unemployment-ridgeline.ts} (79%) rename test/plots/{metro-unemployment-slope.js => metro-unemployment-slope.ts} (77%) rename test/plots/{metro-unemployment-stroke.js => metro-unemployment-stroke.ts} (66%) rename test/plots/{metro-unemployment.js => metro-unemployment.ts} (60%) rename test/plots/{moby-dick-faceted.js => moby-dick-faceted.ts} (93%) rename test/plots/{moby-dick-letter-frequency.js => moby-dick-letter-frequency.ts} (88%) rename test/plots/{moby-dick-letter-pairs.js => moby-dick-letter-pairs.ts} (94%) rename test/plots/{moby-dick-letter-position.js => moby-dick-letter-position.ts} (95%) rename test/plots/{moby-dick-letter-relative-frequency.js => moby-dick-letter-relative-frequency.ts} (87%) rename test/plots/{moby-dick.js => moby-dick.ts} (89%) rename test/plots/{morley-boxplot.js => morley-boxplot.ts} (59%) rename test/plots/{movies-profit-by-genre.js => movies-profit-by-genre.ts} (90%) rename test/plots/{movies-rating-by-genre.js => movies-rating-by-genre.ts} (88%) rename test/plots/{multiplication-table.js => multiplication-table.ts} (96%) rename test/plots/{music-revenue.js => music-revenue.ts} (64%) rename test/plots/{npm-versions.js => npm-versions.ts} (98%) rename test/plots/{ordinal-bar.js => ordinal-bar.ts} (80%) rename test/plots/{penguin-annotated.js => penguin-annotated.ts} (82%) rename test/plots/{penguin-culmen-array.js => penguin-culmen-array.ts} (82%) rename test/plots/{penguin-culmen-delaunay-mesh.js => penguin-culmen-delaunay-mesh.ts} (68%) rename test/plots/{penguin-culmen-delaunay-species.js => penguin-culmen-delaunay-species.ts} (76%) rename test/plots/{penguin-culmen-delaunay.js => penguin-culmen-delaunay.ts} (64%) rename test/plots/{penguin-culmen-mark-facet.js => penguin-culmen-mark-facet.ts} (81%) rename test/plots/{penguin-culmen-voronoi.js => penguin-culmen-voronoi.ts} (72%) rename test/plots/{penguin-culmen.js => penguin-culmen.ts} (80%) rename test/plots/{penguin-density-fill.js => penguin-density-fill.ts} (79%) rename test/plots/{penguin-density-z.js => penguin-density-z.ts} (82%) rename test/plots/{penguin-density.js => penguin-density.ts} (65%) rename test/plots/{penguin-dodge-hexbin.js => penguin-dodge-hexbin.ts} (83%) rename test/plots/{penguin-dodge-voronoi.js => penguin-dodge-voronoi.ts} (70%) rename test/plots/{penguin-dodge.js => penguin-dodge.ts} (62%) rename test/plots/{penguins-facet-annotated-x.js => penguin-facet-annotated-x.ts} (79%) rename test/plots/{penguins-facet-annotated.js => penguin-facet-annotated.ts} (79%) rename test/plots/{penguin-facet-dodge-identity.js => penguin-facet-dodge-identity.ts} (75%) rename test/plots/{penguin-facet-dodge-island.js => penguin-facet-dodge-island.ts} (75%) rename test/plots/{penguin-facet-dodge-symbol.js => penguin-facet-dodge-symbol.ts} (69%) rename test/plots/{penguin-facet-dodge.js => penguin-facet-dodge.ts} (72%) rename test/plots/{penguin-hexbin-color-explicit.js => penguin-hexbin-color-explicit.ts} (84%) rename test/plots/{penguin-island-unknown.js => penguin-island-unknown.ts} (69%) rename test/plots/{penguin-mass-sex-species.js => penguin-mass-sex-species.ts} (75%) rename test/plots/{penguin-mass-sex.js => penguin-mass-sex.ts} (75%) rename test/plots/{penguin-mass-species.js => penguin-mass-species.ts} (77%) rename test/plots/{penguin-mass.js => penguin-mass.ts} (73%) rename test/plots/{penguin-quantile-unknown.js => penguin-quantile-unknown.ts} (78%) rename test/plots/{penguin-sex-mass-culmen-species.js => penguin-sex-mass-culmen-species.ts} (84%) rename test/plots/{penguin-sex.js => penguin-sex.ts} (63%) rename test/plots/{penguin-size-symbols.js => penguin-size-symbols.ts} (76%) rename test/plots/{penguin-species-cheysson.js => penguin-species-cheysson.ts} (98%) rename test/plots/{penguin-species-gradient.js => penguin-species-gradient.ts} (81%) rename test/plots/{penguin-species-group.js => penguin-species-group.ts} (75%) rename test/plots/{penguin-species-island-relative.js => penguin-species-island-relative.ts} (72%) rename test/plots/{penguin-species-island-sex.js => penguin-species-island-sex.ts} (74%) rename test/plots/{penguin-species-island.js => penguin-species-island.ts} (67%) rename test/plots/{penguin-voronoi-1d.js => penguin-voronoi-1d.ts} (85%) rename test/plots/{polylinear.js => polylinear.ts} (90%) rename test/plots/{population-by-latitude.js => population-by-latitude.ts} (76%) rename test/plots/{population-by-longitude.js => population-by-longitude.ts} (76%) rename test/plots/{projection-bleed-edges.js => projection-bleed-edges.ts} (83%) rename test/plots/{projection-bleed-edges2.js => projection-bleed-edges2.ts} (83%) rename test/plots/{projection-clip-angle-frame.js => projection-clip-angle-frame.ts} (79%) rename test/plots/{projection-clip-angle.js => projection-clip-angle.ts} (79%) rename test/plots/{projection-clip-berghaus.js => projection-clip-berghaus.ts} (80%) rename test/plots/{projection-fit-antarctica.js => projection-fit-antarctica.ts} (88%) rename test/plots/{projection-fit-bertin1953.js => projection-fit-bertin1953.ts} (84%) rename test/plots/{projection-fit-conic.js => projection-fit-conic.ts} (79%) rename test/plots/{projection-fit-identity.js => projection-fit-identity.ts} (90%) rename test/plots/{projection-fit-us-albers.js => projection-fit-us-albers.ts} (90%) rename test/plots/{projection-height-albers.js => projection-height-albers.ts} (84%) rename test/plots/{projection-height-equal-earth.js => projection-height-equal-earth.ts} (78%) rename test/plots/{projection-height-geometry.js => projection-height-geometry.ts} (87%) rename test/plots/{projection-height-mercator.js => projection-height-mercator.ts} (78%) rename test/plots/{projection-height-orthographic.js => projection-height-orthographic.ts} (79%) rename test/plots/{random-bins-xy.js => random-bins-xy.ts} (86%) rename test/plots/{random-bins.js => random-bins.ts} (86%) rename test/plots/{random-quantile.js => random-quantile.ts} (87%) rename test/plots/{random-walk.js => random-walk.ts} (69%) rename test/plots/{raster-ca55.js => raster-ca55.ts} (87%) rename test/plots/{raster-penguins.js => raster-penguins.ts} (90%) rename test/plots/{raster-vapor.js => raster-vapor.ts} (100%) rename test/plots/{raster-walmart.js => raster-walmart.ts} (89%) rename test/plots/{rect-band.js => rect-band.ts} (91%) rename test/plots/{seattle-precipitation-density.js => seattle-precipitation-density.ts} (70%) rename test/plots/{seattle-precipitation-rule.js => seattle-precipitation-rule.ts} (54%) rename test/plots/{seattle-precipitation-sum.js => seattle-precipitation-sum.ts} (82%) rename test/plots/{seattle-temperature-amplitude.js => seattle-temperature-amplitude.ts} (88%) rename test/plots/{seattle-temperature-band.js => seattle-temperature-band.ts} (78%) rename test/plots/{seattle-temperature-cell.js => seattle-temperature-cell.ts} (78%) rename test/plots/{sf-covid-deaths.js => sf-covid-deaths.ts} (83%) rename test/plots/{sf-temperature-band-area.js => sf-temperature-band-area.ts} (79%) rename test/plots/{sf-temperature-band.js => sf-temperature-band.ts} (81%) rename test/plots/{shorthand-area.js => shorthand-area.ts} (97%) rename test/plots/{shorthand-areaY.js => shorthand-areaY.ts} (91%) rename test/plots/{shorthand-barY.js => shorthand-barY.ts} (91%) rename test/plots/{shorthand-binRectY.js => shorthand-binRectY.ts} (90%) rename test/plots/{shorthand-boxX.js => shorthand-boxX.ts} (91%) rename test/plots/{shorthand-cell.js => shorthand-cell.ts} (90%) rename test/plots/{shorthand-cellX.js => shorthand-cellX.ts} (91%) rename test/plots/{shorthand-dot.js => shorthand-dot.ts} (97%) rename test/plots/{shorthand-dotX.js => shorthand-dotX.ts} (91%) rename test/plots/{shorthand-groupBarY.js => shorthand-groupBarY.ts} (82%) rename test/plots/{shorthand-line.js => shorthand-line.ts} (97%) rename test/plots/{shorthand-lineY.js => shorthand-lineY.ts} (91%) rename test/plots/{shorthand-rectY.js => shorthand-rectY.ts} (91%) rename test/plots/{shorthand-ruleX.js => shorthand-ruleX.ts} (91%) rename test/plots/{shorthand-text.js => shorthand-text.ts} (97%) rename test/plots/{shorthand-textX.js => shorthand-textX.ts} (91%) rename test/plots/{shorthand-tickX.js => shorthand-tickX.ts} (91%) rename test/plots/{shorthand-vector.js => shorthand-vector.ts} (97%) rename test/plots/{shorthand-vectorX.js => shorthand-vectorX.ts} (90%) rename test/plots/{simpsons-ratings-dots.js => simpsons-ratings-dots.ts} (81%) rename test/plots/{simpsons-ratings.js => simpsons-ratings.ts} (85%) rename test/plots/{simpsons-views.js => simpsons-views.ts} (83%) rename test/plots/{single-value-bar.js => single-value-bar.ts} (84%) rename test/plots/{single-value-bin.js => single-value-bin.ts} (69%) rename test/plots/{software-versions.js => software-versions.ts} (78%) rename test/plots/{sparse-cell.js => sparse-cell.ts} (83%) rename test/plots/{stacked-bar.js => stacked-bar.ts} (89%) rename test/plots/{stacked-rect.js => stacked-rect.ts} (88%) rename test/plots/{stargazers-binned.js => stargazers-binned.ts} (84%) rename test/plots/{stargazers-hourly-group.js => stargazers-hourly-group.ts} (83%) rename test/plots/{stargazers-hourly.js => stargazers-hourly.ts} (80%) rename test/plots/{stargazers.js => stargazers.ts} (77%) rename test/plots/{stocks-index.js => stocks-index.ts} (96%) rename test/plots/{text-overflow.js => text-overflow.ts} (92%) rename test/plots/{this-is-just-to-say.js => this-is-just-to-say.ts} (91%) rename test/plots/{traffic-horizon.js => traffic-horizon.ts} (87%) rename test/plots/{travelers-covid-drop.js => travelers-covid-drop.ts} (82%) rename test/plots/{travelers-year-over-year.js => travelers-year-over-year.ts} (86%) rename test/plots/{uniform-random-difference.js => uniform-random-difference.ts} (89%) rename test/plots/{untyped-date-bin.js => untyped-date-bin.ts} (74%) rename test/plots/{us-congress-age-color-explicit.js => us-congress-age-color-explicit.ts} (80%) rename test/plots/{us-congress-age-gender.js => us-congress-age-gender.ts} (82%) rename test/plots/{us-congress-age-symbol-explicit.js => us-congress-age-symbol-explicit.ts} (79%) rename test/plots/{us-congress-age.js => us-congress-age.ts} (77%) rename test/plots/{us-county-choropleth.js => us-county-choropleth.ts} (78%) rename test/plots/{us-county-spikes.js => us-county-spikes.ts} (91%) rename test/plots/{us-population-state-age-dots.js => us-population-state-age-dots.ts} (87%) rename test/plots/{us-population-state-age.js => us-population-state-age.ts} (80%) rename test/plots/{us-president-favorability-dots.js => us-president-favorability-dots.ts} (82%) delete mode 100644 test/plots/us-president-gallery.js create mode 100644 test/plots/us-president-gallery.ts rename test/plots/{us-presidential-election-2020.js => us-presidential-election-2020.ts} (88%) rename test/plots/{us-presidential-election-map-2020.js => us-presidential-election-map-2020.ts} (77%) rename test/plots/{us-presidential-forecast-2016.js => us-presidential-forecast-2016.ts} (78%) rename test/plots/{us-retail-sales.js => us-retail-sales.ts} (75%) rename test/plots/{us-state-capitals-voronoi.js => us-state-capitals-voronoi.ts} (78%) rename test/plots/{us-state-capitals.js => us-state-capitals.ts} (83%) rename test/plots/{us-state-population-change.js => us-state-population-change.ts} (84%) rename test/plots/{vector-field.js => vector-field.ts} (89%) rename test/plots/{vector-frame.js => vector-frame.ts} (96%) rename test/plots/{volcano.js => volcano.ts} (84%) rename test/plots/{walmarts-decades.js => walmarts-decades.ts} (88%) rename test/plots/{walmarts-density-unprojected.js => walmarts-density-unprojected.ts} (76%) rename test/plots/{walmarts-density.js => walmarts-density.ts} (82%) rename test/plots/{walmarts.js => walmarts.ts} (85%) rename test/plots/{wealth-britain-bar.js => wealth-britain-bar.ts} (70%) rename test/plots/{wealth-britain-proportion-plot.js => wealth-britain-proportion-plot.ts} (91%) rename test/plots/{word-cloud.js => word-cloud.ts} (96%) rename test/plots/{word-length-moby-dick.js => word-length-moby-dick.ts} (94%) rename test/plots/{yearly-requests-dot.js => yearly-requests-dot.ts} (89%) rename test/plots/{yearly-requests-line.js => yearly-requests-line.ts} (91%) rename test/plots/{yearly-requests.js => yearly-requests.ts} (91%) diff --git a/.eslintrc.json b/.eslintrc.json index b9dfdbc8ff..19dead2a01 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,6 +13,7 @@ "no-sparse-arrays": 0, "no-unexpected-multiline": 0, "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-this-alias": 0, "@typescript-eslint/no-unused-vars": ["error", {"ignoreRestSiblings": true}] } diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 4cbdf100f7..b817465ed0 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -27,7 +27,7 @@ jobs: echo ::add-matcher::.github/eslint.json yarn run eslint src test --format=compact - run: yarn run prettier --check src test - - run: yarn test + - run: yarn test:mocha - name: Test artifacts uses: actions/upload-artifact@v3 if: failure() diff --git a/.gitignore b/.gitignore index 606fc96519..2f4a6bea14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ .DS_Store -build/ dist/ -types/ node_modules/ test/output/*-changed.svg test/output/*-changed.html -tsconfig.tsbuildinfo yarn-error.log diff --git a/README.md b/README.md index 7d121df932..ada8ff287f 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,6 @@ See also our [Plot + React example](https://github.com/observablehq/plot-create- ## Plot.plot(*options*) - - Renders a new plot given the specified *options* and returns the corresponding SVG or HTML figure element. All *options* are optional. ### Mark options @@ -223,8 +221,6 @@ Plot.plot({ }) ``` - - #### *plot*.scale(*scaleName*) Scale definitions can be exposed through the *plot*.**scale**(*scaleName*) function of a returned plot. The *scaleName* must be one of the known scale names: `"x"`, `"y"`, `"fx"`, `"fy"`, `"r"`, `"color"`, `"opacity"`, `"symbol"`, or `"length"`. If the associated *plot* has no scale with the given *scaleName*, returns undefined. @@ -237,8 +233,6 @@ console.log(color.range); // inspect the color scale’s range, ["red", "blue"] #### Plot.scale(*options*) - - You can also create a standalone scale with Plot.**scale**(*options*). The *options* object must define at least one scale; see [Scale options](#scale-options) for how to define a scale. For example, here is a linear color scale with the default domain of [0, 1] and default scheme *turbo*: ```js @@ -258,8 +252,6 @@ const plot2 = Plot.plot({…, color: plot1.scale("color")}); For convenience, scale objects expose a *scale*.**apply**(*input*) method which returns the scale’s output for the given *input* value. When applicable, scale objects also expose a *scale*.**invert**(*output*) method which returns the corresponding input value from the scale’s domain for the given *output* value. - - ### Position options The position scales (*x*, *y*, *fx*, and *fy*) support additional options: @@ -692,8 +684,6 @@ The **style** legend option allows custom styles to override Plot’s defaults; #### Plot.legend(*options*) - - Returns a standalone legend for the scale defined by the given *options* object. The *options* object must define at least one scale; see [Scale options](#scale-options) for how to define a scale. For example, here is a ramp legend of a linear color scale with the default domain of [0, 1] and default scheme *turbo*: ```js @@ -711,8 +701,6 @@ Plot.legend({ }) ``` - - ## Marks [Marks](https://observablehq.com/@observablehq/plot-marks) visualize data as geometric shapes such as bars, dots, and lines. An single mark can generate multiple shapes: for example, passing a [Plot.barY](#plotbarydata-options) to [Plot.plot](#plotplotoptions) will produce a bar for each element in the associated data. Multiple marks can be layered into [plots](#plotplotoptions). @@ -868,12 +856,8 @@ Plot.barY(alphabet, {x: "letter", y: "frequency"}).plot({width: 1024}) #### Plot.marks(...*marks*) - - A convenience method for composing a mark from a series of other marks. Returns an array of marks that implements the *mark*.plot function. See the [box mark implementation](./src/marks/box.js) for an example. - - ### Area [an area chart](https://observablehq.com/@observablehq/plot-area) @@ -903,20 +887,14 @@ The area mark supports [curve options](#curves) to control interpolation between #### Plot.area(*data*, *options*) - - ```js Plot.area(aapl, {x1: "Date", y1: 0, y2: "Close"}) ``` Returns a new area with the given *data* and *options*. Plot.area is rarely used directly; it is only needed when the baseline and topline have neither common *x* nor *y* values. [Plot.areaY](#plotareaydata-options) is used in the common horizontal orientation where the baseline and topline share *x* values, while [Plot.areaX](#plotareaxdata-options) is used in the vertical orientation where the baseline and topline share *y* values. - - #### Plot.areaX(*data*, *options*) - - ```js Plot.areaX(aapl, {y: "Date", x: "Close"}) ``` @@ -931,12 +909,8 @@ Plot.areaX(observations, {y: "date", x: "temperature", interval: "day"}) The **interval** option is recommended to “regularize” sampled data; for example, if your data represents timestamped temperature measurements and you expect one sample per day, use "day" as the interval. - - #### Plot.areaY(*data*, *options*) - - ```js Plot.areaY(aapl, {x: "Date", y: "Close"}) ``` @@ -951,8 +925,6 @@ Plot.areaY(observations, {x: "date", y: "temperature", interval: "day") The **interval** option is recommended to “regularize” sampled data; for example, if your data represents timestamped temperature measurements and you expect one sample per day, use "day" as the interval. - - ### Arrow [a scatterplot with arrows](https://observablehq.com/@observablehq/plot-arrow) @@ -981,16 +953,12 @@ The **bend** option sets the angle between the straight line between the two poi #### Plot.arrow(*data*, *options*) - - ```js Plot.arrow(inequality, {x1: "POP_1980", y1: "R90_10_1980", x2: "POP_2015", y2: "R90_10_2015", bend: true}) ``` Returns a new arrow with the given *data* and *options*. - - ### Auto [Source](./src/marks/auto.js) · [Examples](https://observablehq.com/@observablehq/plot-auto) · Automatically selects a mark type that best represents the dimensions of the given data according to some simple heuristics. Plot.auto seeks to provide a useful initial plot as quickly as possible through opinionated defaults, and to accelerate exploratory analysis by letting you refine views with minimal changes to code. For example, @@ -1078,16 +1046,12 @@ The chosen mark type depends both on the options you provide (*e.g.*, whether yo #### Plot.auto(*data*, *options*) - - ```js Plot.auto(athletes, {x: "height", y: "weight", color: "count"}) // equivalent to rect + bin, say ``` Returns an automatically-chosen mark with the given *data* and *options*, suitable for a quick view of the data. - - ### Axis [Source](./src/marks/axis.js) · [Examples](https://observablehq.com/@observablehq/plot-axis) · Draws an axis to document the visual encoding of the corresponding position scale: *x* or *y*, and *fx* or *fy* if faceting. The axis mark is a [composite mark](#marks) comprised of (up to) three marks: a [vector](#vector) for ticks, a [text](#text) for tick labels, and another [text](#text) for an axis label. @@ -1131,52 +1095,36 @@ For simplicity’s sake and for consistent layout across plots, axis margins are #### Plot.axisX(*data*, *options*) - - ```js Plot.axisX({anchor: "bottom", tickSpacing: 80}) ``` Returns a new *x* axis with the given *options*. - - #### Plot.axisY(*data*, *options*) - - ```js Plot.axisY({anchor: "left", tickSpacing: 35}) ``` Returns a new *y* axis with the given *options*. - - #### Plot.axisFx(*data*, *options*) - - ```js Plot.axisFx({anchor: "top", label: null}) ``` Returns a new *fx* axis with the given *options*. - - #### Plot.axisFy(*data*, *options*) - - ```js Plot.axisFy({anchor: "right", label: null}) ``` Returns a new *fy* axis with the given *options*. - - ### Bar [a bar chart](https://observablehq.com/@observablehq/plot-bar) @@ -1187,8 +1135,6 @@ For the required channels, see [Plot.barX](#plotbarxdata-options) and [Plot.barY #### Plot.barX(*data*, *options*) - - ```js Plot.barX(alphabet, {y: "letter", x: "frequency"}) ``` @@ -1208,12 +1154,8 @@ In addition to the [standard bar channels](#bar), the following optional channel If the **y** channel is not specified, the bar will span the full vertical extent of the plot (or facet). - - #### Plot.barY(*data*, *options*) - - ```js Plot.barY(alphabet, {x: "letter", y: "frequency"}) ``` @@ -1233,8 +1175,6 @@ In addition to the [standard bar channels](#bar), the following optional channel If the **x** channel is not specified, the bar will span the full horizontal extent of the plot (or facet). - - ### Box [a boxplot of Michelson’s 1879 measurements of the speed of light](https://observablehq.com/@observablehq/plot-box) @@ -1258,28 +1198,20 @@ The given *options* are passed through to these underlying marks, with the excep #### Plot.boxX(*data*, *options*) - - ```js Plot.boxX(simpsons.map(d => d.imdb_rating)) ``` Returns a horizontal boxplot mark. If the **x** option is not specified, it defaults to the identity function, as when *data* is an array of numbers. If the **y** option is not specified, it defaults to null; if the **y** option is specified, it should represent an ordinal (discrete) value. - - #### Plot.boxY(*data*, *options*) - - ```js Plot.boxY(simpsons.map(d => d.imdb_rating)) ``` Returns a vertical boxplot mark. If the **y** option is not specified, it defaults to the identity function, as when *data* is an array of numbers. If the **x** option is not specified, it defaults to null; if the **x** option is specified, it should represent an ordinal (discrete) value. - - ### Cell [a heatmap](https://observablehq.com/@observablehq/plot-cell) @@ -1297,40 +1229,28 @@ The **stroke** defaults to none. The **fill** defaults to currentColor if the st #### Plot.cell(*data*, *options*) - - ```js Plot.cell(simpsons, {x: "number_in_season", y: "season", fill: "imdb_rating"}) ``` Returns a new cell with the given *data* and *options*. If neither the **x** nor **y** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …]. - - #### Plot.cellX(*data*, *options*) - - ```js Plot.cellX(simpsons.map(d => d.imdb_rating)) ``` Equivalent to [Plot.cell](#plotcelldata-options), except that if the **x** option is not specified, it defaults to [0, 1, 2, …], and if the **fill** option is not specified and **stroke** is not a channel, the fill defaults to the identity function and assumes that *data* = [*x₀*, *x₁*, *x₂*, …]. - - #### Plot.cellY(*data*, *options*) - - ```js Plot.cellY(simpsons.map(d => d.imdb_rating)) ``` Equivalent to [Plot.cell](#plotcelldata-options), except that if the **y** option is not specified, it defaults to [0, 1, 2, …], and if the **fill** option is not specified and **stroke** is not a channel, the fill defaults to the identity function and assumes that *data* = [*y₀*, *y₁*, *y₂*, …]. - - ### Contour [Source](./src/marks/contour.js) · [Examples](https://observablehq.com/@observablehq/plot-contour) · Renders contour polygons from spatial samples. If data is provided, it represents discrete samples in abstract coordinates *x* and *y*; the *value* channel specifies further abstract values (_e.g._, height in a topographic map) to be [spatially interpolated](#spatial-interpolation) to produce a [raster grid](#raster) of quantitative values, and lastly contours via marching squares. @@ -1376,16 +1296,12 @@ Plot.contour(volcano.values, {width: volcano.width, height: volcano.height, fill #### Plot.contour(*data*, *options*) - - ```js Plot.contour(volcano.values, {width: volcano.width, height: volcano.height, fill: Plot.identity}) ``` Returns a new contour mark with the given (optional) *data* and *options*. - - ### Delaunay [a Voronoi diagram of penguin culmens, showing the length and depth of several species](https://observablehq.com/@observablehq/plot-delaunay) @@ -1394,54 +1310,34 @@ Returns a new contour mark with the given (optional) *data* and *options*. #### Plot.delaunayLink(*data*, *options*) - - Draws links for each edge of the Delaunay triangulation of the points given by the **x** and **y** channels. Supports the same options as the [link mark](#link), except that **x1**, **y1**, **x2**, and **y2** are derived automatically from **x** and **y**. When an aesthetic channel is specified (such as **stroke** or **strokeWidth**), the link inherits the corresponding channel value from one of its two endpoints arbitrarily. If a **z** channel is specified, the input points are grouped by *z*, and separate Delaunay triangulations are constructed for each group. - - #### Plot.delaunayMesh(*data*, *options*) - - Draws a mesh of the Delaunay triangulation of the points given by the **x** and **y** channels. The **stroke** option defaults to _currentColor_, and the **strokeOpacity** defaults to 0.2. The **fill** option is not supported. When an aesthetic channel is specified (such as **stroke** or **strokeWidth**), the mesh inherits the corresponding channel value from one of its constituent points arbitrarily. If a **z** channel is specified, the input points are grouped by *z*, and separate Delaunay triangulations are constructed for each group. - - #### Plot.hull(*data*, *options*) - - Draws a convex hull around the points given by the **x** and **y** channels. The **stroke** option defaults to _currentColor_ and the **fill** option defaults to _none_. When an aesthetic channel is specified (such as **stroke** or **strokeWidth**), the hull inherits the corresponding channel value from one of its constituent points arbitrarily. If a **z** channel is specified, the input points are grouped by *z*, and separate convex hulls are constructed for each group. If the **z** channel is not specified, it defaults to either the **fill** channel, if any, or the **stroke** channel, if any. - - #### Plot.voronoi(*data*, *options*) - - Draws polygons for each cell of the Voronoi tesselation of the points given by the **x** and **y** channels. If a **z** channel is specified, the input points are grouped by *z*, and separate Voronoi tesselations are constructed for each group. - - #### Plot.voronoiMesh(*data*, *options*) - - Draws a mesh for the cell boundaries of the Voronoi tesselation of the points given by the **x** and **y** channels. The **stroke** option defaults to _currentColor_, and the **strokeOpacity** defaults to 0.2. The **fill** option is not supported. When an aesthetic channel is specified (such as **stroke** or **strokeWidth**), the mesh inherits the corresponding channel value from one of its constituent points arbitrarily. If a **z** channel is specified, the input points are grouped by *z*, and separate Voronoi tesselations are constructed for each group. - - ### Density [A scatterplot showing the relationship between the idle duration and eruption duration for Old Faithful](https://observablehq.com/@observablehq/plot-density) @@ -1450,16 +1346,12 @@ If a **z** channel is specified, the input points are grouped by *z*, and separa #### Plot.density(*data*, *options*) - - Draws contours representing the estimated density of the two-dimensional points given by the **x** and **y** channels, and possibly weighted by the **weight** channel. If either of the **x** or **y** channels are not specified, the corresponding position is controlled by the **frameAnchor** option. The **thresholds** option, which defaults to 20, specifies one more than the number of contours that will be computed at uniformly-spaced intervals between 0 (exclusive) and the maximum density (exclusive). The **thresholds** option may also be specified as an array or iterable of explicit density values. The **bandwidth** option, which defaults to 20, specifies the standard deviation of the Gaussian kernel used for estimation in pixels. If a **z**, **stroke** or **fill** channel is specified, the input points are grouped by series, and separate sets of contours are generated for each series. If the **stroke** or **fill** is specified as *density*, a color channel is constructed with values representing the density threshold value of each contour. - - ### Dot [a scatterplot](https://observablehq.com/@observablehq/plot-dot) @@ -1493,20 +1385,14 @@ Dots are sorted by descending radius by default to mitigate overplotting; set th #### Plot.dot(*data*, *options*) - - ```js Plot.dot(sales, {x: "units", y: "fruit"}) ``` Returns a new dot with the given *data* and *options*. If neither the **x** nor **y** nor **frameAnchor** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …]. - - #### Plot.dotX(*data*, *options*) - - ```js Plot.dotX(cars.map(d => d["economy (mpg)"])) ``` @@ -1515,12 +1401,8 @@ Equivalent to [Plot.dot](#plotdotdata-options) except that if the **x** option i If an **interval** is specified, such as d3.utcDay, **y** is transformed to (*interval*.floor(*y*) + *interval*.offset(*interval*.floor(*y*))) / 2. If the interval is specified as a number *n*, *y* will be the midpoint of two consecutive multiples of *n* that bracket *y*. - - #### Plot.dotY(*data*, *options*) - - ```js Plot.dotY(cars.map(d => d["economy (mpg)"])) ``` @@ -1529,24 +1411,14 @@ Equivalent to [Plot.dot](#plotdotdata-options) except that if the **y** option i If an **interval** is specified, such as d3.utcDay, **x** is transformed to (*interval*.floor(*x*) + *interval*.offset(*interval*.floor(*x*))) / 2. If the interval is specified as a number *n*, *x* will be the midpoint of two consecutive multiples of *n* that bracket *x*. - - #### Plot.circle(*data*, *options*) - - Equivalent to [Plot.dot](#plotdotdata-options) except that the **symbol** option is set to *circle*. - - #### Plot.hexagon(*data*, *options*) - - Equivalent to [Plot.dot](#plotdotdata-options) except that the **symbol** option is set to *hexagon*. - - ### Geo [Source](./src/marks/geo.js) · [Examples](https://observablehq.com/@observablehq/plot-geo) · Draws polygons, lines, points, and other GeoJSON geometry, often in conjunction with a [geographic projection](#projection-options) to produce a thematic map. The **geometry** channel specifies the geometry (GeoJSON object) to draw; if not specified, the mark’s *data* is assumed to be GeoJSON. @@ -1599,52 +1471,36 @@ All the other common options are supported when applicable (e.g., **title**). #### Plot.gridX(*data*, *options*) - - ```js Plot.gridX({strokeDasharray: "5,3"}) ``` Returns a new *x* grid with the given *options*. - - #### Plot.gridY(*data*, *options*) - - ```js Plot.gridY({strokeDasharray: "5,3"}) ``` Returns a new *y* grid with the given *options*. - - #### Plot.gridFx(*data*, *options*) - - ```js Plot.gridFx({strokeDasharray: "5,3"}) ``` Returns a new *fx* grid with the given *options*. - - #### Plot.gridFy(*data*, *options*) - - ```js Plot.gridFy({strokeDasharray: "5,3"}) ``` Returns a new *fy* grid with the given *options*. - - ### Hexgrid The hexgrid mark can be used to support marks using the [hexbin](#hexbin) layout. @@ -1655,12 +1511,8 @@ The hexgrid mark can be used to support marks using the [hexbin](#hexbin) layout Plot.hexgrid() ``` - - The **binWidth** option specifies the distance between the centers of neighboring hexagons, in pixels (defaults to 20). The **clip** option defaults to true, clipping the mark to the frame’s dimensions. - - ### Image [a scatterplot of Presidential portraits](https://observablehq.com/@observablehq/plot-image) @@ -1691,16 +1543,12 @@ Images are drawn in input order, with the last data drawn on top. If sorting is #### Plot.image(*data*, *options*) - - ```js Plot.image(presidents, {x: "inauguration", y: "favorability", src: "portrait"}) ``` Returns a new image with the given *data* and *options*. If neither the **x** nor **y** nor **frameAnchor** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …]. - - ### Linear regression [a scatterplot of penguin culmens, showing the length and depth of several species, with linear regression models by species and for the whole population, illustrating Simpson’s paradox](https://observablehq.com/@observablehq/plot-linear-regression) @@ -1719,28 +1567,20 @@ Multiple regressions can be defined by specifying the *z*, *fill*, or *stroke* c #### Plot.linearRegressionX(*data*, *options*) - - ```js Plot.linearRegressionX(mtcars, {y: "wt", x: "hp"}) ``` Returns a linear regression mark where *x* is the dependent variable and *y* is the independent variable. - - #### Plot.linearRegressionY(*data*, *options*) - - ```js Plot.linearRegressionY(mtcars, {x: "wt", y: "hp"}) ``` Returns a linear regression mark where *y* is the dependent variable and *x* is the independent variable. - - ### Line [a line chart](https://observablehq.com/@observablehq/plot-line) @@ -1766,20 +1606,14 @@ The line mark supports [curve options](#curves) to control interpolation between #### Plot.line(*data*, *options*) - - ```js Plot.line(aapl, {x: "Date", y: "Close"}) ``` Returns a new line with the given *data* and *options*. If neither the **x** nor **y** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …]. - - #### Plot.lineX(*data*, *options*) - - ```js Plot.lineX(aapl.map(d => d.Close)) ``` @@ -1794,12 +1628,8 @@ Plot.lineX(observations, {y: "date", x: "temperature", interval: "day"}) The **interval** option is recommended to “regularize” sampled data; for example, if your data represents timestamped temperature measurements and you expect one sample per day, use "day" as the interval. - - #### Plot.lineY(*data*, *options*) - - ```js Plot.lineY(aapl.map(d => d.Close)) ``` @@ -1814,8 +1644,6 @@ Plot.lineY(observations, {x: "date", y: "temperature", interval: "day"}) The **interval** option is recommended to “regularize” sampled data; for example, if your data represents timestamped temperature measurements and you expect one sample per day, use "day" as the interval. - - ### Link [a chart with links](https://observablehq.com/@observablehq/plot-link) @@ -1837,16 +1665,12 @@ The link mark supports [curve options](#curves) to control interpolation between #### Plot.link(*data*, *options*) - - ```js Plot.link(inequality, {x1: "POP_1980", y1: "R90_10_1980", x2: "POP_2015", y2: "R90_10_2015"}) ``` Returns a new link with the given *data* and *options*. - - ### Raster [Source](./src/marks/raster.js) · [Examples](https://observablehq.com/@observablehq/plot-raster) · Renders a raster image from spatial samples. If data is provided, it represents discrete samples in abstract coordinates *x* and *y*; the *fill* and *fillOpacity* channels specify further abstract values (_e.g._, height in a topographic map) to be [spatially interpolated](#spatial-interpolation) to produce an image. @@ -1886,16 +1710,12 @@ The **imageRendering** option may be set to *pixelated* to disable bilinear inte #### Plot.raster(*data*, *options*) - - ```js Plot.raster(volcano.values, {width: volcano.width, height: volcano.height, fill: Plot.identity}) ``` Returns a new raster mark with the given (optional) *data* and *options*. - - ### Rect [a histogram](https://observablehq.com/@observablehq/plot-rect) @@ -1917,40 +1737,28 @@ The rect mark supports the [standard mark options](#marks), including insets and #### Plot.rect(*data*, *options*) - - ```js Plot.rect(athletes, Plot.bin({fill: "count"}, {x: "weight", y: "height"})) ``` Returns a new rect with the given *data* and *options*. - - #### Plot.rectX(*data*, *options*) - - ```js Plot.rectX(athletes, Plot.binY({x: "count"}, {y: "weight"})) ``` Equivalent to [Plot.rect](#plotrectdata-options), except that if neither the **x1** nor **x2** option is specified, the **x** option may be specified as shorthand to apply an implicit [stackX transform](#plotstackxstack-options); this is the typical configuration for a histogram with rects aligned at *x* = 0. If the **x** option is not specified, it defaults to the identity function. - - #### Plot.rectY(*data*, *options*) - - ```js Plot.rectY(athletes, Plot.binX({y: "count"}, {x: "weight"})) ``` Equivalent to [Plot.rect](#plotrectdata-options), except that if neither the **y1** nor **y2** option is specified, the **y** option may be specified as shorthand to apply an implicit [stackY transform](#plotstackystack-options); this is the typical configuration for a histogram with rects aligned at *y* = 0. If the **y** option is not specified, it defaults to the identity function. - - ### Rule [a line chart with a highlighted rule](https://observablehq.com/@observablehq/plot-rule) @@ -1961,8 +1769,6 @@ For the required channels, see [Plot.ruleX](#plotrulexdata-options) and [Plot.ru #### Plot.ruleX(*data*, *options*) - - ```js Plot.ruleX([0]) // as annotation ``` @@ -1980,12 +1786,8 @@ If the **x** option is not specified, it defaults to the identity function and a If an **interval** is specified, such as d3.utcDay, **y1** and **y2** can be derived from **y**: *interval*.floor(*y*) is invoked for each *y* to produce *y1*, and *interval*.offset(*y1*) is invoked for each *y1* to produce *y2*. If the interval is specified as a number *n*, *y1* and *y2* are taken as the two consecutive multiples of *n* that bracket *y*. - - #### Plot.ruleY(*data*, *options*) - - ```js Plot.ruleY([0]) // as annotation ``` @@ -2004,8 +1806,6 @@ If the **y** option is not specified, it defaults to the identity function and a If an **interval** is specified, such as d3.utcDay, **x1** and **x2** can be derived from **x**: *interval*.floor(*x*) is invoked for each *x* to produce *x1*, and *interval*.offset(*x1*) is invoked for each *x1* to produce *x2*. If the interval is specified as a number *n*, *x1* and *x2* are taken as the two consecutive multiples of *n* that bracket *x*. - - ### Text [a bar chart with text labels](https://observablehq.com/@observablehq/plot-text) @@ -2062,32 +1862,20 @@ The **paintOrder** option defaults to “stroke” and the **strokeWidth** optio #### Plot.text(*data*, *options*) - - Returns a new text mark with the given *data* and *options*. If neither the **x** nor **y** nor **frameAnchor** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …]. - - #### Plot.textX(*data*, *options*) - - Equivalent to [Plot.text](#plottextdata-options), except **x** defaults to the identity function and assumes that *data* = [*x₀*, *x₁*, *x₂*, …]. If an **interval** is specified, such as d3.utcDay, **y** is transformed to (*interval*.floor(*y*) + *interval*.offset(*interval*.floor(*y*))) / 2. If the interval is specified as a number *n*, *y* will be the midpoint of two consecutive multiples of *n* that bracket *y*. - - #### Plot.textY(*data*, *options*) - - Equivalent to [Plot.text](#plottextdata-options), except **y** defaults to the identity function and assumes that *data* = [*y₀*, *y₁*, *y₂*, …]. If an **interval** is specified, such as d3.utcDay, **x** is transformed to (*interval*.floor(*x*) + *interval*.offset(*interval*.floor(*x*))) / 2. If the interval is specified as a number *n*, *x* will be the midpoint of two consecutive multiples of *n* that bracket *x*. - - ### Tick [a barcode plot](https://observablehq.com/@observablehq/plot-tick) @@ -2098,8 +1886,6 @@ For the required channels, see [Plot.tickX](#plottickxdata-options) and [Plot.ti #### Plot.tickX(*data*, *options*) - - ```js Plot.tickX(stateage, {x: "population", y: "age"}) ``` @@ -2114,12 +1900,8 @@ The following optional channels are supported: If the **y** channel is not specified, the tick will span the full vertical extent of the plot (or facet). - - #### Plot.tickY(*data*, *options*) - - ```js Plot.tickY(stateage, {y: "population", x: "age"}) ``` @@ -2134,8 +1916,6 @@ The following optional channels are supported: If the **x** channel is not specified, the tick will span the full vertical extent of the plot (or facet). - - ### Vector [a vector field](https://observablehq.com/@observablehq/plot-vector) @@ -2176,40 +1956,24 @@ Vectors are drawn in input order, with the last data drawn on top. If sorting is #### Plot.vector(*data*, *options*) - - ```js Plot.vector(wind, {x: "longitude", y: "latitude", length: "speed", rotate: "direction"}) ``` Returns a new vector with the given *data* and *options*. If neither the **x** nor **y** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …]. - - #### Plot.vectorX(*data*, *options*) - - Equivalent to [Plot.vector](#plotvectordata-options) except that if the **x** option is not specified, it defaults to the identity function and assumes that *data* = [*x₀*, *x₁*, *x₂*, …]. - - #### Plot.vectorY(*data*, *options*) - - Equivalent to [Plot.vector](#plotvectordata-options) except that if the **y** option is not specified, it defaults to the identity function and assumes that *data* = [*y₀*, *y₁*, *y₂*, …]. - - #### Plot.spike(*data*, *options*) - - Equivalent to [Plot.vector](#plotvectordata-options) except that the **shape** defaults to *spike*, the **stroke** defaults to *currentColor*, the **strokeWidth** defaults to 1, the **fill** defaults to **stroke**, the **fillOpacity** defaults to 0.3, and the **anchor** defaults to *start*. - - ## Decorations Decorations are static marks that do not represent data. Currently this includes only [Plot.frame](#frame), although internally Plot’s axes are implemented as decorations and may in the future be exposed here for more flexible configuration. @@ -2226,16 +1990,12 @@ If the **anchor** option is specified as one of *left*, *right*, *top*, or *bott #### Plot.frame(*options*) - - ```js Plot.frame({stroke: "red"}) ``` Returns a new frame with the specified *options*. - - ## Transforms Plot’s transforms provide a convenient mechanism for transforming data as part of a plot specification. All marks support the following basic transforms: @@ -2282,52 +2042,36 @@ The *filter*, *sort* and *reverse* transforms are also available as functions, a #### Plot.sort(*order*, *options*) - - ```js Plot.sort("body_mass_g", options) // show data in ascending body mass order ``` Sorts the data by the specified *order*, which can be an accessor function, a comparator function, or a channel value definition such as a field name. See also [index sorting](#sort-options), which allows marks to be sorted by a named channel, such as *r* for radius. - - #### Plot.shuffle(*options*) - - ```js Plot.shuffle(options) // show data in random order ``` Shuffles the data randomly. If a *seed* option is specified, a linear congruential generator with the given seed is used to generate random numbers deterministically; otherwise, Math.random is used. - - #### Plot.reverse(*options*) - - ```js Plot.reverse(options) // reverse the input order ``` Reverses the order of the data. - - #### Plot.filter(*test*, *options*) - - ```js Plot.filter(d => d.body_mass_g > 3000, options) // show data whose body mass is greater than 3kg ``` Filters the data given the specified *test*. The test can be given as an accessor function (which receives the datum and index), or as a channel value definition such as a field name; truthy values are retained. - - ### Bin [a histogram of athletes by weight](https://observablehq.com/@observablehq/plot-bin) @@ -2450,66 +2194,46 @@ Lastly, the bin transform changes the default [mark insets](#marks): rather than #### Plot.bin(*outputs*, *options*) - - ```js Plot.rect(athletes, Plot.bin({fillOpacity: "count"}, {x: "weight", y: "height"})) ``` Bins on *x* and *y*. Also groups on the first channel of *z*, *fill*, or *stroke*, if any. - - #### Plot.binX(*outputs*, *options*) - - ```js Plot.rectY(athletes, Plot.binX({y: "count"}, {x: "weight"})) ``` Bins on *x*. Also groups on *y* and the first channel of *z*, *fill*, or *stroke*, if any. - - #### Plot.binY(*outputs*, *options*) - - ```js Plot.rectX(athletes, Plot.binY({x: "count"}, {y: "weight"})) ``` Bins on *y*. Also groups on *x* and first channel of *z*, *fill*, or *stroke*, if any. - - ### Centroid #### Plot.centroid(*options*) - - The centroid initializer derives **x** and **y** channels representing the planar (projected) centroids for the given GeoJSON geometry. If the **geometry** option is not specified, the mark’s data is assumed to be GeoJSON objects. ```js Plot.dot(regions.features, Plot.centroid()).plot({projection: "reflect-y"}) ``` - - #### Plot.geoCentroid(*options*) - - The geoCentroid transform derives **x** and **y** channels representing the spherical centroids for the given GeoJSON geometry. If the **geometry** option is not specified, the mark’s data is assumed to be GeoJSON objects. ```js Plot.dot(counties.features, Plot.geoCentroid()).plot({projection: "albers-usa"}) ``` - - ### Group [a histogram of penguins by species](https://observablehq.com/@observablehq/plot-group) @@ -2587,52 +2311,36 @@ The default reducer for the **title** channel returns a summary list of the top #### Plot.group(*outputs*, *options*) - - ```js Plot.group({fill: "count"}, {x: "island", y: "species"}) ``` Groups on *x*, *y*, and the first channel of *z*, *fill*, or *stroke*, if any. - - #### Plot.groupX(*outputs*, *options*) - - ```js Plot.groupX({y: "sum"}, {x: "species", y: "body_mass_g"}) ``` Groups on *x* and the first channel of *z*, *fill*, or *stroke*, if any. - - #### Plot.groupY(*outputs*, *options*) - - ```js Plot.groupY({x: "sum"}, {y: "species", x: "body_mass_g"}) ``` Groups on *y* and the first channel of *z*, *fill*, or *stroke*, if any. - - #### Plot.groupZ(*outputs*, *options*) - - ```js Plot.groupZ({x: "proportion"}, {fill: "species"}) ``` Groups on the first channel of *z*, *fill*, or *stroke*, if any. If none of *z*, *fill*, or *stroke* are channels, then all data (within each facet) is placed into a single group. - - ### Map [moving averages of daily highs and lows](https://observablehq.com/@observablehq/plot-map) @@ -2697,76 +2405,52 @@ By default, **anchor** is *middle* and **reduce** is *mean*. #### Plot.map(*outputs*, *options*) - - ```js Plot.map({y: "cumsum"}, {y: d3.randomNormal()}) ``` Groups on the first channel of *z*, *fill*, or *stroke*, if any, and then for each channel declared in the specified *outputs* object, applies the corresponding map method. Each channel in *outputs* must have a corresponding input channel in *options*. - - #### Plot.mapX(*map*, *options*) - - ```js Plot.mapX("cumsum", {x: d3.randomNormal()}) ``` Equivalent to Plot.map({x: *map*, x1: *map*, x2: *map*}, *options*), but ignores any of **x**, **x1**, and **x2** not present in *options*. - - #### Plot.mapY(*map*, *options*) - - ```js Plot.mapY("cumsum", {y: d3.randomNormal()}) ``` Equivalent to Plot.map({y: *map*, y1: *map*, y2: *map*}, *options*), but ignores any of **y**, **y1**, and **y2** not present in *options*. - - #### Plot.normalize(*basis*) - - ```js Plot.map({y: Plot.normalize("first")}, {x: "Date", y: "Close", stroke: "Symbol"}) ``` Returns a normalize map method for the given *basis*, suitable for use with Plot.map. - - #### Plot.normalizeX(*basis*, *options*) - - ```js Plot.normalizeX("first", {y: "Date", x: "Close", stroke: "Symbol"}) ``` Like [Plot.mapX](#plotmapxmap-options), but applies the normalize map method with the given *basis*. - - #### Plot.normalizeY(*basis*, *options*) - - ```js Plot.normalizeY("first", {x: "Date", y: "Close", stroke: "Symbol"}) ``` Like [Plot.mapY](#plotmapymap-options), but applies the normalize map method with the given *basis*. - - #### Plot.window(*k*) ```js @@ -2801,8 +2485,6 @@ The select transform derives a filtered mark index; it does not affect the mark #### Plot.select(*selector*, *options*) - - Selects the points of each series selected by the *selector*, which can be specified either as a function which receives as input the index of the series, the shorthand “first” or “last”, or as a {*key*: *value*} object with exactly one *key* being the name of a channel and the *value* being a function which receives as input the index of the series and the channel values. The *value* may alternatively be specified as the shorthand “min” and “max” which respectively select the minimum and maximum points for the specified channel. For example, to select the point within each series that is the closest to the median of the *y* channel: @@ -2833,56 +2515,30 @@ To pick the point in each city with the highest temperature: Plot.select({fill: "max"}, {x: "date", y: "city", fill: "temperature", z: "city"}) ``` - - #### Plot.selectFirst(*options*) - - Selects the first point of each series according to input order. - - #### Plot.selectLast(*options*) - - Selects the last point of each series according to input order. - - #### Plot.selectMinX(*options*) - - Selects the leftmost point of each series. - - #### Plot.selectMinY(*options*) - - Selects the lowest point of each series. - - #### Plot.selectMaxX(*options*) - - Selects the rightmost point of each series. - - #### Plot.selectMaxY(*options*) - - Selects the highest point of each series. - - ### Stack [a stacked area chart of revenue by category](https://observablehq.com/@observablehq/plot-stack) @@ -2929,76 +2585,52 @@ If two arguments are passed to the stack transform functions below, the stack-sp #### Plot.stackY(*stack*, *options*) - - ```js Plot.stackY({x: "year", y: "revenue", z: "format", fill: "group"}) ``` Creates new channels **y1** and **y2**, obtained by stacking the original **y** channel for data points that share a common **x** (and possibly **z**) value. A new **y** channel is also returned, which lazily computes the middle value of **y1** and **y2**. The input **y** channel defaults to a constant 1, resulting in a count of the data points. The stack options (*offset*, *order*, and *reverse*) may be specified as part of the *options* object, if the only argument, or as a separate *stack* options argument. - - #### Plot.stackY1(*stack*, *options*) - - ```js Plot.stackY1({x: "year", y: "revenue", z: "format", fill: "group"}) ``` Equivalent to [Plot.stackY](#plotstackystack-options), except that the **y1** channel is returned as the **y** channel. This can be used, for example, to draw a line at the bottom of each stacked area. - - #### Plot.stackY2(*stack*, *options*) - - ```js Plot.stackY2({x: "year", y: "revenue", z: "format", fill: "group"}) ``` Equivalent to [Plot.stackY](#plotstackystack-options), except that the **y2** channel is returned as the **y** channel. This can be used, for example, to draw a line at the top of each stacked area. - - #### Plot.stackX(*stack*, *options*) - - ```js Plot.stackX({y: "year", x: "revenue", z: "format", fill: "group"}) ``` See Plot.stackY, but with *x* as the input value channel, *y* as the stack index, *x1*, *x2* and *x* as the output channels. - - #### Plot.stackX1(*stack*, *options*) - - ```js Plot.stackX1({y: "year", x: "revenue", z: "format", fill: "group"}) ``` Equivalent to [Plot.stackX](#plotstackxstack-options), except that the **x1** channel is returned as the **x** channel. This can be used, for example, to draw a line at the left edge of each stacked area. - - #### Plot.stackX2(*stack*, *options*) - - ```js Plot.stackX2({y: "year", x: "revenue", z: "format", fill: "group"}) ``` Equivalent to [Plot.stackX](#plotstackxstack-options), except that the **x2** channel is returned as the **x** channel. This can be used, for example, to draw a line at the right edge of each stacked area. - - ### Tree [a node-link tree diagram representing a software hierarchy](https://observablehq.com/@observablehq/plot-tree) @@ -3055,8 +2687,6 @@ The default **treeLayout** implements the Reingold–Tilford “tidy” algorith #### Plot.treeNode(*options*) - - Based on the tree options described above, populates the **x** and **y** channels with the positions for each node. The following defaults are also applied: the default **frameAnchor** inherits the **treeAnchor**. This transform is intended to be used with [dot](#dot), [text](#text), and other point-based marks. This transform is rarely used directly; see the [Plot.tree compound mark](#plottreedata-options). The treeNode transform will derive output columns for any *options* that have one of the following named node values: @@ -3069,12 +2699,8 @@ The treeNode transform will derive output columns for any *options* that have on In addition, if any option value is specified as an object with a **node** method, a derived output column will be generated by invoking the **node** method for each node in the tree. - - #### Plot.treeLink(*options*) - - Based on the tree options described above, populates the **x1**, **y1**, **x2**, and **y2** channels. The following defaults are also applied: the default **curve** is *bump-x*, the default **stroke** is #555, the default **strokeWidth** is 1.5, and the default **strokeOpacity** is 0.5. This transform is intended to be used with [link](#link), [arrow](#arrow), and other two-point-based marks. This transform is rarely used directly; see the [Plot.tree compound mark](#plottreedata-options). The treeLink transform will derive output columns for any *options* that have one of the following named link values: @@ -3091,12 +2717,8 @@ The treeLink transform will derive output columns for any *options* that have on In addition, if any option value is specified as an object with a **node** method, a derived output column will be generated by invoking the **node** method for each child node in the tree; likewise if any option value is specified as an object with a **link** method, a derived output column will be generated by invoking the **link** method for each link in the tree, being passed two node arguments, the child and the parent. - - #### Plot.tree(*data*, *options*) - - A convenience compound mark for rendering a tree diagram, including a [link](#link) to render links from parent to child, an optional [dot](#dot) for nodes, and a [text](#text) for node labels. The link mark uses the [treeLink transform](#plottreelinkoptions), while the dot and text marks use the [treeNode transform](#plottreenodeoptions). The following options are supported: * **fill** - the dot and text fill color; defaults to *node:internal* @@ -3120,16 +2742,10 @@ A convenience compound mark for rendering a tree diagram, including a [link](#li Any additional *options* are passed through to the constituent link, dot, and text marks and their corresponding treeLink or treeNode transform. - - #### Plot.cluster(*data*, *options*) - - Like [Plot.tree](#plottreedata-options), except sets the **treeLayout** option to D3’s cluster (dendrogram) algorithm, which aligns leaf nodes. - - ### Custom transforms The **transform** option defines a custom transform function, allowing data, indexes, or channels to be derived prior to rendering. Custom transforms are rarely implemented directly; see the built-in transforms above. The transform function (if present) is passed two arguments, *data* and *facets*, representing the mark’s data and facet indexes; it must then return a {data, facets} object representing the resulting transformed data and facet indexes. The *facets* are represented as a nested array of arrays such as [[0, 1, 3, …], [2, 5, 10, …], …]; each element in *facets* specifies the zero-based indexes of elements in *data* that are in a given facet (*i.e.*, have a distinct value in the associated *fx* or *fy* dimension). @@ -3140,8 +2756,6 @@ Plot provides a few helpers for implementing transforms. #### Plot.valueof(*data*, *value*, *type*) - - Given an iterable *data* and some *value* accessor, returns an array (a column) of the specified *type* with the corresponding value of each element of the data. The *value* accessor may be one of the following types: * a string - corresponding to the field accessor (`d => d[value]`) @@ -3155,38 +2769,24 @@ If *type* is specified, it must be Array or a similar class that implements the Plot.valueof is not guaranteed to return a new array. When a transform method is used, or when the given *value* is an array that is compatible with the requested *type*, the array may be returned as-is without making a copy. - - #### Plot.transform(*options*, *transform*) - - Given an *options* object that may specify some basic transforms (*filter*, *sort*, or *reverse*) or a custom *transform* function, composes those transforms if any with the given *transform* function, returning a new *options* object. If a custom *transform* function is present on the given *options*, any basic transforms are ignored. Any additional input *options* are passed through in the returned *options* object. This method facilitates applying the basic transforms prior to applying the given custom *transform* and is used internally by Plot’s built-in transforms. - - #### Plot.column(*source*) - - This helper for constructing derived columns returns a [*column*, *setColumn*] array. The *column* object implements *column*.transform, returning whatever value was most recently passed to *setColumn*. If *setColumn* is not called, then *column*.transform returns undefined. If a *source* is specified, then *column*.label exposes the given *source*’s label, if any: if *source* is a string as when representing a named field of data, then *column*.label is *source*; otherwise *column*.label propagates *source*.label. This allows derived columns to propagate a human-readable axis or legend label. Plot.column is typically used by options transforms to define new channels; the associated columns are populated (derived) when the **transform** option function is invoked. - - #### Plot.identity - - This channel helper returns a source array as-is, avoiding an extra copy when defining a channel as being equal to the data: ```js Plot.raster(await readValues(), {width: 300, height: 200, fill: Plot.identity}) ``` - - ## Initializers Initializers can be used to transform and derive new channels prior to rendering. Unlike transforms which operate in abstract data space, initializers can operate in screen space such as pixel coordinates and colors. For example, initializers can modify a marks’ positions to avoid occlusion. Initializers are invoked *after* the initial scales are constructed and can modify the channels or derive new channels; these in turn may (or may not, as desired) be passed to scales. @@ -3206,28 +2806,20 @@ The dodge layout is highly dependent on the input data order: the circles placed #### Plot.dodgeY(*dodgeOptions*, *options*) - - ```js Plot.dodgeY({x: "date"}) ``` Given marks arranged along the *x* axis, the dodgeY transform piles them vertically by defining a *y* position channel that avoids overlapping. The *x* position channel is unchanged. - - #### Plot.dodgeX(*dodgeOptions*, *options*) - - ```js Plot.dodgeX({y: "value"}) ``` Equivalent to Plot.dodgeY, but piling horizontally, creating a new *x* position channel that avoids overlapping. The *y* position channel is unchanged. - - ### Hexbin [a chart showing the inverse relationship of fuel economy to engine displacement, and the positive correlation of engine displacement and weight; hexagonal bins of varying size represent the number of cars at each location, while color encodes the mean weight of nearby cars](https://observablehq.com/@observablehq/plot-hexbin) @@ -3236,8 +2828,6 @@ Equivalent to Plot.dodgeY, but piling horizontally, creating a new *x* position #### Plot.hexbin(*outputs*, *options*) - - Aggregates the given input channels into hexagonal bins, creating output channels with the reduced data. The *options* must specify the **x** and **y** channels. The **binWidth** option (default 20) defines the distance between centers of neighboring hexagons in pixels. If any of **z**, **fill**, or **stroke** is a channel, the first of these channels will be used to subdivide bins. The *outputs* options are similar to the [bin transform](#bin); each output channel receives as input, for each hexagon, the subset of the data which has been matched to its center. The outputs object specifies the aggregation method for each output channel. The following aggregation methods are supported: @@ -3263,8 +2853,6 @@ The following aggregation methods are supported: See also the [hexgrid](#hexgrid) mark. - - ### Custom initializers You can specify a custom initializer by specifying a function as the mark **initializer** option. This function is called after the scales have been computed, and receives as inputs the (possibly transformed) array of *data*, the *facets* index of elements of this array that belong to each facet, the input *channels* (as an object of named channels), the *scales*, and the *dimensions*. The mark itself is the *this* context. The initializer function must return an object with *data*, *facets*, and new *channels*. Any new channels are merged with existing channels, replacing channels of the same name. @@ -3273,12 +2861,8 @@ If an initializer desires a channel that is not supported by the downstream mark #### Plot.initializer(*options*, *initializer*) - - This helper composes the *initializer* function with any other transforms present in the *options*, and returns a new *options* object. - - ## Curves A curve defines how to turn a discrete representation of a line as a sequence of points [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] into a continuous path; *i.e.*, how to interpolate between points. Curves are used by the [line](#line), [area](#area), and [link](#link) mark, and are implemented by [d3-shape](https://github.com/d3/d3-shape/blob/main/README.md#curves). @@ -3338,36 +2922,20 @@ So, *x*[*index*[0]] represents the *x*-position of the first sample, *y*[*index* #### Plot.interpolateNone(*index*, *width*, *height*, *x*, *y*, *value*) - - Applies a simple forward mapping of samples, binning them into pixels in the raster grid without any blending or interpolation. If multiple samples map to the same pixel, the last one wins; this can introduce bias if the points are not in random order, so use [Plot.shuffle](#plotshuffleoptions) to randomize the input if needed. - - #### Plot.interpolateNearest(*index*, *width*, *height*, *x*, *y*, *value*) - - Assigns each pixel in the raster grid the value of the closest sample; effectively a Voronoi diagram. - - #### Plot.interpolatorBarycentric({*random*}) - - Constructs a Delaunay triangulation of the samples, and then for each pixel in the raster grid, determines the triangle that covers the pixel’s centroid and interpolates the values associated with the triangle’s vertices using [barycentric coordinates](https://en.wikipedia.org/wiki/Barycentric_coordinate_system). If the interpolated values are ordinal or categorical (_i.e._, anything other than numbers or dates), then one of the three values will be picked randomly weighted by the barycentric coordinates; the given *random* number generator will be used, which defaults to a [linear congruential generator](https://github.com/d3/d3-random/blob/main/README.md#randomLcg) with a fixed seed (for deterministic results). - - #### Plot.interpolatorRandomWalk({*random*, *minDistance* = 0.5, *maxSteps* = 2}) - - For each pixel in the raster grid, initiates a random walk, stopping when either the walk is within a given distance (*minDistance*) of a sample or the maximum allowable number of steps (*maxSteps*) have been taken, and then assigning the current pixel the closest sample’s value. The random walk uses the “walk on spheres” algorithm in two dimensions described by [Sawhney and Crane](https://www.cs.cmu.edu/~kmcrane/Projects/MonteCarloGeometryProcessing/index.html), SIGGRAPH 2020. - - ## Markers A [marker](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker) defines a graphic drawn on vertices of a [line](#line) or a [link](#link) mark. The supported marker options are: @@ -3395,40 +2963,28 @@ These helper functions are provided for use as a *scale*.tickFormat [axis option #### Plot.formatIsoDate(*date*) - - ```js Plot.formatIsoDate(new Date("2020-01-01T00:00.000Z")) // "2020-01-01" ``` Given a *date*, returns the shortest equivalent ISO 8601 UTC string. If the given *date* is not valid, returns `"Invalid Date"`. - - #### Plot.formatWeekday(*locale*, *format*) - - ```js Plot.formatWeekday("es-MX", "long")(0) // "domingo" ``` Returns a function that formats a given week day number (from 0 = Sunday to 6 = Saturday) according to the specified *locale* and *format*. The *locale* is a [BCP 47 language tag](https://tools.ietf.org/html/bcp47) and defaults to U.S. English. The *format* is a [weekday format](https://tc39.es/ecma402/#datetimeformat-objects): either *narrow*, *short*, or *long*; if not specified, it defaults to *short*. - - #### Plot.formatMonth(*locale*, *format*) - - ```js Plot.formatMonth("es-MX", "long")(0) // "enero" ``` Returns a function that formats a given month number (from 0 = January to 11 = December) according to the specified *locale* and *format*. The *locale* is a [BCP 47 language tag](https://tools.ietf.org/html/bcp47) and defaults to U.S. English. The *format* is a [month format](https://tc39.es/ecma402/#datetimeformat-objects): either *2-digit*, *numeric*, *narrow*, *short*, *long*; if not specified, it defaults to *short*. - - ## Accessibility Plot supports several [ARIA properties](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) to help build the accessibility tree. The accessibility tree is consumed by various assistive technology such as screen readers and browser add-ons to make web contents and web applications more accessible to people with disabilities. It can be inspected in the browser’s inspector. diff --git a/bundle.js b/bundle.js index 3e529a5d68..b475ebe70e 100644 --- a/bundle.js +++ b/bundle.js @@ -1,2 +1,2 @@ export {version} from "./package.json"; -export * from "./dist/index.js"; +export * from "./src/index.js"; diff --git a/package.json b/package.json index ee7e0fbbb9..66b6f6cd7d 100644 --- a/package.json +++ b/package.json @@ -8,31 +8,31 @@ }, "license": "ISC", "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", + "main": "src/index.js", + "module": "src/index.js", "jsdelivr": "dist/plot.umd.min.js", "unpkg": "dist/plot.umd.min.js", "exports": { - "mocha": "./src/index.js", "umd": "./dist/plot.umd.min.js", - "default": "./dist/index.js" + "default": "./src/index.js" }, + "types": "src/index.d.ts", "repository": { "type": "git", "url": "https://github.com/observablehq/plot.git" }, "files": [ "dist/**/*.js", - "src/**/*.js", - "src/**/*.ts", - "types/**/*.d.ts" + "src/**/*.d.ts", + "src/**/*.js" ], "scripts": { - "test": "yarn test:mocha && yarn test:typecheck && yarn test:lint", - "test:mocha": "mkdir -p test/output && mocha --conditions=mocha 'test/**/*-test.*' 'test/plot.js'", + "test": "yarn test:mocha && yarn test:tsc && yarn test:lint && yarn test:prettier", + "test:mocha": "mkdir -p test/output && mocha 'test/**/*-test.*' 'test/plot.js'", "test:lint": "eslint src test", - "test:typecheck": "tsc --noEmit", - "prepublishOnly": "rm -rf build dist && tsc && tsx scripts/readme-to-jsdoc.ts && rollup -c", + "test:prettier": "prettier --check src test", + "test:tsc": "tsc", + "prepublishOnly": "rm -rf dist && rollup -c", "postpublish": "git push && git push --tags", "dev": "vite" }, @@ -49,28 +49,22 @@ "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-terser": "^0.1.0", "@types/d3": "^7.4.0", - "@types/mocha": "^10.0.1", - "@types/node": "^18.11.13", - "@typescript-eslint/eslint-plugin": "^5.25.0", - "@typescript-eslint/parser": "^5.25.0", + "@typescript-eslint/eslint-plugin": "^5.54.1", + "@typescript-eslint/parser": "^5.54.1", "canvas": "^2.0.0", "d3-geo-projection": "^4.0.0", "eslint": "^8.16.0", "eslint-config-prettier": "^8.5.0", "get-tsconfig": "^4.1.0", - "glob": "^8.0.3", "htl": "^0.3.0", "js-beautify": "1", "jsdom": "^21.0.0", - "mkdirp": "^1.0.4", "mocha": "^10.0.0", "module-alias": "^2.0.0", "prettier": "^2.7.1", "rollup": "^3.7.0", "topojson-client": "^3.1.0", - "tsx": "^3.8.0", - "typescript": "^4.6.4", - "typescript-module-alias": "^2.2.9", + "typescript": "^4.9.5", "vite": "^4.0.0" }, "dependencies": { diff --git a/scripts/readme-to-jsdoc.ts b/scripts/readme-to-jsdoc.ts deleted file mode 100755 index 4a30c3b66a..0000000000 --- a/scripts/readme-to-jsdoc.ts +++ /dev/null @@ -1,80 +0,0 @@ -import {readFileSync, writeFileSync} from "fs"; -import {dirname} from "path"; -import glob from "glob"; -import mkdirp from "mkdirp"; - -// Extract the documentation from the README. -const readme = readFileSync("./README.md", "utf-8"); -const docmap = new Map(); -let doc: {name: string; lines: string[]} | null = null; -for (const [i, line] of readme.split(/\r?\n/).entries()) { - if (/$/.exec(line))) { - const [, name] = match; - if (doc) { - throw new Error(`nested jsdoc directive on line ${i}: ${line}`); - } - if (docmap.has(name)) { - throw new Error(`duplicate jsdoc directive on line ${i}: ${line}`); - } - doc = {name, lines: []}; - } else if ((match = /^$/.exec(line))) { - const [, name] = match; - if (!doc) { - throw new Error(`orphaned jsdocEnd directive on line ${i}: ${line}`); - } - if (doc.name !== name) { - throw new Error(`mismatched jsdocEnd ${doc.name} directive on line ${i}: ${line}`); - } - docmap.set(doc.name, doc.lines); - doc = null; - } else { - throw new Error(`malformed jsdoc directive on line ${i}: ${line}`); - } - } else if (doc) { - doc.lines.push(line); - } -} - -// Make relative and anchor links absolute. -for (const lines of docmap.values()) { - for (let i = 0, n = lines.length; i < n; ++i) { - lines[i] = lines[i] - .replace(/\]\(#([^)]+)\)/g, "](./README.md#$1)") - .replace(/\]\(\.\/([^)]+)\)/g, "](https://github.com/observablehq/plot/blob/main/$1)"); - } -} - -// Copy files from build/ to dist/, replacing /** @jsdoc name */ directives. -const unused = new Set(docmap.keys()); -for (const file of glob.sync("build/**/*.js")) { - process.stdout.write(`\x1b[2m${file}\x1b[0m`); - const lines = readFileSync(file, "utf-8").split("\n"); - let count = 0; - for (let i = 0, n = lines.length; i < n; ++i) { - let match: RegExpExecArray | null; - if ((match = /^(\s*(?:\/\*)?\*\s+)@jsdoc\s+(\w+)((?:\s*\*\/)?\s*)$/.exec(lines[i]))) { - const [, pre, name, post] = match; - const docs = docmap.get(name); - if (!docs) throw new Error(`missing @jsdoc definition: ${name}`); - if (!unused.has(name)) throw new Error(`duplicate @jsdoc reference: ${name}`); - unused.delete(name); - ++count; - lines[i] = docs - .map((line, i, lines) => - i === 0 ? `${pre}${line}` : i === lines.length - 1 ? ` * ${line}${post ? `\n${post}` : ""}` : ` * ${line}` - ) - .join("\n"); - } - } - const ofile = file.replace(/^build\//, "dist/"); - process.stdout.write(` → \x1b[36m${ofile}\x1b[0m${count ? ` (${count} jsdoc${count === 1 ? "" : "s"})` : ""}\n`); - const odir = dirname(ofile); - mkdirp.sync(odir); - writeFileSync(ofile, lines.join("\n"), "utf-8"); -} - -for (const name of unused) { - console.warn(`\x1b[33m[warning] unused @jsdoc directive:\x1b[0m ${name}`); -} diff --git a/src/channel.d.ts b/src/channel.d.ts new file mode 100644 index 0000000000..2637240f33 --- /dev/null +++ b/src/channel.d.ts @@ -0,0 +1,84 @@ +import type {Interval} from "./interval.js"; +import type {Reducer} from "./reducer.js"; +import type {ScaleName, ScaleType} from "./scales.js"; + +export interface ChannelTransform { + transform: (data: any[]) => any[]; +} + +export type ChannelName = + | "ariaLabel" + | "fill" + | "fillOpacity" + | "fontSize" + | "fx" + | "fy" + | "geometry" + | "height" + | "href" + | "length" + | "opacity" + | "path" + | "r" + | "rotate" + | "src" + | "stroke" + | "strokeOpacity" + | "strokeWidth" + | "symbol" + | "text" + | "title" + | "weight" + | "width" + | "x" + | "x1" + | "x2" + | "y" + | "y1" + | "y2" + | "z"; + +export type Channels = {[key in ChannelName]?: Channel}; + +export interface Channel { + value: ChannelValueSpec | null; + scale?: ScaleName | "auto" | boolean; + type?: ScaleType; + optional?: boolean; + filter?: (value: any) => boolean; + hint?: any; // TODO +} + +export type ChannelValue = + | Iterable // column of values + | (string & Record) // field or literal color; see also https://github.com/microsoft/TypeScript/issues/29729 + | Date // constant + | number // constant + | boolean // constant + | null // constant + | ((d: any, i: number) => any) // function of data + | ChannelTransform; // function of data + +export type ChannelValueSpec = ChannelValue | {value: ChannelValue; scale?: Channel["scale"]}; // TODO label + +export type ChannelValueIntervalSpec = ChannelValueSpec | {value: ChannelValue; interval?: Interval}; // TODO scale override? + +export type ChannelReducerSpec = Reducer | {reduce: Reducer; scale?: Channel["scale"]}; + +export type ChannelDomainValue = ChannelName | "data" | "width" | "height" | null; + +export interface ChannelDomainOptions { + reduce?: Reducer; + reverse?: boolean; + limit?: number; +} + +export type ChannelDomainSort = { + [key in ScaleName]?: + | ChannelDomainValue + | ({ + value: ChannelDomainValue; + } & ChannelDomainOptions); +} & ChannelDomainOptions; + +export type ChannelReducers = {[key in ChannelName]?: ChannelReducerSpec | null}; diff --git a/src/channel.js b/src/channel.js index 20231f7552..b56e826e62 100644 --- a/src/channel.js +++ b/src/channel.js @@ -1,11 +1,11 @@ import {ascending, descending, InternSet, rollup, sort} from "d3"; import {first, isColor, isEvery, isIterable, isOpacity, labelof, map, maybeValue, range, valueof} from "./options.js"; import {registry} from "./scales/index.js"; -import {isSymbol, maybeSymbol} from "./symbols.js"; +import {isSymbol, maybeSymbol} from "./symbol.js"; import {maybeReduce} from "./transforms/group.js"; // TODO Type coercion? -export function Channel(data, {scale, type, value, filter, hint}, name) { +export function createChannel(data, {scale, type, value, filter, hint}, name) { return inferChannelScale(name, { scale, type, @@ -16,8 +16,10 @@ export function Channel(data, {scale, type, value, filter, hint}, name) { }); } -export function Channels(channels, data) { - return Object.fromEntries(Object.entries(channels).map(([name, channel]) => [name, Channel(data, channel, name)])); +export function createChannels(channels, data) { + return Object.fromEntries( + Object.entries(channels).map(([name, channel]) => [name, createChannel(data, channel, name)]) + ); } // TODO Use Float64Array for scales with numeric ranges, e.g. position? diff --git a/src/color.d.ts b/src/color.d.ts new file mode 100644 index 0000000000..f018b37068 --- /dev/null +++ b/src/color.d.ts @@ -0,0 +1,55 @@ +export type ColorInterpolateName = "rgb" | "hsl" | "hcl" | "lab"; + +type ColorSchemeNameCase = + | "Accent" + | "Category10" + | "Dark2" + | "Paired" + | "Pastel1" + | "Pastel2" + | "Set1" + | "Set2" + | "Set3" + | "Tableau10" + | "BrBG" + | "PRGn" + | "PiYG" + | "PuOr" + | "RdBu" + | "RdGy" + | "RdYlBu" + | "RdYlGn" + | "Spectral" + | "BuRd" + | "BuYlRd" + | "Blues" + | "Greens" + | "Greys" + | "Oranges" + | "Purples" + | "Reds" + | "Turbo" + | "Viridis" + | "Magma" + | "Inferno" + | "Plasma" + | "Cividis" + | "Cubehelix" + | "Warm" + | "Cool" + | "BuGn" + | "BuPu" + | "GnBu" + | "OrRd" + | "PuBu" + | "PuBuGn" + | "PuRd" + | "RdPu" + | "YlGn" + | "YlGnBu" + | "YlOrBr" + | "YlOrRd" + | "Rainbow" + | "Sinebow"; + +export type ColorSchemeName = ColorSchemeNameCase | Lowercase; diff --git a/src/context.d.ts b/src/context.d.ts new file mode 100644 index 0000000000..2be2bbe907 --- /dev/null +++ b/src/context.d.ts @@ -0,0 +1,6 @@ +import type {GeoStreamWrapper} from "d3"; + +export interface Context { + document: Document; + projection?: GeoStreamWrapper; +} diff --git a/src/context.js b/src/context.js index b48f339db0..be41e6802e 100644 --- a/src/context.js +++ b/src/context.js @@ -1,9 +1,9 @@ import {creator, select} from "d3"; -import {Projection} from "./projection.js"; +import {createProjection} from "./projection.js"; -export function Context(options = {}, dimensions) { +export function createContext(options = {}, dimensions) { const {document = typeof window !== "undefined" ? window.document : undefined} = options; - return {document, projection: Projection(options, dimensions)}; + return {document, projection: createProjection(options, dimensions)}; } export function create(name, {document}) { diff --git a/src/curve.d.ts b/src/curve.d.ts new file mode 100644 index 0000000000..7dc0cbba17 --- /dev/null +++ b/src/curve.d.ts @@ -0,0 +1,37 @@ +import type {CurveFactory} from "d3"; + +export type CurveFunction = CurveFactory; + +export type CurveName = + | "basis" + | "basis-closed" + | "basis-open" + | "bundle" + | "bump-x" + | "bump-y" + | "cardinal" + | "cardinal-closed" + | "cardinal-open" + | "catmull-rom" + | "catmull-rom-closed" + | "catmull-rom-open" + | "linear" + | "linear-closed" + | "monotone-x" + | "monotone-y" + | "natural" + | "step" + | "step-after" + | "step-before"; + +export type Curve = CurveName | CurveFunction; + +export interface CurveOptions { + curve?: CurveOptions; + tension?: number; +} + +export interface CurveAutoOptions { + curve?: Curve | "auto"; + tension?: number; +} diff --git a/src/curve.ts b/src/curve.js similarity index 64% rename from src/curve.ts rename to src/curve.js index f9ef6ff4c7..8ca33d59b4 100644 --- a/src/curve.ts +++ b/src/curve.js @@ -20,39 +20,8 @@ import { curveStepAfter, curveStepBefore } from "d3"; -import type { - CurveFactory, - CurveBundleFactory, - CurveCardinalFactory, - CurveCatmullRomFactory, - CurveGenerator, - Path -} from "d3"; - -type CurveFunction = CurveFactory | CurveBundleFactory | CurveCardinalFactory | CurveCatmullRomFactory; -type CurveName = - | "basis" - | "basis-closed" - | "basis-open" - | "bundle" - | "bump-x" - | "bump-y" - | "cardinal" - | "cardinal-closed" - | "cardinal-open" - | "catmull-rom" - | "catmull-rom-closed" - | "catmull-rom-open" - | "linear" - | "linear-closed" - | "monotone-x" - | "monotone-y" - | "natural" - | "step" - | "step-after" - | "step-before"; -const curves = new Map([ +const curves = new Map([ ["basis", curveBasis], ["basis-closed", curveBasisClosed], ["basis-open", curveBasisOpen], @@ -75,9 +44,9 @@ const curves = new Map([ ["step-before", curveStepBefore] ]); -export function Curve(curve: CurveName | CurveFunction = curveLinear, tension?: number): CurveFunction { +export function maybeCurve(curve = curveLinear, tension) { if (typeof curve === "function") return curve; // custom curve - const c = curves.get(`${curve}`.toLowerCase() as CurveName); + const c = curves.get(`${curve}`.toLowerCase()); if (!c) throw new Error(`unknown curve: ${curve}`); if (tension !== undefined) { if ("beta" in c) { @@ -93,13 +62,13 @@ export function Curve(curve: CurveName | CurveFunction = curveLinear, tension?: // For the “auto” curve, return a symbol instead of a curve implementation; // we’ll use d3.geoPath to render if there’s a projection. -export function PathCurve(curve: CurveName | CurveFunction = curveAuto, tension?: number): CurveFunction { - return typeof curve !== "function" && `${curve}`.toLowerCase() === "auto" ? curveAuto : Curve(curve, tension); +export function maybeCurveAuto(curve = curveAuto, tension) { + return typeof curve !== "function" && `${curve}`.toLowerCase() === "auto" ? curveAuto : maybeCurve(curve, tension); } // This is a special built-in curve that will use d3.geoPath when there is a // projection, and the linear curve when there is not. You can explicitly // opt-out of d3.geoPath and instead use d3.line with the "linear" curve. -export function curveAuto(context: CanvasRenderingContext2D | Path): CurveGenerator { +export function curveAuto(context) { return curveLinear(context); } diff --git a/src/defined.js b/src/defined.js new file mode 100644 index 0000000000..27c2170fc2 --- /dev/null +++ b/src/defined.js @@ -0,0 +1,29 @@ +import {ascending, descending} from "d3"; + +export function defined(x) { + return x != null && !Number.isNaN(x); +} + +export function ascendingDefined(a, b) { + return +defined(b) - +defined(a) || ascending(a, b); +} + +export function descendingDefined(a, b) { + return +defined(b) - +defined(a) || descending(a, b); +} + +export function nonempty(x) { + return x != null && `${x}` !== ""; +} + +export function finite(x) { + return isFinite(x) ? x : NaN; +} + +export function positive(x) { + return x > 0 && isFinite(x) ? x : NaN; +} + +export function negative(x) { + return x < 0 && isFinite(x) ? x : NaN; +} diff --git a/src/defined.ts b/src/defined.ts deleted file mode 100644 index ac1031fe32..0000000000 --- a/src/defined.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type {Primitive} from "d3"; -import {ascending, descending} from "d3"; - -export function defined(x: Primitive | undefined): boolean { - return x != null && !Number.isNaN(x); -} - -export function ascendingDefined(a: Primitive | undefined, b: Primitive | undefined): number { - return +defined(b) - +defined(a) || ascending(a, b); -} - -export function descendingDefined(a: Primitive | undefined, b: Primitive | undefined): number { - return +defined(b) - +defined(a) || descending(a, b); -} - -export function nonempty(x: unknown): boolean { - return x != null && `${x}` !== ""; -} - -export function finite(x: number): number { - return isFinite(x) ? x : NaN; -} - -export function positive(x: number): number { - return x > 0 && isFinite(x) ? x : NaN; -} - -export function negative(x: number): number { - return x < 0 && isFinite(x) ? x : NaN; -} diff --git a/src/dimensions.d.ts b/src/dimensions.d.ts new file mode 100644 index 0000000000..b57f28fa9d --- /dev/null +++ b/src/dimensions.d.ts @@ -0,0 +1,14 @@ +export interface Dimensions { + width: number; + height: number; + marginTop: number; + marginRight: number; + marginBottom: number; + marginLeft: number; + facet?: { + marginTop: number; + marginRight: number; + marginBottom: number; + marginLeft: number; + }; +} diff --git a/src/dimensions.js b/src/dimensions.js index 998ce36b54..64458520a4 100644 --- a/src/dimensions.js +++ b/src/dimensions.js @@ -3,7 +3,7 @@ import {projectionAspectRatio} from "./projection.js"; import {isOrdinalScale} from "./scales.js"; import {offset} from "./style.js"; -export function Dimensions(scales, marks, options = {}) { +export function createDimensions(scales, marks, options = {}) { // Compute the default margins: the maximum of the marks’ margins. While not // always used, they may be needed to compute the default height of the plot. let marginTopDefault = 0.5 - offset, diff --git a/src/facet.d.ts b/src/facet.d.ts new file mode 100644 index 0000000000..25a7200cdc --- /dev/null +++ b/src/facet.d.ts @@ -0,0 +1,16 @@ +export type Facet = "auto" | "include" | "exclude" | "super"; + +export type FacetAnchor = + | "top" + | "right" + | "bottom" + | "left" + | "top-left" + | "top-right" + | "bottom-left" + | "bottom-right" + | "top-empty" + | "right-empty" + | "bottom-empty" + | "left-empty" + | "empty"; diff --git a/src/facet.js b/src/facet.js index f22233d3a3..3a6daed12c 100644 --- a/src/facet.js +++ b/src/facet.js @@ -1,10 +1,10 @@ import {cross, rollup, sum} from "d3"; import {range} from "./options.js"; -import {Scales} from "./scales.js"; +import {createScales} from "./scales.js"; // Returns an array of {x?, y?, i} objects representing the facet domain. -export function Facets(channelsByScale, options) { - const {fx, fy} = Scales(channelsByScale, options); +export function createFacets(channelsByScale, options) { + const {fx, fy} = createScales(channelsByScale, options); const fxDomain = fx?.scale.domain(); const fyDomain = fy?.scale.domain(); return fxDomain && fyDomain diff --git a/src/format.d.ts b/src/format.d.ts new file mode 100644 index 0000000000..60fd7c59a4 --- /dev/null +++ b/src/format.d.ts @@ -0,0 +1,34 @@ +/** + * Returns a function that formats a given month number (from 0 = January to 11 + * = December) according to the specified *locale* and *format*. + * + * @param locale a [BCP 47 language tag](https://tools.ietf.org/html/bcp47); + * defaults to U.S. English. + * @param format a [month + * format](https://tc39.es/ecma402/#datetimeformat-objects): either *2-digit*, + * *numeric*, *narrow*, *short*, *long*; defaults to *short*. + */ +export function formatMonth( + locale?: string, + format?: "numeric" | "2-digit" | "long" | "short" | "narrow" +): (i: number) => string; + +/** + * Returns a function that formats a given week day number (from 0 = Sunday to 6 + * = Saturday) according to the specified *locale* and *format*. + * + * @param locale a [BCP 47 language tag](https://tools.ietf.org/html/bcp47); + * defaults to U.S. English. + * @param format a [weekday + * format](https://tc39.es/ecma402/#datetimeformat-objects): either *narrow*, + * *short*, or *long*; defaults to *short*. + */ +export function formatWeekday(locale?: string, format?: "long" | "short" | "narrow"): (i: number) => string; + +/** + * Given a *date*, returns the shortest equivalent ISO 8601 UTC string. If the + * given *date* is not valid, returns `"Invalid Date"`. + * + * @param date a date to format + */ +export function formatIsoDate(date: Date): string; diff --git a/src/format.js b/src/format.js new file mode 100644 index 0000000000..f65fb16901 --- /dev/null +++ b/src/format.js @@ -0,0 +1,44 @@ +import {format as isoFormat} from "isoformat"; +import {string} from "./options.js"; +import {memoize1} from "./memoize.js"; + +const numberFormat = memoize1((locale) => { + return new Intl.NumberFormat(locale); +}); + +const monthFormat = memoize1((locale, month) => { + return new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(month && {month})}); +}); + +const weekdayFormat = memoize1((locale, weekday) => { + return new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(weekday && {weekday})}); +}); + +export function formatNumber(locale = "en-US") { + const format = numberFormat(locale); + return (i) => (i != null && !isNaN(i) ? format.format(i) : undefined); +} + +export function formatMonth(locale = "en-US", format = "short") { + const fmt = monthFormat(locale, format); + return (i) => (i != null && !isNaN((i = +new Date(Date.UTC(2000, +i)))) ? fmt.format(i) : undefined); +} + +export function formatWeekday(locale = "en-US", format = "short") { + const fmt = weekdayFormat(locale, format); + return (i) => (i != null && !isNaN((i = +new Date(Date.UTC(2001, 0, +i)))) ? fmt.format(i) : undefined); +} + +export function formatIsoDate(date) { + return isoFormat(date, "Invalid Date"); +} + +export function formatAuto(locale = "en-US") { + const number = formatNumber(locale); + return (v) => (v instanceof Date ? formatIsoDate : typeof v === "number" ? number : string)(v); +} + +// TODO When Plot supports a top-level locale option, this should be removed +// because it lacks context to know which locale to use; formatAuto should be +// used instead whenever possible. +export const formatDefault = formatAuto(); diff --git a/src/format.ts b/src/format.ts deleted file mode 100644 index def579bd04..0000000000 --- a/src/format.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import {format as isoFormat} from "isoformat"; -import {string} from "./options.js"; -import {memoize1} from "./memoize.js"; - -const numberFormat = memoize1( - (locale: string | string[] | undefined) => new Intl.NumberFormat(locale) -); -const monthFormat = memoize1( - (locale: string | string[] | undefined, month: "numeric" | "2-digit" | "long" | "short" | "narrow" | undefined) => - new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(month && {month})}) -); -const weekdayFormat = memoize1( - (locale: string | string[] | undefined, weekday: "long" | "short" | "narrow" | undefined) => - new Intl.DateTimeFormat(locale, {timeZone: "UTC", ...(weekday && {weekday})}) -); - -export function formatNumber(locale = "en-US"): (value: any) => string | undefined { - const format = numberFormat(locale); - return (i: any) => (i != null && !isNaN(i) ? format.format(i) : undefined); -} - -/** @jsdoc formatMonth */ -export function formatMonth( - locale = "en-US", - format: "numeric" | "2-digit" | "long" | "short" | "narrow" | undefined = "short" -) { - const fmt = monthFormat(locale, format); - return (i: Date | number | null | undefined) => - i != null && !isNaN((i = +new Date(Date.UTC(2000, +i)))) ? fmt.format(i) : undefined; -} - -/** @jsdoc formatWeekday */ -export function formatWeekday(locale = "en-US", format: "long" | "short" | "narrow" | undefined = "short") { - const fmt = weekdayFormat(locale, format); - return (i: Date | number | null | undefined) => - i != null && !isNaN((i = +new Date(Date.UTC(2001, 0, +i)))) ? fmt.format(i) : undefined; -} - -/** @jsdoc formatIsoDate */ -export function formatIsoDate(date: Date): string { - return isoFormat(date, "Invalid Date"); -} - -export function formatAuto(locale = "en-US"): (value: any) => string | number | undefined { - const number = formatNumber(locale); - return (v: any) => (v instanceof Date ? formatIsoDate : typeof v === "number" ? number : string)(v); -} - -// TODO When Plot supports a top-level locale option, this should be removed -// because it lacks context to know which locale to use; formatAuto should be -// used instead whenever possible. -export const formatDefault = formatAuto(); diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000000..177f29e534 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,56 @@ +export * from "./channel.js"; +export * from "./color.js"; +export * from "./context.js"; +export * from "./curve.js"; +export * from "./dimensions.js"; +export * from "./facet.js"; +export * from "./format.js"; +export * from "./inset.js"; +export * from "./interpolate.js"; +export * from "./interval.js"; +export * from "./legends.js"; +export * from "./mark.js"; +export * from "./marker.js"; +export * from "./marks/area.js"; +export * from "./marks/arrow.js"; +export * from "./marks/auto.js"; +export * from "./marks/axis.js"; +export * from "./marks/bar.js"; +export * from "./marks/box.js"; +export * from "./marks/cell.js"; +export * from "./marks/contour.js"; +export * from "./marks/delaunay.js"; +export * from "./marks/density.js"; +export * from "./marks/dot.js"; +export * from "./marks/frame.js"; +export * from "./marks/geo.js"; +export * from "./marks/hexgrid.js"; +export * from "./marks/image.js"; +export * from "./marks/line.js"; +export * from "./marks/linearRegression.js"; +export * from "./marks/link.js"; +export * from "./marks/raster.js"; +export * from "./marks/rect.js"; +export * from "./marks/rule.js"; +export * from "./marks/text.js"; +export * from "./marks/tick.js"; +export * from "./marks/tree.js"; +export * from "./marks/vector.js"; +export * from "./options.js"; +export * from "./plot.js"; +export * from "./projection.js"; +export * from "./reducer.js"; +export * from "./scales.js"; +export * from "./symbol.js"; +export * from "./transforms/basic.js"; +export * from "./transforms/bin.js"; +export * from "./transforms/centroid.js"; +export * from "./transforms/dodge.js"; +export * from "./transforms/group.js"; +export * from "./transforms/hexbin.js"; +export * from "./transforms/map.js"; +export * from "./transforms/normalize.js"; +export * from "./transforms/select.js"; +export * from "./transforms/stack.js"; +export * from "./transforms/tree.js"; +export * from "./transforms/window.js"; diff --git a/src/inset.d.ts b/src/inset.d.ts new file mode 100644 index 0000000000..9de7fbd8a1 --- /dev/null +++ b/src/inset.d.ts @@ -0,0 +1,11 @@ +export interface InsetOptions { + /** + * Shorthand to set the same default for all four insets: {@link insetTop}, + * {@link insetRight}, {@link insetBottom}, and {@link insetLeft}. + */ + inset?: number; + insetTop?: number; + insetRight?: number; + insetBottom?: number; + insetLeft?: number; +} diff --git a/src/interpolate.d.ts b/src/interpolate.d.ts new file mode 100644 index 0000000000..130da03e4f --- /dev/null +++ b/src/interpolate.d.ts @@ -0,0 +1,9 @@ +import type {ColorInterpolateName} from "./color.js"; + +export type InterpolateName = "number" | ColorInterpolateName; + +export type InterpolateFunction = (a: T, b: T) => InterpolateFixedFunction; + +export type InterpolateFixedFunction = (t: number) => T; + +export type Interpolate = InterpolateName | InterpolateFunction | InterpolateFixedFunction; diff --git a/src/interval.d.ts b/src/interval.d.ts new file mode 100644 index 0000000000..cea329311e --- /dev/null +++ b/src/interval.d.ts @@ -0,0 +1,38 @@ +export type TimeIntervalName = + | "second" + | "minute" + | "hour" + | "day" + | "week" + | "month" + | "quarter" + | "half" + | "year" + | "monday" + | "tuesday" + | "wednesday" + | "thursday" + | "friday" + | "saturday" + | "sunday"; + +export interface IntervalImplementation { + floor(value: T): T; + offset(value: T, offset: number): T; +} + +export interface RangeIntervalImplementation extends IntervalImplementation { + range(start: T, stop: T): T[]; +} + +export type TimeInterval = TimeIntervalName | IntervalImplementation; + +export type TimeRangeInterval = TimeIntervalName | RangeIntervalImplementation; + +export type NumberInterval = number | IntervalImplementation; + +export type NumberRangeInterval = number | RangeIntervalImplementation; + +export type Interval = TimeInterval | NumberInterval; + +export type RangeInterval = TimeRangeInterval | NumberRangeInterval; diff --git a/src/legends.d.ts b/src/legends.d.ts new file mode 100644 index 0000000000..911fb56239 --- /dev/null +++ b/src/legends.d.ts @@ -0,0 +1,48 @@ +import type {ScaleOptions} from "./scales.js"; + +export type LegendType = "ramp" | "swatches"; + +export interface LegendOptions { + legend?: LegendType; + + // scale definitions + color?: ScaleOptions | string; + opacity?: ScaleOptions; + symbol?: ScaleOptions; + + // shared options + tickFormat?: ScaleOptions["tickFormat"]; + fontVariant?: ScaleOptions["fontVariant"]; + style?: string | Partial | null; + className?: string | null; + + // symbol options + fill?: string; + fillOpacity?: number; + stroke?: string; + strokeOpacity?: number; + strokeWidth?: number; + r?: number; + + // dimensions + width?: number; + height?: number; + marginTop?: number; + marginRight?: number; + marginBottom?: number; + marginLeft?: number; + + // ramp options + label?: string | null; + ticks?: ScaleOptions["ticks"]; + tickSize?: ScaleOptions["tickSize"]; + round?: ScaleOptions["round"]; + + // swatches options + columns?: string; + swatchSize?: number; + swatchWidth?: number; + swatchHeight?: number; +} + +export function legend(options?: LegendOptions): HTMLElement; diff --git a/src/legends.js b/src/legends.js index 2a8319d064..1fab8f28f0 100644 --- a/src/legends.js +++ b/src/legends.js @@ -1,5 +1,5 @@ import {rgb} from "d3"; -import {Context} from "./context.js"; +import {createContext} from "./context.js"; import {legendRamp} from "./legends/ramp.js"; import {legendSwatches, legendSymbols} from "./legends/swatches.js"; import {inherit, isScaleOptions} from "./options.js"; @@ -11,13 +11,12 @@ const legendRegistry = new Map([ ["opacity", legendOpacity] ]); -/** @jsdoc legend */ export function legend(options = {}) { for (const [key, value] of legendRegistry) { const scale = options[key]; if (isScaleOptions(scale)) { // e.g., ignore {color: "red"} - const context = Context(options); + const context = createContext(options); let hint; // For symbol legends, pass a hint to the symbol scale. if (key === "symbol") { @@ -69,7 +68,7 @@ function interpolateOpacity(color) { return (t) => `rgba(${r},${g},${b},${t})`; } -export function Legends(scales, context, options) { +export function createLegends(scales, context, options) { const legends = []; for (const [key, value] of legendRegistry) { const o = options[key]; diff --git a/src/legends/ramp.js b/src/legends/ramp.js index a5494b1cb8..d52f2d8ccd 100644 --- a/src/legends/ramp.js +++ b/src/legends/ramp.js @@ -1,6 +1,6 @@ import {quantize, interpolateNumber, piecewise, format, scaleBand, scaleLinear, axisBottom} from "d3"; import {inferFontVariant} from "../axes.js"; -import {Context, create} from "../context.js"; +import {createContext, create} from "../context.js"; import {map} from "../options.js"; import {interpolatePiecewise} from "../scales/quantitative.js"; import {applyInlineStyles, impliedString, maybeClassName} from "../style.js"; @@ -22,7 +22,7 @@ export function legendRamp(color, options) { round = true, className } = options; - const context = Context(options); + const context = createContext(options); className = maybeClassName(className); if (tickFormat === null) tickFormat = () => null; diff --git a/src/legends/swatches.js b/src/legends/swatches.js index 2b4820eeb4..8537a3842e 100644 --- a/src/legends/swatches.js +++ b/src/legends/swatches.js @@ -1,6 +1,6 @@ import {pathRound as path} from "d3"; import {inferFontVariant, maybeAutoTickFormat} from "../axes.js"; -import {Context, create} from "../context.js"; +import {createContext, create} from "../context.js"; import {isNoneish, maybeColorChannel, maybeNumberChannel} from "../options.js"; import {isOrdinalScale, isThresholdScale} from "../scales.js"; import {applyInlineStyles, impliedString, maybeClassName} from "../style.js"; @@ -92,7 +92,7 @@ function legendItems(scale, options = {}, swatch, swatchStyle) { style, width } = options; - const context = Context(options); + const context = createContext(options); className = maybeClassName(className); tickFormat = maybeAutoTickFormat(tickFormat, scale.domain); diff --git a/src/mark.d.ts b/src/mark.d.ts new file mode 100644 index 0000000000..103fc94f41 --- /dev/null +++ b/src/mark.d.ts @@ -0,0 +1,101 @@ +import type {Channels, ChannelDomainSort, ChannelName, ChannelValue, ChannelValueSpec} from "./channel.js"; +import type {Context} from "./context.js"; +import type {Dimensions} from "./dimensions.js"; +import type {Facet, FacetAnchor} from "./facet.js"; +import type {plot} from "./plot.js"; +import type {ScaleFunctions} from "./scales.js"; +import type {InitializerFunction, TransformFunction, SortOrder} from "./transforms/basic.js"; + +export type FrameAnchor = + | "middle" + | "top-left" + | "top" + | "top-right" + | "right" + | "bottom-right" + | "bottom" + | "bottom-left" + | "left"; + +export type Data = Iterable | ArrayLike; + +export type RenderFunction = ( + index: number[], + scales: ScaleFunctions, + values: {[key in ChannelName]?: any[]}, + dimensions: Dimensions, + context: Context +) => SVGElement | null; + +export type Markish = RenderFunction | Renderable | Markish[] | null | undefined; + +export interface Renderable { + render: RenderFunction; +} + +export interface MarkOptions { + // transforms + filter?: ChannelValue; + reverse?: boolean; + sort?: SortOrder | ChannelDomainSort; + transform?: TransformFunction; + initializer?: InitializerFunction; + + // faceting + fx?: ChannelValue; + fy?: ChannelValue; + facet?: Facet | boolean | null; + facetAnchor?: FacetAnchor | null; + + // axis margins + margin?: number; + marginTop?: number; + marginRight?: number; + marginBottom?: number; + marginLeft?: number; + + // accessibility and interaction + ariaDescription?: string; + ariaHidden?: string; + ariaLabel?: ChannelValue; + pointerEvents?: string; + title?: ChannelValue; + + // aesthetics + clip?: "frame" | "sphere" | boolean | null; + dx?: number; + dy?: number; + fill?: ChannelValueSpec; + fillOpacity?: ChannelValueSpec; + stroke?: ChannelValueSpec; + strokeDasharray?: string | number; + strokeDashoffset?: string | number; + strokeLinecap?: string; + strokeLinejoin?: string; + strokeMiterlimit?: number; + strokeOpacity?: ChannelValueSpec; + strokeWidth?: ChannelValueSpec; + opacity?: ChannelValueSpec; + mixBlendMode?: string; + paintOrder?: string; + shapeRendering?: string; + + // links + href?: ChannelValue; + target?: string; + + // custom channels + channels?: Channels; +} + +export class Mark { + plot: typeof plot; +} + +export class RenderableMark extends Mark implements Renderable { + render: RenderFunction; +} + +export type CompoundMark = Markish[] & {plot: typeof plot}; + +export function marks(...marks: Markish[]): CompoundMark; diff --git a/src/mark.js b/src/mark.js index a4ef0ea64e..a29ea05a22 100644 --- a/src/mark.js +++ b/src/mark.js @@ -1,4 +1,4 @@ -import {Channels, channelDomain, valueObject} from "./channel.js"; +import {createChannels, channelDomain, valueObject} from "./channel.js"; import {defined} from "./defined.js"; import {maybeFacetAnchor} from "./facet.js"; import {arrayify, isDomainSort, isOptions, range} from "./options.js"; @@ -81,7 +81,7 @@ export class Mark { const originalFacets = facets; if (this.transform != null) ({facets, data} = this.transform(data, facets)), (data = arrayify(data)); if (facets !== undefined) facets.original = originalFacets; // needed up read facetChannels - const channels = Channels(this.channels, data); + const channels = createChannels(this.channels, data); if (this.sort != null) channelDomain(data, facets, channels, facetChannels, this.sort); // mutates facetChannels! return {data, facets, channels}; } @@ -115,7 +115,6 @@ export class Mark { } } -/** @jsdoc marks */ export function marks(...marks) { marks.plot = Mark.prototype.plot; // Note: depends on side-effect in plot! return marks; diff --git a/src/marker.d.ts b/src/marker.d.ts new file mode 100644 index 0000000000..7494f4e537 --- /dev/null +++ b/src/marker.d.ts @@ -0,0 +1,12 @@ +export type MarkerName = "arrow" | "dot" | "circle" | "circle-fill" | "circle-stroke"; + +export type MarkerFunction = (color: string, context: {document: Document}) => SVGElement; + +export type Marker = MarkerName | MarkerFunction; + +export interface MarkerOptions { + marker?: Marker | "none" | boolean | null; + markerStart?: Marker | "none" | boolean | null; + markerMid?: Marker | "none" | boolean | null; + markerEnd?: Marker | "none" | boolean | null; +} diff --git a/src/marks/marker.js b/src/marker.js similarity index 88% rename from src/marks/marker.js rename to src/marker.js index c57a0f3642..237da2f88a 100644 --- a/src/marks/marker.js +++ b/src/marker.js @@ -1,4 +1,4 @@ -import {create} from "../context.js"; +import {create} from "./context.js"; export function markers(mark, {marker, markerStart = marker, markerMid = marker, markerEnd = marker} = {}) { mark.markerStart = maybeMarker(markerStart); @@ -78,15 +78,15 @@ function markerCircleStroke(color, context) { let nextMarkerId = 0; -export function applyMarkers(path, mark, {stroke: S} = {}) { - return applyMarkersColor(path, mark, S && ((i) => S[i])); +export function applyMarkers(path, mark, {stroke: S}, context) { + return applyMarkersColor(path, mark, S && ((i) => S[i]), context); } -export function applyGroupedMarkers(path, mark, {stroke: S} = {}) { - return applyMarkersColor(path, mark, S && (([i]) => S[i])); +export function applyGroupedMarkers(path, mark, {stroke: S}, context) { + return applyMarkersColor(path, mark, S && (([i]) => S[i]), context); } -function applyMarkersColor(path, {markerStart, markerMid, markerEnd, stroke}, strokeof = () => stroke) { +function applyMarkersColor(path, {markerStart, markerMid, markerEnd, stroke}, strokeof = () => stroke, context) { const iriByMarkerColor = new Map(); function applyMarker(marker) { @@ -96,7 +96,6 @@ function applyMarkersColor(path, {markerStart, markerMid, markerEnd, stroke}, st if (!iriByColor) iriByMarkerColor.set(marker, (iriByColor = new Map())); let iri = iriByColor.get(color); if (!iri) { - const context = {document: this.ownerDocument}; const node = this.parentNode.insertBefore(marker(color, context), this); const id = `plot-marker-${++nextMarkerId}`; node.setAttribute("id", id); diff --git a/src/marks/area.d.ts b/src/marks/area.d.ts new file mode 100644 index 0000000000..b5d0f2bdaf --- /dev/null +++ b/src/marks/area.d.ts @@ -0,0 +1,34 @@ +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {CurveAutoOptions} from "../curve.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {Reducer} from "../reducer.js"; +import type {BinOptions} from "../transforms/bin.js"; +import type {StackOptions} from "../transforms/stack.js"; + +export interface AreaOptions extends MarkOptions, StackOptions, CurveAutoOptions { + x1?: ChannelValueSpec; + x2?: ChannelValueSpec; + y1?: ChannelValueSpec; + y2?: ChannelValueSpec; + z?: ChannelValue; +} + +export interface AreaXOptions extends Omit, BinOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec & Omit; // interval must be a mark-level option + reduce?: Reducer; +} + +export interface AreaYOptions extends Omit, BinOptions { + x?: ChannelValueSpec & Omit; // interval must be a mark-level option + y?: ChannelValueSpec; + reduce?: Reducer; +} + +export function area(data?: Data, options?: AreaOptions): Area; + +export function areaX(data?: Data, options?: AreaXOptions): Area; + +export function areaY(data?: Data, options?: AreaYOptions): Area; + +export class Area extends RenderableMark {} diff --git a/src/marks/area.js b/src/marks/area.js index ae7d9e2da7..bd8393926c 100644 --- a/src/marks/area.js +++ b/src/marks/area.js @@ -1,6 +1,6 @@ import {area as shapeArea} from "d3"; import {create} from "../context.js"; -import {Curve} from "../curve.js"; +import {maybeCurve} from "../curve.js"; import {Mark} from "../mark.js"; import {first, indexOf, maybeZ, second} from "../options.js"; import { @@ -38,7 +38,7 @@ export class Area extends Mark { defaults ); this.z = z; - this.curve = Curve(curve, tension); + this.curve = maybeCurve(curve, tension); } filter(index) { return index; @@ -71,19 +71,16 @@ export class Area extends Mark { } } -/** @jsdoc area */ export function area(data, options) { if (options === undefined) return areaY(data, {x: first, y: second}); return new Area(data, options); } -/** @jsdoc areaX */ export function areaX(data, options) { const {y = indexOf, ...rest} = maybeDenseIntervalY(options); return new Area(data, maybeStackX(maybeIdentityX({...rest, y1: y, y2: undefined}))); } -/** @jsdoc areaY */ export function areaY(data, options) { const {x = indexOf, ...rest} = maybeDenseIntervalX(options); return new Area(data, maybeStackY(maybeIdentityY({...rest, x1: x, x2: undefined}))); diff --git a/src/marks/arrow.d.ts b/src/marks/arrow.d.ts new file mode 100644 index 0000000000..d2c9d65771 --- /dev/null +++ b/src/marks/arrow.d.ts @@ -0,0 +1,21 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; + +export interface ArrowOptions extends MarkOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + x1?: ChannelValueSpec; + y1?: ChannelValueSpec; + x2?: ChannelValueSpec; + y2?: ChannelValueSpec; + bend?: number | boolean; + headAngle?: number; + headLength?: number; + inset?: number; + insetStart?: number; + insetEnd?: number; +} + +export function arrow(data?: Data, options?: ArrowOptions): Arrow; + +export class Arrow extends RenderableMark {} diff --git a/src/marks/arrow.js b/src/marks/arrow.js index 087a8a1a12..0229a57239 100644 --- a/src/marks/arrow.js +++ b/src/marks/arrow.js @@ -176,7 +176,6 @@ function circleCircleIntersect([ax, ay, ar], [bx, by, br], sign) { return [ax + (dx * x + dy * y) / d, ay + (dy * x - dx * y) / d]; } -/** @jsdoc arrow */ export function arrow(data, options = {}) { let {x, x1, x2, y, y1, y2, ...remainingOptions} = options; [x1, x2] = maybeSameValue(x, x1, x2); diff --git a/src/marks/auto.d.ts b/src/marks/auto.d.ts new file mode 100644 index 0000000000..378b2feed1 --- /dev/null +++ b/src/marks/auto.d.ts @@ -0,0 +1,28 @@ +import type {ChannelValue} from "../channel.js"; +import type {CompoundMark, Data} from "../mark.js"; +import type {Reducer} from "../reducer.js"; +import type {BinOptions} from "../transforms/bin.js"; + +export interface AutoOptions { + x?: ChannelValue | Reducer | ({value?: ChannelValue; reduce?: Reducer | null; zero?: boolean} & BinOptions); + y?: ChannelValue | Reducer | ({value?: ChannelValue; reduce?: Reducer | null; zero?: boolean} & BinOptions); + color?: ChannelValue | Reducer | {value?: ChannelValue; reduce?: Reducer | null; color?: string}; + size?: ChannelValue | Reducer | {value?: ChannelValue; reduce?: Reducer | null}; + fx?: ChannelValue | {value?: ChannelValue}; + fy?: ChannelValue | {value?: ChannelValue}; + mark?: "dot" | "line" | "area" | "rule" | "bar"; +} + +export interface AutoSpec extends AutoOptions { + x: {value: ChannelValue; reduce: Reducer | null; zero?: boolean} & BinOptions; + y: {value: ChannelValue; reduce: Reducer | null; zero?: boolean} & BinOptions; + color: {value: ChannelValue; reduce: Reducer | null; color?: string}; + size: {value: ChannelValue; reduce: Reducer | null}; + fx: ChannelValue; + fy: ChannelValue; + mark: "dot" | "line" | "area" | "rule" | "bar"; +} + +export function autoSpec(data?: Data, options?: AutoOptions): AutoSpec; + +export function auto(data?: Data, options?: AutoOptions): CompoundMark; diff --git a/src/marks/auto.js b/src/marks/auto.js index 1957dc235d..677556bd06 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -103,7 +103,6 @@ export function autoSpec(data, options) { }; } -/** @jsdoc auto */ export function auto(data, options) { options = normalizeOptions(options); diff --git a/src/marks/axis.d.ts b/src/marks/axis.d.ts new file mode 100644 index 0000000000..7b4b6f8f21 --- /dev/null +++ b/src/marks/axis.d.ts @@ -0,0 +1,70 @@ +import type {CompoundMark, Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {ScaleOptions} from "../scales.js"; +import type {RuleXOptions, RuleYOptions} from "./rule.js"; +import type {TextOptions} from "./text.js"; +import type {TickXOptions, TickYOptions} from "./tick.js"; + +export type AxisAnchor = "top" | "right" | "bottom" | "left"; + +interface GridOptions { + anchor?: AxisAnchor; + interval?: ScaleOptions["interval"]; + ticks?: ScaleOptions["ticks"]; + tickSpacing?: ScaleOptions["tickSpacing"]; + color?: MarkOptions["stroke"]; +} + +interface AxisOptions extends GridOptions, MarkOptions, TextOptions { + tickSize?: ScaleOptions["tickSize"]; + tickPadding?: ScaleOptions["tickPadding"]; + tickFormat?: ScaleOptions["tickFormat"]; + tickRotate?: ScaleOptions["tickRotate"]; + grid?: ScaleOptions["grid"]; + line?: ScaleOptions["line"]; + label?: ScaleOptions["label"]; + labelOffset?: ScaleOptions["labelOffset"]; + labelAnchor?: ScaleOptions["labelAnchor"]; + textStroke?: MarkOptions["stroke"]; + textStrokeOpacity?: MarkOptions["strokeOpacity"]; + textStrokeWidth?: MarkOptions["strokeWidth"]; +} + +export interface AxisXOptions extends AxisOptions, TickXOptions {} + +export interface AxisYOptions extends AxisOptions, TickYOptions {} + +export interface GridXOptions extends GridOptions, RuleXOptions {} + +export interface GridYOptions extends GridOptions, RuleYOptions {} + +export function axisY(options?: AxisYOptions): CompoundMark; + +export function axisY(data?: Data, options?: AxisYOptions): CompoundMark; + +export function axisFy(options?: AxisYOptions): CompoundMark; + +export function axisFy(data?: Data, options?: AxisYOptions): CompoundMark; + +export function axisX(options?: AxisXOptions): CompoundMark; + +export function axisX(data?: Data, options?: AxisXOptions): CompoundMark; + +export function axisFx(options?: AxisXOptions): CompoundMark; + +export function axisFx(data?: Data, options?: AxisXOptions): CompoundMark; + +export function gridY(options?: GridYOptions): RenderableMark; + +export function gridY(data?: Data, options?: GridYOptions): RenderableMark; + +export function gridFy(options?: GridYOptions): RenderableMark; + +export function gridFy(data?: Data, options?: GridYOptions): RenderableMark; + +export function gridX(options?: GridXOptions): RenderableMark; + +export function gridX(data?: Data, options?: GridXOptions): RenderableMark; + +export function gridFx(options?: GridXOptions): RenderableMark; + +export function gridFx(data?: Data, options?: GridXOptions): RenderableMark; diff --git a/src/marks/axis.js b/src/marks/axis.js index a6d4d22615..b205930885 100644 --- a/src/marks/axis.js +++ b/src/marks/axis.js @@ -37,25 +37,21 @@ function anchorFx(options) { return maybeAnchor(options, ["top", "bottom"]); } -/** @jsdoc axisY */ export function axisY() { const [data, options] = maybeData(...arguments); return axisKy("y", anchorY(options), data, options); } -/** @jsdoc axisFy */ export function axisFy() { const [data, options] = maybeData(...arguments); return axisKy("fy", anchorFy(options), data, options); } -/** @jsdoc axisX */ export function axisX() { const [data, options] = maybeData(...arguments); return axisKx("x", anchorX(options), data, options); } -/** @jsdoc axisFx */ export function axisFx() { const [data, options] = maybeData(...arguments); return axisKx("fx", anchorFx(options), data, options); @@ -448,7 +444,6 @@ export function gridFy() { return gridKy("fy", anchorFy(options), data, options); } -/** @jsdoc gridX */ export function gridX() { const [data, options] = maybeData(...arguments); return gridKx("x", anchorX(options), data, options); diff --git a/src/marks/bar.d.ts b/src/marks/bar.d.ts new file mode 100644 index 0000000000..525f8c35e8 --- /dev/null +++ b/src/marks/bar.d.ts @@ -0,0 +1,33 @@ +import type {ChannelValueIntervalSpec, ChannelValueSpec} from "../channel.js"; +import type {Interval} from "../interval.js"; +import type {InsetOptions} from "../inset.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {StackOptions} from "../transforms/stack.js"; + +interface BarOptions extends MarkOptions, InsetOptions, StackOptions { + interval?: Interval; + rx?: number | string; + ry?: number | string; +} + +export interface BarXOptions extends BarOptions { + x?: ChannelValueIntervalSpec; + x1?: ChannelValueSpec; + x2?: ChannelValueSpec; + y?: ChannelValueSpec; +} + +export interface BarYOptions extends BarOptions { + y?: ChannelValueIntervalSpec; + y1?: ChannelValueSpec; + y2?: ChannelValueSpec; + x?: ChannelValueSpec; +} + +export function barX(data?: Data, options?: BarXOptions): BarX; + +export function barY(data?: Data, options?: BarYOptions): BarY; + +export class BarX extends RenderableMark {} + +export class BarY extends RenderableMark {} diff --git a/src/marks/bar.js b/src/marks/bar.js index e365695d99..d7afbb37c4 100644 --- a/src/marks/bar.js +++ b/src/marks/bar.js @@ -129,12 +129,10 @@ export class BarY extends AbstractBar { } } -/** @jsdoc barX */ export function barX(data, options = {y: indexOf, x2: identity}) { return new BarX(data, maybeStackX(maybeIntervalX(maybeIdentityX(options)))); } -/** @jsdoc barY */ export function barY(data, options = {x: indexOf, y2: identity}) { return new BarY(data, maybeStackY(maybeIntervalY(maybeIdentityY(options)))); } diff --git a/src/marks/box.d.ts b/src/marks/box.d.ts new file mode 100644 index 0000000000..1a55e72309 --- /dev/null +++ b/src/marks/box.d.ts @@ -0,0 +1,13 @@ +import type {CompoundMark, Data} from "../mark.js"; +import type {BarXOptions, BarYOptions} from "./bar.js"; +import type {DotOptions} from "./dot.js"; +import type {RuleXOptions, RuleYOptions} from "./rule.js"; +import type {TickXOptions, TickYOptions} from "./tick.js"; + +export type BoxXOptions = DotOptions & BarXOptions & TickXOptions & RuleXOptions; + +export type BoxYOptions = DotOptions & BarYOptions & TickYOptions & RuleYOptions; + +export function boxX(data?: Data, options?: BoxXOptions): CompoundMark; + +export function boxY(data?: Data, options?: BoxYOptions): CompoundMark; diff --git a/src/marks/box.js b/src/marks/box.js index 73b6097c78..230f0ee948 100644 --- a/src/marks/box.js +++ b/src/marks/box.js @@ -7,7 +7,6 @@ import {dot} from "./dot.js"; import {ruleX, ruleY} from "./rule.js"; import {tickX, tickY} from "./tick.js"; -/** @jsdoc boxX */ export function boxX(data, options = {}) { // Returns a composite mark for producing a horizontal box plot, applying the // necessary statistical transforms. The boxes are grouped by y, if present. @@ -31,7 +30,6 @@ export function boxX(data, options = {}) { ); } -/** @jsdoc boxY */ export function boxY(data, options = {}) { // Returns a composite mark for producing a vertical box plot, applying the // necessary statistical transforms. The boxes are grouped by x, if present. diff --git a/src/marks/cell.d.ts b/src/marks/cell.d.ts new file mode 100644 index 0000000000..9042c04243 --- /dev/null +++ b/src/marks/cell.d.ts @@ -0,0 +1,18 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {InsetOptions} from "../inset.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; + +export interface CellOptions extends MarkOptions, InsetOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + rx?: number | string; + ry?: number | string; +} + +export function cell(data?: Data, options?: CellOptions): Cell; + +export function cellX(data?: Data, options?: CellOptions): Cell; + +export function cellY(data?: Data, options?: CellOptions): Cell; + +export class Cell extends RenderableMark {} diff --git a/src/marks/cell.js b/src/marks/cell.js index 92722d588d..8938170fa2 100644 --- a/src/marks/cell.js +++ b/src/marks/cell.js @@ -24,21 +24,18 @@ export class Cell extends AbstractBar { } } -/** @jsdoc cell */ export function cell(data, options = {}) { let {x, y, ...remainingOptions} = options; [x, y] = maybeTuple(x, y); return new Cell(data, {...remainingOptions, x, y}); } -/** @jsdoc cellX */ export function cellX(data, options = {}) { let {x = indexOf, fill, stroke, ...remainingOptions} = options; if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity; return new Cell(data, {...remainingOptions, x, fill, stroke}); } -/** @jsdoc cellY */ export function cellY(data, options = {}) { let {y = indexOf, fill, stroke, ...remainingOptions} = options; if (fill === undefined && maybeColorChannel(stroke)[0] === undefined) fill = identity; diff --git a/src/marks/contour.d.ts b/src/marks/contour.d.ts new file mode 100644 index 0000000000..183fe42e9b --- /dev/null +++ b/src/marks/contour.d.ts @@ -0,0 +1,18 @@ +import type {ChannelValue} from "../channel.js"; +import type {RangeInterval} from "../interval.js"; +import type {Data, RenderableMark} from "../mark.js"; +import type {Thresholds} from "../transforms/bin.js"; +import type {RasterOptions, RasterSampler} from "./raster.js"; + +export interface ContourOptions extends Omit { + smooth?: boolean; + value?: ChannelValue | RasterSampler; + thresholds?: Thresholds; + interval?: RangeInterval; +} + +export function contour(options?: ContourOptions): Contour; + +export function contour(data?: Data, options?: ContourOptions): Contour; + +export class Contour extends RenderableMark {} diff --git a/src/marks/contour.js b/src/marks/contour.js index aef47c3de4..6d692d7d91 100644 --- a/src/marks/contour.js +++ b/src/marks/contour.js @@ -1,8 +1,8 @@ import {blur2, contours, geoPath, max, min, nice, range, ticks, thresholdSturges} from "d3"; -import {Channels} from "../channel.js"; +import {createChannels} from "../channel.js"; import {create} from "../context.js"; import {labelof, identity, arrayify, map} from "../options.js"; -import {Position} from "../projection.js"; +import {applyPosition} from "../projection.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform, styles} from "../style.js"; import {initializer} from "../transforms/basic.js"; import {maybeThresholds} from "../transforms/bin.js"; @@ -120,7 +120,7 @@ function contourGeometry({thresholds, interval, ...options}) { // Interpolate the raster grid, as needed. if (this.interpolate) { - const {x: X, y: Y} = Position(channels, scales, context); + const {x: X, y: Y} = applyPosition(channels, scales, context); // Convert scaled (screen) coordinates to grid (canvas) coordinates. const IX = map(X, (x) => (x - x1) * kx, Float64Array); const IY = map(Y, (y) => (y - y1) * ky, Float64Array); @@ -176,7 +176,7 @@ function contourGeometry({thresholds, interval, ...options}) { return { data: contourData, facets: contourFacets, - channels: Channels(this.contourChannels, contourData) + channels: createChannels(this.contourChannels, contourData) }; }); } @@ -197,7 +197,6 @@ function maybeTicks(thresholds, V, min, max) { return tz; } -/** @jsdoc contour */ export function contour() { return new Contour(...maybeTuples("value", ...arguments)); } diff --git a/src/marks/delaunay.d.ts b/src/marks/delaunay.d.ts new file mode 100644 index 0000000000..dea5dec1a4 --- /dev/null +++ b/src/marks/delaunay.d.ts @@ -0,0 +1,20 @@ +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {CurveOptions} from "../curve.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {MarkerOptions} from "../marker.js"; + +export interface DelaunayOptions extends MarkOptions, MarkerOptions, CurveOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + z?: ChannelValue; +} + +export function delaunayLink(data?: Data, options?: DelaunayOptions): RenderableMark; + +export function delaunayMesh(data?: Data, options?: DelaunayOptions): RenderableMark; + +export function hull(data?: Data, options?: DelaunayOptions): RenderableMark; + +export function voronoi(data?: Data, options?: DelaunayOptions): RenderableMark; + +export function voronoiMesh(data?: Data, options?: DelaunayOptions): RenderableMark; diff --git a/src/marks/delaunay.js b/src/marks/delaunay.js index c3ce36118c..63efaac6ff 100644 --- a/src/marks/delaunay.js +++ b/src/marks/delaunay.js @@ -1,7 +1,8 @@ import {group, pathRound as path, select, Delaunay} from "d3"; import {create} from "../context.js"; -import {Curve} from "../curve.js"; +import {maybeCurve} from "../curve.js"; import {Mark} from "../mark.js"; +import {markers, applyMarkers} from "../marker.js"; import {constant, maybeTuple, maybeZ} from "../options.js"; import { applyChannelStyles, @@ -10,7 +11,6 @@ import { applyIndirectStyles, applyTransform } from "../style.js"; -import {markers, applyMarkers} from "./marker.js"; const delaunayLinkDefaults = { ariaLabel: "delaunay link", @@ -61,7 +61,7 @@ class DelaunayLink extends Mark { options, delaunayLinkDefaults ); - this.curve = Curve(curve, tension); + this.curve = maybeCurve(curve, tension); markers(this, options); } render(index, scales, channels, dimensions, context) { @@ -120,7 +120,7 @@ class DelaunayLink extends Mark { return p; }) .call(applyChannelStyles, mark, newChannels) - .call(applyMarkers, mark, newChannels); + .call(applyMarkers, mark, newChannels, context); } return create("svg:g", context) @@ -283,27 +283,22 @@ function delaunayMark(DelaunayMark, data, {x, y, ...options} = {}) { return new DelaunayMark(data, {...options, x, y}); } -/** @jsdoc delaunayLink */ export function delaunayLink(data, options) { return delaunayMark(DelaunayLink, data, options); } -/** @jsdoc delaunayMesh */ export function delaunayMesh(data, options) { return delaunayMark(DelaunayMesh, data, options); } -/** @jsdoc hull */ export function hull(data, options) { return delaunayMark(Hull, data, options); } -/** @jsdoc voronoi */ export function voronoi(data, options) { return delaunayMark(Voronoi, data, options); } -/** @jsdoc voronoiMesh */ export function voronoiMesh(data, options) { return delaunayMark(VoronoiMesh, data, options); } diff --git a/src/marks/density.d.ts b/src/marks/density.d.ts new file mode 100644 index 0000000000..abac1a9b29 --- /dev/null +++ b/src/marks/density.d.ts @@ -0,0 +1,15 @@ +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; + +export interface DensityOptions extends MarkOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + z?: ChannelValue; + weight?: ChannelValue; + bandwidth?: number; + thresholds?: number | Iterable; +} + +export function density(data?: Data, options?: DensityOptions): Density; + +export class Density extends RenderableMark {} diff --git a/src/marks/density.js b/src/marks/density.js index 122ae4bd7b..a78cb8ff6a 100644 --- a/src/marks/density.js +++ b/src/marks/density.js @@ -1,7 +1,7 @@ import {contourDensity, create, geoPath} from "d3"; import {Mark} from "../mark.js"; import {coerceNumbers, maybeTuple, maybeZ, TypedArray} from "../options.js"; -import {Position} from "../projection.js"; +import {applyPosition} from "../projection.js"; import { applyFrameAnchor, applyDirectStyles, @@ -65,7 +65,6 @@ export class Density extends Mark { } } -/** @jsdoc density */ export function density(data, options = {}) { let {x, y, ...remainingOptions} = options; [x, y] = maybeTuple(x, y); @@ -92,7 +91,7 @@ function densityInitializer(options, fillDensity, strokeDensity) { const {width, height} = dimensions; // Get the (either scaled or projected) xy channels. - const {x: X, y: Y} = Position(channels, scales, context); + const {x: X, y: Y} = applyPosition(channels, scales, context); // Group any of the input channels according to the first index associated // with each z-series or facet. Drop any channels not be needed for diff --git a/src/marks/dot.d.ts b/src/marks/dot.d.ts new file mode 100644 index 0000000000..b1b4508d6d --- /dev/null +++ b/src/marks/dot.d.ts @@ -0,0 +1,35 @@ +import type {ChannelValue, ChannelValueIntervalSpec, ChannelValueSpec} from "../channel.js"; +import type {Interval} from "../interval.js"; +import type {Data, FrameAnchor, MarkOptions, RenderableMark} from "../mark.js"; +import type {SymbolImplementation, SymbolName} from "../symbol.js"; + +export interface DotOptions extends MarkOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + r?: ChannelValueSpec | number; + rotate?: ChannelValue | number; + symbol?: ChannelValueSpec | SymbolName | SymbolImplementation; + frameAnchor?: FrameAnchor; +} + +export interface DotXOptions extends Omit { + y?: ChannelValueIntervalSpec; + interval?: Interval; +} + +export interface DotYOptions extends Omit { + x?: ChannelValueIntervalSpec; + interval?: Interval; +} + +export function dot(data?: Data, options?: DotOptions): Dot; + +export function dotX(data?: Data, options?: DotXOptions): Dot; + +export function dotY(data?: Data, options?: DotYOptions): Dot; + +export function circle(data?: Data, options?: Exclude): Dot; + +export function hexagon(data?: Data, options?: Exclude): Dot; + +export class Dot extends RenderableMark {} diff --git a/src/marks/dot.js b/src/marks/dot.js index 634e4c6d1c..0545b482a8 100644 --- a/src/marks/dot.js +++ b/src/marks/dot.js @@ -10,7 +10,7 @@ import { applyIndirectStyles, applyTransform } from "../style.js"; -import {maybeSymbolChannel} from "../symbols.js"; +import {maybeSymbolChannel} from "../symbol.js"; import {template} from "../template.js"; import {sort} from "../transforms/basic.js"; import {maybeIntervalMidX, maybeIntervalMidY} from "../transforms/interval.js"; @@ -132,31 +132,26 @@ export class Dot extends Mark { } } -/** @jsdoc dot */ export function dot(data, options = {}) { let {x, y, ...remainingOptions} = options; if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); return new Dot(data, {...remainingOptions, x, y}); } -/** @jsdoc dotX */ export function dotX(data, options = {}) { const {x = identity, ...remainingOptions} = options; return new Dot(data, maybeIntervalMidY({...remainingOptions, x})); } -/** @jsdoc dotY */ export function dotY(data, options = {}) { const {y = identity, ...remainingOptions} = options; return new Dot(data, maybeIntervalMidX({...remainingOptions, y})); } -/** @jsdoc circle */ export function circle(data, options) { return dot(data, {...options, symbol: "circle"}); } -/** @jsdoc hexagon */ export function hexagon(data, options) { return dot(data, {...options, symbol: "hexagon"}); } diff --git a/src/marks/frame.d.ts b/src/marks/frame.d.ts new file mode 100644 index 0000000000..7c4dd4056d --- /dev/null +++ b/src/marks/frame.d.ts @@ -0,0 +1,12 @@ +import type {InsetOptions} from "../inset.js"; +import type {MarkOptions, RenderableMark} from "../mark.js"; + +export interface FrameOptions extends MarkOptions, InsetOptions { + anchor?: "top" | "right" | "bottom" | "left" | null; + rx?: number | string; + ry?: number | string; +} + +export function frame(options?: FrameOptions): Frame; + +export class Frame extends RenderableMark {} diff --git a/src/marks/frame.js b/src/marks/frame.js index d295f71e2f..88d5d56943 100644 --- a/src/marks/frame.js +++ b/src/marks/frame.js @@ -70,7 +70,6 @@ export class Frame extends Mark { } } -/** @jsdoc frame */ export function frame(options) { return new Frame(options); } diff --git a/src/marks/geo.d.ts b/src/marks/geo.d.ts new file mode 100644 index 0000000000..b7819f8481 --- /dev/null +++ b/src/marks/geo.d.ts @@ -0,0 +1,16 @@ +import type {GeoPermissibleObjects} from "d3"; +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; + +export interface GeoOptions extends MarkOptions { + geometry?: ChannelValue; + r?: ChannelValueSpec; +} + +export function geo(data?: Data | GeoPermissibleObjects, options?: GeoOptions): Geo; + +export function sphere(options?: GeoOptions): Geo; + +export function graticule(options?: GeoOptions): Geo; + +export class Geo extends RenderableMark {} diff --git a/src/marks/hexgrid.d.ts b/src/marks/hexgrid.d.ts new file mode 100644 index 0000000000..df8f1e926a --- /dev/null +++ b/src/marks/hexgrid.d.ts @@ -0,0 +1,9 @@ +import type {MarkOptions, RenderableMark} from "../mark.js"; + +export interface HexgridOptions extends MarkOptions { + binWidth?: number; +} + +export function hexgrid(options?: HexgridOptions): Hexgrid; + +export class Hexgrid extends RenderableMark {} diff --git a/src/marks/hexgrid.js b/src/marks/hexgrid.js index 2ec7d0a5b0..a57784abe9 100644 --- a/src/marks/hexgrid.js +++ b/src/marks/hexgrid.js @@ -2,7 +2,7 @@ import {create} from "../context.js"; import {Mark} from "../mark.js"; import {number} from "../options.js"; import {applyDirectStyles, applyIndirectStyles, applyTransform, offset} from "../style.js"; -import {sqrt4_3} from "../symbols.js"; +import {sqrt4_3} from "../symbol.js"; import {ox, oy} from "../transforms/hexbin.js"; const defaults = { @@ -12,7 +12,6 @@ const defaults = { strokeOpacity: 0.1 }; -/** @jsdoc hexgrid */ export function hexgrid(options) { return new Hexgrid(options); } diff --git a/src/marks/image.d.ts b/src/marks/image.d.ts new file mode 100644 index 0000000000..ff90f13773 --- /dev/null +++ b/src/marks/image.d.ts @@ -0,0 +1,18 @@ +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {Data, FrameAnchor, MarkOptions, RenderableMark} from "../mark.js"; + +export interface ImageOptions extends MarkOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + width?: ChannelValue; + height?: ChannelValue; + src?: ChannelValue; + preserveAspectRatio?: string; + crossOrigin?: string; + frameAnchor?: FrameAnchor; + imageRendering?: string; +} + +export function image(data?: Data, options?: ImageOptions): Image; + +export class Image extends RenderableMark {} diff --git a/src/marks/image.js b/src/marks/image.js index a134464c0f..d4f183d81e 100644 --- a/src/marks/image.js +++ b/src/marks/image.js @@ -112,7 +112,6 @@ export class Image extends Mark { } } -/** @jsdoc image */ export function image(data, options = {}) { let {x, y, ...remainingOptions} = options; if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); diff --git a/src/marks/line.d.ts b/src/marks/line.d.ts new file mode 100644 index 0000000000..277ab56428 --- /dev/null +++ b/src/marks/line.d.ts @@ -0,0 +1,30 @@ +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {CurveAutoOptions} from "../curve.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {MarkerOptions} from "../marker.js"; +import type {Reducer} from "../reducer.js"; +import type {BinOptions} from "../transforms/bin.js"; + +export interface LineOptions extends MarkOptions, MarkerOptions, CurveAutoOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + z?: ChannelValue; +} + +export interface LineXOptions extends LineOptions, BinOptions { + y?: ChannelValueSpec & Omit; // interval must be mark-level option + reduce?: Reducer; +} + +export interface LineYOptions extends LineOptions, BinOptions { + x?: ChannelValueSpec & Omit; // interval must be mark-level option + reduce?: Reducer; +} + +export function line(data?: Data, options?: LineOptions): Line; + +export function lineX(data?: Data, options?: LineXOptions): Line; + +export function lineY(data?: Data, options?: LineYOptions): Line; + +export class Line extends RenderableMark {} diff --git a/src/marks/line.js b/src/marks/line.js index 4b170f1608..c165627260 100644 --- a/src/marks/line.js +++ b/src/marks/line.js @@ -1,7 +1,8 @@ import {geoPath, line as shapeLine} from "d3"; import {create} from "../context.js"; -import {curveAuto, PathCurve} from "../curve.js"; +import {curveAuto, maybeCurveAuto} from "../curve.js"; import {Mark} from "../mark.js"; +import {applyGroupedMarkers, markers} from "../marker.js"; import {coerceNumbers, indexOf, identity, maybeTuple, maybeZ} from "../options.js"; import { applyDirectStyles, @@ -11,7 +12,6 @@ import { groupIndex } from "../style.js"; import {maybeDenseIntervalX, maybeDenseIntervalY} from "../transforms/bin.js"; -import {applyGroupedMarkers, markers} from "./marker.js"; const defaults = { ariaLabel: "line", @@ -37,7 +37,7 @@ export class Line extends Mark { defaults ); this.z = z; - this.curve = PathCurve(curve, tension); + this.curve = maybeCurveAuto(curve, tension); markers(this, options); } filter(index) { @@ -63,7 +63,7 @@ export class Line extends Mark { .append("path") .call(applyDirectStyles, this) .call(applyGroupedChannelStyles, this, channels) - .call(applyGroupedMarkers, this, channels) + .call(applyGroupedMarkers, this, channels, context) .attr( "d", curve === curveAuto && context.projection @@ -99,20 +99,17 @@ function sphereLine(projection, X, Y) { }; } -/** @jsdoc line */ export function line(data, options = {}) { let {x, y, ...remainingOptions} = options; [x, y] = maybeTuple(x, y); return new Line(data, {...remainingOptions, x, y}); } -/** @jsdoc lineX */ export function lineX(data, options = {}) { const {x = identity, y = indexOf, ...remainingOptions} = options; return new Line(data, maybeDenseIntervalY({...remainingOptions, x, y})); } -/** @jsdoc lineY */ export function lineY(data, options = {}) { const {x = indexOf, y = identity, ...remainingOptions} = options; return new Line(data, maybeDenseIntervalX({...remainingOptions, x, y})); diff --git a/src/marks/linearRegression.d.ts b/src/marks/linearRegression.d.ts new file mode 100644 index 0000000000..16b2a2c0ab --- /dev/null +++ b/src/marks/linearRegression.d.ts @@ -0,0 +1,25 @@ +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {Reducer} from "../reducer.js"; +import type {BinOptions} from "../transforms/bin.js"; + +interface LinearRegressionOptions extends MarkOptions, BinOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + z?: ChannelValue; + ci?: number; + precision?: number; + reduce?: Reducer; +} + +export interface LinearRegressionXOptions extends LinearRegressionOptions { + y?: ChannelValueSpec & Omit; // interval must be mark-level option +} + +export interface LinearRegressionYOptions extends LinearRegressionOptions { + x?: ChannelValueSpec & Omit; // interval must be mark-level option +} + +export function linearRegressionX(data?: Data, options?: LinearRegressionXOptions): RenderableMark; + +export function linearRegressionY(data?: Data, options?: LinearRegressionYOptions): RenderableMark; diff --git a/src/marks/linearRegression.js b/src/marks/linearRegression.js index 39cd8182e3..7bd85ff9b0 100644 --- a/src/marks/linearRegression.js +++ b/src/marks/linearRegression.js @@ -122,7 +122,6 @@ class LinearRegressionY extends LinearRegression { } } -/** @jsdoc linearRegressionX */ export function linearRegressionX(data, options = {}) { const { y = indexOf, @@ -134,7 +133,6 @@ export function linearRegressionX(data, options = {}) { return new LinearRegressionX(data, maybeDenseIntervalY({...remainingOptions, x, y, fill, stroke})); } -/** @jsdoc linearRegressionY */ export function linearRegressionY(data, options = {}) { const { x = indexOf, diff --git a/src/marks/link.d.ts b/src/marks/link.d.ts new file mode 100644 index 0000000000..f226754607 --- /dev/null +++ b/src/marks/link.d.ts @@ -0,0 +1,17 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {CurveAutoOptions} from "../curve.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {MarkerOptions} from "../marker.js"; + +export interface LinkOptions extends MarkOptions, MarkerOptions, CurveAutoOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + x1?: ChannelValueSpec; + y1?: ChannelValueSpec; + x2?: ChannelValueSpec; + y2?: ChannelValueSpec; +} + +export function link(data?: Data, options?: LinkOptions): Link; + +export class Link extends RenderableMark {} diff --git a/src/marks/link.js b/src/marks/link.js index 7d8aa71fd0..c7f384fad4 100644 --- a/src/marks/link.js +++ b/src/marks/link.js @@ -1,10 +1,10 @@ import {geoPath, pathRound as path} from "d3"; import {create} from "../context.js"; -import {curveAuto, PathCurve} from "../curve.js"; +import {curveAuto, maybeCurveAuto} from "../curve.js"; import {Mark} from "../mark.js"; +import {markers, applyMarkers} from "../marker.js"; import {coerceNumbers} from "../options.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, applyTransform} from "../style.js"; -import {markers, applyMarkers} from "./marker.js"; const defaults = { ariaLabel: "link", @@ -27,7 +27,7 @@ export class Link extends Mark { options, defaults ); - this.curve = PathCurve(curve, tension); + this.curve = maybeCurveAuto(curve, tension); markers(this, options); } project(channels, values, context) { @@ -64,7 +64,7 @@ export class Link extends Mark { } ) .call(applyChannelStyles, this, channels) - .call(applyMarkers, this, channels) + .call(applyMarkers, this, channels, context) ) .node(); } @@ -86,7 +86,6 @@ function sphereLink(projection, X1, Y1, X2, Y2) { }); } -/** @jsdoc link */ export function link(data, options = {}) { let {x, x1, x2, y, y1, y2, ...remainingOptions} = options; [x1, x2] = maybeSameValue(x, x1, x2); diff --git a/src/marks/raster.d.ts b/src/marks/raster.d.ts new file mode 100644 index 0000000000..33acb74911 --- /dev/null +++ b/src/marks/raster.d.ts @@ -0,0 +1,54 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; + +export type RasterInterpolateName = "nearest" | "barycentric" | "random-walk"; + +export type RasterInterpolateFunction = ( + index: number[], + width: number, + height: number, + X: number[], + Y: number[], + V: any[] +) => any[]; + +export type RasterInterpolate = RasterInterpolateName | RasterInterpolateFunction; + +export type RandomSource = () => number; + +export type RasterSampler = (x: number, y: number, facet: number[] & {fx: any; fy: any}) => any; + +export interface RasterOptions extends Omit { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + x1?: number; + x2?: number; + y1?: number; + y2?: number; + width?: number; + height?: number; + pixelSize?: number; + blur?: number; + interpolate?: RasterInterpolate | "none" | null; + imageRendering?: string; + fill?: ChannelValueSpec | RasterSampler; + fillOpacity?: ChannelValueSpec | RasterSampler; +} + +export function raster(options?: RasterOptions): Raster; + +export function raster(data?: Data, options?: RasterOptions): Raster; + +export const interpolateNone: RasterInterpolateFunction; + +export function interpolatorBarycentric(options?: {random?: RandomSource}): RasterInterpolateFunction; + +export const interpolateNearest: RasterInterpolateFunction; + +export function interpolatorRandomWalk(options?: { + random?: RandomSource; + minDistance?: number; + maxSteps?: number; +}): RasterInterpolateFunction; + +export class Raster extends RenderableMark {} diff --git a/src/marks/raster.js b/src/marks/raster.js index 3351c92ff8..a2989d16f4 100644 --- a/src/marks/raster.js +++ b/src/marks/raster.js @@ -186,7 +186,6 @@ export function maybeTuples(k, data, options) { return [data, {...rest, x, y, [k]: z}]; } -/** @jsdoc raster */ export function raster() { const [data, options] = maybeTuples("fill", ...arguments); return new Raster( @@ -272,7 +271,6 @@ function maybeInterpolate(interpolate) { // any blending or interpolation. Note: if multiple samples map to the same // pixel, the last one wins; this can introduce bias if the points are not in // random order, so use Plot.shuffle to randomize the input if needed. -/** @jsdoc interpolateNone */ export function interpolateNone(index, width, height, X, Y, V) { const W = new Array(width * height); for (const i of index) { @@ -282,7 +280,6 @@ export function interpolateNone(index, width, height, X, Y, V) { return W; } -/** @jsdoc interpolatorBarycentric */ export function interpolatorBarycentric({random = randomLcg(42)} = {}) { return (index, width, height, X, Y, V) => { // Flatten the input coordinates to prepare to insert extrapolated points @@ -348,7 +345,6 @@ export function interpolatorBarycentric({random = randomLcg(42)} = {}) { }; } -/** @jsdoc interpolateNearest */ export function interpolateNearest(index, width, height, X, Y, V) { const W = new V.constructor(width * height); const delaunay = Delaunay.from( @@ -370,7 +366,6 @@ export function interpolateNearest(index, width, height, X, Y, V) { } // https://observablehq.com/@observablehq/walk-on-spheres-precision -/** @jsdoc interpolatorRandomWalk */ export function interpolatorRandomWalk({random = randomLcg(42), minDistance = 0.5, maxSteps = 2} = {}) { return (index, width, height, X, Y, V) => { const W = new V.constructor(width * height); diff --git a/src/marks/rect.d.ts b/src/marks/rect.d.ts new file mode 100644 index 0000000000..9cd4a2099d --- /dev/null +++ b/src/marks/rect.d.ts @@ -0,0 +1,33 @@ +import type {ChannelValueIntervalSpec, ChannelValueSpec} from "../channel.js"; +import type {InsetOptions} from "../inset.js"; +import type {Interval} from "../interval.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; +import type {StackOptions} from "../transforms/stack.js"; + +export interface RectOptions extends MarkOptions, InsetOptions, StackOptions { + x?: ChannelValueIntervalSpec; + x1?: ChannelValueSpec; + x2?: ChannelValueSpec; + y?: ChannelValueIntervalSpec; + y1?: ChannelValueSpec; + y2?: ChannelValueSpec; + interval?: Interval; + rx?: number | string; + ry?: number | string; +} + +export interface RectXOptions extends RectOptions { + x?: ChannelValueSpec; // disallow x interval +} + +export interface RectYOptions extends RectOptions { + y?: ChannelValueSpec; // disallow y interval +} + +export function rect(data?: Data, options?: RectOptions): Rect; + +export function rectX(data?: Data, options?: RectXOptions): Rect; + +export function rectY(data?: Data, options?: RectYOptions): Rect; + +export class Rect extends RenderableMark {} diff --git a/src/marks/rect.js b/src/marks/rect.js index 5230122cad..8c8f5993cd 100644 --- a/src/marks/rect.js +++ b/src/marks/rect.js @@ -99,17 +99,14 @@ export class Rect extends Mark { } } -/** @jsdoc rect */ export function rect(data, options) { return new Rect(data, maybeTrivialIntervalX(maybeTrivialIntervalY(options))); } -/** @jsdoc rectX */ export function rectX(data, options = {y: indexOf, interval: 1, x2: identity}) { return new Rect(data, maybeStackX(maybeTrivialIntervalY(maybeIdentityX(options)))); } -/** @jsdoc rectY */ export function rectY(data, options = {x: indexOf, interval: 1, y2: identity}) { return new Rect(data, maybeStackY(maybeTrivialIntervalX(maybeIdentityY(options)))); } diff --git a/src/marks/rule.d.ts b/src/marks/rule.d.ts new file mode 100644 index 0000000000..4af050673a --- /dev/null +++ b/src/marks/rule.d.ts @@ -0,0 +1,30 @@ +import type {ChannelValueIntervalSpec, ChannelValueSpec} from "../channel.js"; +import type {InsetOptions} from "../inset.js"; +import type {Interval} from "../interval.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; + +interface RuleOptions extends MarkOptions { + interval?: Interval; +} + +export interface RuleXOptions extends RuleOptions, Omit { + x?: ChannelValueSpec; + y?: ChannelValueIntervalSpec; + y1?: ChannelValueSpec; + y2?: ChannelValueSpec; +} + +export interface RuleYOptions extends RuleOptions, Omit { + x?: ChannelValueIntervalSpec; + x1?: ChannelValueSpec; + x2?: ChannelValueSpec; + y?: ChannelValueSpec; +} + +export function ruleX(data?: Data, options?: RuleXOptions): RuleX; + +export function ruleY(data?: Data, options?: RuleYOptions): RuleY; + +export class RuleX extends RenderableMark {} + +export class RuleY extends RenderableMark {} diff --git a/src/marks/rule.js b/src/marks/rule.js index 8de70a1e0d..e47ed93723 100644 --- a/src/marks/rule.js +++ b/src/marks/rule.js @@ -107,14 +107,12 @@ export class RuleY extends Mark { } } -/** @jsdoc ruleX */ export function ruleX(data, options) { let {x = identity, y, y1, y2, ...rest} = maybeIntervalY(options); [y1, y2] = maybeOptionalZero(y, y1, y2); return new RuleX(data, {...rest, x, y1, y2}); } -/** @jsdoc ruleY */ export function ruleY(data, options) { let {y = identity, x, x1, x2, ...rest} = maybeIntervalX(options); [x1, x2] = maybeOptionalZero(x, x1, x2); diff --git a/src/marks/text.d.ts b/src/marks/text.d.ts new file mode 100644 index 0000000000..52831232b1 --- /dev/null +++ b/src/marks/text.d.ts @@ -0,0 +1,53 @@ +import type {ChannelValue, ChannelValueIntervalSpec, ChannelValueSpec} from "../channel.js"; +import type {Interval} from "../interval.js"; +import type {Data, FrameAnchor, MarkOptions, RenderableMark} from "../mark.js"; + +export type TextAnchor = "start" | "middle" | "end"; + +export type LineAnchor = "top" | "middle" | "bottom"; + +export type TextOverflow = + | "clip" + | "ellipsis" + | "clip-start" + | "clip-end" + | "ellipsis-start" + | "ellipsis-middle" + | "ellipsis-end"; + +export interface TextOptions extends MarkOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + text?: ChannelValue; + frameAnchor?: FrameAnchor; + textAnchor?: TextAnchor; + lineAnchor?: LineAnchor; + lineHeight?: number; + lineWidth?: number; + textOverflow?: TextOverflow; + monospace?: boolean; + fontFamily?: string; + fontSize?: ChannelValue; + fontStyle?: string; + fontVariant?: string; + fontWeight?: string | number; + rotate?: ChannelValue; +} + +export interface TextXOptions extends Omit { + y?: ChannelValueIntervalSpec; + interval?: Interval; +} + +export interface TextYOptions extends Omit { + x?: ChannelValueIntervalSpec; + interval?: Interval; +} + +export function text(data?: Data, options?: TextOptions): Text; + +export function textX(data?: Data, options?: TextXOptions): Text; + +export function textY(data?: Data, options?: TextYOptions): Text; + +export class Text extends RenderableMark {} diff --git a/src/marks/text.js b/src/marks/text.js index f0baffa053..3aed8f8d64 100644 --- a/src/marks/text.js +++ b/src/marks/text.js @@ -161,20 +161,17 @@ function applyMultilineText(selection, mark, T, TL) { }); } -/** @jsdoc text */ export function text(data, options = {}) { let {x, y, ...remainingOptions} = options; if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); return new Text(data, {...remainingOptions, x, y}); } -/** @jsdoc textX */ export function textX(data, options = {}) { const {x = identity, ...remainingOptions} = options; return new Text(data, maybeIntervalMidY({...remainingOptions, x})); } -/** @jsdoc textY */ export function textY(data, options = {}) { const {y = identity, ...remainingOptions} = options; return new Text(data, maybeIntervalMidX({...remainingOptions, y})); diff --git a/src/marks/tick.d.ts b/src/marks/tick.d.ts new file mode 100644 index 0000000000..cf04debaf9 --- /dev/null +++ b/src/marks/tick.d.ts @@ -0,0 +1,21 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {InsetOptions} from "../inset.js"; +import type {Data, MarkOptions, RenderableMark} from "../mark.js"; + +export interface TickXOptions extends MarkOptions, Omit { + x?: ChannelValueSpec; + y?: ChannelValueSpec; +} + +export interface TickYOptions extends MarkOptions, Omit { + y?: ChannelValueSpec; + x?: ChannelValueSpec; +} + +export function tickX(data?: Data, options?: TickXOptions): TickX; + +export function tickY(data?: Data, options?: TickYOptions): TickY; + +export class TickX extends RenderableMark {} + +export class TickY extends RenderableMark {} diff --git a/src/marks/tick.js b/src/marks/tick.js index 981e534d8d..6901471f0a 100644 --- a/src/marks/tick.js +++ b/src/marks/tick.js @@ -100,13 +100,11 @@ export class TickY extends AbstractTick { } } -/** @jsdoc tickX */ export function tickX(data, options = {}) { const {x = identity, ...remainingOptions} = options; return new TickX(data, {...remainingOptions, x}); } -/** @jsdoc tickY */ export function tickY(data, options = {}) { const {y = identity, ...remainingOptions} = options; return new TickY(data, {...remainingOptions, y}); diff --git a/src/marks/tree.d.ts b/src/marks/tree.d.ts new file mode 100644 index 0000000000..b6066ef773 --- /dev/null +++ b/src/marks/tree.d.ts @@ -0,0 +1,15 @@ +import type {CompoundMark, Data, MarkOptions} from "../mark.js"; +import type {TreeTransformOptions} from "../transforms/tree.js"; +import type {DotOptions} from "./dot.js"; +import type {LinkOptions} from "./link.js"; +import type {TextOptions} from "./text.js"; + +// TODO tree channels, e.g., "node:name" | "node:path" | "node:internal"? +export interface TreeOptions extends DotOptions, LinkOptions, TextOptions, TreeTransformOptions { + dot?: boolean; + textStroke?: MarkOptions["stroke"]; +} + +export function tree(data?: Data, options?: TreeOptions): CompoundMark; + +export function cluster(data?: Data, options?: TreeOptions): CompoundMark; diff --git a/src/marks/tree.js b/src/marks/tree.js index d24c219bb9..bed674b4af 100644 --- a/src/marks/tree.js +++ b/src/marks/tree.js @@ -6,7 +6,6 @@ import {dot} from "./dot.js"; import {link} from "./link.js"; import {text} from "./text.js"; -/** @jsdoc tree */ export function tree(data, options = {}) { let { fill, @@ -67,7 +66,6 @@ export function tree(data, options = {}) { ); } -/** @jsdoc cluster */ export function cluster(data, options) { return tree(data, {...options, treeLayout: Cluster}); } diff --git a/src/marks/vector.d.ts b/src/marks/vector.d.ts new file mode 100644 index 0000000000..bc802f6965 --- /dev/null +++ b/src/marks/vector.d.ts @@ -0,0 +1,31 @@ +import type {ChannelValue, ChannelValueSpec} from "../channel.js"; +import type {Data, FrameAnchor, MarkOptions, RenderableMark} from "../mark.js"; + +export type VectorShapeName = "arrow" | "spike"; + +export interface VectorShapeImplementation { + draw(context: CanvasPath, length: number, radius: number): void; +} + +export type VectorShapeSpec = VectorShapeName | VectorShapeImplementation; + +export interface VectorOptions extends MarkOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + r?: ChannelValueSpec; + length?: ChannelValueSpec; + rotate?: ChannelValue; + shape?: VectorShapeSpec; + anchor?: "start" | "middle" | "end"; + frameAnchor?: FrameAnchor; +} + +export function vector(data?: Data, options?: VectorOptions): Vector; + +export function vectorX(data?: Data, options?: VectorOptions): Vector; + +export function vectorY(data?: Data, options?: VectorOptions): Vector; + +export function spike(data?: Data, options?: VectorOptions): Vector; + +export class Vector extends RenderableMark {} diff --git a/src/marks/vector.js b/src/marks/vector.js index cae03e0b63..c2420f869e 100644 --- a/src/marks/vector.js +++ b/src/marks/vector.js @@ -56,7 +56,7 @@ function isShapeObject(value) { return value && typeof value.draw === "function"; } -function Shape(shape) { +function maybeShape(shape) { if (isShapeObject(shape)) return shape; const value = shapes.get(`${shape}`.toLowerCase()); if (value) return value; @@ -82,7 +82,7 @@ export class Vector extends Mark { this.r = +r; this.length = cl; this.rotate = cr; - this.shape = Shape(shape); + this.shape = maybeShape(shape); this.anchor = keyword(anchor, "anchor", ["start", "middle", "end"]); this.frameAnchor = maybeFrameAnchor(frameAnchor); } @@ -137,26 +137,22 @@ export class Vector extends Mark { } } -/** @jsdoc vector */ export function vector(data, options = {}) { let {x, y, ...rest} = options; if (options.frameAnchor === undefined) [x, y] = maybeTuple(x, y); return new Vector(data, {...rest, x, y}); } -/** @jsdoc vectorX */ export function vectorX(data, options = {}) { const {x = identity, ...rest} = options; return new Vector(data, {...rest, x}); } -/** @jsdoc vectorY */ export function vectorY(data, options = {}) { const {y = identity, ...rest} = options; return new Vector(data, {...rest, y}); } -/** @jsdoc spike */ export function spike(data, options = {}) { const { shape = shapeSpike, diff --git a/src/math.js b/src/math.js new file mode 100644 index 0000000000..3733545514 --- /dev/null +++ b/src/math.js @@ -0,0 +1 @@ +export const radians = Math.PI / 180; diff --git a/src/math.ts b/src/math.ts deleted file mode 100644 index 588f12d538..0000000000 --- a/src/math.ts +++ /dev/null @@ -1 +0,0 @@ -export const radians: number = Math.PI / 180; diff --git a/src/memoize.js b/src/memoize.js new file mode 100644 index 0000000000..9395e658be --- /dev/null +++ b/src/memoize.js @@ -0,0 +1,10 @@ +export function memoize1(compute) { + let cacheValue, cacheKeys; + return (...keys) => { + if (cacheKeys?.length !== keys.length || cacheKeys.some((k, i) => k !== keys[i])) { + cacheKeys = keys; + cacheValue = compute(...keys); + } + return cacheValue; + }; +} diff --git a/src/memoize.ts b/src/memoize.ts deleted file mode 100644 index 90926c420c..0000000000 --- a/src/memoize.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -export function memoize1(compute: (...rest: any[]) => T) { - let cacheValue: T, cacheKeys: any[] | undefined; - return (...keys: any[]) => { - if (cacheKeys?.length !== keys.length || cacheKeys.some((k, i) => k !== keys[i])) { - cacheKeys = keys; - cacheValue = compute(...keys); - } - return cacheValue; - }; -} diff --git a/src/options.d.ts b/src/options.d.ts new file mode 100644 index 0000000000..022889a2ae --- /dev/null +++ b/src/options.d.ts @@ -0,0 +1,8 @@ +import type {ChannelTransform, ChannelValue} from "./channel.js"; +import type {Data} from "./mark.js"; + +export function valueof(data: Data | null, value: ChannelValue | null, type?: any): any[] | null; + +export function column(source?: any): [ChannelTransform, (value: T) => T]; + +export const identity: ChannelTransform; diff --git a/src/options.js b/src/options.js index 4d76ce1ce2..aa09100073 100644 --- a/src/options.js +++ b/src/options.js @@ -6,7 +6,6 @@ import {maybeTimeInterval, maybeUtcInterval} from "./time.js"; export const TypedArray = Object.getPrototypeOf(Uint8Array); const objectToString = Object.prototype.toString; -/** @jsdoc valueof */ export function valueof(data, value, type) { const valueType = typeof value; return valueType === "string" @@ -40,7 +39,6 @@ function floater(f) { export const field = (name) => (d) => d[name]; export const indexOf = (d, i) => i; -/** @jsdoc identity */ export const identity = {transform: (d) => d}; export const zero = () => 0; export const one = () => 1; @@ -231,7 +229,6 @@ export function maybeInput(key, options) { return options[key]; } -/** @jsdoc column */ export function column(source) { // Defines a column whose values are lazily populated by calling the returned // setter. If the given source is labeled, the label is propagated to the @@ -460,7 +457,7 @@ export function inherit(options = {}, ...rest) { // Given an iterable of named things (objects with a name property), returns a // corresponding object with properties associated with the given name. -export function Named(things) { +export function named(things) { console.warn("named iterables are deprecated; please use an object instead"); const names = new Set(); return Object.fromEntries( @@ -477,5 +474,5 @@ export function Named(things) { } export function maybeNamed(things) { - return isIterable(things) ? Named(things) : things; + return isIterable(things) ? named(things) : things; } diff --git a/src/plot.d.ts b/src/plot.d.ts new file mode 100644 index 0000000000..2c70ac3725 --- /dev/null +++ b/src/plot.d.ts @@ -0,0 +1,279 @@ +import type {ChannelValue} from "./channel.js"; +import type {LegendOptions} from "./legends.js"; +import type {Data, Markish} from "./mark.js"; +import type {ProjectionFactory, ProjectionImplementation, ProjectionName, ProjectionOptions} from "./projection.js"; +import type {Scale, ScaleDefaults, ScaleOptions} from "./scales.js"; + +export interface PlotOptions extends ScaleDefaults { + // dimensions + + /** + * The outer width of the plot in pixels, including margins. Defaults to 640. + * On Observable, this can be set to the built-in + * [width](https://github.com/observablehq/stdlib/blob/main/README.md#width) + * for full-width responsive plots. Note: the default style has a max-width of + * 100%; the plot will automatically shrink to fit even when a fixed width is + * specified. + */ + width?: number; + + /** + * The outer height of the plot in pixels, including margins. The default + * depends on the plot’s scales, and the plot’s {@link width} if an + * {@link aspectRatio} is specified. For example, if {@link y} is linear and + * there is no {@link fy} scale, it might be 396. + */ + height?: number; + + /** + * The desired aspect ratio of the *x* and *y* scales, affecting the default + * {@link height}. Given an aspect ratio of *dx* / *dy*, and assuming that the + * *x* and *y* scales represent equivalent units (say, degrees Celsius or + * meters), computes a default {@link height} such that *dx* pixels along *x* + * represents the same variation as *dy* pixels along *y*. Note: when + * faceting, set the *fx* and *fy* scales’ **round** option to false for an + * exact aspect ratio. + */ + aspectRatio?: number | boolean | null; + + /** + * Shorthand to set the same default for all four margins: {@link marginTop}, + * {@link marginRight}, {@link marginBottom}, and {@link marginLeft}. + * Otherwise, the default margins depend on the maximum margins of the plot’s + * {@link marks}. While most marks default to zero margins (because they are + * drawn inside the chart area), Plot’s axis marks have non-zero default + * margins. + */ + margin?: number; + + /** + * The top margin; the distance in pixels between the top edges of the inner + * and outer plot area. Defaults to the maximum top margin of the plot’s + * {@link marks}. + */ + marginTop?: number; + + /** + * The right margin; the distance in pixels between the right edges of the + * inner and outer plot area. Defaults to the maximum right margin of the + * plot’s {@link marks}. + */ + marginRight?: number; + + /** + * The bottom margin; the distance in pixels between the bottom edges of the + * inner and outer plot area. Defaults to the maximum bottom margin of the + * plot’s {@link marks}. + */ + marginBottom?: number; + + /** + * The left margin; the distance in pixels between the left edges of the inner + * and outer plot area. Defaults to the maximum left margin of the plot’s + * {@link marks}. + */ + marginLeft?: number; + + // other top-level options + + /** + * The **style** option allows custom styles to override Plot’s defaults. It + * may be specified either as a string of inline styles (*e.g.*, `"color: + * red;"`, in the same fashion as assigning + * [*element*.style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style)) + * or an object of properties (*e.g.*, `{color: "red"}`, in the same fashion + * as assigning [*element*.style + * properties](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration)). + * Note that unitless numbers ([quirky + * lengths](https://www.w3.org/TR/css-values-4/#deprecated-quirky-length)) + * such as `{padding: 20}` may not supported by some browsers; you should + * instead specify a string with units such as `{padding: "20px"}`. By + * default, the returned plot has a white background, a max-width of 100%, and + * the system-ui font. Plot’s marks and axes default to + * [currentColor](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword), + * meaning that they will inherit the surrounding content’s color. For + * example, a dark theme: + * + * ```js + * Plot.plot({ + * style: "background: black; color: white;", + * marks: … + * }) + * ``` + */ + style?: string | Partial | null; + + /** + * The generated SVG element has a random class name which applies a default + * stylesheet. Use the top-level **className** option to specify that class + * name. + */ + className?: string | null; + + /** + * If a **caption** is specified, Plot wraps the generated SVG element in an + * HTML figure element with a figcaption, returning the figure. To specify an + * HTML caption, consider using the [`html` tagged template + * literal](http://github.com/observablehq/htl); otherwise, the specified + * string represents text that will be escaped as needed. + * + * ```js + * Plot.plot({ + * caption: html`Figure 1. This chart has a fancy caption.`, + * marks: … + * }) + * ``` + */ + caption?: string | Node | null; + + /** + * The [aria-label + * attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label) + * on the SVG root. + */ + ariaLabel?: string | null; + + /** + * The [aria-description + * attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-description) + * on the SVG root. + */ + ariaDescription?: string | null; + + /** + * The owner + * [Document](https://developer.mozilla.org/en-US/docs/Web/API/Document) used + * to create DOM elements. Defaults to window.document, but can be changed to + * another document, say when using a virtual DOM library for server-side + * rendering in Node. + */ + document?: Document; + + // scale, axis, and legend definitions + + /** horizontal facet position scale; always a band scale */ + fx?: ScaleOptions; + + /** vertical facet position scale; always a band scale */ + fy?: ScaleOptions; + + /** horizontal position scale */ + x?: ScaleOptions; + + /** vertical position scale */ + y?: ScaleOptions; + + /** radius (size) scale; for dots and Point geos */ + r?: ScaleOptions; + + /** color scale; for fill or stroke */ + color?: ScaleOptions; + + /** opacity scale; for fill or stroke opacity */ + opacity?: ScaleOptions; + + /** categorical symbol scale; for dots */ + symbol?: ScaleOptions; + + /** length scale; for vectors */ + length?: ScaleOptions; + + /** projection; alternative to the x and y scales */ + projection?: ProjectionOptions | ProjectionName | ProjectionFactory | ProjectionImplementation | null; + + /** plot facet options */ + facet?: PlotFacetOptions; + + /** + * The array of marks to render. Each mark has its own data and options; see + * the respective mark type for details. + * + * Each mark (or “markish”) may also be a nested array of marks, allowing + * composition. Marks may also be a function which returns an SVG element, to + * insert arbitrary content into the plot. And marks may be null or undefined + * to produce no output; this is useful for showing marks conditionally + * (*e.g.*, when a box is checked). + * + * Marks are drawn in *z* order, last on top. For example, here a single rule + * at *y* = 0 is drawn on top of blue bars for the [*alphabet* + * dataset](./test/data/alphabet.csv). + * + * ```js + * Plot.plot({ + * marks: [ + * Plot.barY(alphabet, {x: "letter", y: "frequency", fill: "steelblue"}), + * Plot.ruleY([0]) + * ] + * }) + * ``` + */ + marks?: Markish[]; +} + +export interface PlotFacetOptions { + /** data for top-level faceting */ + data?: Data; + + /** x channel for top-level faceting; implies fx scale */ + x?: ChannelValue; + + /** y channel for top-level faceting; implies fy scale */ + y?: ChannelValue; + + /** + * Shorthand to set the same default for all four facet margins: + * {@link marginTop}, {@link marginRight}, {@link marginBottom}, and + * {@link marginLeft}. + */ + margin?: number; + + /** + * The top facet margin; the (minimum) distance in pixels between the top + * edges of the inner and outer plot area. + */ + marginTop?: number; + + /** + * The right facet margin; the (minimum) distance in pixels between the right + * edges of the inner and outer plot area. + */ + marginRight?: number; + + /** + * The bottom facet margin; the (minimum) distance in pixels between the + * bottom edges of the inner and outer plot area. + */ + marginBottom?: number; + + /** + * The left facet margin; the (minimum) distance in pixels between the left + * edges of the inner and outer plot area. + */ + marginLeft?: number; + + /** + * default axis grid for fx and fy scales; typically set to true to enable + */ + grid?: ScaleOptions["grid"]; + + /** + * default axis label for fx and fy scales; typically set to null to disable + */ + label?: ScaleOptions["label"]; +} + +/** + * The SVG or HTML figure element returned by {@link plot} is decorated with + * additional methods to allow sharing of scales and legends across plots. + */ +export interface Plot { + scale(name: string): Scale | undefined; + legend(name: string, options?: LegendOptions): HTMLElement | undefined; +} + +/** + * Renders a new plot given the specified *options* and returns the + * corresponding SVG element, or an HTML figure element if a caption or legend + * is requested. + */ +export function plot(options?: PlotOptions): (SVGSVGElement | HTMLElement) & Plot; diff --git a/src/plot.js b/src/plot.js index 040870f149..76c99a55d8 100644 --- a/src/plot.js +++ b/src/plot.js @@ -1,19 +1,19 @@ import {select} from "d3"; -import {Channel, inferChannelScale} from "./channel.js"; -import {Context, create} from "./context.js"; -import {Dimensions} from "./dimensions.js"; -import {Facets, facetExclude, facetGroups, facetOrder, facetTranslate, facetFilter} from "./facet.js"; -import {Legends, exposeLegends} from "./legends.js"; +import {createChannel, inferChannelScale} from "./channel.js"; +import {createContext, create} from "./context.js"; +import {createDimensions} from "./dimensions.js"; +import {createFacets, facetExclude, facetGroups, facetOrder, facetTranslate, facetFilter} from "./facet.js"; +import {createLegends, exposeLegends} from "./legends.js"; import {Mark} from "./mark.js"; import {axisFx, axisFy, axisX, axisY, gridFx, gridFy, gridX, gridY} from "./marks/axis.js"; import {frame} from "./marks/frame.js"; import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeInterval} from "./options.js"; -import {Scales, ScaleFunctions, autoScaleRange, exposeScales, innerDimensions, outerDimensions} from "./scales.js"; +import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js"; +import {innerDimensions, outerDimensions} from "./scales.js"; import {position, registry as scaleRegistry} from "./scales/index.js"; import {applyInlineStyles, maybeClassName} from "./style.js"; import {consumeWarnings, warn} from "./warnings.js"; -/** @jsdoc plot */ export function plot(options = {}) { const {facet, style, caption, ariaLabel, ariaDescription} = options; @@ -57,7 +57,7 @@ export function plot(options = {}) { // All the possible facets are given by the domains of the fx or fy scales, or // the cross-product of these domains if we facet by both x and y. We sort // them in order to apply the facet filters afterwards. - const facets = Facets(channelsByScale, options); + const facets = createFacets(channelsByScale, options); if (facets !== undefined) { const topFacetsIndex = topFacetState ? facetFilter(facets, topFacetState) : undefined; @@ -132,16 +132,16 @@ export function plot(options = {}) { } // Initalize the scales and dimensions. - const scaleDescriptors = Scales(addScaleChannels(channelsByScale, stateByMark), options); - const scales = ScaleFunctions(scaleDescriptors); - const dimensions = Dimensions(scaleDescriptors, marks, options); + const scaleDescriptors = createScales(addScaleChannels(channelsByScale, stateByMark), options); + const scales = createScaleFunctions(scaleDescriptors); + const dimensions = createDimensions(scaleDescriptors, marks, options); autoScaleRange(scaleDescriptors, dimensions); const {fx, fy} = scales; const subdimensions = fx || fy ? innerDimensions(scaleDescriptors, dimensions) : dimensions; const superdimensions = fx || fy ? actualDimensions(scales, dimensions) : dimensions; - const context = Context(options, subdimensions, scaleDescriptors); + const context = createContext(options, subdimensions, scaleDescriptors); // Reinitialize; for deriving channels dependent on other channels. const newByScale = new Set(); @@ -191,8 +191,8 @@ export function plot(options = {}) { const newChannelsByScale = new Map(); addScaleChannels(newChannelsByScale, stateByMark, (key) => newByScale.has(key)); addScaleChannels(channelsByScale, stateByMark, (key) => newByScale.has(key)); - const newScaleDescriptors = inheritScaleLabels(Scales(newChannelsByScale, options), scaleDescriptors); - const newScales = ScaleFunctions(newScaleDescriptors); + const newScaleDescriptors = inheritScaleLabels(createScales(newChannelsByScale, options), scaleDescriptors); + const newScales = createScaleFunctions(newScaleDescriptors); Object.assign(scaleDescriptors, newScaleDescriptors); Object.assign(scales, newScales); } @@ -287,7 +287,7 @@ export function plot(options = {}) { // Wrap the plot in a figure with a caption, if desired. let figure = svg; - const legends = Legends(scaleDescriptors, context, options); + const legends = createLegends(scaleDescriptors, context, options); if (caption != null || legends.length > 0) { const {document} = context; figure = document.createElement("figure"); @@ -400,8 +400,8 @@ function maybeTopFacet(facet, options) { const data = arrayify(facet.data ?? x ?? y); if (data === undefined) throw new Error(`missing facet data`); const channels = {}; - if (x != null) channels.fx = Channel(data, {value: x, scale: "fx"}); - if (y != null) channels.fy = Channel(data, {value: y, scale: "fy"}); + if (x != null) channels.fx = createChannel(data, {value: x, scale: "fx"}); + if (y != null) channels.fy = createChannel(data, {value: y, scale: "fy"}); applyScaleTransforms(channels, options); const groups = facetGroups(data, channels); return {channels, groups, data: facet.data}; @@ -420,8 +420,8 @@ function maybeMarkFacet(mark, topFacetState, options) { if (data === undefined) throw new Error(`missing facet data in ${mark.ariaLabel}`); if (data === null) return; // ignore channel definitions if no data is provided TODO this right? const channels = {}; - if (fx != null) channels.fx = Channel(data, {value: fx, scale: "fx"}); - if (fy != null) channels.fy = Channel(data, {value: fy, scale: "fy"}); + if (fx != null) channels.fx = createChannel(data, {value: fx, scale: "fx"}); + if (fy != null) channels.fy = createChannel(data, {value: fy, scale: "fy"}); applyScaleTransforms(channels, options); return {channels, groups: facetGroups(data, channels)}; } diff --git a/src/projection.d.ts b/src/projection.d.ts new file mode 100644 index 0000000000..ed096a8c77 --- /dev/null +++ b/src/projection.d.ts @@ -0,0 +1,32 @@ +import type {GeoPermissibleObjects, GeoStreamWrapper} from "d3"; +import type {InsetOptions} from "./inset.js"; + +export type ProjectionName = + | "albers-usa" + | "albers" + | "azimuthal-equal-area" + | "azimuthal-equidistant" + | "conic-conformal" + | "conic-equal-area" + | "conic-equidistant" + | "equal-earth" + | "equirectangular" + | "gnomonic" + | "identity" + | "reflect-y" + | "mercator" + | "orthographic" + | "stereographic" + | "transverse-mercator"; + +export type ProjectionImplementation = GeoStreamWrapper; + +export type ProjectionFactory = (options: any) => ProjectionImplementation; + +export interface ProjectionOptions extends InsetOptions { + type?: ProjectionName | ProjectionFactory | null; + domain?: GeoPermissibleObjects; + rotate?: [x: number, y: number, z?: number]; + parallels?: [y1: number, y2: number]; + clip?: boolean | number | "frame" | null; +} diff --git a/src/projection.js b/src/projection.js index 73c995c2e8..07e8f1e5f2 100644 --- a/src/projection.js +++ b/src/projection.js @@ -25,7 +25,7 @@ const pi = Math.PI; const tau = 2 * pi; const defaultAspectRatio = 0.618; -export function Projection( +export function createProjection( { projection, inset: globalInset = 0, @@ -254,7 +254,7 @@ export function projectionAspectRatio(projection, marks) { // Extract the (possibly) scaled values for the x and y channels, and apply the // projection if any. -export function Position(channels, scales, context) { +export function applyPosition(channels, scales, context) { const {x, y} = channels; let position = {}; if (x) position.x = x; diff --git a/src/reducer.d.ts b/src/reducer.d.ts new file mode 100644 index 0000000000..2110e9c7d2 --- /dev/null +++ b/src/reducer.d.ts @@ -0,0 +1,40 @@ +type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + +export type ReducerPercentile = + | (`p${Digit}${Digit}` & Record) // see https://github.com/microsoft/TypeScript/issues/29729 + | "p25" + | "p50" + | "p75"; + +export type ReducerName = + | "first" + | "last" + | "count" + | "distinct" + | "sum" + | "proportion" + | "proportion-facet" + | "deviation" + | "min" + | "min-index" + | "max" + | "max-index" + | "mean" + | "median" + | "variance" + | "mode" + | "x" + | "x1" + | "x2" + | "y" + | "y1" + | "y2" + | ReducerPercentile; + +export type ReducerFunction = (values: any[], extent: {x1: any; y1: any; x2: any; y2: any}) => any; // TODO extent only for bin + +export interface ReducerImplementation { + reduce(index: number[], values: any[], extent: {x1: any; y1: any; x2: any; y2: any}): any; // TODO extent only for bin +} + +export type Reducer = ReducerName | ReducerFunction | ReducerImplementation; diff --git a/src/scales.d.ts b/src/scales.d.ts new file mode 100644 index 0000000000..e9267c09b6 --- /dev/null +++ b/src/scales.d.ts @@ -0,0 +1,175 @@ +import type {ColorSchemeName} from "./color.js"; +import type {InsetOptions} from "./inset.js"; +import type {Interpolate} from "./interpolate.js"; +import type {Interval} from "./interval.js"; +import type {LegendType} from "./legends.js"; +import type {AxisAnchor} from "./marks/axis.js"; + +export type ScaleName = "x" | "y" | "fx" | "fy" | "r" | "color" | "opacity" | "symbol" | "length"; + +export type ScaleFunctions = {[key in ScaleName]?: (value: any) => any}; + +export type ScaleType = + | "linear" + | "pow" + | "sqrt" + | "log" + | "symlog" + | "utc" + | "time" + | "point" + | "band" + | "ordinal" + | "sequential" + | "diverging" + | "diverging-log" + | "diverging-pow" + | "diverging-sqrt" + | "diverging-symlog" + | "categorical" + | "threshold" + | "quantile" + | "quantize" + | "identity"; + +export interface ScaleDefaults extends InsetOptions { + clamp?: boolean; + nice?: boolean | number | Interval; + zero?: boolean; + round?: boolean; + align?: number; + padding?: number; + + // axis options + axis?: AxisAnchor | "both" | boolean | null; // for position scales + grid?: boolean | string | Interval | Iterable; + label?: string | null; +} + +export interface ScaleOptions extends ScaleDefaults { + /** + * For quantitative data: + * * *linear* (default) - linear transform (translate and scale) + * * *pow* - power (exponential) transform + * * *sqrt* - square-root transform; *pow* with *exponent* = 0.5 + * * *log* - logarithmic transform + * * *symlog* - bi-symmetric logarithmic transform per Webber et al. + * + * For temporal data: + * * *utc* (default, recommended) - UTC time + * * *time* - local time + * + * For ordinal data: + * * *ordinal* - + * * *point* - + * * *band* - + * + * For color: + * * *categorical* - equivalent to *ordinal*; defaults to *tableau10* + * * *sequential* - equivalent to *linear*; defaults to *turbo* + * * *cyclical* - equivalent to *linear*; defaults to *rainbow* + * * *threshold* - encodes using discrete thresholds; defaults to *rdylbu* + * * *quantile* - encodes using quantile thresholds; defaults to *rdylbu* + * * *quantize* - uniformly quantizes a continuous domain; defaults to *rdylbu* + * * *diverging* - *linear*, but with a pivot; defaults to *rdbu* + * * *diverging-log* - *log*, but with a pivot; defaults to *rdbu* + * * *diverging-pow* - *pow*, but with a pivot; defaults to *rdbu* + * * *diverging-sqrt* - *sqrt*, but with a pivot; defaults to *rdbu* + * * *diverging-symlog* - *symlog*, but with a pivot; defaults to *rdbu* + * + * Other scale types: + * * *identity* - + */ + type?: ScaleType | null; + + /** + * For continuous data: [*min*, *max*]. Can be [*max*, *min*] to reverse the scale. + * + * For ordinal data: [...*values*], in order. + */ + domain?: Iterable; + + /** + * For continuous data: [*min*, *max*]. Can be [*max*, *min*] to reverse the scale. + * + * For ordinal data: [...*values*], in order. + */ + range?: Iterable; + + unknown?: any; + reverse?: boolean; + transform?: (t: any) => any; + + // quantitative scale options + interval?: Interval; + percent?: boolean; + + // color scale options + scheme?: ColorSchemeName; + interpolate?: Interpolate; + + // power scale options + exponent?: number; + + // log scale options + base?: number; + + // symlog scale options + constant?: number; + + // quantize and quantile scale options + n?: number; + quantiles?: number; // deprecated; use n instead + + // diverging scale options + pivot?: any; + symmetric?: boolean; + + // position scale options + paddingInner?: number; + paddingOuter?: number; + + // axis and legend options + legend?: LegendType | boolean | null; // for color, opacity, and symbol scales + ticks?: number | Interval | Iterable; + tickSize?: number; + tickSpacing?: number; + tickPadding?: number; + tickFormat?: string | ((t: any, i: number) => any) | null; + tickRotate?: number; + fontVariant?: string; + ariaLabel?: string; + ariaDescription?: string; + labelAnchor?: "top" | "right" | "bottom" | "left" | "center"; + labelOffset?: number; + line?: boolean; +} + +// TODO greater specificity +export interface Scale { + type: ScaleType; + domain: any[]; + range?: any[]; + transform?: (t: any) => any; + percent?: boolean; + unknown?: any; + interval?: Interval; + interpolate?: Interpolate; + clamp?: boolean; + pivot?: any; + symmetric?: boolean; + base?: number; + exponent?: number; + constant?: number; + align?: number; + round?: boolean; + padding?: number; + paddingInner?: number; + paddingOuter?: number; + bandwidth?: number; + step?: number; + apply(t: any): any; + invert?(t: any): any; +} + +export function scale(options?: {[name in ScaleName]?: ScaleOptions}): any; diff --git a/src/scales.js b/src/scales.js index fb92cb609f..1e0138380c 100644 --- a/src/scales.js +++ b/src/scales.js @@ -11,30 +11,30 @@ import { } from "./options.js"; import {registry, color, position, radius, opacity, symbol, length} from "./scales/index.js"; import { - ScaleLinear, - ScaleSqrt, - ScalePow, - ScaleLog, - ScaleSymlog, - ScaleQuantile, - ScaleQuantize, - ScaleThreshold, - ScaleIdentity + createScaleLinear, + createScaleSqrt, + createScalePow, + createScaleLog, + createScaleSymlog, + createScaleQuantile, + createScaleQuantize, + createScaleThreshold, + createScaleIdentity } from "./scales/quantitative.js"; import { - ScaleDiverging, - ScaleDivergingSqrt, - ScaleDivergingPow, - ScaleDivergingLog, - ScaleDivergingSymlog + createScaleDiverging, + createScaleDivergingSqrt, + createScaleDivergingPow, + createScaleDivergingLog, + createScaleDivergingSymlog } from "./scales/diverging.js"; import {isDivergingScheme} from "./scales/schemes.js"; -import {ScaleTime, ScaleUtc} from "./scales/temporal.js"; -import {ScaleOrdinal, ScalePoint, ScaleBand, ordinalImplicit} from "./scales/ordinal.js"; -import {maybeSymbol} from "./symbols.js"; +import {createScaleTime, createScaleUtc} from "./scales/temporal.js"; +import {createScaleOrdinal, createScalePoint, createScaleBand, ordinalImplicit} from "./scales/ordinal.js"; +import {maybeSymbol} from "./symbol.js"; import {warn} from "./warnings.js"; -export function Scales( +export function createScales( channelsByScale, { label: globalLabel, @@ -57,7 +57,7 @@ export function Scales( const scales = {}; for (const [key, channels] of channelsByScale) { const scaleOptions = options[key]; - const scale = Scale(key, channels, { + const scale = createScale(key, channels, { round: registry.get(key) === position ? round : undefined, // only for position nice, clamp, @@ -97,7 +97,7 @@ export function Scales( return scales; } -export function ScaleFunctions(scales) { +export function createScaleFunctions(scales) { return Object.fromEntries( Object.entries(scales) .filter(([, {scale}]) => scale) // drop identity scales @@ -237,10 +237,10 @@ function piecewiseRange(scale) { } export function normalizeScale(key, scale, hint) { - return Scale(key, hint === undefined ? undefined : [{hint}], {...scale}); + return createScale(key, hint === undefined ? undefined : [{hint}], {...scale}); } -function Scale(key, channels = [], options = {}) { +function createScale(key, channels = [], options = {}) { const type = inferScaleType(key, channels, options); // Warn for common misuses of implicit ordinal scales. We disable this test if @@ -323,47 +323,47 @@ function Scale(key, channels = [], options = {}) { switch (type) { case "diverging": - return ScaleDiverging(key, channels, options); + return createScaleDiverging(key, channels, options); case "diverging-sqrt": - return ScaleDivergingSqrt(key, channels, options); + return createScaleDivergingSqrt(key, channels, options); case "diverging-pow": - return ScaleDivergingPow(key, channels, options); + return createScaleDivergingPow(key, channels, options); case "diverging-log": - return ScaleDivergingLog(key, channels, options); + return createScaleDivergingLog(key, channels, options); case "diverging-symlog": - return ScaleDivergingSymlog(key, channels, options); + return createScaleDivergingSymlog(key, channels, options); case "categorical": case "ordinal": case ordinalImplicit: - return ScaleOrdinal(key, channels, options); + return createScaleOrdinal(key, channels, options); case "cyclical": case "sequential": case "linear": - return ScaleLinear(key, channels, options); + return createScaleLinear(key, channels, options); case "sqrt": - return ScaleSqrt(key, channels, options); + return createScaleSqrt(key, channels, options); case "threshold": - return ScaleThreshold(key, channels, options); + return createScaleThreshold(key, channels, options); case "quantile": - return ScaleQuantile(key, channels, options); + return createScaleQuantile(key, channels, options); case "quantize": - return ScaleQuantize(key, channels, options); + return createScaleQuantize(key, channels, options); case "pow": - return ScalePow(key, channels, options); + return createScalePow(key, channels, options); case "log": - return ScaleLog(key, channels, options); + return createScaleLog(key, channels, options); case "symlog": - return ScaleSymlog(key, channels, options); + return createScaleSymlog(key, channels, options); case "utc": - return ScaleUtc(key, channels, options); + return createScaleUtc(key, channels, options); case "time": - return ScaleTime(key, channels, options); + return createScaleTime(key, channels, options); case "point": - return ScalePoint(key, channels, options); + return createScalePoint(key, channels, options); case "band": - return ScaleBand(key, channels, options); + return createScaleBand(key, channels, options); case "identity": - return registry.get(key) === position ? ScaleIdentity() : {type: "identity"}; + return registry.get(key) === position ? createScaleIdentity() : {type: "identity"}; case undefined: return; default: @@ -499,7 +499,6 @@ function coerceSymbols(values) { return map(values, maybeSymbol); } -/** @jsdoc scale */ export function scale(options = {}) { let scale; for (const key in options) { diff --git a/src/scales/diverging.js b/src/scales/diverging.js index b111f9d3dd..83c40c8606 100644 --- a/src/scales/diverging.js +++ b/src/scales/diverging.js @@ -11,9 +11,9 @@ import { import {positive, negative} from "../defined.js"; import {quantitativeScheme} from "./schemes.js"; import {registry, color} from "./index.js"; -import {inferDomain, Interpolator, flip, interpolatePiecewise} from "./quantitative.js"; +import {inferDomain, maybeInterpolator, flip, interpolatePiecewise} from "./quantitative.js"; -function ScaleD( +function createScaleD( key, scale, transform, @@ -48,7 +48,7 @@ function ScaleD( // function is a “fixed” interpolator on the [0, 1] interval, as when a // color scheme such as interpolateRdBu is used. if (typeof interpolate !== "function") { - interpolate = Interpolator(interpolate); + interpolate = maybeInterpolator(interpolate); } // If an explicit range is specified, promote it to a piecewise interpolator. @@ -75,31 +75,35 @@ function ScaleD( return {type, domain: [min, max], pivot, interpolate, scale}; } -export function ScaleDiverging(key, channels, options) { - return ScaleD(key, scaleDiverging(), transformIdentity, channels, options); +export function createScaleDiverging(key, channels, options) { + return createScaleD(key, scaleDiverging(), transformIdentity, channels, options); } -export function ScaleDivergingSqrt(key, channels, options) { - return ScaleDivergingPow(key, channels, {...options, exponent: 0.5}); +export function createScaleDivergingSqrt(key, channels, options) { + return createScaleDivergingPow(key, channels, {...options, exponent: 0.5}); } -export function ScaleDivergingPow(key, channels, {exponent = 1, ...options}) { - return ScaleD(key, scaleDivergingPow().exponent((exponent = +exponent)), transformPow(exponent), channels, { +export function createScaleDivergingPow(key, channels, {exponent = 1, ...options}) { + return createScaleD(key, scaleDivergingPow().exponent((exponent = +exponent)), transformPow(exponent), channels, { ...options, type: "diverging-pow" }); } -export function ScaleDivergingLog( +export function createScaleDivergingLog( key, channels, {base = 10, pivot = 1, domain = inferDomain(channels, pivot < 0 ? negative : positive), ...options} ) { - return ScaleD(key, scaleDivergingLog().base((base = +base)), transformLog, channels, {domain, pivot, ...options}); + return createScaleD(key, scaleDivergingLog().base((base = +base)), transformLog, channels, { + domain, + pivot, + ...options + }); } -export function ScaleDivergingSymlog(key, channels, {constant = 1, ...options}) { - return ScaleD( +export function createScaleDivergingSymlog(key, channels, {constant = 1, ...options}) { + return createScaleD( key, scaleDivergingSymlog().constant((constant = +constant)), transformSymlog(constant), diff --git a/src/scales/ordinal.js b/src/scales/ordinal.js index 2d6bdf4ca1..ed8a2be900 100644 --- a/src/scales/ordinal.js +++ b/src/scales/ordinal.js @@ -2,7 +2,7 @@ import {InternSet, extent, quantize, reverse as reverseof, sort, symbolsFill, sy import {scaleBand, scaleOrdinal, scalePoint, scaleImplicit} from "d3"; import {ascendingDefined} from "../defined.js"; import {isNoneish, map, maybeInterval} from "../options.js"; -import {maybeSymbol} from "../symbols.js"; +import {maybeSymbol} from "../symbol.js"; import {registry, color, position, symbol} from "./index.js"; import {maybeBooleanRange, ordinalScheme, quantitativeScheme} from "./schemes.js"; @@ -12,7 +12,7 @@ import {maybeBooleanRange, ordinalScheme, quantitativeScheme} from "./schemes.js // of this by setting the type explicitly. export const ordinalImplicit = Symbol("ordinal"); -function ScaleO(key, scale, channels, {type, interval, domain, range, reverse, hint}) { +function createScaleO(key, scale, channels, {type, interval, domain, range, reverse, hint}) { interval = maybeInterval(interval, type); if (domain === undefined) domain = inferDomain(channels, interval, key); if (type === "categorical" || type === ordinalImplicit) type = "ordinal"; // shorthand for color schemes @@ -26,7 +26,7 @@ function ScaleO(key, scale, channels, {type, interval, domain, range, reverse, h return {type, domain, range, scale, hint, interval}; } -export function ScaleOrdinal(key, channels, {type, interval, domain, range, scheme, unknown, ...options}) { +export function createScaleOrdinal(key, channels, {type, interval, domain, range, scheme, unknown, ...options}) { interval = maybeInterval(interval, type); if (domain === undefined) domain = inferDomain(channels, interval, key); let hint; @@ -55,14 +55,14 @@ export function ScaleOrdinal(key, channels, {type, interval, domain, range, sche if (unknown === scaleImplicit) { throw new Error(`implicit unknown on ${key} scale is not supported`); } - return ScaleO(key, scaleOrdinal().unknown(unknown), channels, {...options, type, domain, range, hint}); + return createScaleO(key, scaleOrdinal().unknown(unknown), channels, {...options, type, domain, range, hint}); } -export function ScalePoint(key, channels, {align = 0.5, padding = 0.5, ...options}) { +export function createScalePoint(key, channels, {align = 0.5, padding = 0.5, ...options}) { return maybeRound(scalePoint().align(align).padding(padding), channels, options, key); } -export function ScaleBand( +export function createScaleBand( key, channels, { @@ -84,7 +84,7 @@ export function ScaleBand( function maybeRound(scale, channels, options, key) { let {round} = options; if (round !== undefined) scale.round((round = !!round)); - scale = ScaleO(key, scale, channels, options); + scale = createScaleO(key, scale, channels, options); scale.round = round; // preserve for autoScaleRound return scale; } diff --git a/src/scales/quantitative.js b/src/scales/quantitative.js index 73ea08c520..8bf32c823a 100644 --- a/src/scales/quantitative.js +++ b/src/scales/quantitative.js @@ -41,13 +41,13 @@ const interpolators = new Map([ ["lab", interpolateLab] ]); -export function Interpolator(interpolate) { +export function maybeInterpolator(interpolate) { const i = `${interpolate}`.toLowerCase(); if (!interpolators.has(i)) throw new Error(`unknown interpolator: ${i}`); return interpolators.get(i); } -export function ScaleQ( +export function createScaleQ( key, scale, channels, @@ -88,7 +88,7 @@ export function ScaleQ( // function is a “fixed” interpolator on the [0, 1] interval, as when a // color scheme such as interpolateRdBu is used. if (typeof interpolate !== "function") { - interpolate = Interpolator(interpolate); + interpolate = maybeInterpolator(interpolate); } if (interpolate.length === 1) { if (reverse) { @@ -127,27 +127,27 @@ export function ScaleQ( return {type, domain, range, scale, interpolate, interval}; } -export function ScaleLinear(key, channels, options) { - return ScaleQ(key, scaleLinear(), channels, options); +export function createScaleLinear(key, channels, options) { + return createScaleQ(key, scaleLinear(), channels, options); } -export function ScaleSqrt(key, channels, options) { - return ScalePow(key, channels, {...options, exponent: 0.5}); +export function createScaleSqrt(key, channels, options) { + return createScalePow(key, channels, {...options, exponent: 0.5}); } -export function ScalePow(key, channels, {exponent = 1, ...options}) { - return ScaleQ(key, scalePow().exponent(exponent), channels, {...options, type: "pow"}); +export function createScalePow(key, channels, {exponent = 1, ...options}) { + return createScaleQ(key, scalePow().exponent(exponent), channels, {...options, type: "pow"}); } -export function ScaleLog(key, channels, {base = 10, domain = inferLogDomain(channels), ...options}) { - return ScaleQ(key, scaleLog().base(base), channels, {...options, domain}); +export function createScaleLog(key, channels, {base = 10, domain = inferLogDomain(channels), ...options}) { + return createScaleQ(key, scaleLog().base(base), channels, {...options, domain}); } -export function ScaleSymlog(key, channels, {constant = 1, ...options}) { - return ScaleQ(key, scaleSymlog().constant(constant), channels, options); +export function createScaleSymlog(key, channels, {constant = 1, ...options}) { + return createScaleQ(key, scaleSymlog().constant(constant), channels, options); } -export function ScaleQuantile( +export function createScaleQuantile( key, channels, { @@ -172,10 +172,10 @@ export function ScaleQuantile( if (domain.length > 0) { domain = scaleQuantile(domain, range === undefined ? {length: n} : range).quantiles(); } - return ScaleThreshold(key, channels, {domain, range, reverse, unknown}); + return createScaleThreshold(key, channels, {domain, range, reverse, unknown}); } -export function ScaleQuantize( +export function createScaleQuantize( key, channels, { @@ -206,10 +206,10 @@ export function ScaleQuantize( if (min instanceof Date) thresholds = thresholds.map((x) => new Date(x)); // preserve date types } if (orderof(arrayify(domain)) < 0) thresholds.reverse(); // preserve descending domain - return ScaleThreshold(key, channels, {domain: thresholds, range, reverse, unknown}); + return createScaleThreshold(key, channels, {domain: thresholds, range, reverse, unknown}); } -export function ScaleThreshold( +export function createScaleThreshold( key, channels, { @@ -245,7 +245,7 @@ function isOrdered(domain, sign) { return true; } -export function ScaleIdentity() { +export function createScaleIdentity() { return {type: "identity", scale: scaleIdentity()}; } diff --git a/src/scales/temporal.js b/src/scales/temporal.js index 022ad9751c..22241ddc72 100644 --- a/src/scales/temporal.js +++ b/src/scales/temporal.js @@ -1,14 +1,14 @@ import {scaleTime, scaleUtc} from "d3"; -import {ScaleQ} from "./quantitative.js"; +import {createScaleQ} from "./quantitative.js"; -function ScaleT(key, scale, channels, options) { - return ScaleQ(key, scale, channels, options); +function createScaleT(key, scale, channels, options) { + return createScaleQ(key, scale, channels, options); } -export function ScaleTime(key, channels, options) { - return ScaleT(key, scaleTime(), channels, options); +export function createScaleTime(key, channels, options) { + return createScaleT(key, scaleTime(), channels, options); } -export function ScaleUtc(key, channels, options) { - return ScaleT(key, scaleUtc(), channels, options); +export function createScaleUtc(key, channels, options) { + return createScaleT(key, scaleUtc(), channels, options); } diff --git a/src/symbol.d.ts b/src/symbol.d.ts new file mode 100644 index 0000000000..cc58703b58 --- /dev/null +++ b/src/symbol.d.ts @@ -0,0 +1,19 @@ +import type {SymbolType} from "d3"; + +export type SymbolName = + | "asterisk" + | "circle" + | "cross" + | "diamond" + | "diamond2" + | "hexagon" + | "plus" + | "square" + | "square2" + | "star" + | "times" + | "triangle" + | "triangle2" + | "wye"; + +export type SymbolImplementation = SymbolType; diff --git a/src/symbols.js b/src/symbol.js similarity index 100% rename from src/symbols.js rename to src/symbol.js diff --git a/src/transforms/basic.d.ts b/src/transforms/basic.d.ts new file mode 100644 index 0000000000..0b2bcfbd1a --- /dev/null +++ b/src/transforms/basic.d.ts @@ -0,0 +1,47 @@ +import type {ChannelName, Channels, ChannelValue} from "../channel.js"; +import type {Context} from "../context.js"; +import type {Dimensions} from "../dimensions.js"; +import type {ScaleFunctions} from "../scales.js"; + +export type TransformFunction = (data: any[], facets: number[][]) => {data?: any[]; facets?: number[][]}; + +export type InitializerFunction = ( + data: any[], + facets: number[][], + channels: Channels, + scales: ScaleFunctions, + dimensions: Dimensions, + context: Context +) => { + data?: any[]; + facets?: number[][]; + channels?: Channels; +}; + +export type FilterFunction = (d: any, i: number) => boolean; + +export type CompareFunction = (a: any, b: any) => number; + +export type Transformed = T & {transform: TransformFunction}; + +export type Initialized = T & {initializer: InitializerFunction}; + +export function transform(options: T, transform: TransformFunction): Transformed; + +export function initializer(options: T, initializer: InitializerFunction): Initialized; + +export function filter(test: FilterFunction, options?: T): Transformed; + +export function reverse(options?: T): Transformed; + +export function shuffle(options?: T): Transformed; + +export interface SortOrderOptions { + channel?: ChannelName; + value?: ChannelValue; + order?: CompareFunction | "ascending" | "descending"; +} + +export type SortOrder = CompareFunction | ChannelValue | SortOrderOptions; + +export function sort(order: SortOrder, options?: T): Transformed; diff --git a/src/transforms/basic.js b/src/transforms/basic.js index bc8a1c3755..d0b9122a25 100644 --- a/src/transforms/basic.js +++ b/src/transforms/basic.js @@ -2,7 +2,6 @@ import {randomLcg} from "d3"; import {ascendingDefined, descendingDefined} from "../defined.js"; import {arrayify, isDomainSort, isOptions, maybeValue, valueof} from "../options.js"; -/** @jsdoc transform */ export function basic({filter: f1, sort: s1, reverse: r1, transform: t1, initializer: i1, ...options} = {}, transform) { // If both t1 and t2 are defined, returns a composite transform that first // applies t1 and then applies t2. @@ -20,7 +19,6 @@ export function basic({filter: f1, sort: s1, reverse: r1, transform: t1, initial }; } -/** @jsdoc initializer */ export function initializer({filter: f1, sort: s1, reverse: r1, initializer: i1, ...options} = {}, initializer) { // If both i1 and i2 are defined, returns a composite initializer that first // applies i1 and then applies i2. @@ -61,7 +59,6 @@ function apply(options, t) { return (options.initializer != null ? initializer : basic)(options, t); } -/** @jsdoc filter */ export function filter(test, options) { return apply(options, filterTransform(test)); } @@ -73,7 +70,6 @@ function filterTransform(value) { }; } -/** @jsdoc reverse */ export function reverse({sort, ...options} = {}) { return { ...apply(options, reverseTransform), @@ -85,7 +81,6 @@ function reverseTransform(data, facets) { return {data, facets: facets.map((I) => I.slice().reverse())}; } -/** @jsdoc shuffle */ export function shuffle({seed, sort, ...options} = {}) { return { ...apply(options, sortValue(seed == null ? Math.random : randomLcg(seed))), @@ -93,7 +88,6 @@ export function shuffle({seed, sort, ...options} = {}) { }; } -/** @jsdoc sort */ export function sort(order, {sort, ...options} = {}) { return { ...(isOptions(order) && order.channel !== undefined ? initializer : apply)(options, sortTransform(order)), diff --git a/src/transforms/bin.d.ts b/src/transforms/bin.d.ts new file mode 100644 index 0000000000..ec2ad46d19 --- /dev/null +++ b/src/transforms/bin.d.ts @@ -0,0 +1,30 @@ +import type {ChannelReducers} from "../channel.js"; +import type {Interval, RangeInterval} from "../interval.js"; +import type {Reducer} from "../reducer.js"; +import type {Transformed} from "./basic.js"; + +export type ThresholdsName = "freedman-diaconis" | "scott" | "sturges" | "auto"; + +export type ThresholdsFunction = (values: any[], min: any, max: any) => any[]; + +export type Thresholds = ThresholdsName | ThresholdsFunction | Interval; + +export interface BinOptions { + cumulative?: boolean | number; + domain?: ((values: any[]) => [min: any, max: any]) | [min: any, max: any]; + thresholds?: Thresholds; + interval?: RangeInterval; +} + +export interface BinReducers extends BinOptions { + data?: Reducer | null; + filter?: Reducer | null; + sort?: Reducer | null; + reverse?: boolean; +} + +export function binX(outputs?: ChannelReducers & BinReducers, options?: T & BinOptions): Transformed; + +export function binY(outputs?: ChannelReducers & BinReducers, options?: T & BinOptions): Transformed; + +export function bin(outputs?: ChannelReducers & BinReducers, options?: T & BinOptions): Transformed; diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 3919ff2eef..dd0a4bb227 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -40,7 +40,6 @@ import { } from "./group.js"; import {maybeInsetX, maybeInsetY} from "./inset.js"; -/** @jsdoc binX */ export function binX(outputs = {y: "count"}, options = {}) { // Group on {z, fill, stroke}, then optionally on y, then bin x. [outputs, options] = mergeOptions(outputs, options); @@ -48,7 +47,6 @@ export function binX(outputs = {y: "count"}, options = {}) { return binn(maybeBinValue(x, options, identity), null, null, y, outputs, maybeInsetX(options)); } -/** @jsdoc binY */ export function binY(outputs = {x: "count"}, options = {}) { // Group on {z, fill, stroke}, then optionally on x, then bin y. [outputs, options] = mergeOptions(outputs, options); @@ -56,7 +54,6 @@ export function binY(outputs = {x: "count"}, options = {}) { return binn(null, maybeBinValue(y, options, identity), x, null, outputs, maybeInsetY(options)); } -/** @jsdoc bin */ export function bin(outputs = {fill: "count"}, options = {}) { // Group on {z, fill, stroke}, then bin on x and y. [outputs, options] = mergeOptions(outputs, options); @@ -161,7 +158,7 @@ function binn( const BX2 = bx && setBX2([]); const BY1 = by && setBY1([]); const BY2 = by && setBY2([]); - const bin = Bin(bx?.(data), by?.(data)); + const bin = bing(bx?.(data), by?.(data)); let i = 0; for (const o of outputs) o.initialize(data); if (sort) sort.initialize(data); @@ -344,7 +341,7 @@ function isInterval(t) { return t ? typeof t.range === "function" : false; } -function Bin(EX, EY) { +function bing(EX, EY) { return EX && EY ? function* (I) { const X = EX.bin(I); // first bin on x diff --git a/src/transforms/centroid.d.ts b/src/transforms/centroid.d.ts new file mode 100644 index 0000000000..62104ef262 --- /dev/null +++ b/src/transforms/centroid.d.ts @@ -0,0 +1,10 @@ +import type {ChannelTransform, ChannelValue} from "../channel.js"; +import type {Initialized} from "./basic.js"; + +export interface CentroidOptions { + geometry?: ChannelValue; +} + +export function centroid(options?: T & CentroidOptions): Initialized; + +export function geoCentroid(options?: T & CentroidOptions): T & {x: ChannelTransform; y: ChannelTransform}; diff --git a/src/transforms/centroid.js b/src/transforms/centroid.js index 69575135b1..0588d681c0 100644 --- a/src/transforms/centroid.js +++ b/src/transforms/centroid.js @@ -2,7 +2,6 @@ import {geoCentroid as GeoCentroid, geoPath} from "d3"; import {identity, valueof} from "../options.js"; import {initializer} from "./basic.js"; -/** @jsdoc centroid */ export function centroid({geometry = identity, ...options} = {}) { // Suppress defaults for x and y since they will be computed by the initializer. return initializer({...options, x: null, y: null}, (data, facets, channels, scales, dimensions, {projection}) => { @@ -16,7 +15,6 @@ export function centroid({geometry = identity, ...options} = {}) { }); } -/** @jsdoc geoCentroid */ export function geoCentroid({geometry = identity, ...options} = {}) { let C; return { diff --git a/src/transforms/dodge.d.ts b/src/transforms/dodge.d.ts new file mode 100644 index 0000000000..9ce65cf1cb --- /dev/null +++ b/src/transforms/dodge.d.ts @@ -0,0 +1,20 @@ +import type {ChannelValueSpec} from "../channel.js"; +import type {Initialized} from "./basic.js"; + +export type DodgeAnchor = "top" | "right" | "bottom" | "left" | "middle"; + +export interface DodgeOptions { + x?: ChannelValueSpec; + y?: ChannelValueSpec; + r?: ChannelValueSpec; + anchor?: DodgeAnchor; + padding?: number; +} + +export function dodgeX(options?: T & DodgeOptions): Initialized; + +export function dodgeX(dodgeOptions?: DodgeOptions | DodgeAnchor, options?: T): Initialized; + +export function dodgeY(options?: T & DodgeOptions): Initialized; + +export function dodgeY(dodgeOptions?: DodgeOptions | DodgeAnchor, options?: T): Initialized; diff --git a/src/transforms/dodge.js b/src/transforms/dodge.js index fc5afa586b..2cf0314447 100644 --- a/src/transforms/dodge.js +++ b/src/transforms/dodge.js @@ -2,7 +2,7 @@ import IntervalTree from "interval-tree-1d"; import {finite, positive} from "../defined.js"; import {identity, maybeNamed, number, valueof} from "../options.js"; import {initializer} from "./basic.js"; -import {Position} from "../projection.js"; +import {applyPosition} from "../projection.js"; const anchorXLeft = ({marginLeft}) => [1, marginLeft]; const anchorXRight = ({width, marginRight}) => [-1, width - marginRight]; @@ -15,7 +15,6 @@ function maybeAnchor(anchor) { return typeof anchor === "string" ? {anchor} : anchor; } -/** @jsdoc dodgeX */ export function dodgeX(dodgeOptions = {}, options = {}) { if (arguments.length === 1) [dodgeOptions, options] = mergeOptions(dodgeOptions); let {anchor = "left", padding = 1} = maybeAnchor(dodgeOptions); @@ -35,7 +34,6 @@ export function dodgeX(dodgeOptions = {}, options = {}) { return dodge("x", "y", anchor, number(padding), options); } -/** @jsdoc dodgeY */ export function dodgeY(dodgeOptions = {}, options = {}) { if (arguments.length === 1) [dodgeOptions, options] = mergeOptions(dodgeOptions); let {anchor = "bottom", padding = 1} = maybeAnchor(dodgeOptions); @@ -70,7 +68,7 @@ function dodge(y, x, anchor, padding, options) { return initializer(options, function (data, facets, channels, scales, dimensions, context) { let {[x]: X, r: R} = channels; if (!channels[x]) throw new Error(`missing channel: ${x}`); - ({[x]: X} = Position(channels, scales, context)); + ({[x]: X} = applyPosition(channels, scales, context)); const r = R ? undefined : this.r !== undefined ? this.r : options.r !== undefined ? number(options.r) : 3; if (R) R = valueof(R.value, scales[R.scale] || identity, Float64Array); let [ky, ty] = anchor(dimensions); diff --git a/src/transforms/group.d.ts b/src/transforms/group.d.ts new file mode 100644 index 0000000000..827470a34f --- /dev/null +++ b/src/transforms/group.d.ts @@ -0,0 +1,18 @@ +import type {ChannelReducers} from "../channel.js"; +import type {Reducer} from "../reducer.js"; +import type {Transformed} from "./basic.js"; + +export interface GroupReducers { + data?: Reducer | null; + filter?: Reducer | null; + sort?: Reducer | null; + reverse?: boolean; +} + +export function groupZ(outputs?: ChannelReducers & GroupReducers, options?: T): Transformed; + +export function groupX(outputs?: ChannelReducers & GroupReducers, options?: T): Transformed; + +export function groupY(outputs?: ChannelReducers & GroupReducers, options?: T): Transformed; + +export function group(outputs?: ChannelReducers & GroupReducers, options?: T): Transformed; diff --git a/src/transforms/group.js b/src/transforms/group.js index eeeda79cf6..80174590d9 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -34,31 +34,27 @@ import { } from "../options.js"; import {basic} from "./basic.js"; -/** @jsdoc groupZ */ +// Group on {z, fill, stroke}. export function groupZ(outputs, options) { - // Group on {z, fill, stroke}. return groupn(null, null, outputs, options); } -/** @jsdoc groupX */ +// Group on {z, fill, stroke}, then on x. export function groupX(outputs = {y: "count"}, options = {}) { - // Group on {z, fill, stroke}, then on x. const {x = identity} = options; if (x == null) throw new Error("missing channel: x"); return groupn(x, null, outputs, options); } -/** @jsdoc groupY */ +// Group on {z, fill, stroke}, then on y. export function groupY(outputs = {x: "count"}, options = {}) { - // Group on {z, fill, stroke}, then on y. const {y = identity} = options; if (y == null) throw new Error("missing channel: y"); return groupn(null, y, outputs, options); } -/** @jsdoc group */ +// Group on {z, fill, stroke}, then on x and y. export function group(outputs = {fill: "count"}, options = {}) { - // Group on {z, fill, stroke}, then on x and y. let {x, y} = options; [x, y] = maybeTuple(x, y); if (x == null) throw new Error("missing channel: x"); diff --git a/src/transforms/hexbin.d.ts b/src/transforms/hexbin.d.ts new file mode 100644 index 0000000000..851e30c215 --- /dev/null +++ b/src/transforms/hexbin.d.ts @@ -0,0 +1,8 @@ +import type {ChannelReducers} from "../channel.js"; +import type {Transformed} from "./basic.js"; + +export interface HexbinOptions { + binWidth?: number; +} + +export function hexbin(outputs?: ChannelReducers, options?: T & HexbinOptions): Transformed; diff --git a/src/transforms/hexbin.js b/src/transforms/hexbin.js index 370839e9df..ec06624bef 100644 --- a/src/transforms/hexbin.js +++ b/src/transforms/hexbin.js @@ -1,8 +1,8 @@ -import {sqrt3} from "../symbols.js"; +import {sqrt3} from "../symbol.js"; import {isNoneish, number, valueof} from "../options.js"; import {initializer} from "./basic.js"; import {hasOutput, maybeGroup, maybeOutputs, maybeSubgroup} from "./group.js"; -import {Position} from "../projection.js"; +import {applyPosition} from "../projection.js"; // We don’t want the hexagons to align with the edges of the plot frame, as that // would cause extreme x-values (the upper bound of the default x-scale domain) @@ -12,7 +12,6 @@ import {Position} from "../projection.js"; export const ox = 0.5, oy = 0; -/** @jsdoc hexbin */ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) { // TODO filter e.g. to show empty hexbins? // TODO disallow x, x1, x2, y, y1, y2 reducers? @@ -36,7 +35,7 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) { if (Y === undefined) throw new Error("missing channel: y"); // Get the (either scaled or projected) xy channels. - ({x: X, y: Y} = Position(channels, scales, context)); + ({x: X, y: Y} = applyPosition(channels, scales, context)); // Extract the values for channels that are eligible for grouping; not all // marks define a z channel, so compute one if it not already computed. If z diff --git a/src/transforms/map.d.ts b/src/transforms/map.d.ts new file mode 100644 index 0000000000..04e3c64067 --- /dev/null +++ b/src/transforms/map.d.ts @@ -0,0 +1,22 @@ +import type {ChannelName, ChannelValue} from "../channel.js"; +import type {Transformed} from "./basic.js"; + +export type MapFunction = (values: S[]) => T[]; + +export type MapName = "cumsum" | "rank" | "quantile"; + +export interface MapImplementation { + map(index: number[], source: S[], target: T[]): void; +} + +export type Map = MapImplementation | MapFunction | MapName; + +export interface MapOptions { + z?: ChannelValue; +} + +export function mapX(map: Map, options?: T & MapOptions): Transformed; + +export function mapY(map: Map, options?: T & MapOptions): Transformed; + +export function map(outputs?: {[key in ChannelName]?: Map}, options?: T & MapOptions): Transformed; diff --git a/src/transforms/map.js b/src/transforms/map.js index db0365f5d2..23d08acc15 100644 --- a/src/transforms/map.js +++ b/src/transforms/map.js @@ -2,7 +2,6 @@ import {count, group, rank} from "d3"; import {maybeZ, take, valueof, maybeInput, column} from "../options.js"; import {basic} from "./basic.js"; -/** @jsdoc mapX */ export function mapX(map, options = {}) { return mapAlias( Object.fromEntries(["x", "x1", "x2"].filter((key) => options[key] != null).map((key) => [key, map])), @@ -10,7 +9,6 @@ export function mapX(map, options = {}) { ); } -/** @jsdoc mapY */ export function mapY(map, options = {}) { return mapAlias( Object.fromEntries(["y", "y1", "y2"].filter((key) => options[key] != null).map((key) => [key, map])), @@ -18,7 +16,6 @@ export function mapY(map, options = {}) { ); } -/** @jsdoc map */ export function map(outputs = {}, options = {}) { const z = maybeZ(options); const channels = Object.entries(outputs).map(([key, map]) => { diff --git a/src/transforms/normalize.d.ts b/src/transforms/normalize.d.ts new file mode 100644 index 0000000000..316f2240cd --- /dev/null +++ b/src/transforms/normalize.d.ts @@ -0,0 +1,33 @@ +import type {ReducerPercentile} from "../reducer.js"; +import type {Transformed} from "./basic.js"; +import type {Map} from "./map.js"; + +export type NormalizeBasisName = + | "deviation" + | "first" + | "last" + | "max" + | "mean" + | "median" + | "min" + | "sum" + | "extent" + | ReducerPercentile; + +export type NormalizeBasisFunction = (index: number[], values: any[]) => any; + +export type NormalizeBasis = NormalizeBasisName | NormalizeBasisFunction; + +export interface NormalizeOptions { + basis?: NormalizeBasis; +} + +export function normalizeX(options?: T & NormalizeOptions): Transformed; + +export function normalizeX(basis?: NormalizeBasis, options?: T): Transformed; + +export function normalizeY(options?: T & NormalizeOptions): Transformed; + +export function normalizeY(basis?: NormalizeBasis, options?: T): Transformed; + +export function normalize(basis: NormalizeBasis): Map; diff --git a/src/transforms/normalize.js b/src/transforms/normalize.js index d5b1ca4181..3eb03bef0c 100644 --- a/src/transforms/normalize.js +++ b/src/transforms/normalize.js @@ -3,19 +3,16 @@ import {defined} from "../defined.js"; import {percentile, take} from "../options.js"; import {mapX, mapY} from "./map.js"; -/** @jsdoc normalizeX */ export function normalizeX(basis, options) { if (arguments.length === 1) ({basis, ...options} = basis); return mapX(normalize(basis), options); } -/** @jsdoc normalizeY */ export function normalizeY(basis, options) { if (arguments.length === 1) ({basis, ...options} = basis); return mapY(normalize(basis), options); } -/** @jsdoc normalize */ export function normalize(basis) { if (basis === undefined) return normalizeFirst; if (typeof basis === "function") return normalizeBasis((I, S) => basis(take(S, I))); diff --git a/src/transforms/select.d.ts b/src/transforms/select.d.ts new file mode 100644 index 0000000000..74fe57a4fa --- /dev/null +++ b/src/transforms/select.d.ts @@ -0,0 +1,28 @@ +import type {ChannelValue} from "../channel.js"; +import type {Transformed} from "./basic.js"; + +export type SelectorName = "first" | "last" | "min" | "max"; // TODO restrict based on context + +export type SelectorFunction = (index: number[], values: any[] | null) => number[]; + +export type SelectorChannel = {[key in keyof T]?: SelectorName | SelectorFunction}; + +export type Selector = SelectorName | SelectorFunction | SelectorChannel; + +export interface SelectOptions { + z?: ChannelValue; +} + +export function select(selector: Selector, options?: T & SelectOptions): Transformed; + +export function selectFirst(options?: T & SelectOptions): Transformed; + +export function selectLast(options?: T & SelectOptions): Transformed; + +export function selectMinX(options?: T & SelectOptions): Transformed; + +export function selectMinY(options?: T & SelectOptions): Transformed; + +export function selectMaxX(options?: T & SelectOptions): Transformed; + +export function selectMaxY(options?: T & SelectOptions): Transformed; diff --git a/src/transforms/select.js b/src/transforms/select.js index 27f9557d3a..66c9ebd854 100644 --- a/src/transforms/select.js +++ b/src/transforms/select.js @@ -2,7 +2,6 @@ import {greatest, group, least} from "d3"; import {maybeZ, valueof} from "../options.js"; import {basic} from "./basic.js"; -/** @jsdoc select */ export function select(selector, options = {}) { // If specified selector is a string or function, it’s a selector without an // input channel such as first or last. @@ -40,32 +39,26 @@ function maybeSelector(selector) { throw new Error(`unknown selector: ${selector}`); } -/** @jsdoc selectFirst */ export function selectFirst(options) { return selectChannel(null, selectorFirst, options); } -/** @jsdoc selectLast */ export function selectLast(options) { return selectChannel(null, selectorLast, options); } -/** @jsdoc selectMinX */ export function selectMinX(options) { return selectChannel("x", selectorMin, options); } -/** @jsdoc selectMinY */ export function selectMinY(options) { return selectChannel("y", selectorMin, options); } -/** @jsdoc selectMaxX */ export function selectMaxX(options) { return selectChannel("x", selectorMax, options); } -/** @jsdoc selectMaxY */ export function selectMaxY(options) { return selectChannel("y", selectorMax, options); } diff --git a/src/transforms/stack.d.ts b/src/transforms/stack.d.ts new file mode 100644 index 0000000000..3b3e27704d --- /dev/null +++ b/src/transforms/stack.d.ts @@ -0,0 +1,52 @@ +import type {ChannelValue} from "../channel.js"; +import type {Transformed} from "./basic.js"; + +export type StackOffsetName = + | "center" + | "expand" // deprecated; use normalize + | "normalize" + | "silhouette" // deprecated; use center + | "wiggle"; + +export type StackOffsetFunction = (stacks: number[][][], y1: number[], y2: number[], z: any[]) => void; + +export type StackOffset = StackOffsetName | StackOffsetFunction; + +export type StackOrderName = "value" | "x" | "y" | "z" | "sum" | "appearance" | "inside-out"; + +export type StackOrder = + | StackOrderName + | (string & Record) // field name; see also https://github.com/microsoft/TypeScript/issues/29729 + | ((d: any, i: number) => any) // function of data + | any[]; // explicit ordinal values + +export interface StackOptions { + offset?: StackOffset | null; + order?: StackOrder | null; + reverse?: boolean; + z?: ChannelValue; +} + +export function stackX(options?: T & StackOptions): Transformed; + +export function stackX(stackOptions?: StackOptions, options?: T): Transformed; + +export function stackX1(options?: T & StackOptions): Transformed; + +export function stackX1(stackOptions?: StackOptions, options?: T): Transformed; + +export function stackX2(options?: T & StackOptions): Transformed; + +export function stackX2(stackOptions?: StackOptions, options?: T): Transformed; + +export function stackY(options?: T & StackOptions): Transformed; + +export function stackY(stackOptions?: StackOptions, options?: T): Transformed; + +export function stackY1(options?: T & StackOptions): Transformed; + +export function stackY1(stackOptions?: StackOptions, options?: T): Transformed; + +export function stackY2(options?: T & StackOptions): Transformed; + +export function stackY2(stackOptions?: StackOptions, options?: T): Transformed; diff --git a/src/transforms/stack.js b/src/transforms/stack.js index 7afec21e88..fabf460b04 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -3,7 +3,6 @@ import {ascendingDefined} from "../defined.js"; import {field, column, maybeColumn, maybeZ, mid, range, valueof, maybeZero, one} from "../options.js"; import {basic} from "./basic.js"; -/** @jsdoc stackX */ export function stackX(stack = {}, options = {}) { if (arguments.length === 1) [stack, options] = mergeOptions(stack); const {y1, y = y1, x, ...rest} = options; // note: consumes x! @@ -11,7 +10,6 @@ export function stackX(stack = {}, options = {}) { return {...transform, y1, y: Y, x1, x2, x: mid(x1, x2)}; } -/** @jsdoc stackX1 */ export function stackX1(stack = {}, options = {}) { if (arguments.length === 1) [stack, options] = mergeOptions(stack); const {y1, y = y1, x} = options; @@ -19,7 +17,6 @@ export function stackX1(stack = {}, options = {}) { return {...transform, y1, y: Y, x: X}; } -/** @jsdoc stackX2 */ export function stackX2(stack = {}, options = {}) { if (arguments.length === 1) [stack, options] = mergeOptions(stack); const {y1, y = y1, x} = options; @@ -27,7 +24,6 @@ export function stackX2(stack = {}, options = {}) { return {...transform, y1, y: Y, x: X}; } -/** @jsdoc stackY */ export function stackY(stack = {}, options = {}) { if (arguments.length === 1) [stack, options] = mergeOptions(stack); const {x1, x = x1, y, ...rest} = options; // note: consumes y! @@ -35,7 +31,6 @@ export function stackY(stack = {}, options = {}) { return {...transform, x1, x: X, y1, y2, y: mid(y1, y2)}; } -/** @jsdoc stackY1 */ export function stackY1(stack = {}, options = {}) { if (arguments.length === 1) [stack, options] = mergeOptions(stack); const {x1, x = x1, y} = options; @@ -43,7 +38,6 @@ export function stackY1(stack = {}, options = {}) { return {...transform, x1, x: X, y: Y}; } -/** @jsdoc stackY2 */ export function stackY2(stack = {}, options = {}) { if (arguments.length === 1) [stack, options] = mergeOptions(stack); const {x1, x = x1, y} = options; diff --git a/src/transforms/tree.d.ts b/src/transforms/tree.d.ts new file mode 100644 index 0000000000..b2750a76f8 --- /dev/null +++ b/src/transforms/tree.d.ts @@ -0,0 +1,15 @@ +import type {ChannelValue} from "../channel.js"; +import type {CompareFunction, Transformed} from "./basic.js"; + +export interface TreeTransformOptions { + path?: ChannelValue; + delimiter?: string; + treeAnchor?: "left" | "right"; + treeLayout?: () => any; + treeSeparation?: CompareFunction | null; + treeSort?: CompareFunction | {node: (node: any) => any} | string | null; +} + +export function treeNode(options?: T & TreeTransformOptions): Transformed; + +export function treeLink(options?: T & TreeTransformOptions): Transformed; diff --git a/src/transforms/tree.js b/src/transforms/tree.js index a485cd2a4c..4d7bfa999e 100644 --- a/src/transforms/tree.js +++ b/src/transforms/tree.js @@ -3,7 +3,6 @@ import {ascendingDefined} from "../defined.js"; import {column, identity, isObject, one, valueof} from "../options.js"; import {basic} from "./basic.js"; -/** @jsdoc treeNode */ export function treeNode(options = {}) { let { path = identity, // the delimited path @@ -57,7 +56,6 @@ export function treeNode(options = {}) { }; } -/** @jsdoc treeLink */ export function treeLink(options = {}) { let { path = identity, // the delimited path diff --git a/src/transforms/window.d.ts b/src/transforms/window.d.ts new file mode 100644 index 0000000000..de9e028f4a --- /dev/null +++ b/src/transforms/window.d.ts @@ -0,0 +1,40 @@ +import type {ReducerPercentile} from "../reducer.js"; +import type {Transformed} from "./basic.js"; +import type {Map} from "./map.js"; + +export type WindowReducerName = + | "deviation" + | "max" + | "mean" + | "median" + | "min" + | "mode" + | "sum" + | "variance" + | "difference" + | "ratio" + | "first" + | "last" + | ReducerPercentile; + +export type WindowReducerFunction = (values: any[]) => any; + +export type WindowReducer = WindowReducerName | WindowReducerFunction; + +export interface WindowOptions { + k?: number; + reduce?: WindowReducer; + anchor?: "start" | "middle" | "end"; + shift?: "leading" | "centered" | "trailing"; // deprecated! + strict?: boolean; +} + +export function windowX(options?: T & WindowOptions): Transformed; + +export function windowX(windowOptions?: WindowOptions | number, options?: T): Transformed; + +export function windowY(options?: T & WindowOptions): Transformed; + +export function windowY(windowOptions?: WindowOptions | number, options?: T): Transformed; + +export function window(options?: WindowOptions | number): Map; diff --git a/src/types/isoformat.d.ts b/src/types/isoformat.d.ts deleted file mode 100644 index 90b729d177..0000000000 --- a/src/types/isoformat.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -declare module "isoformat" { - export function format(value: any, fallback: string): string; - export function format(value: any, fallback: any): any; - export function parse(value: string): Date; -} diff --git a/src/warnings.ts b/src/warnings.js similarity index 78% rename from src/warnings.ts rename to src/warnings.js index 626362ed85..6c13e8db1e 100644 --- a/src/warnings.ts +++ b/src/warnings.js @@ -6,7 +6,7 @@ export function consumeWarnings() { return w; } -export function warn(message: string) { +export function warn(message) { console.warn(message); ++warnings; } diff --git a/test/legends/legends-test.ts b/test/legend-test.js similarity index 97% rename from test/legends/legends-test.ts rename to test/legend-test.js index 298460b859..f66aa45c2a 100644 --- a/test/legends/legends-test.ts +++ b/test/legend-test.js @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; import * as assert from "assert"; -import it from "../jsdom.js"; +import it from "./jsdom.js"; it(`Plot.legend({color: {type: "identity"}}) returns undefined`, () => { assert.strictEqual(Plot.legend({color: {type: "identity"}}), undefined); diff --git a/test/plot.js b/test/plot.js index 2a4fa678f1..c6851e2d5d 100644 --- a/test/plot.js +++ b/test/plot.js @@ -3,7 +3,7 @@ import {promises as fs} from "fs"; import * as path from "path"; import beautify from "js-beautify"; import it from "./jsdom.js"; -import * as plots from "./plots/index.js"; +import * as plots from "./plots/index.ts"; for (const [name, plot] of Object.entries(plots)) { it(`plot ${name}`, async () => { diff --git a/test/plots/aapl-bollinger.js b/test/plots/aapl-bollinger.ts similarity index 92% rename from test/plots/aapl-bollinger.js rename to test/plots/aapl-bollinger.ts index 958980893c..768f20cec4 100644 --- a/test/plots/aapl-bollinger.js +++ b/test/plots/aapl-bollinger.ts @@ -2,7 +2,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function aaplBollinger() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ y: { grid: true @@ -16,7 +16,7 @@ export async function aaplBollinger() { } export async function aaplBollingerGridInterval() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ marks: [ Plot.frame({fill: "#eaeaea"}), @@ -34,7 +34,7 @@ export async function aaplBollingerGridInterval() { } export async function aaplBollingerGridSpacing() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ marks: [ Plot.frame({fill: "#eaeaea"}), diff --git a/test/plots/aapl-candlestick.js b/test/plots/aapl-candlestick.ts similarity index 83% rename from test/plots/aapl-candlestick.js rename to test/plots/aapl-candlestick.ts index ea6e00edf8..2740967ac6 100644 --- a/test/plots/aapl-candlestick.js +++ b/test/plots/aapl-candlestick.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const AAPL = (await d3.csv("data/aapl.csv", d3.autoType)).slice(-120); +export async function aaplCandlestick() { + const AAPL = (await d3.csv("data/aapl.csv", d3.autoType)).slice(-120); return Plot.plot({ width: 960, inset: 6, diff --git a/test/plots/aapl-change-volume.js b/test/plots/aapl-change-volume.ts similarity index 80% rename from test/plots/aapl-change-volume.js rename to test/plots/aapl-change-volume.ts index e5e3478e40..8b958a3a65 100644 --- a/test/plots/aapl-change-volume.js +++ b/test/plots/aapl-change-volume.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/aapl.csv", d3.autoType); +export async function aaplChangeVolume() { + const data = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ x: { label: "Daily change (%) →", diff --git a/test/plots/aapl-close-untyped.js b/test/plots/aapl-close-untyped.ts similarity index 73% rename from test/plots/aapl-close-untyped.js rename to test/plots/aapl-close-untyped.ts index 7803ff35bf..bcf043f4eb 100644 --- a/test/plots/aapl-close-untyped.js +++ b/test/plots/aapl-close-untyped.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const AAPL = await d3.csv("data/aapl.csv"); +export async function aaplCloseUntyped() { + const AAPL = await d3.csv("data/aapl.csv"); return Plot.plot({ x: { type: "utc" diff --git a/test/plots/aapl-close.js b/test/plots/aapl-close.ts similarity index 74% rename from test/plots/aapl-close.js rename to test/plots/aapl-close.ts index 52438d3be2..6d4a8053ef 100644 --- a/test/plots/aapl-close.js +++ b/test/plots/aapl-close.ts @@ -2,7 +2,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function aaplClose() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ y: { grid: true @@ -16,14 +16,14 @@ export async function aaplClose() { } export async function aaplCloseDataTicks() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ marks: [Plot.axisY(d3.ticks(0, 200, 10), {anchor: "left"}), Plot.lineY(AAPL, {x: "Date", y: "Close"})] }); } export async function aaplCloseImplicitGrid() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ y: {grid: true}, // appears even though there’s an explicit axis marks: [Plot.axisY({anchor: "left"}), Plot.lineY(AAPL, {x: "Date", y: "Close"})] @@ -31,21 +31,21 @@ export async function aaplCloseImplicitGrid() { } export async function aaplCloseGridColor() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.lineY(AAPL, {x: "Date", y: "Close"}).plot({y: {grid: "red"}}); } export async function aaplCloseGridInterval() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.lineY(AAPL, {x: "Date", y: "Close"}).plot({x: {grid: d3.utcMonth.every(3)}}); } export async function aaplCloseGridIntervalName() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.lineY(AAPL, {x: "Date", y: "Close"}).plot({x: {grid: "month"}}); } export async function aaplCloseGridIterable() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv("data/aapl.csv", d3.autoType); return Plot.lineY(AAPL, {x: "Date", y: "Close"}).plot({y: {grid: [100, 120, 140]}}); } diff --git a/test/plots/aapl-fancy-axis.js b/test/plots/aapl-fancy-axis.ts similarity index 82% rename from test/plots/aapl-fancy-axis.js rename to test/plots/aapl-fancy-axis.ts index 9d673d5836..a44257fd2e 100644 --- a/test/plots/aapl-fancy-axis.js +++ b/test/plots/aapl-fancy-axis.ts @@ -2,7 +2,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function aaplFancyAxis() { - const AAPL = await d3.csv("data/aapl.csv", d3.autoType); + const AAPL = await d3.csv<{Close: number; Date: Date}>("data/aapl.csv", d3.autoType); return Plot.plot({ marks: [ Plot.ruleY([0]), diff --git a/test/plots/aapl-monthly.js b/test/plots/aapl-monthly.ts similarity index 84% rename from test/plots/aapl-monthly.js rename to test/plots/aapl-monthly.ts index 6235b78124..90db69879d 100644 --- a/test/plots/aapl-monthly.js +++ b/test/plots/aapl-monthly.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/aapl.csv", d3.autoType); +export async function aaplMonthly() { + const data = await d3.csv("data/aapl.csv", d3.autoType); const bin = {x: "Date", y: "Volume", thresholds: 40}; return Plot.plot({ y: { diff --git a/test/plots/aapl-volume-rect.js b/test/plots/aapl-volume-rect.ts similarity index 77% rename from test/plots/aapl-volume-rect.js rename to test/plots/aapl-volume-rect.ts index be97a68453..5bd7ba3b23 100644 --- a/test/plots/aapl-volume-rect.js +++ b/test/plots/aapl-volume-rect.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const AAPL = (await d3.csv("data/aapl.csv", d3.autoType)).slice(-40); +export async function aaplVolumeRect() { + const AAPL = (await d3.csv("data/aapl.csv", d3.autoType)).slice(-40); return Plot.plot({ y: { grid: true, diff --git a/test/plots/aapl-volume.js b/test/plots/aapl-volume.ts similarity index 77% rename from test/plots/aapl-volume.js rename to test/plots/aapl-volume.ts index 9cec703cfe..7cd5a4fec3 100644 --- a/test/plots/aapl-volume.js +++ b/test/plots/aapl-volume.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/aapl.csv", d3.autoType); +export async function aaplVolume() { + const data = await d3.csv("data/aapl.csv", d3.autoType); return Plot.plot({ x: { round: true, diff --git a/test/plots/anscombe-quartet.js b/test/plots/anscombe-quartet.ts similarity index 71% rename from test/plots/anscombe-quartet.js rename to test/plots/anscombe-quartet.ts index 908cbc50b1..2d1cc65878 100644 --- a/test/plots/anscombe-quartet.js +++ b/test/plots/anscombe-quartet.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const anscombe = await d3.csv("data/anscombe.csv", d3.autoType); +export async function anscombeQuartet() { + const anscombe = await d3.csv("data/anscombe.csv", d3.autoType); return Plot.plot({ grid: true, inset: 10, diff --git a/test/plots/armadillo.js b/test/plots/armadillo.ts similarity index 83% rename from test/plots/armadillo.js rename to test/plots/armadillo.ts index 73d69dc5b9..c5de2b5029 100644 --- a/test/plots/armadillo.js +++ b/test/plots/armadillo.ts @@ -3,8 +3,8 @@ import * as d3 from "d3"; import {geoArmadillo} from "d3-geo-projection"; import {feature} from "topojson-client"; -export default async function () { - const world = await d3.json("data/countries-50m.json"); +export async function armadillo() { + const world = await d3.json("data/countries-50m.json"); const land = feature(world, world.objects.land); return Plot.plot({ width: 960, diff --git a/test/plots/aspectRatio.js b/test/plots/aspectRatio.ts similarity index 100% rename from test/plots/aspectRatio.js rename to test/plots/aspectRatio.ts diff --git a/test/plots/athletes-bins-colors.js b/test/plots/athletes-bins-colors.ts similarity index 63% rename from test/plots/athletes-bins-colors.js rename to test/plots/athletes-bins-colors.ts index e6f8ac1e9b..fdc8a1aa42 100644 --- a/test/plots/athletes-bins-colors.js +++ b/test/plots/athletes-bins-colors.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesBinsColors() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ marks: [Plot.rectY(athletes, Plot.binX({fill: "x", y: "count"}, {x: "weight"})), Plot.ruleY([0])] }); diff --git a/test/plots/athletes-birthdays.js b/test/plots/athletes-birthdays.ts similarity index 80% rename from test/plots/athletes-birthdays.js rename to test/plots/athletes-birthdays.ts index 208007707c..05003e4da0 100644 --- a/test/plots/athletes-birthdays.js +++ b/test/plots/athletes-birthdays.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesBirthdays() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ marginRight: 40, y: { diff --git a/test/plots/athletes-boxing-height.js b/test/plots/athletes-boxing-height.ts similarity index 73% rename from test/plots/athletes-boxing-height.js rename to test/plots/athletes-boxing-height.ts index 3b68d2cc35..b30879ab5b 100644 --- a/test/plots/athletes-boxing-height.js +++ b/test/plots/athletes-boxing-height.ts @@ -2,7 +2,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; // Country code to continent; coverage limited to sport=boxing. -const continent = new Map( +const continents = new Map( // prettier-ignore [ ["Africa", ["ALG", "EGY", "MAR", "SEY", "KEN", "TUN", "CPV", "CMR", "NGR", "NAM", "CAF", "UGA", "MRI", "CGO"]], @@ -10,17 +10,19 @@ const continent = new Map( ["Asia", ["AZE", "KAZ", "RUS", "TUR", "THA", "TJK", "ARM", "JPN", "TKM", "UZB", "CHN", "PHI", "MGL", "TPE", "IRI", "KGZ", "QAT", "JOR", "IND", "KOR", "IRQ"]], ["Europe", ["SWE", "GBR", "GER", "IRL", "ITA", "FRA", "BUL", "UKR", "BLR", "LTU", "NED", "CRO", "POL", "HUN", "ROU", "FIN", "ESP", "HON"]], ["Oceania", ["AUS", "FSM", "PNG"]] - ].flatMap(([continent, codes]) => codes.map((code) => [code, continent])) + ].flatMap(([continent, codes]: [string, string[]]) => codes.map((code) => [code, continent])) ); -export default async function () { - const athletes = (await d3.csv("data/athletes.csv", d3.autoType)).filter((d) => d.sport === "boxing" && d.height); +export async function athletesBoxingHeight() { + const athletes = (await d3.csv("data/athletes.csv", d3.autoType)).filter( + (d) => d.sport === "boxing" && d.height + ); return Plot.plot({ width: 600, height: 350, facet: {data: athletes, x: "nationality"}, y: {domain: [1.45, 2.1]}, - fx: {transform: (countryCode) => continent.get(countryCode), label: "continent"}, + fx: {transform: (code) => continents.get(code), label: "continent"}, marks: [ Plot.frame(), Plot.dot(athletes, Plot.dodgeX({y: "height", title: "nationality", fill: "currentColor", anchor: "middle"})) diff --git a/test/plots/athletes-height-weight-bin-stroke.js b/test/plots/athletes-height-weight-bin-stroke.ts similarity index 79% rename from test/plots/athletes-height-weight-bin-stroke.js rename to test/plots/athletes-height-weight-bin-stroke.ts index aebf53991a..e9635545b6 100644 --- a/test/plots/athletes-height-weight-bin-stroke.js +++ b/test/plots/athletes-height-weight-bin-stroke.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesHeightWeightBinStroke() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ round: true, grid: true, diff --git a/test/plots/athletes-height-weight-bin.js b/test/plots/athletes-height-weight-bin.ts similarity index 71% rename from test/plots/athletes-height-weight-bin.js rename to test/plots/athletes-height-weight-bin.ts index 562adf755e..683b52f239 100644 --- a/test/plots/athletes-height-weight-bin.js +++ b/test/plots/athletes-height-weight-bin.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesHeightWeightBin() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ round: true, grid: true, diff --git a/test/plots/athletes-height-weight-sex.js b/test/plots/athletes-height-weight-sex.ts similarity index 72% rename from test/plots/athletes-height-weight-sex.js rename to test/plots/athletes-height-weight-sex.ts index 919069ef13..70eef87bf2 100644 --- a/test/plots/athletes-height-weight-sex.js +++ b/test/plots/athletes-height-weight-sex.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesHeightWeightSex() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ round: true, grid: true, diff --git a/test/plots/athletes-height-weight-sport.js b/test/plots/athletes-height-weight-sport.ts similarity index 65% rename from test/plots/athletes-height-weight-sport.js rename to test/plots/athletes-height-weight-sport.ts index b58028401d..cc43abe13d 100644 --- a/test/plots/athletes-height-weight-sport.js +++ b/test/plots/athletes-height-weight-sport.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesHeightWeightSport() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ grid: true, height: 640, diff --git a/test/plots/athletes-height-weight.js b/test/plots/athletes-height-weight.ts similarity index 61% rename from test/plots/athletes-height-weight.js rename to test/plots/athletes-height-weight.ts index e4866ae859..ea63a1f504 100644 --- a/test/plots/athletes-height-weight.js +++ b/test/plots/athletes-height-weight.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesHeightWeight() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ grid: true, height: 640, diff --git a/test/plots/athletes-nationality.js b/test/plots/athletes-nationality.ts similarity index 71% rename from test/plots/athletes-nationality.js rename to test/plots/athletes-nationality.ts index 0a5629272a..b5ea9a40fd 100644 --- a/test/plots/athletes-nationality.js +++ b/test/plots/athletes-nationality.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesNationality() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ x: { grid: true diff --git a/test/plots/athletes-sample.js b/test/plots/athletes-sample.ts similarity index 86% rename from test/plots/athletes-sample.js rename to test/plots/athletes-sample.ts index 4dd523a3d9..3810bc38e2 100644 --- a/test/plots/athletes-sample.js +++ b/test/plots/athletes-sample.ts @@ -2,7 +2,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function athletesSample() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ marginLeft: 100, x: {grid: true}, @@ -23,7 +23,7 @@ export async function athletesSample() { } export async function athletesSampleFacet() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ x: {grid: true}, color: {scheme: "dark2"}, diff --git a/test/plots/athletes-sex-weight.js b/test/plots/athletes-sex-weight.ts similarity index 73% rename from test/plots/athletes-sex-weight.js rename to test/plots/athletes-sex-weight.ts index 14bf686309..6ad25ae079 100644 --- a/test/plots/athletes-sex-weight.js +++ b/test/plots/athletes-sex-weight.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesSexWeight() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ y: { grid: true diff --git a/test/plots/athletes-sort.js b/test/plots/athletes-sort.ts similarity index 73% rename from test/plots/athletes-sort.js rename to test/plots/athletes-sort.ts index b0d831553d..cf488f82b7 100644 --- a/test/plots/athletes-sort.js +++ b/test/plots/athletes-sort.ts @@ -2,8 +2,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function athletesSortFacet() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); - const female = (d) => d.sex === "female"; + const athletes = await d3.csv<{sex: string; sport: string}>("data/athletes.csv", d3.autoType); + const female = (d: (typeof athletes)[number]) => d.sex === "female"; return Plot.plot({ marginLeft: 100, marks: [ @@ -14,7 +14,7 @@ export async function athletesSortFacet() { } export async function athletesSortNationality() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ color: {legend: true}, marks: [ @@ -32,7 +32,7 @@ export async function athletesSortNationality() { } export async function athletesSortNullLimit() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ color: {legend: true}, marks: [Plot.dot(athletes, {x: "height", y: "weight", stroke: "nationality", sort: {color: null, limit: 10}})] diff --git a/test/plots/athletes-sport-sex.js b/test/plots/athletes-sport-sex.ts similarity index 77% rename from test/plots/athletes-sport-sex.js rename to test/plots/athletes-sport-sex.ts index 7632fd8490..d60ad3a2f3 100644 --- a/test/plots/athletes-sport-sex.js +++ b/test/plots/athletes-sport-sex.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesSportSex() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ marginLeft: 100, x: { diff --git a/test/plots/athletes-sport-weight.js b/test/plots/athletes-sport-weight.ts similarity index 75% rename from test/plots/athletes-sport-weight.js rename to test/plots/athletes-sport-weight.ts index bd06a5907d..8f008a19a0 100644 --- a/test/plots/athletes-sport-weight.js +++ b/test/plots/athletes-sport-weight.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesSportWeight() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ marginLeft: 100, grid: true, diff --git a/test/plots/athletes-weight-cumulative.js b/test/plots/athletes-weight-cumulative.ts similarity index 63% rename from test/plots/athletes-weight-cumulative.js rename to test/plots/athletes-weight-cumulative.ts index d979013607..abfc839787 100644 --- a/test/plots/athletes-weight-cumulative.js +++ b/test/plots/athletes-weight-cumulative.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesWeightCumulative() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ marginLeft: 44, marks: [Plot.rectY(athletes, Plot.binX({y: "count"}, {x: "weight", cumulative: true}))] diff --git a/test/plots/athletes-weight.js b/test/plots/athletes-weight.ts similarity index 60% rename from test/plots/athletes-weight.js rename to test/plots/athletes-weight.ts index 675cda2a69..ef190a3ee4 100644 --- a/test/plots/athletes-weight.js +++ b/test/plots/athletes-weight.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); +export async function athletesWeight() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.plot({ marks: [Plot.rectY(athletes, Plot.binX({y: "count"}, {x: "weight"}))] }); diff --git a/test/plots/autoplot.js b/test/plots/autoplot.ts similarity index 64% rename from test/plots/autoplot.js rename to test/plots/autoplot.ts index b49fb654fa..b6badf0426 100644 --- a/test/plots/autoplot.js +++ b/test/plots/autoplot.ts @@ -2,233 +2,233 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function autoHistogram() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "weight"}).plot(); } export async function autoHistogramDate() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "date_of_birth"}).plot(); } export async function autoHistogramGroup() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "island"}).plot(); } export async function autoNullReduceContinuous() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", y: {reduce: null}}).plot(); } export async function autoNullReduceOrdinal() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "island", y: {reduce: null}}).plot(); } export async function autoNullReduceDate() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "date_of_birth", y: {reduce: null}}).plot(); } export async function autoLineHistogram() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Volume", mark: "line"}).plot(); } export async function autoLine() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", y: "Close"}).plot(); } export async function autoArea() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", y: "Close", mark: "area"}).plot(); } export async function autoAreaColor() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", y: "Close", color: "Close", mark: "area"}).plot(); } export async function autoAreaColorValue() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", y: "Close", color: {value: "Close"}, mark: "area"}).plot(); } export async function autoAreaColorName() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", y: "Close", color: "red", mark: "area"}).plot(); } export async function autoAreaColorColor() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", y: "Close", color: {color: "red"}, mark: "area"}).plot(); } export async function autoDot() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", y: "body_mass_g"}).plot(); } export async function autoDotOrdinal() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "sex", y: "nationality"}).plot(); } export async function autoDotOrdCont() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", y: "species"}).plot(); } export async function autoBar() { - const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); + const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); return Plot.auto(alphabet, {x: "frequency", y: "letter", mark: "bar"}).plot(); } export async function autoBarZero() { - const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); + const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); return Plot.auto(alphabet, {x: {value: "frequency", zero: true}, y: "letter"}).plot(); } export async function autoConnectedScatterplot() { - const driving = await d3.csv("data/driving.csv", d3.autoType); + const driving = await d3.csv("data/driving.csv", d3.autoType); return Plot.auto(driving, {x: "miles", y: "gas", mark: "line"}).plot(); } // shouldnt make a line export async function autoDotUnsortedDate() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "date_of_birth", y: "height"}).plot(); } export async function autoDotSize() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", size: "Volume"}).plot(); } export async function autoDotSize2() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", size: "body_mass_g"}).plot(); } export async function autoDotGroup() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "island", y: "species", size: "count"}).plot(); } export async function autoDotBin() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", y: "body_mass_g", size: "count"}).plot(); } export async function autoDotColor() { - const cars = await d3.csv("data/cars.csv", d3.autoType); + const cars = await d3.csv("data/cars.csv", d3.autoType); return Plot.auto(cars, {x: "power (hp)", y: "weight (lb)", color: "0-60 mph (s)"}).plot(); } export async function autoHeatmapOrdCont() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", y: "species", color: "count"}).plot(); } export async function autoHeatmap() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", y: "body_mass_g", color: "count"}).plot(); } export async function autoHeatmapOrdinal() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "island", y: "species", color: "count"}).plot(); } export async function autoBarStackColor() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {y: "species", color: "sex"}).plot(); } export async function autoBarColorReducer() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {y: "species", color: "count"}).plot(); } export async function autoRectStackColor() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", color: "island"}).plot(); } export async function autoRectColorReducer() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "culmen_length_mm", color: {value: "island", reduce: "mode"}}).plot(); } export async function autoLineColor() { - const aapl = await d3.csv("data/aapl.csv", d3.autoType); + const aapl = await d3.csv("data/aapl.csv", d3.autoType); return Plot.auto(aapl, {x: "Date", y: "Close", color: "Close"}).plot(); } export async function autoLineColorSeries() { - const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); + const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); return Plot.auto(industries, {x: "date", y: "unemployed", color: "industry"}).plot(); } export async function autoAreaStackColor() { - const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); + const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); return Plot.auto(industries, {x: "date", y: "unemployed", color: "industry", mark: "area"}).plot(); } export async function autoAutoHistogram() { - const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); + const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); return Plot.auto(weather, {x: "temp_max", mark: "area"}).plot(); } export async function autoBarStackColorField() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "height", color: {value: "gold"}}).plot(); } export async function autoBarStackColorConstant() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "height", color: "gold"}).plot(); } export async function autoBarMeanZero() { - const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); + const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); return Plot.auto(weather, {x: "date", y: {value: "temp_max", reduce: "mean", zero: true}}).plot(); } export async function autoBarMode() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "island", y: {value: "species", reduce: "mode"}, mark: "bar"}).plot(); } export async function autoLineMean() { - const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); + const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); return Plot.auto(weather, {x: "date", y: {value: "temp_max", reduce: "mean"}}).plot(); } export async function autoLineMeanColor() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "date_of_birth", y: {value: "height", reduce: "mean"}, color: "sex"}).plot(); } export async function autoLineMeanThresholds() { - const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); + const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); return Plot.auto(weather, {x: {value: "date", thresholds: "month"}, y: {value: "temp_max", reduce: "mean"}}).plot(); } export async function autoLineFacet() { - const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); + const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); return Plot.auto(industries, {x: "date", y: "unemployed", fy: "industry"}).plot(); } export async function autoDotFacet() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "body_mass_g", y: "culmen_length_mm", fx: "island", color: "sex"}).plot(); } export async function autoDotFacet2() { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, { x: "body_mass_g", y: "culmen_length_mm", @@ -239,6 +239,6 @@ export async function autoDotFacet2() { } export async function autoChannels() { - const athletes = await d3.csv("data/athletes.csv", d3.autoType); + const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: Plot.valueof(athletes, "height"), y: Plot.valueof(athletes, "sport")}).plot(); } diff --git a/test/plots/availability.js b/test/plots/availability.ts similarity index 82% rename from test/plots/availability.js rename to test/plots/availability.ts index 329d544717..4764e34f34 100644 --- a/test/plots/availability.js +++ b/test/plots/availability.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/availability.csv", d3.autoType); +export async function availability() { + const data = await d3.csv("data/availability.csv", d3.autoType); const sum = (d) => (d.length ? d3.sum(d) : NaN); // force gaps return Plot.plot({ height: 180, diff --git a/test/plots/axis-labels.js b/test/plots/axis-labels.ts similarity index 100% rename from test/plots/axis-labels.js rename to test/plots/axis-labels.ts diff --git a/test/plots/ballot-status-race.js b/test/plots/ballot-status-race.ts similarity index 83% rename from test/plots/ballot-status-race.js rename to test/plots/ballot-status-race.ts index b036d1c2c5..48e876bf76 100644 --- a/test/plots/ballot-status-race.js +++ b/test/plots/ballot-status-race.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - let votes = await d3.csv("data/nc-absentee-votes.csv", d3.autoType); +export async function ballotStatusRace() { + let votes: any = await d3.csv("data/nc-absentee-votes.csv", d3.autoType); // Filter for mail ballots. const types = ["MAIL"]; @@ -36,18 +36,18 @@ export default async function () { // Group by race, // then by (normalized) status, // then rollup the count for each group. - let rollup = d3.rollups( + let rollup: any = d3.rollups( votes, - (votes) => d3.sum(votes, (d) => d.count), - (d) => d.race, - (d) => d.status + (votes) => d3.sum(votes, (d: any) => d.count), + (d: any) => d.race, + (d: any) => d.status ); // Compute the count for each race, // then the percentage for each status within each race. - rollup = rollup.flatMap(([race, group]) => { - const total = d3.sum(group, ([, count]) => count); - return group.map(([status, count]) => { + rollup = rollup.flatMap(([race, group]: any) => { + const total = d3.sum(group, ([, count]: any) => count); + return group.map(([status, count]: any) => { return {race, status, percent: (count / total) * 100}; }); }); diff --git a/test/plots/band-clip.js b/test/plots/band-clip.ts similarity index 96% rename from test/plots/band-clip.js rename to test/plots/band-clip.ts index dd40541fa4..34380664ce 100644 --- a/test/plots/band-clip.js +++ b/test/plots/band-clip.ts @@ -1,7 +1,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { +export async function bandClip() { return Plot.plot({ y: {type: "band"}, marks: [ diff --git a/test/plots/beagle.js b/test/plots/beagle.ts similarity index 87% rename from test/plots/beagle.js rename to test/plots/beagle.ts index d5d34b43eb..53b708a7fc 100644 --- a/test/plots/beagle.js +++ b/test/plots/beagle.ts @@ -2,8 +2,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; import {feature} from "topojson-client"; -export default async function () { - const world = await d3.json("data/countries-50m.json"); +export async function beagle() { + const world = await d3.json("data/countries-50m.json"); // note: this returns strings; we should clean it up to make a better example, // but we keep it to help test the projection’s robustness const beagle = await d3.text("data/beagle.csv").then(d3.csvParseRows); diff --git a/test/plots/becker-barley.js b/test/plots/becker-barley.ts similarity index 84% rename from test/plots/becker-barley.js rename to test/plots/becker-barley.ts index 6835a9a7ec..41a3ad41be 100644 --- a/test/plots/becker-barley.js +++ b/test/plots/becker-barley.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const barley = await d3.csv("data/barley.csv", d3.autoType); +export async function beckerBarley() { + const barley = await d3.csv("data/barley.csv", d3.autoType); return Plot.plot({ marginLeft: 110, height: 800, diff --git a/test/plots/bigint.js b/test/plots/bigint.ts similarity index 100% rename from test/plots/bigint.js rename to test/plots/bigint.ts diff --git a/test/plots/bin-1m.js b/test/plots/bin-1m.ts similarity index 100% rename from test/plots/bin-1m.js rename to test/plots/bin-1m.ts diff --git a/test/plots/bin-strings.js b/test/plots/bin-strings.ts similarity index 77% rename from test/plots/bin-strings.js rename to test/plots/bin-strings.ts index 863e9a0350..5b940d4ecf 100644 --- a/test/plots/bin-strings.js +++ b/test/plots/bin-strings.ts @@ -1,5 +1,5 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function binStrings() { return Plot.rectY(["9.6", "9.6", "14.8", "14.8", "7.2"], Plot.binX()).plot(); } diff --git a/test/plots/bin-timestamps.js b/test/plots/bin-timestamps.ts similarity index 88% rename from test/plots/bin-timestamps.js rename to test/plots/bin-timestamps.ts index 2392355a0c..3c2227323f 100644 --- a/test/plots/bin-timestamps.js +++ b/test/plots/bin-timestamps.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function binTimestamps() { const timestamps = Float64Array.of( 1609459200000, 1609545600000, diff --git a/test/plots/bounding-boxes.js b/test/plots/bounding-boxes.ts similarity index 90% rename from test/plots/bounding-boxes.js rename to test/plots/bounding-boxes.ts index d1a41d985e..f5755e729c 100644 --- a/test/plots/bounding-boxes.js +++ b/test/plots/bounding-boxes.ts @@ -3,8 +3,8 @@ import * as d3 from "d3"; import {geoChamberlinAfrica} from "d3-geo-projection"; import {feature} from "topojson-client"; -export default async function () { - const world = await d3.json("data/countries-110m.json"); +export async function boundingBoxes() { + const world = await d3.json("data/countries-110m.json"); const land = feature(world, world.objects.land); const countries = feature(world, world.objects.countries).features; return Plot.plot({ diff --git a/test/plots/boxplot.js b/test/plots/boxplot.ts similarity index 74% rename from test/plots/boxplot.js rename to test/plots/boxplot.ts index a338444efc..6f3a48144c 100644 --- a/test/plots/boxplot.js +++ b/test/plots/boxplot.ts @@ -1,5 +1,5 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function boxplot() { return Plot.boxX([0, 3, 4.4, 4.5, 4.6, 5, 7]).plot(); } diff --git a/test/plots/caltrain-direction.js b/test/plots/caltrain-direction.ts similarity index 83% rename from test/plots/caltrain-direction.js rename to test/plots/caltrain-direction.ts index a8db3d5ff0..a2b2e7443c 100644 --- a/test/plots/caltrain-direction.js +++ b/test/plots/caltrain-direction.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const caltrain = await d3.csv("data/caltrain.csv"); +export async function caltrainDirection() { + const caltrain = await d3.csv("data/caltrain.csv"); return Plot.plot({ x: { tickFormat: "%I %p" diff --git a/test/plots/caltrain.js b/test/plots/caltrain.ts similarity index 93% rename from test/plots/caltrain.js rename to test/plots/caltrain.ts index 4cc1aae0ad..e53b76aa6e 100644 --- a/test/plots/caltrain.js +++ b/test/plots/caltrain.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const caltrain = await d3.csv("data/caltrain.csv"); +export async function caltrain() { + const caltrain = await d3.csv("data/caltrain.csv"); return Plot.plot({ width: 240, axis: null, diff --git a/test/plots/cars-dodge.js b/test/plots/cars-dodge.ts similarity index 68% rename from test/plots/cars-dodge.js rename to test/plots/cars-dodge.ts index d00321cb98..e45476d638 100644 --- a/test/plots/cars-dodge.js +++ b/test/plots/cars-dodge.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const cars = await d3.csv("data/cars.csv", d3.autoType); +export async function carsDodge() { + const cars = await d3.csv("data/cars.csv", d3.autoType); return Plot.plot({ height: 200, x: {line: true}, diff --git a/test/plots/cars-hexbin.js b/test/plots/cars-hexbin.ts similarity index 77% rename from test/plots/cars-hexbin.js rename to test/plots/cars-hexbin.ts index 79318fd00b..d946cabd4f 100644 --- a/test/plots/cars-hexbin.js +++ b/test/plots/cars-hexbin.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const cars = await d3.csv("data/cars.csv", d3.autoType); +export async function carsHexbin() { + const cars = await d3.csv("data/cars.csv", d3.autoType); return Plot.plot({ color: { scheme: "reds", diff --git a/test/plots/cars-jitter.js b/test/plots/cars-jitter.ts similarity index 85% rename from test/plots/cars-jitter.js rename to test/plots/cars-jitter.ts index 7dbb244b1c..26ad194e88 100644 --- a/test/plots/cars-jitter.js +++ b/test/plots/cars-jitter.ts @@ -2,9 +2,9 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; import {remap} from "../transforms/remap.js"; -export default async function () { +export async function carsJitter() { const random = d3.randomNormal.source(d3.randomLcg(42))(0, 7); - const data = await d3.csv("data/cars.csv", d3.autoType); + const data = await d3.csv("data/cars.csv", d3.autoType); return Plot.plot({ height: 350, nice: true, diff --git a/test/plots/cars-mpg.js b/test/plots/cars-mpg.ts similarity index 87% rename from test/plots/cars-mpg.js rename to test/plots/cars-mpg.ts index e3ce9ca96d..527585fa11 100644 --- a/test/plots/cars-mpg.js +++ b/test/plots/cars-mpg.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/cars.csv", d3.autoType); +export async function carsMpg() { + const data = await d3.csv("data/cars.csv", d3.autoType); return Plot.plot({ x: { type: "point" diff --git a/test/plots/cars-parcoords.js b/test/plots/cars-parcoords.ts similarity index 79% rename from test/plots/cars-parcoords.js rename to test/plots/cars-parcoords.ts index d52d98fbe9..18ac14e135 100644 --- a/test/plots/cars-parcoords.js +++ b/test/plots/cars-parcoords.ts @@ -1,12 +1,12 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const cars = await d3.csv("data/cars.csv", d3.autoType); +export async function carsParcoords() { + const cars = await d3.csv("data/cars.csv", d3.autoType); const dimensions = cars.columns.slice(1); // Reshape wide data to make it tidy. - const data = dimensions.flatMap((dimension) => { + const data = dimensions.flatMap((dimension: string) => { return cars.map(({name, year, [dimension]: value}) => { return {name: `${name}-${year}`, dimension, value}; }); @@ -14,7 +14,7 @@ export default async function () { // Compute ticks for each dimension. const ticks = dimensions.flatMap((dimension) => { - return d3.ticks(...d3.extent(cars, (d) => d[dimension]), 7).map((value) => { + return d3.ticks(...(d3.extent(cars, (d) => d[dimension]) as [number, number]), 7).map((value) => { return {dimension, value}; }); }); diff --git a/test/plots/clamp.js b/test/plots/clamp.ts similarity index 78% rename from test/plots/clamp.js rename to test/plots/clamp.ts index c4885d245a..c46b95cec4 100644 --- a/test/plots/clamp.js +++ b/test/plots/clamp.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); +export async function clamp() { + const data = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); return Plot.plot({ clamp: true, x: {domain: [new Date(Date.UTC(2006, 0, 1)), new Date(Date.UTC(2008, 0, 1))], clamp: false}, diff --git a/test/plots/collapsed-histogram.js b/test/plots/collapsed-histogram.ts similarity index 68% rename from test/plots/collapsed-histogram.js rename to test/plots/collapsed-histogram.ts index de17a01926..5adfac5bfb 100644 --- a/test/plots/collapsed-histogram.js +++ b/test/plots/collapsed-histogram.ts @@ -1,5 +1,5 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function collapsedHistogram() { return Plot.rectY([1, 1, 1], Plot.binX()).plot(); } diff --git a/test/plots/country-centroids.js b/test/plots/country-centroids.ts similarity index 84% rename from test/plots/country-centroids.js rename to test/plots/country-centroids.ts index 3344f3d1c2..2d61f236cd 100644 --- a/test/plots/country-centroids.js +++ b/test/plots/country-centroids.ts @@ -2,8 +2,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; import {feature} from "topojson-client"; -export default async function () { - const world = await d3.json("data/countries-110m.json"); +export async function countryCentroids() { + const world = await d3.json("data/countries-110m.json"); const land = feature(world, world.objects.land); const countries = feature(world, world.objects.countries).features; return Plot.plot({ diff --git a/test/plots/covid-ihme-projected-deaths.js b/test/plots/covid-ihme-projected-deaths.ts similarity index 90% rename from test/plots/covid-ihme-projected-deaths.js rename to test/plots/covid-ihme-projected-deaths.ts index 4366d3b16b..4a98e8cd57 100644 --- a/test/plots/covid-ihme-projected-deaths.js +++ b/test/plots/covid-ihme-projected-deaths.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/covid-ihme-projected-deaths.csv", d3.autoType); +export async function covidIhmeProjectedDeaths() { + const data = await d3.csv("data/covid-ihme-projected-deaths.csv", d3.autoType); const i = data.findIndex((d) => d.projected) - 1; return Plot.plot({ width: 960, diff --git a/test/plots/crimean-war-arrow.js b/test/plots/crimean-war-arrow.ts similarity index 78% rename from test/plots/crimean-war-arrow.js rename to test/plots/crimean-war-arrow.ts index 44bfa3383d..9ef497a032 100644 --- a/test/plots/crimean-war-arrow.js +++ b/test/plots/crimean-war-arrow.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); +export async function crimeanWarArrow() { + const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); const causes = crimea.columns.slice(2); const data = causes.flatMap((cause) => crimea.map(({date, [cause]: deaths}) => ({date, cause, deaths}))); return Plot.plot({ diff --git a/test/plots/crimean-war-line.js b/test/plots/crimean-war-line.ts similarity index 78% rename from test/plots/crimean-war-line.js rename to test/plots/crimean-war-line.ts index 0e5a04aa1d..4e5775ef37 100644 --- a/test/plots/crimean-war-line.js +++ b/test/plots/crimean-war-line.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); +export async function crimeanWarLine() { + const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); const causes = crimea.columns.slice(2); const data = causes.flatMap((cause) => crimea.map(({date, [cause]: deaths}) => ({date, cause, deaths}))); return Plot.plot({ diff --git a/test/plots/crimean-war-overlapped.js b/test/plots/crimean-war-overlapped.ts similarity index 79% rename from test/plots/crimean-war-overlapped.js rename to test/plots/crimean-war-overlapped.ts index 3a8e6c8b52..6a6fb54e73 100644 --- a/test/plots/crimean-war-overlapped.js +++ b/test/plots/crimean-war-overlapped.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); +export async function crimeanWarOverlapped() { + const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); const causes = crimea.columns.slice(2); const data = causes.flatMap((cause) => crimea.map(({date, [cause]: deaths}) => ({date, cause, deaths}))); return Plot.plot({ diff --git a/test/plots/crimean-war-stacked.js b/test/plots/crimean-war-stacked.ts similarity index 79% rename from test/plots/crimean-war-stacked.js rename to test/plots/crimean-war-stacked.ts index 8375592168..22800ceff8 100644 --- a/test/plots/crimean-war-stacked.js +++ b/test/plots/crimean-war-stacked.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); +export async function crimeanWarStacked() { + const crimea = await d3.csv("data/crimean-war.csv", d3.autoType); const causes = crimea.columns.slice(2); const data = causes.flatMap((cause) => crimea.map(({date, [cause]: deaths}) => ({date, cause, deaths}))); return Plot.plot({ diff --git a/test/plots/d3-survey-2015-comfort.js b/test/plots/d3-survey-2015-comfort.ts similarity index 86% rename from test/plots/d3-survey-2015-comfort.js rename to test/plots/d3-survey-2015-comfort.ts index 073604ab51..56f43521ca 100644 --- a/test/plots/d3-survey-2015-comfort.js +++ b/test/plots/d3-survey-2015-comfort.ts @@ -1,8 +1,8 @@ import * as d3 from "d3"; import {chooseOne} from "./d3-survey-2015.js"; -export default async function () { - const responses = await d3.json("data/d3-survey-2015.json"); +export async function d3Survey2015Comfort() { + const responses = await d3.json("data/d3-survey-2015.json"); return chooseOne(responses, "comfort", "How comfortable are you with d3 now?"); // return chooseOne(responses, "comfort", "How comfortable are you with d3 now?"); // return chooseOne(responses, "forloops", "Are you comfortable with for loops?"); diff --git a/test/plots/d3-survey-2015-why.js b/test/plots/d3-survey-2015-why.ts similarity index 83% rename from test/plots/d3-survey-2015-why.js rename to test/plots/d3-survey-2015-why.ts index 3c5e7f905e..919bb45348 100644 --- a/test/plots/d3-survey-2015-why.js +++ b/test/plots/d3-survey-2015-why.ts @@ -1,8 +1,8 @@ import * as d3 from "d3"; import {chooseMany} from "./d3-survey-2015.js"; -export default async function () { - const responses = await d3.json("data/d3-survey-2015.json"); +export async function d3Survey2015Why() { + const responses = await d3.json("data/d3-survey-2015.json"); return chooseMany(responses, "why", "Why do you want to learn d3?"); // return chooseMany(responses, "tech", "Have you used any of the following technologies?"); // return chooseMany(responses, "projects", "What kind of projects do you want to use d3 for?"); diff --git a/test/plots/d3-survey-2015.js b/test/plots/d3-survey-2015.ts similarity index 100% rename from test/plots/d3-survey-2015.js rename to test/plots/d3-survey-2015.ts diff --git a/test/plots/darker-dodge.js b/test/plots/darker-dodge.ts similarity index 85% rename from test/plots/darker-dodge.js rename to test/plots/darker-dodge.ts index d061a9aabb..8e7e31a0dc 100644 --- a/test/plots/darker-dodge.js +++ b/test/plots/darker-dodge.ts @@ -2,14 +2,14 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; import {remap} from "../transforms/remap.js"; -function darker(outputs, inputs) { +function darker(outputs: {[name: string]: number}, inputs) { return remap( Object.fromEntries(Object.entries(outputs).map(([name, value]) => [name, (v) => d3.lab(v).darker(value)])), inputs ); } -export default async function () { +export async function darkerDodge() { const random = d3.randomLogNormal.source(d3.randomLcg(42))(); return Plot.plot({ height: 170, diff --git a/test/plots/decathlon.js b/test/plots/decathlon.ts similarity index 71% rename from test/plots/decathlon.js rename to test/plots/decathlon.ts index 795ae00530..45fc92c1a4 100644 --- a/test/plots/decathlon.js +++ b/test/plots/decathlon.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const decathlon = await d3.csv("data/decathlon.csv", d3.autoType); +export async function decathlon() { + const decathlon = await d3.csv("data/decathlon.csv", d3.autoType); return Plot.plot({ grid: true, inset: 12, diff --git a/test/plots/diamonds-boxplot.js b/test/plots/diamonds-boxplot.ts similarity index 60% rename from test/plots/diamonds-boxplot.js rename to test/plots/diamonds-boxplot.ts index c004902e47..3202d07dd6 100644 --- a/test/plots/diamonds-boxplot.js +++ b/test/plots/diamonds-boxplot.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const diamonds = await d3.csv("data/diamonds.csv", d3.autoType); +export async function diamondsBoxplot() { + const diamonds = await d3.csv("data/diamonds.csv", d3.autoType); return Plot.plot({ marks: [Plot.boxX(diamonds, {x: "price", y: "clarity", sort: {y: "x"}})] }); diff --git a/test/plots/diamonds-carat-price-dots.js b/test/plots/diamonds-carat-price-dots.ts similarity index 77% rename from test/plots/diamonds-carat-price-dots.js rename to test/plots/diamonds-carat-price-dots.ts index a9f9bf7b0a..849680e365 100644 --- a/test/plots/diamonds-carat-price-dots.js +++ b/test/plots/diamonds-carat-price-dots.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/diamonds.csv", d3.autoType); +export async function diamondsCaratPriceDots() { + const data = await d3.csv("data/diamonds.csv", d3.autoType); return Plot.plot({ height: 640, grid: true, diff --git a/test/plots/diamonds-carat-price.js b/test/plots/diamonds-carat-price.ts similarity index 72% rename from test/plots/diamonds-carat-price.js rename to test/plots/diamonds-carat-price.ts index 0a6337f99b..115e29283c 100644 --- a/test/plots/diamonds-carat-price.js +++ b/test/plots/diamonds-carat-price.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/diamonds.csv", d3.autoType); +export async function diamondsCaratPrice() { + const data = await d3.csv("data/diamonds.csv", d3.autoType); return Plot.plot({ height: 640, marginLeft: 44, diff --git a/test/plots/diamonds-carat-sampling.js b/test/plots/diamonds-carat-sampling.ts similarity index 87% rename from test/plots/diamonds-carat-sampling.js rename to test/plots/diamonds-carat-sampling.ts index e76db2467b..1e59c24b15 100644 --- a/test/plots/diamonds-carat-sampling.js +++ b/test/plots/diamonds-carat-sampling.ts @@ -14,8 +14,8 @@ function sample(n, options) { return Plot.transform(options, (data, facets) => ({data, facets: Array.from(facets, (I) => samples(I, n))})); } -export default async function () { - const data = await d3.csv("data/diamonds.csv", d3.autoType); +export async function diamondsCaratSampling() { + const data = await d3.csv("data/diamonds.csv", d3.autoType); return Plot.plot({ marginLeft: 44, marks: [ diff --git a/test/plots/documentation-links.js b/test/plots/documentation-links.ts similarity index 83% rename from test/plots/documentation-links.js rename to test/plots/documentation-links.ts index a6c255747e..cc5a15f425 100644 --- a/test/plots/documentation-links.js +++ b/test/plots/documentation-links.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.json("data/plot-documentation.json").then((d) => d.listings); +export async function documentationLinks() { + const data = await d3.json("data/plot-documentation.json").then((d) => d.listings); return Plot.plot({ marginLeft: 140, x: { diff --git a/test/plots/dodge-rule.js b/test/plots/dodge-rule.ts similarity index 73% rename from test/plots/dodge-rule.js rename to test/plots/dodge-rule.ts index 1eded49c2b..3e72c1fcce 100644 --- a/test/plots/dodge-rule.js +++ b/test/plots/dodge-rule.ts @@ -1,5 +1,5 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function dodgeRule() { return Plot.ruleX([1, 2, 3], Plot.dodgeY()).plot(); } diff --git a/test/plots/dodge-text-radius.js b/test/plots/dodge-text-radius.ts similarity index 91% rename from test/plots/dodge-text-radius.js rename to test/plots/dodge-text-radius.ts index 693d07fa2b..09d6cf5ad7 100644 --- a/test/plots/dodge-text-radius.js +++ b/test/plots/dodge-text-radius.ts @@ -1,7 +1,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { +export async function dodgeTextRadius() { const random = d3.randomLcg(42); const length = 100; const X = Float64Array.from({length}, random); diff --git a/test/plots/dodge-tick.js b/test/plots/dodge-tick.ts similarity index 73% rename from test/plots/dodge-tick.js rename to test/plots/dodge-tick.ts index 88b77e63ef..8ac042b3a6 100644 --- a/test/plots/dodge-tick.js +++ b/test/plots/dodge-tick.ts @@ -1,5 +1,5 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function dodgeTick() { return Plot.tickX([1, 2, 3], Plot.dodgeY()).plot(); } diff --git a/test/plots/dot-sort.js b/test/plots/dot-sort.ts similarity index 86% rename from test/plots/dot-sort.js rename to test/plots/dot-sort.ts index f9d64dccd4..7e72d80aca 100644 --- a/test/plots/dot-sort.js +++ b/test/plots/dot-sort.ts @@ -1,11 +1,11 @@ import * as Plot from "@observablehq/plot"; import {html} from "htl"; -export default async function () { +export async function dotSort() { const x = [..."ABDCEFGH"]; const r = [30, 60, 20, 20, 35, 22, 20, 28]; const options = {x, r, stroke: "black", fill: x, fillOpacity: 0.8}; - const p = {width: 300, axis: null, r: {type: "identity"}, x: {inset: 50}, margin: 0}; + const p: Plot.PlotOptions = {width: 300, axis: null, r: {type: "identity"}, x: {inset: 50}, margin: 0}; return html` ${Plot.dot(x, options).plot({...p, caption: "default sort (r desc)"})}
diff --git a/test/plots/downloads-ordinal.js b/test/plots/downloads-ordinal.ts similarity index 79% rename from test/plots/downloads-ordinal.js rename to test/plots/downloads-ordinal.ts index 626fafab07..3cadf0715b 100644 --- a/test/plots/downloads-ordinal.js +++ b/test/plots/downloads-ordinal.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const downloads = (await d3.csv("data/downloads.csv", d3.autoType)).filter( +export async function downloadsOrdinal() { + const downloads = (await d3.csv("data/downloads.csv", d3.autoType)).filter( (d) => d.date.getUTCFullYear() === 2019 && d.date.getUTCMonth() <= 1 && d.downloads > 0 ); return Plot.plot({ diff --git a/test/plots/downloads.js b/test/plots/downloads.ts similarity index 71% rename from test/plots/downloads.js rename to test/plots/downloads.ts index 219ea0f142..84a1d44b4a 100644 --- a/test/plots/downloads.js +++ b/test/plots/downloads.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const downloads = (await d3.csv("data/downloads.csv", d3.autoType)).filter((d) => d.downloads > 0); +export async function downloads() { + const downloads = (await d3.csv("data/downloads.csv", d3.autoType)).filter((d) => d.downloads > 0); return Plot.plot({ marks: [ Plot.areaY(downloads, {x: "date", interval: "day", y: "downloads", curve: "step", fill: "#ccc"}), diff --git a/test/plots/driving.js b/test/plots/driving.ts similarity index 82% rename from test/plots/driving.js rename to test/plots/driving.ts index daf7582ea8..b46c8097b3 100644 --- a/test/plots/driving.js +++ b/test/plots/driving.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const driving = await d3.csv("data/driving.csv", d3.autoType); +export async function driving() { + const driving = await d3.csv("data/driving.csv", d3.autoType); return Plot.plot({ inset: 10, grid: true, diff --git a/test/plots/electricity-demand.js b/test/plots/electricity-demand.ts similarity index 90% rename from test/plots/electricity-demand.js rename to test/plots/electricity-demand.ts index 97fe7a816b..998f9e7abd 100644 --- a/test/plots/electricity-demand.js +++ b/test/plots/electricity-demand.ts @@ -2,7 +2,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function electricityDemand() { - const electricity = await d3.csv("data/electricity-demand.csv", d3.autoType); + const electricity = await d3.csv("data/electricity-demand.csv", d3.autoType); return Plot.plot({ width: 960, marginLeft: 50, diff --git a/test/plots/empty-facet.js b/test/plots/empty-facet.ts similarity index 88% rename from test/plots/empty-facet.js rename to test/plots/empty-facet.ts index ce19669a5d..e03ce872eb 100644 --- a/test/plots/empty-facet.js +++ b/test/plots/empty-facet.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function emptyFacet() { const data = [ {PERIOD: 1, VALUE: 3, TYPE: "c"}, {PERIOD: 2, VALUE: 4, TYPE: "c"} diff --git a/test/plots/empty-legend.js b/test/plots/empty-legend.ts similarity index 81% rename from test/plots/empty-legend.js rename to test/plots/empty-legend.ts index 7edcdefb8d..9e677ca625 100644 --- a/test/plots/empty-legend.js +++ b/test/plots/empty-legend.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function emptyLegend() { return Plot.plot({ color: { legend: true // ignored because no color scale diff --git a/test/plots/empty-x.js b/test/plots/empty-x.ts similarity index 83% rename from test/plots/empty-x.js rename to test/plots/empty-x.ts index 5e3454aca2..2bb90c17f7 100644 --- a/test/plots/empty-x.js +++ b/test/plots/empty-x.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function emptyX() { return Plot.plot({ grid: true, x: { diff --git a/test/plots/empty.js b/test/plots/empty.ts similarity index 90% rename from test/plots/empty.js rename to test/plots/empty.ts index 379cf190eb..407bfdd063 100644 --- a/test/plots/empty.js +++ b/test/plots/empty.ts @@ -1,7 +1,7 @@ import * as Plot from "@observablehq/plot"; import {svg} from "htl"; -export default async function () { +export async function empty() { return Plot.plot({ grid: true, inset: 6, diff --git a/test/plots/energy-production.js b/test/plots/energy-production.ts similarity index 93% rename from test/plots/energy-production.js rename to test/plots/energy-production.ts index 58a71408f0..4eaf7ca8cb 100644 --- a/test/plots/energy-production.js +++ b/test/plots/energy-production.ts @@ -25,8 +25,8 @@ const types = new Map([ ["Total Renewable Energy Production", "Renewable"] ]); -export default async function () { - const energy = (await d3.csv("data/energy-production.csv")) +export async function energyProduction() { + const energy = (await d3.csv("data/energy-production.csv")) .filter((d) => d.YYYYMM.slice(-2) === "13") // only take annual data .filter((d) => types.has(d.Description)) // don’t double-count categories .map((d) => ({...d, Year: +d.YYYYMM.slice(0, 4), Value: +d.Value})); diff --git a/test/plots/faithful-density-1d.js b/test/plots/faithful-density-1d.ts similarity index 78% rename from test/plots/faithful-density-1d.js rename to test/plots/faithful-density-1d.ts index 5dd15dd66f..f78282f680 100644 --- a/test/plots/faithful-density-1d.js +++ b/test/plots/faithful-density-1d.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const faithful = await d3.tsv("data/faithful.tsv", d3.autoType); +export async function faithfulDensity1d() { + const faithful = await d3.tsv("data/faithful.tsv", d3.autoType); return Plot.plot({ height: 100, inset: 20, diff --git a/test/plots/faithful-density.js b/test/plots/faithful-density.ts similarity index 78% rename from test/plots/faithful-density.js rename to test/plots/faithful-density.ts index 1cc81eaa82..bc72213f23 100644 --- a/test/plots/faithful-density.js +++ b/test/plots/faithful-density.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const faithful = await d3.tsv("data/faithful.tsv", d3.autoType); +export async function faithfulDensity() { + const faithful = await d3.tsv("data/faithful.tsv", d3.autoType); return Plot.plot({ inset: 20, marks: [ diff --git a/test/plots/federal-funds.js b/test/plots/federal-funds.ts similarity index 100% rename from test/plots/federal-funds.js rename to test/plots/federal-funds.ts diff --git a/test/plots/figcaption-html.js b/test/plots/figcaption-html.ts similarity index 80% rename from test/plots/figcaption-html.js rename to test/plots/figcaption-html.ts index 3a59b677cc..6f24eb6a55 100644 --- a/test/plots/figcaption-html.js +++ b/test/plots/figcaption-html.ts @@ -2,8 +2,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; import {html} from "htl"; -export default async function () { - const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); +export async function figcaptionHtml() { + const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); return Plot.plot({ caption: html`Figure 1. The relative frequency of letters in the English language. Data: Cryptographical Mathematics`, diff --git a/test/plots/figcaption.js b/test/plots/figcaption.ts similarity index 79% rename from test/plots/figcaption.js rename to test/plots/figcaption.ts index b8252f7fb5..1718a02d06 100644 --- a/test/plots/figcaption.js +++ b/test/plots/figcaption.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); +export async function figcaption() { + const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); return Plot.plot({ caption: "The relative frequency of letters in the English language. Data: Robert Edward Lewand", x: { diff --git a/test/plots/first-ladies.js b/test/plots/first-ladies.ts similarity index 83% rename from test/plots/first-ladies.js rename to test/plots/first-ladies.ts index ac0e913ccb..b50fe7e2db 100644 --- a/test/plots/first-ladies.js +++ b/test/plots/first-ladies.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/first-ladies.csv", d3.autoType); +export async function firstLadies() { + const data = await d3.csv("data/first-ladies.csv", d3.autoType); const now = new Date("2021-07-19"); return Plot.plot({ width: 960, diff --git a/test/plots/flare-cluster.js b/test/plots/flare-cluster.ts similarity index 68% rename from test/plots/flare-cluster.js rename to test/plots/flare-cluster.ts index c9df36ed50..0020403e7e 100644 --- a/test/plots/flare-cluster.js +++ b/test/plots/flare-cluster.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const flare = await d3.csv("data/flare.csv", d3.autoType); +export async function flareCluster() { + const flare = await d3.csv("data/flare.csv", d3.autoType); return Plot.plot({ axis: null, inset: 10, diff --git a/test/plots/flare-indent.js b/test/plots/flare-indent.ts similarity index 82% rename from test/plots/flare-indent.js rename to test/plots/flare-indent.ts index 664d6d774e..a4fa8bdf08 100644 --- a/test/plots/flare-indent.js +++ b/test/plots/flare-indent.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const flare = await d3.csv("data/flare.csv", d3.autoType); +export async function flareIndent() { + const flare = await d3.csv("data/flare.csv", d3.autoType); return Plot.plot({ axis: null, inset: 10, diff --git a/test/plots/flare-tree.js b/test/plots/flare-tree.ts similarity index 71% rename from test/plots/flare-tree.js rename to test/plots/flare-tree.ts index 5dfba68ed7..510e8a6101 100644 --- a/test/plots/flare-tree.js +++ b/test/plots/flare-tree.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const flare = await d3.csv("data/flare.csv", d3.autoType); +export async function flareTree() { + const flare = await d3.csv("data/flare.csv", d3.autoType); return Plot.plot({ axis: null, inset: 10, diff --git a/test/plots/football-coverage.js b/test/plots/football-coverage.ts similarity index 77% rename from test/plots/football-coverage.js rename to test/plots/football-coverage.ts index b29957e0a9..14cdfe65ea 100644 --- a/test/plots/football-coverage.js +++ b/test/plots/football-coverage.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const football = await d3.csv("data/football-coverage.csv", d3.autoType); +export async function footballCoverage() { + const football = await d3.csv("data/football-coverage.csv", d3.autoType); return Plot.plot({ x: { axis: null diff --git a/test/plots/frame.js b/test/plots/frame.ts similarity index 100% rename from test/plots/frame.js rename to test/plots/frame.ts diff --git a/test/plots/fruit-sales-date.js b/test/plots/fruit-sales-date.ts similarity index 75% rename from test/plots/fruit-sales-date.js rename to test/plots/fruit-sales-date.ts index 78e0d9a18c..0aabf16283 100644 --- a/test/plots/fruit-sales-date.js +++ b/test/plots/fruit-sales-date.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const sales = await d3.csv("data/fruit-sales.csv", d3.autoType); +export async function fruitSalesDate() { + const sales = await d3.csv("data/fruit-sales.csv", d3.autoType); return Plot.plot({ x: { type: "band" // treat dates as ordinal, not temporal diff --git a/test/plots/fruit-sales.js b/test/plots/fruit-sales.ts similarity index 74% rename from test/plots/fruit-sales.js rename to test/plots/fruit-sales.ts index a2befd816c..0c3b0dd085 100644 --- a/test/plots/fruit-sales.js +++ b/test/plots/fruit-sales.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const sales = await d3.csv("data/fruit-sales.csv", d3.autoType); +export async function fruitSales() { + const sales = await d3.csv("data/fruit-sales.csv", d3.autoType); return Plot.plot({ marginLeft: 50, y: { diff --git a/test/plots/function-contour.js b/test/plots/function-contour.ts similarity index 97% rename from test/plots/function-contour.js rename to test/plots/function-contour.ts index 6724f5ad70..99b8ad53e5 100644 --- a/test/plots/function-contour.js +++ b/test/plots/function-contour.ts @@ -28,7 +28,7 @@ export async function functionContourFaceted() { fx: {tickFormat: (f) => f?.name}, fy: {tickFormat: (f) => f?.name}, marks: [ - Plot.contour({ + Plot.contour(undefined, { fill: (x, y, {fx, fy}) => fx(x) * fy(y), fx: [Math.sin, Math.sin, lin, lin], fy: [Math.cos, lin, lin, Math.cos], diff --git a/test/plots/geo-link.js b/test/plots/geo-link.ts similarity index 100% rename from test/plots/geo-link.js rename to test/plots/geo-link.ts diff --git a/test/plots/gistemp-anomaly-moving.js b/test/plots/gistemp-anomaly-moving.ts similarity index 79% rename from test/plots/gistemp-anomaly-moving.js rename to test/plots/gistemp-anomaly-moving.ts index 049c541e59..3453d1e44e 100644 --- a/test/plots/gistemp-anomaly-moving.js +++ b/test/plots/gistemp-anomaly-moving.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/gistemp.csv", d3.autoType); +export async function gistempAnomalyMoving() { + const data = await d3.csv("data/gistemp.csv", d3.autoType); return Plot.plot({ y: { label: "↑ Temperature anomaly (°C)", diff --git a/test/plots/gistemp-anomaly-transform.js b/test/plots/gistemp-anomaly-transform.ts similarity index 80% rename from test/plots/gistemp-anomaly-transform.js rename to test/plots/gistemp-anomaly-transform.ts index 2b40d6b270..f445c52c97 100644 --- a/test/plots/gistemp-anomaly-transform.js +++ b/test/plots/gistemp-anomaly-transform.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/gistemp.csv", d3.autoType); +export async function gistempAnomalyTransform() { + const data = await d3.csv("data/gistemp.csv", d3.autoType); const transform = (c) => (c * 9) / 5; // convert (relative) Celsius to Fahrenheit return Plot.plot({ y: { diff --git a/test/plots/gistemp-anomaly.js b/test/plots/gistemp-anomaly.ts similarity index 75% rename from test/plots/gistemp-anomaly.js rename to test/plots/gistemp-anomaly.ts index 7190dc73a7..f5f12b4113 100644 --- a/test/plots/gistemp-anomaly.js +++ b/test/plots/gistemp-anomaly.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/gistemp.csv", d3.autoType); +export async function gistempAnomaly() { + const data = await d3.csv("data/gistemp.csv", d3.autoType); return Plot.plot({ y: { label: "↑ Temperature anomaly (°C)", diff --git a/test/plots/google-trends-ridgeline.js b/test/plots/google-trends-ridgeline.ts similarity index 82% rename from test/plots/google-trends-ridgeline.js rename to test/plots/google-trends-ridgeline.ts index f7bf9a8b53..e6b9e2495f 100644 --- a/test/plots/google-trends-ridgeline.js +++ b/test/plots/google-trends-ridgeline.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const data = await d3.csv("data/google-trends-2020.csv", d3.autoType); +export async function googleTrendsRidgeline() { + const data = await d3.csv("data/google-trends-2020.csv", d3.autoType); return Plot.plot({ width: 960, x: { diff --git a/test/plots/graticule.js b/test/plots/graticule.ts similarity index 86% rename from test/plots/graticule.js rename to test/plots/graticule.ts index dc19404a2d..3fa55510be 100644 --- a/test/plots/graticule.js +++ b/test/plots/graticule.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function graticule() { return Plot.plot({ width: 960, height: 470, diff --git a/test/plots/greek-gods.js b/test/plots/greek-gods.ts similarity index 91% rename from test/plots/greek-gods.js rename to test/plots/greek-gods.ts index 1657711039..b50c7655f3 100644 --- a/test/plots/greek-gods.js +++ b/test/plots/greek-gods.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function greekGods() { const gods = `Chaos Gaia Mountains Chaos Gaia Pontus Chaos Gaia Uranus diff --git a/test/plots/grid-choropleth-dx.js b/test/plots/grid-choropleth-dx.ts similarity index 80% rename from test/plots/grid-choropleth-dx.js rename to test/plots/grid-choropleth-dx.ts index 395e7ae945..482b7b0667 100644 --- a/test/plots/grid-choropleth-dx.js +++ b/test/plots/grid-choropleth-dx.ts @@ -1,10 +1,10 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { +export async function gridChoroplethDx() { const [grid, data] = await Promise.all([ - await d3.csv("data/us-state-grid.csv", d3.autoType).then(gridmap), - await d3.csv("data/us-state-population-2010-2019.csv", d3.autoType) + await d3.csv("data/us-state-grid.csv", d3.autoType).then(gridmap), + await d3.csv("data/us-state-population-2010-2019.csv", d3.autoType) ]); const states = data.filter((d) => grid.has(d.State)).map((d) => ({...d, ...grid.get(d.State)})); return Plot.plot({ @@ -37,7 +37,7 @@ export default async function () { }); } -function gridmap(states) { +function gridmap(states: {name: string}[]) { return new Map(states.map((state) => [state.name, state])); } diff --git a/test/plots/grid-choropleth.js b/test/plots/grid-choropleth.ts similarity index 78% rename from test/plots/grid-choropleth.js rename to test/plots/grid-choropleth.ts index ef9f25aeaf..1f6f0b4a04 100644 --- a/test/plots/grid-choropleth.js +++ b/test/plots/grid-choropleth.ts @@ -1,10 +1,10 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { +export async function gridChoropleth() { const [grid, data] = await Promise.all([ - await d3.csv("data/us-state-grid.csv", d3.autoType).then(gridmap), - await d3.csv("data/us-state-population-2010-2019.csv", d3.autoType) + await d3.csv("data/us-state-grid.csv", d3.autoType).then(gridmap), + await d3.csv("data/us-state-population-2010-2019.csv", d3.autoType) ]); const states = data.filter((d) => grid.has(d.State)).map((d) => ({...d, ...grid.get(d.State)})); return Plot.plot({ @@ -36,7 +36,7 @@ export default async function () { }); } -function gridmap(states) { +function gridmap(states: {name: string}[]) { return new Map(states.map((state) => [state.name, state])); } diff --git a/test/plots/grouped-rects.js b/test/plots/grouped-rects.ts similarity index 81% rename from test/plots/grouped-rects.js rename to test/plots/grouped-rects.ts index ae85a68a73..58e432e34d 100644 --- a/test/plots/grouped-rects.js +++ b/test/plots/grouped-rects.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function groupedRects() { return Plot.plot({ marks: [Plot.rectY({length: 10}, Plot.groupX({y: "count"}, {x: (d, i) => "ABCDEFGHIJ"[i]}))] }); diff --git a/test/plots/hadcrut-warming-stripes.js b/test/plots/hadcrut-warming-stripes.ts similarity index 88% rename from test/plots/hadcrut-warming-stripes.js rename to test/plots/hadcrut-warming-stripes.ts index e1b369d8c4..d12fd455e3 100644 --- a/test/plots/hadcrut-warming-stripes.js +++ b/test/plots/hadcrut-warming-stripes.ts @@ -1,14 +1,14 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { +export async function hadcrutWarmingStripes() { const hadcrut = (await d3.text("data/hadcrut-annual.txt")) .trim() // trim trailing newline .split(/\n/g) // split into lines .map((line) => line.split(/\s+/g)) // split each line into fields .map(([year, anomaly]) => ({ // extract the year and median anomaly - year: new Date(Date.UTC(year, 0, 1)), + year: new Date(Date.UTC(+year, 0, 1)), anomaly: +anomaly })); return Plot.plot({ diff --git a/test/plots/heatmap.js b/test/plots/heatmap.ts similarity index 100% rename from test/plots/heatmap.js rename to test/plots/heatmap.ts diff --git a/test/plots/hexbin-oranges.js b/test/plots/hexbin-oranges.ts similarity index 73% rename from test/plots/hexbin-oranges.js rename to test/plots/hexbin-oranges.ts index e0d5743938..693c6d2af9 100644 --- a/test/plots/hexbin-oranges.js +++ b/test/plots/hexbin-oranges.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); +export async function hexbinOranges() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.plot({ inset: 10, color: { diff --git a/test/plots/hexbin-r.js b/test/plots/hexbin-r.ts similarity index 82% rename from test/plots/hexbin-r.js rename to test/plots/hexbin-r.ts index 6414c673dc..5f56775a36 100644 --- a/test/plots/hexbin-r.js +++ b/test/plots/hexbin-r.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); +export async function hexbinR() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); const xy = {fx: "sex", x: "culmen_depth_mm", y: "culmen_length_mm"}; return Plot.plot({ width: 960, diff --git a/test/plots/hexbin-symbol.js b/test/plots/hexbin-symbol.ts similarity index 71% rename from test/plots/hexbin-symbol.js rename to test/plots/hexbin-symbol.ts index 761ff2e384..637d6af56e 100644 --- a/test/plots/hexbin-symbol.js +++ b/test/plots/hexbin-symbol.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); +export async function hexbinSymbol() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.plot({ grid: true, symbol: { diff --git a/test/plots/hexbin-text.js b/test/plots/hexbin-text.ts similarity index 81% rename from test/plots/hexbin-text.js rename to test/plots/hexbin-text.ts index 18e212bc36..5768178c9a 100644 --- a/test/plots/hexbin-text.js +++ b/test/plots/hexbin-text.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); +export async function hexbinText() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); const xy = {fx: "sex", x: "culmen_depth_mm", y: "culmen_length_mm"}; return Plot.plot({ width: 960, diff --git a/test/plots/hexbin-z-null.js b/test/plots/hexbin-z-null.ts similarity index 78% rename from test/plots/hexbin-z-null.js rename to test/plots/hexbin-z-null.ts index df69c8e4ce..87be46bb2e 100644 --- a/test/plots/hexbin-z-null.js +++ b/test/plots/hexbin-z-null.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); +export async function hexbinZNull() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.plot({ inset: 10, marks: [ diff --git a/test/plots/hexbin-z.js b/test/plots/hexbin-z.ts similarity index 75% rename from test/plots/hexbin-z.js rename to test/plots/hexbin-z.ts index 023cf56892..9a8f7653d3 100644 --- a/test/plots/hexbin-z.js +++ b/test/plots/hexbin-z.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); +export async function hexbinZ() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.plot({ inset: 10, color: { diff --git a/test/plots/hexbin.js b/test/plots/hexbin.ts similarity index 71% rename from test/plots/hexbin.js rename to test/plots/hexbin.ts index 5cde26650a..760e260a49 100644 --- a/test/plots/hexbin.js +++ b/test/plots/hexbin.ts @@ -1,8 +1,8 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { - const penguins = await d3.csv("data/penguins.csv", d3.autoType); +export async function hexbin() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.plot({ marks: [ Plot.hexgrid(), diff --git a/test/plots/high-cardinality-ordinal.js b/test/plots/high-cardinality-ordinal.ts similarity index 77% rename from test/plots/high-cardinality-ordinal.js rename to test/plots/high-cardinality-ordinal.ts index 2feb45cb6c..806169dbb8 100644 --- a/test/plots/high-cardinality-ordinal.js +++ b/test/plots/high-cardinality-ordinal.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function highCardinalityOrdinal() { return Plot.plot({ color: { type: "ordinal" diff --git a/test/plots/href-fill.js b/test/plots/href-fill.ts similarity index 86% rename from test/plots/href-fill.js rename to test/plots/href-fill.ts index cf41200adc..61c5399a48 100644 --- a/test/plots/href-fill.js +++ b/test/plots/href-fill.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; -export default async function () { +export async function hrefFill() { return Plot.text( {length: 1}, { diff --git a/test/plots/ibm-trading.js b/test/plots/ibm-trading.ts similarity index 86% rename from test/plots/ibm-trading.js rename to test/plots/ibm-trading.ts index e01b22e3f6..caf113fdac 100644 --- a/test/plots/ibm-trading.js +++ b/test/plots/ibm-trading.ts @@ -5,8 +5,8 @@ import * as d3 from "d3"; // custom transform to parse the dates from their string representation. This is // not a recommended pattern: you should instead parse strings to dates when // loading the data, say by applying d3.autoType or calling array.map. -export default async function () { - const ibm = await d3.csv("data/ibm.csv").then((data) => data.slice(-20)); +export async function ibmTrading() { + const ibm = await d3.csv("data/ibm.csv").then((data) => data.slice(-20)); return Plot.plot({ marginBottom: 65, x: { diff --git a/test/plots/identity-scale.js b/test/plots/identity-scale.ts similarity index 92% rename from test/plots/identity-scale.js rename to test/plots/identity-scale.ts index 17fafecf97..481e79a506 100644 --- a/test/plots/identity-scale.js +++ b/test/plots/identity-scale.ts @@ -1,7 +1,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -export default async function () { +export async function identityScale() { const random = d3.randomLcg(42); return Plot.plot({ x: { diff --git a/test/plots/image-rendering.js b/test/plots/image-rendering.ts similarity index 100% rename from test/plots/image-rendering.js rename to test/plots/image-rendering.ts diff --git a/test/plots/index.html b/test/plots/index.html index 87413d399a..43a601d854 100644 --- a/test/plots/index.html +++ b/test/plots/index.html @@ -20,7 +20,7 @@ }