From 376e272c08fa38b55de01ec5c3ca88a7948a589d Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 22 Aug 2022 11:42:21 -0300 Subject: [PATCH 01/24] add offset --- .gitignore | 3 +++ packages/components/src/lib/distributionSpecBuilder.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5b48f91c..c05ec659 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ yarn-error.log **/.sync.ffs_db .direnv .log + +.vscode +todo.txt \ No newline at end of file diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 2b3ac952..aec730f3 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -239,6 +239,7 @@ export function buildVegaSpec( x: { scale: "xscale", field: "x", + offset: 0.5, }, y: { scale: "yscale", From 90ad0fdbdc6d3f3065f193eb57f7cc08126ecd5c Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 22 Aug 2022 12:00:28 -0300 Subject: [PATCH 02/24] announcer for hover values --- .../src/components/DistributionChart.tsx | 2 ++ .../src/lib/distributionSpecBuilder.ts | 34 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 79536e12..1bfa64fe 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -47,6 +47,8 @@ export function makePlot(record: { export const DistributionChart: React.FC = (props) => { const { plot, height, showSummary, width, logX, actions = false } = props; + // const [xAxis, setXAxis] = React.useState<"dateAndTime" | "numbers">("dateAndTime") + const [sized] = useSize((size) => { let shapes = flattenResult( plot.distributions.map((x) => diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index aec730f3..143c10d6 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -91,7 +91,21 @@ export function buildVegaSpec( name: "domain", }, ], - signals: [], + signals: [ + { + "name": "hover", + "value": null, + "on": [ + {"events": "symbol:mouseover", "update": "datum"}, + {"events": "symbol:mouseout", "update": "null"} + ] + }, + { + "name": "announcer", + "value": "", + "update": "hover ? 'Probability: ' + hover.y + ', Value: ' + hover.x : ''" + } + ], scales: [ xScale, expY ? expYScale : linearYScale, @@ -239,7 +253,7 @@ export function buildVegaSpec( x: { scale: "xscale", field: "x", - offset: 0.5, + offset: 0.5, // if this is not included, the circles are slightly left of center. }, y: { scale: "yscale", @@ -256,6 +270,22 @@ export function buildVegaSpec( }, ], }, + { + "type": "text", + "interactive": false, + "encode": { + "enter": { + "x": {"signal": "width", "offset": 1}, + "y": {"value": 0}, + "fill": {"value": "black"}, + "fontSize": {"value": 20}, + "align": {"value": "right"} + }, + "update": { + "text": {"signal": "announcer"} + } + } + } ], legends: [ { From d0468f9ea3f744b31b854ebf1eeab7bfa1f371d2 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Tue, 23 Aug 2022 11:05:02 -0300 Subject: [PATCH 03/24] sample points below --- .../src/lib/distributionSpecBuilder.ts | 76 +++++++++++++++---- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 143c10d6..ec3c8462 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -84,27 +84,34 @@ export function buildVegaSpec( height: 100, padding: 5, data: [ - { - name: "data", - }, - { - name: "domain", - }, + {name: "data",}, + { name: "domain",}, ], signals: [ { "name": "hover", "value": null, "on": [ - {"events": "symbol:mouseover", "update": "datum"}, - {"events": "symbol:mouseout", "update": "null"} + {"events": "mouseover", "update": "datum"}, + {"events": "mouseout", "update": "null"} ] }, { - "name": "announcer", - "value": "", - "update": "hover ? 'Probability: ' + hover.y + ', Value: ' + hover.x : ''" - } + "name": "position", + "value": "[0, 0]", + "on": [ + { "events": "mousemove", "update": "hover ? xy() : null"}, + { "events": "mouseout", "update": "null"}, + ] + }, + + // { + // "name": "announcer", + // "value": "", + // "update": "hover ? 'Value: ' + hover.x : ''" + // }, + + ], scales: [ xScale, @@ -134,6 +141,7 @@ export function buildVegaSpec( }, ], marks: [ + { name: "all_distributions", type: "group", @@ -145,6 +153,7 @@ export function buildVegaSpec( }, }, marks: [ + { name: "continuous_distribution", type: "group", @@ -203,6 +212,22 @@ export function buildVegaSpec( }, }, marks: [ + { + "name": "samples", + "type": "rect", + "from": {"data": "discrete_facet"}, + "encode": { + "enter": { + "x": {"scale": "xscale", "field":"x"}, + "width": {"value": 1}, + + "y": {"value": 25, "offset": {"signal": "height"}}, + "height": {"value": 5}, + "fill": {"value": "steelblue"}, + "fillOpacity": {"value": 0.8} + } + } + }, { type: "rect", from: { @@ -241,6 +266,7 @@ export function buildVegaSpec( }, encode: { enter: { + shape: { value: "circle", }, @@ -282,10 +308,32 @@ export function buildVegaSpec( "align": {"value": "right"} }, "update": { - "text": {"signal": "announcer"} + "text": {"signal": "position ? position[0]/width : ''"} } } - } + }, + // { + // "type": "rule", + // "encode": { + // "enter": { + // x: {value: 0}, + // "y": {"scale": "yscale", "value":0}, + + // y2: { + // signal: "height", + // offset: 2 + // }, + // "strokeDash": {"value": [5, 5]}, + // }, + + // "update": { + // "x": {"signal": "position[0] < 0 ? null : position[0] > width ? null : position[0]"}, + + // "opacity": {"signal": "position ? 1 : 0"} + // }, + // } + + // } ], legends: [ { From 10db239920d2af1f81662e85a2f78fff4cd8b042 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Tue, 23 Aug 2022 12:04:35 -0300 Subject: [PATCH 04/24] hover line --- .../src/lib/distributionSpecBuilder.ts | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index ec3c8462..5fdc0803 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -16,7 +16,7 @@ export type DistributionChartSpecOptions = { format?: string; }; -export let linearXScale: LinearScale = { +export const linearXScale: LinearScale = { name: "xscale", clamp: true, type: "linear", @@ -25,7 +25,7 @@ export let linearXScale: LinearScale = { nice: false, domain: { data: "domain", field: "x" }, }; -export let linearYScale: LinearScale = { +export const linearYScale: LinearScale = { name: "yscale", type: "linear", range: "height", @@ -33,7 +33,7 @@ export let linearYScale: LinearScale = { domain: { data: "domain", field: "y" }, }; -export let logXScale: LogScale = { +export const logXScale: LogScale = { name: "xscale", type: "log", range: "width", @@ -44,7 +44,7 @@ export let logXScale: LogScale = { domain: { data: "domain", field: "x" }, }; -export let expYScale: PowScale = { +export const expYScale: PowScale = { name: "yscale", type: "pow", exponent: 0.1, @@ -77,7 +77,7 @@ export function buildVegaSpec( xScale = { ...xScale, domainMax: maxX }; } - let spec: VisualizationSpec = { + const spec: VisualizationSpec = { $schema: "https://vega.github.io/schema/vega/v5.json", description: "Squiggle plot chart", width: 500, @@ -100,16 +100,20 @@ export function buildVegaSpec( "name": "position", "value": "[0, 0]", "on": [ - { "events": "mousemove", "update": "hover ? xy() : null"}, + { "events": "mousemove", "update": "xy() "}, { "events": "mouseout", "update": "null"}, ] }, - - // { - // "name": "announcer", - // "value": "", - // "update": "hover ? 'Value: ' + hover.x : ''" - // }, + { + "name": "position_scaled", + "value": 0, + "update": "position ? invert('xscale', position[0]) : null" + }, + { + "name": "announcer", + "value": "", + "update": "hover ? 'Value: ' + hover.x : ''" + }, ], @@ -302,38 +306,37 @@ export function buildVegaSpec( "encode": { "enter": { "x": {"signal": "width", "offset": 1}, - "y": {"value": 0}, "fill": {"value": "black"}, "fontSize": {"value": 20}, "align": {"value": "right"} }, "update": { - "text": {"signal": "position ? position[0]/width : ''"} + "text": {"signal": "position_scaled ? position_scaled : ''", } } } }, - // { - // "type": "rule", - // "encode": { - // "enter": { - // x: {value: 0}, - // "y": {"scale": "yscale", "value":0}, + { + "type": "rule", + "encode": { + "enter": { + x: {value: 0}, + "y": {"scale": "yscale", "value":0}, - // y2: { - // signal: "height", - // offset: 2 - // }, - // "strokeDash": {"value": [5, 5]}, - // }, + y2: { + signal: "height", + offset: 2 + }, + "strokeDash": {"value": [5, 5]}, + }, - // "update": { - // "x": {"signal": "position[0] < 0 ? null : position[0] > width ? null : position[0]"}, + "update": { + "x": {"signal": "position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null"}, - // "opacity": {"signal": "position ? 1 : 0"} - // }, - // } + "opacity": {"signal": "position ? 1 : 0"} + }, + } - // } + } ], legends: [ { From 865b01f48dd1b2c9b9943cda8555f7e67f91e3d6 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 24 Aug 2022 13:57:35 -0300 Subject: [PATCH 05/24] significant digits on hover value --- packages/components/src/lib/distributionSpecBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 5fdc0803..228159ab 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -311,7 +311,7 @@ export function buildVegaSpec( "align": {"value": "right"} }, "update": { - "text": {"signal": "position_scaled ? position_scaled : ''", } + "text": {"signal": "position_scaled ? format(position_scaled, ',.4r') : ''", } } } }, From f8e5396d579efcaed62d41945fd5332c129f1cc1 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 24 Aug 2022 15:26:47 -0300 Subject: [PATCH 06/24] use domain to feed sample for bottom band --- .../src/components/DistributionChart.tsx | 5 +- .../src/lib/distributionSpecBuilder.ts | 47 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 1bfa64fe..52172d22 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -48,9 +48,8 @@ export function makePlot(record: { export const DistributionChart: React.FC = (props) => { const { plot, height, showSummary, width, logX, actions = false } = props; // const [xAxis, setXAxis] = React.useState<"dateAndTime" | "numbers">("dateAndTime") - const [sized] = useSize((size) => { - let shapes = flattenResult( + const shapes = flattenResult( plot.distributions.map((x) => resultMap(x.distribution.pointSet(), (shape) => ({ name: x.name, @@ -60,6 +59,7 @@ export const DistributionChart: React.FC = (props) => { })) ) ); + if (shapes.tag === "Error") { return ( @@ -81,6 +81,7 @@ export const DistributionChart: React.FC = (props) => { shape.discrete.concat(shape.continuous) ); + console.log({domain, data: shapes.value}) return (
{logX && shapes.value.some(hasMassBelowZero) ? ( diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 228159ab..65820cf2 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -145,7 +145,35 @@ export function buildVegaSpec( }, ], marks: [ - + { + name: "sample_distributions", + "type": "group", + from: { + facet: { + name: "distribution_facet", + data: "domain", + groupby: ["name"], + }, + }, + marks: [ + { + "name": "samples", + "type": "rect", + "from": {"data": "distribution_facet"}, + "encode": { + "enter": { + "x": {"scale": "xscale", "field":"x"}, + "width": {"value": 0.5}, + + "y": {"value": 25, "offset": {"signal": "height"}}, + "height": {"value": 5}, + "fill": {"value": "steelblue"}, + "fillOpacity": {"value": 0.8} + } + } + }, + ] + }, { name: "all_distributions", type: "group", @@ -216,22 +244,7 @@ export function buildVegaSpec( }, }, marks: [ - { - "name": "samples", - "type": "rect", - "from": {"data": "discrete_facet"}, - "encode": { - "enter": { - "x": {"scale": "xscale", "field":"x"}, - "width": {"value": 1}, - - "y": {"value": 25, "offset": {"signal": "height"}}, - "height": {"value": 5}, - "fill": {"value": "steelblue"}, - "fillOpacity": {"value": 0.8} - } - } - }, + { type: "rect", from: { From a99ee96f5c266404bf392e9cd9434fb58dfac41b Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 24 Aug 2022 20:56:22 -0300 Subject: [PATCH 07/24] announce x-axis date properly --- .../src/components/DistributionChart.tsx | 126 +++++- .../src/lib/dateDistributionSpecBuilder.ts | 361 ++++++++++++++++++ .../src/lib/distributionSpecBuilder.ts | 143 ++++--- 3 files changed, 545 insertions(+), 85 deletions(-) create mode 100644 packages/components/src/lib/dateDistributionSpecBuilder.ts diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 52172d22..6b4bdbd6 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -15,6 +15,7 @@ import { buildVegaSpec, DistributionChartSpecOptions, } from "../lib/distributionSpecBuilder"; +import { buildDateVegaSpec } from "../lib/dateDistributionSpecBuilder"; import { NumberShower } from "./NumberShower"; import { Plot, parsePlot } from "../lib/plotParser"; import { flattenResult } from "../lib/utility"; @@ -30,6 +31,7 @@ export type DistributionChartProps = { plot: Plot; width?: number; height: number; + xAxis?: "number" | "dateTime"; } & DistributionPlottingSettings; export function defaultPlot(distribution: Distribution): Plot { @@ -45,8 +47,102 @@ export function makePlot(record: { } } +const DateDistributionChart: React.FC = (props) => { + const { + plot, + height, + showSummary, + width, + logX, + actions = false, + xAxis = "dateTime", + } = props; + // const [xAxis, setXAxis] = React.useState<"dateAndTime" | "numbers">("dateAndTime") + const [sized] = useSize((size) => { + const shapes = flattenResult( + plot.distributions.map((x) => + resultMap(x.distribution.pointSet(), (shape) => ({ + name: x.name, + // color: x.color, // not supported yet + continuous: shape.continuous, + discrete: shape.discrete, + })) + ) + ); + + if (shapes.tag === "Error") { + return ( + + {distributionErrorToString(shapes.value)} + + ); + } + + const spec = buildDateVegaSpec(props); + + let widthProp = width ? width : size.width; + if (widthProp < 20) { + console.warn( + `Width of Distribution is set to ${widthProp}, which is too small` + ); + widthProp = 20; + } + const domain = shapes.value.flatMap((shape) => + shape.discrete.concat(shape.continuous) + ); + + const dateData = { + name: "default", + continuous: [], + discrete: [ + { dateTime: new Date().getTime() - 1000000, y: 0.3 }, + { dateTime: new Date().getTime(), y: 0.5 }, + { dateTime: new Date().getTime() + 1000000, y: 0.7 }, + ], + }; + + const dateDomain = [ + { dateTime: new Date().getTime() - 1000000, y: 0.2 }, + { dateTime: new Date().getTime(), y: 0.5 }, + { dateTime: new Date().getTime() + 1000000, y: 0.7 }, + ]; + + return ( +
+ {logX && shapes.value.some(hasMassBelowZero) ? ( + + Cannot graph distribution with negative values on logarithmic scale. + + ) : ( + + )} +
+ {showSummary && plot.distributions.length === 1 && ( + + )} +
+
+ ); + }); + return sized; +}; + export const DistributionChart: React.FC = (props) => { - const { plot, height, showSummary, width, logX, actions = false } = props; + const { + plot, + height, + showSummary, + width, + logX, + actions = false, + xAxis = "number", + } = props; // const [xAxis, setXAxis] = React.useState<"dateAndTime" | "numbers">("dateAndTime") const [sized] = useSize((size) => { const shapes = flattenResult( @@ -81,7 +177,6 @@ export const DistributionChart: React.FC = (props) => { shape.discrete.concat(shape.continuous) ); - console.log({domain, data: shapes.value}) return (
{logX && shapes.value.some(hasMassBelowZero) ? ( @@ -89,13 +184,26 @@ export const DistributionChart: React.FC = (props) => { Cannot graph distribution with negative values on logarithmic scale. ) : ( - + <> + +
+ +
+ )}
{showSummary && plot.distributions.length === 1 && ( diff --git a/packages/components/src/lib/dateDistributionSpecBuilder.ts b/packages/components/src/lib/dateDistributionSpecBuilder.ts new file mode 100644 index 00000000..140dd954 --- /dev/null +++ b/packages/components/src/lib/dateDistributionSpecBuilder.ts @@ -0,0 +1,361 @@ +import { VisualizationSpec } from "react-vega"; +import type { LogScale, LinearScale, PowScale, TimeScale } from "vega"; + +export type DistributionChartSpecOptions = { + /** Set the x scale to be logarithmic by deault */ + logX: boolean; + /** Set the y scale to be exponential by deault */ + expY: boolean; + /** The minimum x coordinate shown on the chart */ + minX?: number; + /** The maximum x coordinate shown on the chart */ + maxX?: number; + /** The title of the chart */ + title?: string; + /** The formatting of the ticks */ + format?: string; +}; + +export const timeXScale: TimeScale = { + name: "xscale", + clamp: true, + type: "time", + range: "width", + nice: false, + domain: { data: "domain", field: "dateTime" }, +}; +export const timeYScale: TimeScale = { + name: "yscale", + type: "time", + range: "height", + domain: { data: "domain", field: "y" }, +}; + +export const defaultTickFormat = "%b %d, %Y %H:%M"; + +export function buildDateVegaSpec( + specOptions: DistributionChartSpecOptions +): VisualizationSpec { + const { + format = defaultTickFormat, + title, + minX, + maxX, + logX, + expY, + } = specOptions; + + let xScale = timeXScale; + if (minX !== undefined && Number.isFinite(minX)) { + xScale = { ...xScale, domainMin: minX }; + } + + if (maxX !== undefined && Number.isFinite(maxX)) { + xScale = { ...xScale, domainMax: maxX }; + } + + const spec: VisualizationSpec = { + $schema: "https://vega.github.io/schema/vega/v5.json", + description: "Squiggle plot chart", + width: 500, + height: 100, + padding: 5, + data: [{ name: "data" }, { name: "domain" }], + signals: [ + { + name: "hover", + value: null, + on: [ + { events: "mouseover", update: "datum" }, + { events: "mouseout", update: "null" }, + ], + }, + { + name: "position", + value: "[0, 0]", + on: [ + { events: "mousemove", update: "xy() " }, + { events: "mouseout", update: "null" }, + ], + }, + { + name: "position_scaled", + value: null, + update: "isArray(position) ? invert('xscale', position[0]) : ''", + }, + ], + scales: [ + xScale, + timeYScale, + { + name: "color", + type: "ordinal", + domain: { + data: "data", + field: "name", + }, + range: { scheme: "blues" }, + }, + ], + axes: [ + { + orient: "bottom", + scale: "xscale", + labelColor: "#727d93", + tickColor: "#fff", + tickOpacity: 0.0, + domainColor: "#fff", + domainOpacity: 0.0, + format: format, + tickCount: 3, + labelOverlap: "greedy", + }, + ], + marks: [ + { + name: "sample_distributions", + type: "group", + from: { + facet: { + name: "distribution_facet", + data: "domain", + groupby: ["name"], + }, + }, + marks: [ + { + name: "samples", + type: "rect", + from: { data: "distribution_facet" }, + encode: { + enter: { + x: { scale: "xscale", field: "dateTime" }, + width: { value: 0.5 }, + + y: { value: 25, offset: { signal: "height" } }, + height: { value: 5 }, + fill: { value: "steelblue" }, + fillOpacity: { value: 0.8 }, + }, + }, + }, + ], + }, + { + name: "all_distributions", + type: "group", + from: { + facet: { + name: "distribution_facet", + data: "data", + groupby: ["name"], + }, + }, + marks: [ + { + name: "continuous_distribution", + type: "group", + from: { + facet: { + name: "continuous_facet", + data: "distribution_facet", + field: "continuous", + }, + }, + encode: { + update: {}, + }, + marks: [ + { + name: "continuous_area", + type: "area", + from: { + data: "continuous_facet", + }, + encode: { + update: { + // interpolate: { value: "linear" }, + x: { + scale: "xscale", + field: "dateTime", + }, + y: { + scale: "yscale", + field: "y", + }, + fill: { + scale: "color", + field: { parent: "name" }, + }, + y2: { + scale: "yscale", + value: 0, + }, + fillOpacity: { + value: 1, + }, + }, + }, + }, + ], + }, + { + name: "discrete_distribution", + type: "group", + from: { + facet: { + name: "discrete_facet", + data: "distribution_facet", + field: "discrete", + }, + }, + marks: [ + { + type: "rect", + from: { + data: "discrete_facet", + }, + encode: { + enter: { + width: { + value: 1, + }, + }, + update: { + x: { + scale: "xscale", + field: "dateTime", + }, + y: { + scale: "yscale", + field: "y", + }, + y2: { + scale: "yscale", + value: 0, + }, + fill: { + scale: "color", + field: { parent: "name" }, + }, + }, + }, + }, + { + type: "symbol", + from: { + data: "discrete_facet", + }, + encode: { + enter: { + shape: { + value: "circle", + }, + size: [{ value: 100 }], + tooltip: { + signal: + "{ probability: datum.y, value: datetime(datum.dateTime) }", + }, + }, + update: { + x: { + scale: "xscale", + field: "dateTime", + offset: 0.5, // if this is not included, the circles are slightly left of center. + }, + y: { + scale: "yscale", + field: "y", + }, + fill: { + scale: "color", + field: { parent: "name" }, + }, + }, + }, + }, + ], + }, + ], + }, + { + type: "text", + interactive: false, + encode: { + enter: { + text: { + signal: "", + }, + x: { signal: "width", offset: 1 }, + fill: { value: "black" }, + fontSize: { value: 20 }, + align: { value: "right" }, + }, + update: { + text: { + signal: + "position_scaled ? utcyear(position_scaled) + '-' + utcmonth(position_scaled) + '-' + utcdate(position_scaled) + 'T' + utchours(position_scaled)+':' +utcminutes(position_scaled) : ''", + }, + }, + }, + }, + { + type: "rule", + encode: { + enter: { + x: { value: 0 }, + y: { scale: "yscale", value: 0 }, + + y2: { + signal: "height", + offset: 2, + }, + strokeDash: { value: [5, 5] }, + }, + + update: { + x: { + signal: + "position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null", + }, + + opacity: { signal: "position ? 1 : 0" }, + }, + }, + }, + ], + legends: [ + { + fill: "color", + orient: "top", + labelFontSize: 12, + encode: { + symbols: { + update: { + fill: [ + { test: "length(domain('color')) == 1", value: "transparent" }, + { scale: "color", field: "value" }, + ], + }, + }, + labels: { + interactive: true, + update: { + fill: [ + { test: "length(domain('color')) == 1", value: "transparent" }, + { value: "black" }, + ], + }, + }, + }, + }, + ], + ...(title && { + title: { + text: title, + }, + }), + }; + + return spec; +} diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 65820cf2..03673882 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -83,39 +83,29 @@ export function buildVegaSpec( width: 500, height: 100, padding: 5, - data: [ - {name: "data",}, - { name: "domain",}, - ], + data: [{ name: "data" }, { name: "domain" }], signals: [ { - "name": "hover", - "value": null, - "on": [ - {"events": "mouseover", "update": "datum"}, - {"events": "mouseout", "update": "null"} - ] + name: "hover", + value: null, + on: [ + { events: "mouseover", update: "datum" }, + { events: "mouseout", update: "null" }, + ], }, { - "name": "position", - "value": "[0, 0]", - "on": [ - { "events": "mousemove", "update": "xy() "}, - { "events": "mouseout", "update": "null"}, - ] + name: "position", + value: "[0, 0]", + on: [ + { events: "mousemove", update: "xy() " }, + { events: "mouseout", update: "null" }, + ], }, { - "name": "position_scaled", - "value": 0, - "update": "position ? invert('xscale', position[0]) : null" + name: "position_scaled", + value: 0, + update: "position ? invert('xscale', position[0]) : null", }, - { - "name": "announcer", - "value": "", - "update": "hover ? 'Value: ' + hover.x : ''" - }, - - ], scales: [ xScale, @@ -147,32 +137,32 @@ export function buildVegaSpec( marks: [ { name: "sample_distributions", - "type": "group", + type: "group", from: { facet: { name: "distribution_facet", data: "domain", groupby: ["name"], }, - }, + }, marks: [ { - "name": "samples", - "type": "rect", - "from": {"data": "distribution_facet"}, - "encode": { - "enter": { - "x": {"scale": "xscale", "field":"x"}, - "width": {"value": 0.5}, - - "y": {"value": 25, "offset": {"signal": "height"}}, - "height": {"value": 5}, - "fill": {"value": "steelblue"}, - "fillOpacity": {"value": 0.8} - } - } + name: "samples", + type: "rect", + from: { data: "distribution_facet" }, + encode: { + enter: { + x: { scale: "xscale", field: "x" }, + width: { value: 0.5 }, + + y: { value: 25, offset: { signal: "height" } }, + height: { value: 5 }, + fill: { value: "steelblue" }, + fillOpacity: { value: 0.8 }, + }, + }, }, - ] + ], }, { name: "all_distributions", @@ -185,7 +175,6 @@ export function buildVegaSpec( }, }, marks: [ - { name: "continuous_distribution", type: "group", @@ -244,7 +233,6 @@ export function buildVegaSpec( }, }, marks: [ - { type: "rect", from: { @@ -283,7 +271,6 @@ export function buildVegaSpec( }, encode: { enter: { - shape: { value: "circle", }, @@ -296,7 +283,7 @@ export function buildVegaSpec( x: { scale: "xscale", field: "x", - offset: 0.5, // if this is not included, the circles are slightly left of center. + offset: 0.5, // if this is not included, the circles are slightly left of center. }, y: { scale: "yscale", @@ -314,42 +301,46 @@ export function buildVegaSpec( ], }, { - "type": "text", - "interactive": false, - "encode": { - "enter": { - "x": {"signal": "width", "offset": 1}, - "fill": {"value": "black"}, - "fontSize": {"value": 20}, - "align": {"value": "right"} + type: "text", + interactive: false, + encode: { + enter: { + x: { signal: "width", offset: 1 }, + fill: { value: "black" }, + fontSize: { value: 20 }, + align: { value: "right" }, }, - "update": { - "text": {"signal": "position_scaled ? format(position_scaled, ',.4r') : ''", } - } - } + update: { + text: { + signal: "position_scaled ? format(position_scaled, ',.4r') : ''", + }, + }, + }, }, { - "type": "rule", - "encode": { - "enter": { - x: {value: 0}, - "y": {"scale": "yscale", "value":0}, + type: "rule", + encode: { + enter: { + x: { value: 0 }, + y: { scale: "yscale", value: 0 }, y2: { - signal: "height", - offset: 2 + signal: "height", + offset: 2, }, - "strokeDash": {"value": [5, 5]}, - }, - - "update": { - "x": {"signal": "position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null"}, - - "opacity": {"signal": "position ? 1 : 0"} + strokeDash: { value: [5, 5] }, }, - } - - } + + update: { + x: { + signal: + "position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null", + }, + + opacity: { signal: "position ? 1 : 0" }, + }, + }, + }, ], legends: [ { From a9e43ee7cfd7cfffff3008c7b243968660095e22 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 24 Aug 2022 21:13:50 -0300 Subject: [PATCH 08/24] bring back the stems --- .../src/components/DistributionChart.tsx | 2 +- .../src/lib/dateDistributionSpecBuilder.ts | 29 +++++++++++++++++-- .../src/lib/distributionSpecBuilder.ts | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 6b4bdbd6..13bac83b 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -95,7 +95,7 @@ const DateDistributionChart: React.FC = (props) => { name: "default", continuous: [], discrete: [ - { dateTime: new Date().getTime() - 1000000, y: 0.3 }, + { dateTime: new Date().getTime() - 1000000, y: 0.2 }, { dateTime: new Date().getTime(), y: 0.5 }, { dateTime: new Date().getTime() + 1000000, y: 0.7 }, ], diff --git a/packages/components/src/lib/dateDistributionSpecBuilder.ts b/packages/components/src/lib/dateDistributionSpecBuilder.ts index 140dd954..5e64a18b 100644 --- a/packages/components/src/lib/dateDistributionSpecBuilder.ts +++ b/packages/components/src/lib/dateDistributionSpecBuilder.ts @@ -24,10 +24,33 @@ export const timeXScale: TimeScale = { nice: false, domain: { data: "domain", field: "dateTime" }, }; -export const timeYScale: TimeScale = { + +export const linearYScale: LinearScale = { name: "yscale", - type: "time", + type: "linear", range: "height", + zero: true, + domain: { data: "domain", field: "y" }, +}; + +export const logXScale: LogScale = { + name: "xscale", + type: "log", + range: "width", + zero: false, + base: 10, + nice: false, + clamp: true, + domain: { data: "domain", field: "x" }, +}; + +export const expYScale: PowScale = { + name: "yscale", + type: "pow", + exponent: 0.1, + range: "height", + zero: true, + nice: false, domain: { data: "domain", field: "y" }, }; @@ -86,7 +109,7 @@ export function buildDateVegaSpec( ], scales: [ xScale, - timeYScale, + expY ? expYScale : linearYScale, { name: "color", type: "ordinal", diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 03673882..cbab3039 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -25,6 +25,7 @@ export const linearXScale: LinearScale = { nice: false, domain: { data: "domain", field: "x" }, }; + export const linearYScale: LinearScale = { name: "yscale", type: "linear", From 754fc8531f8afa935ad4aa89e26a2c500ef94c50 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 24 Aug 2022 21:45:46 -0300 Subject: [PATCH 09/24] combined distribution chart component --- .../src/components/DistributionChart.tsx | 141 ++++-------------- 1 file changed, 32 insertions(+), 109 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 13bac83b..783de071 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -47,92 +47,6 @@ export function makePlot(record: { } } -const DateDistributionChart: React.FC = (props) => { - const { - plot, - height, - showSummary, - width, - logX, - actions = false, - xAxis = "dateTime", - } = props; - // const [xAxis, setXAxis] = React.useState<"dateAndTime" | "numbers">("dateAndTime") - const [sized] = useSize((size) => { - const shapes = flattenResult( - plot.distributions.map((x) => - resultMap(x.distribution.pointSet(), (shape) => ({ - name: x.name, - // color: x.color, // not supported yet - continuous: shape.continuous, - discrete: shape.discrete, - })) - ) - ); - - if (shapes.tag === "Error") { - return ( - - {distributionErrorToString(shapes.value)} - - ); - } - - const spec = buildDateVegaSpec(props); - - let widthProp = width ? width : size.width; - if (widthProp < 20) { - console.warn( - `Width of Distribution is set to ${widthProp}, which is too small` - ); - widthProp = 20; - } - const domain = shapes.value.flatMap((shape) => - shape.discrete.concat(shape.continuous) - ); - - const dateData = { - name: "default", - continuous: [], - discrete: [ - { dateTime: new Date().getTime() - 1000000, y: 0.2 }, - { dateTime: new Date().getTime(), y: 0.5 }, - { dateTime: new Date().getTime() + 1000000, y: 0.7 }, - ], - }; - - const dateDomain = [ - { dateTime: new Date().getTime() - 1000000, y: 0.2 }, - { dateTime: new Date().getTime(), y: 0.5 }, - { dateTime: new Date().getTime() + 1000000, y: 0.7 }, - ]; - - return ( -
- {logX && shapes.value.some(hasMassBelowZero) ? ( - - Cannot graph distribution with negative values on logarithmic scale. - - ) : ( - - )} -
- {showSummary && plot.distributions.length === 1 && ( - - )} -
-
- ); - }); - return sized; -}; - export const DistributionChart: React.FC = (props) => { const { plot, @@ -143,7 +57,6 @@ export const DistributionChart: React.FC = (props) => { actions = false, xAxis = "number", } = props; - // const [xAxis, setXAxis] = React.useState<"dateAndTime" | "numbers">("dateAndTime") const [sized] = useSize((size) => { const shapes = flattenResult( plot.distributions.map((x) => @@ -164,7 +77,8 @@ export const DistributionChart: React.FC = (props) => { ); } - const spec = buildVegaSpec(props); + const spec = + xAxis === "dateTime" ? buildDateVegaSpec(props) : buildVegaSpec(props); let widthProp = width ? width : size.width; if (widthProp < 20) { @@ -173,10 +87,32 @@ export const DistributionChart: React.FC = (props) => { ); widthProp = 20; } - const domain = shapes.value.flatMap((shape) => + const predomain = shapes.value.flatMap((shape) => shape.discrete.concat(shape.continuous) ); + const domain = + xAxis === "dateTime" + ? predomain.map((p) => { + return { dateTime: p.x, y: p.y }; + }) + : predomain; + + const data = + xAxis === "dateTime" + ? shapes.value.map((val) => { + return { + ...val, + continuous: val.continuous.map((p) => { + return { dateTime: p.x, y: p.y }; + }), + discrete: val.discrete.map((p) => { + return { dateTime: p.x, y: p.y }; + }), + }; + }) + : shapes.value; + return (
{logX && shapes.value.some(hasMassBelowZero) ? ( @@ -184,26 +120,13 @@ export const DistributionChart: React.FC = (props) => { Cannot graph distribution with negative values on logarithmic scale. ) : ( - <> - -
- -
- + )}
{showSummary && plot.distributions.length === 1 && ( From e88d93ac9e999baba201ceb5951cd9ba9d142c2f Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 29 Aug 2022 15:28:31 -0300 Subject: [PATCH 10/24] more stringent logic over visibility of rule-line and announcer --- .../components/src/lib/distributionSpecBuilder.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index cbab3039..0bd10c69 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -105,7 +105,9 @@ export function buildVegaSpec( { name: "position_scaled", value: 0, - update: "position ? invert('xscale', position[0]) : null", + update: "position ? position[0] < 0 ? null : position[0] > width ? null : invert('xscale', position[0]) : null", + // "position ? position[0] < 0 ? 0 : position[0] > width ? 0 : 1 : 0", + }, ], scales: [ @@ -303,6 +305,7 @@ export function buildVegaSpec( }, { type: "text", + name: "announcer", interactive: false, encode: { enter: { @@ -338,7 +341,11 @@ export function buildVegaSpec( "position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null", }, - opacity: { signal: "position ? 1 : 0" }, + opacity: { + signal: + "position ? position[0] < 0 ? 0 : position[0] > width ? 0 : 1 : 0", + }, + // opacity: { signal: "position ? 1 : 0" }, }, }, }, From 7c00897833cc50d65991706426f459f491d361f9 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 29 Aug 2022 16:00:51 -0300 Subject: [PATCH 11/24] don't let rule-line intercept hover events --- .../src/lib/distributionSpecBuilder.ts | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 0bd10c69..983866ea 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -138,35 +138,7 @@ export function buildVegaSpec( }, ], marks: [ - { - name: "sample_distributions", - type: "group", - from: { - facet: { - name: "distribution_facet", - data: "domain", - groupby: ["name"], - }, - }, - marks: [ - { - name: "samples", - type: "rect", - from: { data: "distribution_facet" }, - encode: { - enter: { - x: { scale: "xscale", field: "x" }, - width: { value: 0.5 }, - y: { value: 25, offset: { signal: "height" } }, - height: { value: 5 }, - fill: { value: "steelblue" }, - fillOpacity: { value: 0.8 }, - }, - }, - }, - ], - }, { name: "all_distributions", type: "group", @@ -323,6 +295,7 @@ export function buildVegaSpec( }, { type: "rule", + interactive: false, encode: { enter: { x: { value: 0 }, From 0292c66c805bd05782979190a2194b555d508c63 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 29 Aug 2022 17:27:13 -0300 Subject: [PATCH 12/24] combined distribution chart component --- .../src/components/DistributionChart.tsx | 5 +- .../src/lib/dateDistributionSpecBuilder.ts | 384 ------------------ .../src/lib/distributionSpecBuilder.ts | 88 +++- 3 files changed, 72 insertions(+), 405 deletions(-) delete mode 100644 packages/components/src/lib/dateDistributionSpecBuilder.ts diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 783de071..caf01e6d 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -15,7 +15,6 @@ import { buildVegaSpec, DistributionChartSpecOptions, } from "../lib/distributionSpecBuilder"; -import { buildDateVegaSpec } from "../lib/dateDistributionSpecBuilder"; import { NumberShower } from "./NumberShower"; import { Plot, parsePlot } from "../lib/plotParser"; import { flattenResult } from "../lib/utility"; @@ -31,6 +30,7 @@ export type DistributionChartProps = { plot: Plot; width?: number; height: number; + sample: boolean; xAxis?: "number" | "dateTime"; } & DistributionPlottingSettings; @@ -77,8 +77,7 @@ export const DistributionChart: React.FC = (props) => { ); } - const spec = - xAxis === "dateTime" ? buildDateVegaSpec(props) : buildVegaSpec(props); + const spec = buildVegaSpec(props); let widthProp = width ? width : size.width; if (widthProp < 20) { diff --git a/packages/components/src/lib/dateDistributionSpecBuilder.ts b/packages/components/src/lib/dateDistributionSpecBuilder.ts deleted file mode 100644 index 5e64a18b..00000000 --- a/packages/components/src/lib/dateDistributionSpecBuilder.ts +++ /dev/null @@ -1,384 +0,0 @@ -import { VisualizationSpec } from "react-vega"; -import type { LogScale, LinearScale, PowScale, TimeScale } from "vega"; - -export type DistributionChartSpecOptions = { - /** Set the x scale to be logarithmic by deault */ - logX: boolean; - /** Set the y scale to be exponential by deault */ - expY: boolean; - /** The minimum x coordinate shown on the chart */ - minX?: number; - /** The maximum x coordinate shown on the chart */ - maxX?: number; - /** The title of the chart */ - title?: string; - /** The formatting of the ticks */ - format?: string; -}; - -export const timeXScale: TimeScale = { - name: "xscale", - clamp: true, - type: "time", - range: "width", - nice: false, - domain: { data: "domain", field: "dateTime" }, -}; - -export const linearYScale: LinearScale = { - name: "yscale", - type: "linear", - range: "height", - zero: true, - domain: { data: "domain", field: "y" }, -}; - -export const logXScale: LogScale = { - name: "xscale", - type: "log", - range: "width", - zero: false, - base: 10, - nice: false, - clamp: true, - domain: { data: "domain", field: "x" }, -}; - -export const expYScale: PowScale = { - name: "yscale", - type: "pow", - exponent: 0.1, - range: "height", - zero: true, - nice: false, - domain: { data: "domain", field: "y" }, -}; - -export const defaultTickFormat = "%b %d, %Y %H:%M"; - -export function buildDateVegaSpec( - specOptions: DistributionChartSpecOptions -): VisualizationSpec { - const { - format = defaultTickFormat, - title, - minX, - maxX, - logX, - expY, - } = specOptions; - - let xScale = timeXScale; - if (minX !== undefined && Number.isFinite(minX)) { - xScale = { ...xScale, domainMin: minX }; - } - - if (maxX !== undefined && Number.isFinite(maxX)) { - xScale = { ...xScale, domainMax: maxX }; - } - - const spec: VisualizationSpec = { - $schema: "https://vega.github.io/schema/vega/v5.json", - description: "Squiggle plot chart", - width: 500, - height: 100, - padding: 5, - data: [{ name: "data" }, { name: "domain" }], - signals: [ - { - name: "hover", - value: null, - on: [ - { events: "mouseover", update: "datum" }, - { events: "mouseout", update: "null" }, - ], - }, - { - name: "position", - value: "[0, 0]", - on: [ - { events: "mousemove", update: "xy() " }, - { events: "mouseout", update: "null" }, - ], - }, - { - name: "position_scaled", - value: null, - update: "isArray(position) ? invert('xscale', position[0]) : ''", - }, - ], - scales: [ - xScale, - expY ? expYScale : linearYScale, - { - name: "color", - type: "ordinal", - domain: { - data: "data", - field: "name", - }, - range: { scheme: "blues" }, - }, - ], - axes: [ - { - orient: "bottom", - scale: "xscale", - labelColor: "#727d93", - tickColor: "#fff", - tickOpacity: 0.0, - domainColor: "#fff", - domainOpacity: 0.0, - format: format, - tickCount: 3, - labelOverlap: "greedy", - }, - ], - marks: [ - { - name: "sample_distributions", - type: "group", - from: { - facet: { - name: "distribution_facet", - data: "domain", - groupby: ["name"], - }, - }, - marks: [ - { - name: "samples", - type: "rect", - from: { data: "distribution_facet" }, - encode: { - enter: { - x: { scale: "xscale", field: "dateTime" }, - width: { value: 0.5 }, - - y: { value: 25, offset: { signal: "height" } }, - height: { value: 5 }, - fill: { value: "steelblue" }, - fillOpacity: { value: 0.8 }, - }, - }, - }, - ], - }, - { - name: "all_distributions", - type: "group", - from: { - facet: { - name: "distribution_facet", - data: "data", - groupby: ["name"], - }, - }, - marks: [ - { - name: "continuous_distribution", - type: "group", - from: { - facet: { - name: "continuous_facet", - data: "distribution_facet", - field: "continuous", - }, - }, - encode: { - update: {}, - }, - marks: [ - { - name: "continuous_area", - type: "area", - from: { - data: "continuous_facet", - }, - encode: { - update: { - // interpolate: { value: "linear" }, - x: { - scale: "xscale", - field: "dateTime", - }, - y: { - scale: "yscale", - field: "y", - }, - fill: { - scale: "color", - field: { parent: "name" }, - }, - y2: { - scale: "yscale", - value: 0, - }, - fillOpacity: { - value: 1, - }, - }, - }, - }, - ], - }, - { - name: "discrete_distribution", - type: "group", - from: { - facet: { - name: "discrete_facet", - data: "distribution_facet", - field: "discrete", - }, - }, - marks: [ - { - type: "rect", - from: { - data: "discrete_facet", - }, - encode: { - enter: { - width: { - value: 1, - }, - }, - update: { - x: { - scale: "xscale", - field: "dateTime", - }, - y: { - scale: "yscale", - field: "y", - }, - y2: { - scale: "yscale", - value: 0, - }, - fill: { - scale: "color", - field: { parent: "name" }, - }, - }, - }, - }, - { - type: "symbol", - from: { - data: "discrete_facet", - }, - encode: { - enter: { - shape: { - value: "circle", - }, - size: [{ value: 100 }], - tooltip: { - signal: - "{ probability: datum.y, value: datetime(datum.dateTime) }", - }, - }, - update: { - x: { - scale: "xscale", - field: "dateTime", - offset: 0.5, // if this is not included, the circles are slightly left of center. - }, - y: { - scale: "yscale", - field: "y", - }, - fill: { - scale: "color", - field: { parent: "name" }, - }, - }, - }, - }, - ], - }, - ], - }, - { - type: "text", - interactive: false, - encode: { - enter: { - text: { - signal: "", - }, - x: { signal: "width", offset: 1 }, - fill: { value: "black" }, - fontSize: { value: 20 }, - align: { value: "right" }, - }, - update: { - text: { - signal: - "position_scaled ? utcyear(position_scaled) + '-' + utcmonth(position_scaled) + '-' + utcdate(position_scaled) + 'T' + utchours(position_scaled)+':' +utcminutes(position_scaled) : ''", - }, - }, - }, - }, - { - type: "rule", - encode: { - enter: { - x: { value: 0 }, - y: { scale: "yscale", value: 0 }, - - y2: { - signal: "height", - offset: 2, - }, - strokeDash: { value: [5, 5] }, - }, - - update: { - x: { - signal: - "position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null", - }, - - opacity: { signal: "position ? 1 : 0" }, - }, - }, - }, - ], - legends: [ - { - fill: "color", - orient: "top", - labelFontSize: 12, - encode: { - symbols: { - update: { - fill: [ - { test: "length(domain('color')) == 1", value: "transparent" }, - { scale: "color", field: "value" }, - ], - }, - }, - labels: { - interactive: true, - update: { - fill: [ - { test: "length(domain('color')) == 1", value: "transparent" }, - { value: "black" }, - ], - }, - }, - }, - }, - ], - ...(title && { - title: { - text: title, - }, - }), - }; - - return spec; -} diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 983866ea..98a06a08 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -1,5 +1,5 @@ import { VisualizationSpec } from "react-vega"; -import type { LogScale, LinearScale, PowScale } from "vega"; +import type { LogScale, LinearScale, PowScale, TimeScale } from "vega"; export type DistributionChartSpecOptions = { /** Set the x scale to be logarithmic by deault */ @@ -14,6 +14,12 @@ export type DistributionChartSpecOptions = { title?: string; /** The formatting of the ticks */ format?: string; + + /** Whether or not to show the band of sample data at the bottom */ + sample?: boolean; + + /** Whether the x-axis should be dates or numbers */ + xAxis?: "number" | "dateTime"; }; export const linearXScale: LinearScale = { @@ -26,14 +32,6 @@ export const linearXScale: LinearScale = { domain: { data: "domain", field: "x" }, }; -export const linearYScale: LinearScale = { - name: "yscale", - type: "linear", - range: "height", - zero: true, - domain: { data: "domain", field: "y" }, -}; - export const logXScale: LogScale = { name: "xscale", type: "log", @@ -45,6 +43,23 @@ export const logXScale: LogScale = { domain: { data: "domain", field: "x" }, }; +export const timeXScale: TimeScale = { + name: "xscale", + clamp: true, + type: "time", + range: "width", + nice: false, + domain: { data: "domain", field: "dateTime" }, +}; + +export const linearYScale: LinearScale = { + name: "yscale", + type: "linear", + range: "height", + zero: true, + domain: { data: "domain", field: "y" }, +}; + export const expYScale: PowScale = { name: "yscale", type: "pow", @@ -56,6 +71,7 @@ export const expYScale: PowScale = { }; export const defaultTickFormat = ".9~s"; +export const timeTickFormat = "%b %d, %Y %H:%M"; export function buildVegaSpec( specOptions: DistributionChartSpecOptions @@ -67,9 +83,14 @@ export function buildVegaSpec( maxX, logX, expY, + sample = false, + xAxis = "number", } = specOptions; - let xScale = logX ? logXScale : linearXScale; + const dateTime = xAxis === "dateTime"; + + let xScale = dateTime ? timeXScale : logX ? logXScale : linearXScale; + if (minX !== undefined && Number.isFinite(minX)) { xScale = { ...xScale, domainMin: minX }; } @@ -105,9 +126,7 @@ export function buildVegaSpec( { name: "position_scaled", value: 0, - update: "position ? position[0] < 0 ? null : position[0] > width ? null : invert('xscale', position[0]) : null", - // "position ? position[0] < 0 ? 0 : position[0] > width ? 0 : 1 : 0", - + update: "position ? invert('xscale', position[0]) : null", }, ], scales: [ @@ -138,7 +157,6 @@ export function buildVegaSpec( }, ], marks: [ - { name: "all_distributions", type: "group", @@ -175,7 +193,7 @@ export function buildVegaSpec( interpolate: { value: "linear" }, x: { scale: "xscale", - field: "x", + field: dateTime ? "dateTime" : "x", }, y: { scale: "yscale", @@ -222,7 +240,7 @@ export function buildVegaSpec( update: { x: { scale: "xscale", - field: "x", + field: dateTime ? "dateTime" : "x", }, y: { scale: "yscale", @@ -251,13 +269,15 @@ export function buildVegaSpec( }, size: [{ value: 100 }], tooltip: { - signal: "{ probability: datum.y, value: datum.x }", + signal: dateTime + ? "{ probability: datum.y, value: datetime(datum.dateTime) }" + : "{ probability: datum.y, value: datum.x }", }, }, update: { x: { scale: "xscale", - field: "x", + field: dateTime ? "dateTime" : "x", offset: 0.5, // if this is not included, the circles are slightly left of center. }, y: { @@ -356,5 +376,37 @@ export function buildVegaSpec( }), }; + // include the band at the bottom if specified in the React component + if (sample) { + spec.marks?.push({ + name: "sample_distributions", + type: "group", + from: { + facet: { + name: "distribution_facet", + data: "domain", + groupby: ["name"], + }, + }, + marks: [ + { + name: "samples", + type: "rect", + from: { data: "distribution_facet" }, + encode: { + enter: { + x: { scale: "xscale", field: dateTime ? "dateTime" : "x" }, + width: { value: 0.5 }, + + y: { value: 25, offset: { signal: "height" } }, + height: { value: 5 }, + fill: { value: "steelblue" }, + fillOpacity: { value: 0.8 }, + }, + }, + }, + ], + }); + } return spec; } From 496bd0e53904fa9de228b950fb9cbb27730c61ca Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 29 Aug 2022 18:01:07 -0300 Subject: [PATCH 13/24] storybook for date distribution, grab missing things necessary for date distribution in spec --- .../src/components/DistributionChart.tsx | 1 + .../src/components/SquiggleChart.tsx | 4 ++++ .../src/lib/distributionSpecBuilder.ts | 20 ++++++++++++++----- .../src/stories/SquiggleChart.stories.mdx | 15 ++++++++++++++ 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index caf01e6d..b570f4e6 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -57,6 +57,7 @@ export const DistributionChart: React.FC = (props) => { actions = false, xAxis = "number", } = props; + console.log({ plot }); const [sized] = useSize((size) => { const shapes = flattenResult( plot.distributions.map((x) => diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 00688512..5baae229 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -51,6 +51,8 @@ export interface SquiggleChartProps { minX?: number; /** Specify the upper bound of the x scale */ maxX?: number; + /** Whether the x-axis should be dates or numbers */ + xAxis?: "number" | "dateTime"; /** Whether to show vega actions to the user, so they can copy the chart spec */ distributionChartActions?: boolean; enableLocalSettings?: boolean; @@ -79,6 +81,7 @@ export const SquiggleChart: React.FC = React.memo( maxX, color, title, + xAxis = "number", distributionChartActions, enableLocalSettings = false, }) => { @@ -100,6 +103,7 @@ export const SquiggleChart: React.FC = React.memo( maxX, color, title, + xAxis, actions: distributionChartActions, }; diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 98a06a08..9f3cf02a 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -77,7 +77,6 @@ export function buildVegaSpec( specOptions: DistributionChartSpecOptions ): VisualizationSpec { const { - format = defaultTickFormat, title, minX, maxX, @@ -89,6 +88,13 @@ export function buildVegaSpec( const dateTime = xAxis === "dateTime"; + // some fallbacks + const format = specOptions?.format + ? specOptions.format + : dateTime + ? timeTickFormat + : defaultTickFormat; + let xScale = dateTime ? timeXScale : logX ? logXScale : linearXScale; if (minX !== undefined && Number.isFinite(minX)) { @@ -125,8 +131,10 @@ export function buildVegaSpec( }, { name: "position_scaled", - value: 0, - update: "position ? invert('xscale', position[0]) : null", + value: null, + update: "isArray(position) ? invert('xscale', position[0]) : ''", + // value: 0, + // update: "position ? invert('xscale', position[0]) : null", }, ], scales: [ @@ -152,7 +160,7 @@ export function buildVegaSpec( domainColor: "#fff", domainOpacity: 0.0, format: format, - tickCount: 10, + tickCount: dateTime ? 3 : 10, labelOverlap: "greedy", }, ], @@ -308,7 +316,9 @@ export function buildVegaSpec( }, update: { text: { - signal: "position_scaled ? format(position_scaled, ',.4r') : ''", + signal: dateTime + ? "position_scaled ? utcyear(position_scaled) + '-' + utcmonth(position_scaled) + '-' + utcdate(position_scaled) + 'T' + utchours(position_scaled)+':' +utcminutes(position_scaled) : ''" + : "position_scaled ? format(position_scaled, ',.4r') : ''", }, }, }, diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index 3c272982..3dc74761 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -79,6 +79,21 @@ could be continuous, discrete or mixed. +### Date Distribution + + + + {Template.bind({})} + + + ## Mixed distributions From 87c6ff1c3d39261633ea6120dce93bafaf4532ba Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 29 Aug 2022 20:32:32 -0300 Subject: [PATCH 14/24] width substitute --- packages/components/src/lib/distributionSpecBuilder.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 9f3cf02a..3476abeb 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -72,6 +72,7 @@ export const expYScale: PowScale = { export const defaultTickFormat = ".9~s"; export const timeTickFormat = "%b %d, %Y %H:%M"; +const width = 500; export function buildVegaSpec( specOptions: DistributionChartSpecOptions @@ -108,7 +109,7 @@ export function buildVegaSpec( const spec: VisualizationSpec = { $schema: "https://vega.github.io/schema/vega/v5.json", description: "Squiggle plot chart", - width: 500, + width: width, height: 100, padding: 5, data: [{ name: "data" }, { name: "domain" }], @@ -133,8 +134,6 @@ export function buildVegaSpec( name: "position_scaled", value: null, update: "isArray(position) ? invert('xscale', position[0]) : ''", - // value: 0, - // update: "position ? invert('xscale', position[0]) : null", }, ], scales: [ @@ -309,7 +308,7 @@ export function buildVegaSpec( interactive: false, encode: { enter: { - x: { signal: "width", offset: 1 }, + x: { signal: String(width), offset: 1 }, // vega would prefer its internal ` "width" ` variable, but that breaks the squiggle playground. Just setting it to the same var as used elsewhere in the spec achieves the same result. fill: { value: "black" }, fontSize: { value: 20 }, align: { value: "right" }, From a1db813b8b7cbd610745d41aa524af12b448bb3f Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Mon, 29 Aug 2022 21:11:48 -0300 Subject: [PATCH 15/24] pr comment tweaks --- packages/components/src/components/DistributionChart.tsx | 7 ++----- packages/components/src/components/SquiggleChart.tsx | 2 +- packages/components/src/lib/distributionSpecBuilder.ts | 2 -- packages/components/src/stories/SquiggleChart.stories.mdx | 1 + 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index b570f4e6..1cedc372 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -30,7 +30,7 @@ export type DistributionChartProps = { plot: Plot; width?: number; height: number; - sample: boolean; + sample?: boolean; xAxis?: "number" | "dateTime"; } & DistributionPlottingSettings; @@ -57,7 +57,6 @@ export const DistributionChart: React.FC = (props) => { actions = false, xAxis = "number", } = props; - console.log({ plot }); const [sized] = useSize((size) => { const shapes = flattenResult( plot.distributions.map((x) => @@ -93,9 +92,7 @@ export const DistributionChart: React.FC = (props) => { const domain = xAxis === "dateTime" - ? predomain.map((p) => { - return { dateTime: p.x, y: p.y }; - }) + ? predomain.map((p) => ({ dateTime: p.x, y: p.y })) : predomain; const data = diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 5baae229..7bc2e737 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -51,7 +51,7 @@ export interface SquiggleChartProps { minX?: number; /** Specify the upper bound of the x scale */ maxX?: number; - /** Whether the x-axis should be dates or numbers */ + // /** Whether the x-axis should be dates or numbers */ xAxis?: "number" | "dateTime"; /** Whether to show vega actions to the user, so they can copy the chart spec */ distributionChartActions?: boolean; diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 3476abeb..b1f49f23 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -14,10 +14,8 @@ export type DistributionChartSpecOptions = { title?: string; /** The formatting of the ticks */ format?: string; - /** Whether or not to show the band of sample data at the bottom */ sample?: boolean; - /** Whether the x-axis should be dates or numbers */ xAxis?: "number" | "dateTime"; }; diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index 3dc74761..2a4aa48d 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -88,6 +88,7 @@ could be continuous, discrete or mixed. code: "mx(10^12, 10^12 + 10^11, 10^12 + 2 * 10^11)", width, xAxis: "dateTime", + width, }} > {Template.bind({})} From 07b7b26d605d18164910371cf77a2d0066a8d754 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Tue, 30 Aug 2022 09:14:25 -0300 Subject: [PATCH 16/24] include sample band on sampleset story --- packages/components/src/components/DistributionChart.tsx | 2 ++ packages/components/src/components/SquiggleChart.tsx | 8 ++++++-- packages/components/src/lib/distributionSpecBuilder.ts | 1 + packages/components/src/stories/SquiggleChart.stories.mdx | 3 ++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 1cedc372..1a055ea9 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -110,6 +110,8 @@ export const DistributionChart: React.FC = (props) => { }) : shapes.value; + console.log({ data }); + return (
{logX && shapes.value.some(hasMassBelowZero) ? ( diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 7bc2e737..2d05c1be 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -51,7 +51,9 @@ export interface SquiggleChartProps { minX?: number; /** Specify the upper bound of the x scale */ maxX?: number; - // /** Whether the x-axis should be dates or numbers */ + // /** Whether to include the sample band below the chart */ + sample?: boolean; + /** Whether the x-axis should be dates or numbers */ xAxis?: "number" | "dateTime"; /** Whether to show vega actions to the user, so they can copy the chart spec */ distributionChartActions?: boolean; @@ -81,6 +83,7 @@ export const SquiggleChart: React.FC = React.memo( maxX, color, title, + sample = false, xAxis = "number", distributionChartActions, enableLocalSettings = false, @@ -93,7 +96,7 @@ export const SquiggleChart: React.FC = React.memo( onChange, executionId, }); - + console.log({ result }); const distributionPlotSettings = { showSummary, logX, @@ -104,6 +107,7 @@ export const SquiggleChart: React.FC = React.memo( color, title, xAxis, + sample, actions: distributionChartActions, }; diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index b1f49f23..07253c4c 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -218,6 +218,7 @@ export function buildVegaSpec( }, }, }, + ], }, { diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index 2a4aa48d..72f708d7 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -59,6 +59,7 @@ could be continuous, discrete or mixed. args={{ code: "SampleSet.fromDist(normal(5,2))", width, + sample: true, }} > {Template.bind({})} @@ -85,7 +86,7 @@ could be continuous, discrete or mixed. Date: Wed, 31 Aug 2022 11:19:11 -0300 Subject: [PATCH 17/24] more elegantly handle samples, properly use sample data in SampleSets for bands --- .../src/components/DistributionChart.tsx | 13 +++- .../src/components/SquiggleChart.tsx | 6 +- .../src/lib/distributionSpecBuilder.ts | 65 +++++++++---------- .../src/stories/SquiggleChart.stories.mdx | 1 - 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 1a055ea9..a6f2608c 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -19,6 +19,7 @@ import { NumberShower } from "./NumberShower"; import { Plot, parsePlot } from "../lib/plotParser"; import { flattenResult } from "../lib/utility"; import { hasMassBelowZero } from "../lib/distributionUtils"; +import { point } from "@quri/squiggle-lang/src/js/distribution"; export type DistributionPlottingSettings = { /** Whether to show a summary of means, stdev, percentiles etc */ @@ -65,6 +66,7 @@ export const DistributionChart: React.FC = (props) => { // color: x.color, // not supported yet continuous: shape.continuous, discrete: shape.discrete, + samples: [] as point[], })) ) ); @@ -77,6 +79,12 @@ export const DistributionChart: React.FC = (props) => { ); } + // if this is a sample set, include the samples + const t = plot?.distributions[0]?.distribution?.t; + if (t.tag === "SampleSet") { + shapes.value[0].samples = t.value.map((v) => ({ x: v, y: 0 })); + } + const spec = buildVegaSpec(props); let widthProp = width ? width : size.width; @@ -106,12 +114,13 @@ export const DistributionChart: React.FC = (props) => { discrete: val.discrete.map((p) => { return { dateTime: p.x, y: p.y }; }), + samples: val.samples.map((p) => { + return { dateTime: p, y: 0 }; + }), }; }) : shapes.value; - console.log({ data }); - return (
{logX && shapes.value.some(hasMassBelowZero) ? ( diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 2d05c1be..7b651e8f 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -51,8 +51,6 @@ export interface SquiggleChartProps { minX?: number; /** Specify the upper bound of the x scale */ maxX?: number; - // /** Whether to include the sample band below the chart */ - sample?: boolean; /** Whether the x-axis should be dates or numbers */ xAxis?: "number" | "dateTime"; /** Whether to show vega actions to the user, so they can copy the chart spec */ @@ -83,7 +81,6 @@ export const SquiggleChart: React.FC = React.memo( maxX, color, title, - sample = false, xAxis = "number", distributionChartActions, enableLocalSettings = false, @@ -96,7 +93,7 @@ export const SquiggleChart: React.FC = React.memo( onChange, executionId, }); - console.log({ result }); + const distributionPlotSettings = { showSummary, logX, @@ -107,7 +104,6 @@ export const SquiggleChart: React.FC = React.memo( color, title, xAxis, - sample, actions: distributionChartActions, }; diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 07253c4c..b4e79609 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -81,7 +81,6 @@ export function buildVegaSpec( maxX, logX, expY, - sample = false, xAxis = "number", } = specOptions; @@ -110,7 +109,7 @@ export function buildVegaSpec( width: width, height: 100, padding: 5, - data: [{ name: "data" }, { name: "domain" }], + data: [{ name: "data" }, { name: "domain" }, { name: "samples" }], signals: [ { name: "hover", @@ -218,7 +217,6 @@ export function buildVegaSpec( }, }, }, - ], }, { @@ -299,6 +297,35 @@ export function buildVegaSpec( }, ], }, + { + name: "sample_distributions", + type: "group", + from: { + facet: { + name: "sample_facet", + data: "distribution_facet", + field: "samples", + }, + }, + marks: [ + { + name: "samples", + type: "rect", + from: { data: "sample_facet" }, + encode: { + enter: { + x: { scale: "xscale", field: dateTime ? "dateTime" : "x" }, + width: { value: 0.1 }, + + y: { value: 25, offset: { signal: "height" } }, + height: { value: 5 }, + fill: { value: "steelblue" }, + fillOpacity: { value: 1 }, + }, + }, + }, + ], + }, ], }, { @@ -384,37 +411,5 @@ export function buildVegaSpec( }), }; - // include the band at the bottom if specified in the React component - if (sample) { - spec.marks?.push({ - name: "sample_distributions", - type: "group", - from: { - facet: { - name: "distribution_facet", - data: "domain", - groupby: ["name"], - }, - }, - marks: [ - { - name: "samples", - type: "rect", - from: { data: "distribution_facet" }, - encode: { - enter: { - x: { scale: "xscale", field: dateTime ? "dateTime" : "x" }, - width: { value: 0.5 }, - - y: { value: 25, offset: { signal: "height" } }, - height: { value: 5 }, - fill: { value: "steelblue" }, - fillOpacity: { value: 0.8 }, - }, - }, - }, - ], - }); - } return spec; } diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index 72f708d7..887146f0 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -59,7 +59,6 @@ could be continuous, discrete or mixed. args={{ code: "SampleSet.fromDist(normal(5,2))", width, - sample: true, }} > {Template.bind({})} From 2087a30b6bbd5c09693baafc09b5ebf3d139c236 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 31 Aug 2022 12:15:43 -0300 Subject: [PATCH 18/24] use in multiple plots --- .../src/components/DistributionChart.tsx | 16 ++++++++++++---- .../src/lib/distributionSpecBuilder.ts | 14 +++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index a6f2608c..7b05d65f 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -80,11 +80,19 @@ export const DistributionChart: React.FC = (props) => { } // if this is a sample set, include the samples - const t = plot?.distributions[0]?.distribution?.t; - if (t.tag === "SampleSet") { - shapes.value[0].samples = t.value.map((v) => ({ x: v, y: 0 })); + const sampleSets = plot?.distributions.filter( + (dist) => dist.distribution.t.tag === "SampleSet" + ); + if (sampleSets.length) { + for (const set of sampleSets) { + if (set.distribution.t.tag === "SampleSet") { // this must be duplicated to please typescript, more elegant solution probably exists + shapes.value[0].samples.push( + ...set.distribution.t.value.map((v) => ({ x: v, y: 0 })) + ); + } + } } - + const spec = buildVegaSpec(props); let widthProp = width ? width : size.width; diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index b4e79609..06b23919 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -75,14 +75,7 @@ const width = 500; export function buildVegaSpec( specOptions: DistributionChartSpecOptions ): VisualizationSpec { - const { - title, - minX, - maxX, - logX, - expY, - xAxis = "number", - } = specOptions; + const { title, minX, maxX, logX, expY, xAxis = "number" } = specOptions; const dateTime = xAxis === "dateTime"; @@ -319,7 +312,10 @@ export function buildVegaSpec( y: { value: 25, offset: { signal: "height" } }, height: { value: 5 }, - fill: { value: "steelblue" }, + fill: { + scale: "color", + field: { parent: "name" }, + }, fillOpacity: { value: 1 }, }, }, From 3ea747fae4ba82dc4a225e15afc88b14f779f807 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Tue, 6 Sep 2022 14:27:01 -0300 Subject: [PATCH 19/24] simplify code by not mapping x property to dateTime property --- .../src/components/DistributionChart.tsx | 30 +++---------------- .../src/lib/distributionSpecBuilder.ts | 17 ++++++----- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 7b05d65f..0eabd0f6 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -85,7 +85,8 @@ export const DistributionChart: React.FC = (props) => { ); if (sampleSets.length) { for (const set of sampleSets) { - if (set.distribution.t.tag === "SampleSet") { // this must be duplicated to please typescript, more elegant solution probably exists + if (set.distribution.t.tag === "SampleSet") { + // this must be duplicated to please typescript, more elegant solution probably exists shapes.value[0].samples.push( ...set.distribution.t.value.map((v) => ({ x: v, y: 0 })) ); @@ -102,33 +103,10 @@ export const DistributionChart: React.FC = (props) => { ); widthProp = 20; } - const predomain = shapes.value.flatMap((shape) => + const domain = shapes.value.flatMap((shape) => shape.discrete.concat(shape.continuous) ); - const domain = - xAxis === "dateTime" - ? predomain.map((p) => ({ dateTime: p.x, y: p.y })) - : predomain; - - const data = - xAxis === "dateTime" - ? shapes.value.map((val) => { - return { - ...val, - continuous: val.continuous.map((p) => { - return { dateTime: p.x, y: p.y }; - }), - discrete: val.discrete.map((p) => { - return { dateTime: p.x, y: p.y }; - }), - samples: val.samples.map((p) => { - return { dateTime: p, y: 0 }; - }), - }; - }) - : shapes.value; - return (
{logX && shapes.value.some(hasMassBelowZero) ? ( @@ -138,7 +116,7 @@ export const DistributionChart: React.FC = (props) => { ) : ( width ? 0 : 1 : 0", }, - // opacity: { signal: "position ? 1 : 0" }, }, }, }, From 6c87a9e712ae8a294abef5896dd775ee9c5f250a Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Tue, 6 Sep 2022 14:30:52 -0300 Subject: [PATCH 20/24] rename to xAxisType --- .../src/components/DistributionChart.tsx | 4 ++-- .../src/components/SquiggleChart.tsx | 6 +++--- .../src/lib/distributionSpecBuilder.ts | 21 ++++++++++++------- .../src/stories/SquiggleChart.stories.mdx | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 0eabd0f6..fb0ff95c 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -32,7 +32,7 @@ export type DistributionChartProps = { width?: number; height: number; sample?: boolean; - xAxis?: "number" | "dateTime"; + xAxisType?: "number" | "dateTime"; } & DistributionPlottingSettings; export function defaultPlot(distribution: Distribution): Plot { @@ -56,7 +56,7 @@ export const DistributionChart: React.FC = (props) => { width, logX, actions = false, - xAxis = "number", + xAxisType = "number", } = props; const [sized] = useSize((size) => { const shapes = flattenResult( diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index 7b651e8f..e68ba427 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -52,7 +52,7 @@ export interface SquiggleChartProps { /** Specify the upper bound of the x scale */ maxX?: number; /** Whether the x-axis should be dates or numbers */ - xAxis?: "number" | "dateTime"; + xAxisType?: "number" | "dateTime"; /** Whether to show vega actions to the user, so they can copy the chart spec */ distributionChartActions?: boolean; enableLocalSettings?: boolean; @@ -81,7 +81,7 @@ export const SquiggleChart: React.FC = React.memo( maxX, color, title, - xAxis = "number", + xAxisType = "number", distributionChartActions, enableLocalSettings = false, }) => { @@ -103,7 +103,7 @@ export const SquiggleChart: React.FC = React.memo( maxX, color, title, - xAxis, + xAxisType, actions: distributionChartActions, }; diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 170eb44c..261545b5 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -17,10 +17,10 @@ export type DistributionChartSpecOptions = { /** Whether or not to show the band of sample data at the bottom */ sample?: boolean; /** Whether the x-axis should be dates or numbers */ - xAxis?: "number" | "dateTime"; + xAxisType?: "number" | "dateTime"; }; -/** X Scales */ +/** X Scales */ export const linearXScale: LinearScale = { name: "xscale", clamp: true, @@ -51,7 +51,7 @@ export const timeXScale: TimeScale = { domain: { data: "domain", field: "x" }, }; -/** Y Scales */ +/** Y Scales */ export const linearYScale: LinearScale = { name: "yscale", type: "linear", @@ -77,9 +77,16 @@ const width = 500; export function buildVegaSpec( specOptions: DistributionChartSpecOptions ): VisualizationSpec { - const { title, minX, maxX, logX, expY, xAxis = "number" } = specOptions; + const { + title, + minX, + maxX, + logX, + expY, + xAxisType = "number", + } = specOptions; - const dateTime = xAxis === "dateTime"; + const dateTime = xAxisType === "dateTime"; // some fallbacks const format = specOptions?.format @@ -270,13 +277,13 @@ export function buildVegaSpec( tooltip: { signal: dateTime ? "{ probability: datum.y, value: datetime(datum.x) }" - : "{ probability: datum.y, value: datum.x }", + : "{ probability: datum.y, value: datum.x }", }, }, update: { x: { scale: "xscale", - field: "x", + field: "x", offset: 0.5, // if this is not included, the circles are slightly left of center. }, y: { diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index 887146f0..45fec12e 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -87,7 +87,7 @@ could be continuous, discrete or mixed. args={{ code: "mx(1661819770311, 1661829770311, 1661839770311)", width, - xAxis: "dateTime", + xAxisType: "dateTime", width, }} > From e0cd95fe5ca0ea9c758cf535b6e218c2e6168592 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Tue, 6 Sep 2022 14:52:57 -0300 Subject: [PATCH 21/24] remove unnecessary mapping of sample array to array of objects --- .../components/src/components/DistributionChart.tsx | 10 +++++----- packages/components/src/lib/distributionSpecBuilder.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index fb0ff95c..d423b197 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -66,7 +66,8 @@ export const DistributionChart: React.FC = (props) => { // color: x.color, // not supported yet continuous: shape.continuous, discrete: shape.discrete, - samples: [] as point[], + samples: [] as number[], + // samples: [] as point[], })) ) ); @@ -86,14 +87,13 @@ export const DistributionChart: React.FC = (props) => { if (sampleSets.length) { for (const set of sampleSets) { if (set.distribution.t.tag === "SampleSet") { - // this must be duplicated to please typescript, more elegant solution probably exists - shapes.value[0].samples.push( - ...set.distribution.t.value.map((v) => ({ x: v, y: 0 })) - ); + // this conditional must be duplicated to please typescript, more elegant solution probably exists + shapes.value[0].samples.push(...set.distribution.t.value); } } } + console.log(shapes.value); const spec = buildVegaSpec(props); let widthProp = width ? width : size.width; diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 261545b5..2880aa4b 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -316,7 +316,7 @@ export function buildVegaSpec( from: { data: "sample_facet" }, encode: { enter: { - x: { scale: "xscale", field: "x" }, + x: { scale: "xscale", field: "data"}, width: { value: 0.1 }, y: { value: 25, offset: { signal: "height" } }, From 0c614a3fcfef9290da78ce76d4dfb47386d8d73f Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 7 Sep 2022 09:18:01 -0300 Subject: [PATCH 22/24] samples in own shape --- .../src/components/DistributionChart.tsx | 9 +-- .../src/components/SquiggleChart.tsx | 2 +- .../src/lib/distributionSpecBuilder.ts | 58 +++++-------------- 3 files changed, 22 insertions(+), 47 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 90cb4f1e..d1773993 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -32,7 +32,6 @@ export type DistributionChartProps = { environment: environment; width?: number; height: number; - sample?: boolean; xAxisType?: "number" | "dateTime"; } & DistributionPlottingSettings; @@ -64,7 +63,7 @@ export const DistributionChart: React.FC = (props) => { name: x.name, // color: x.color, // not supported yet ...pointSet.asShape(), - samples: [] as number[], + // samples: [] as number[], })) ) ); @@ -78,6 +77,7 @@ export const DistributionChart: React.FC = (props) => { } // if this is a sample set, include the samples + const samples: number[] = []; const sampleSets = plot?.distributions.filter( (dist) => dist.distribution.tag === SqDistributionTag.SampleSet ); @@ -85,7 +85,8 @@ export const DistributionChart: React.FC = (props) => { for (const { distribution } of sampleSets) { if (distribution.tag === SqDistributionTag.SampleSet) { // this conditional must be duplicated to please typescript, more elegant solution probably exists - shapes.value[0].samples.push(...distribution.value()); + samples.push(...distribution.value()); + // shapes.value[0].samples.push(...distribution.value()); } } } @@ -112,7 +113,7 @@ export const DistributionChart: React.FC = (props) => { ) : ( = React.memo( onChange, executionId, }); - + const distributionPlotSettings = { showSummary, logX, diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts index 2880aa4b..d48078d3 100644 --- a/packages/components/src/lib/distributionSpecBuilder.ts +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -14,8 +14,6 @@ export type DistributionChartSpecOptions = { title?: string; /** The formatting of the ticks */ format?: string; - /** Whether or not to show the band of sample data at the bottom */ - sample?: boolean; /** Whether the x-axis should be dates or numbers */ xAxisType?: "number" | "dateTime"; }; @@ -77,14 +75,7 @@ const width = 500; export function buildVegaSpec( specOptions: DistributionChartSpecOptions ): VisualizationSpec { - const { - title, - minX, - maxX, - logX, - expY, - xAxisType = "number", - } = specOptions; + const { title, minX, maxX, logX, expY, xAxisType = "number" } = specOptions; const dateTime = xAxisType === "dateTime"; @@ -299,40 +290,23 @@ export function buildVegaSpec( }, ], }, - { - name: "sample_distributions", - type: "group", - from: { - facet: { - name: "sample_facet", - data: "distribution_facet", - field: "samples", - }, - }, - marks: [ - { - name: "samples", - type: "rect", - from: { data: "sample_facet" }, - encode: { - enter: { - x: { scale: "xscale", field: "data"}, - width: { value: 0.1 }, - - y: { value: 25, offset: { signal: "height" } }, - height: { value: 5 }, - fill: { - scale: "color", - field: { parent: "name" }, - }, - fillOpacity: { value: 1 }, - }, - }, - }, - ], - }, ], }, + + { + name: "sampleset", + type: "rect", + from: { data: "samples" }, + encode: { + enter: { + x: { scale: "xscale", field: "data" }, + width: { value: 0.1 }, + + y: { value: 25, offset: { signal: "height" } }, + height: { value: 5 }, + }, + }, + }, { type: "text", name: "announcer", From 5b0d7a898fe5e35236c6384a4cece7074eda728e Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Wed, 7 Sep 2022 11:01:18 -0300 Subject: [PATCH 23/24] remove dead comments and simplify sample inclusion --- .../src/components/DistributionChart.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index d1773993..61ee3c5b 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -63,7 +63,6 @@ export const DistributionChart: React.FC = (props) => { name: x.name, // color: x.color, // not supported yet ...pointSet.asShape(), - // samples: [] as number[], })) ) ); @@ -78,16 +77,9 @@ export const DistributionChart: React.FC = (props) => { // if this is a sample set, include the samples const samples: number[] = []; - const sampleSets = plot?.distributions.filter( - (dist) => dist.distribution.tag === SqDistributionTag.SampleSet - ); - if (sampleSets.length) { - for (const { distribution } of sampleSets) { - if (distribution.tag === SqDistributionTag.SampleSet) { - // this conditional must be duplicated to please typescript, more elegant solution probably exists - samples.push(...distribution.value()); - // shapes.value[0].samples.push(...distribution.value()); - } + for (const { distribution } of plot?.distributions) { + if (distribution.tag === SqDistributionTag.SampleSet) { + samples.push(...distribution.value()); } } From 2736fb9a1791e3049926a977706c01b589d3b403 Mon Sep 17 00:00:00 2001 From: Conor Barnes Date: Thu, 8 Sep 2022 16:31:02 -0300 Subject: [PATCH 24/24] delete conflicting file --- .../ReducerProject_IncludeParser.js | 1183 ----------------- 1 file changed, 1183 deletions(-) delete mode 100644 packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_IncludeParser.js diff --git a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_IncludeParser.js b/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_IncludeParser.js deleted file mode 100644 index 7f9c0418..00000000 --- a/packages/squiggle-lang/src/rescript/ReducerProject/ReducerProject_IncludeParser.js +++ /dev/null @@ -1,1183 +0,0 @@ -// Generated by Peggy 2.0.1. -// -// https://peggyjs.org/ - -"use strict"; - -function peg$subclass(child, parent) { - function C() { this.constructor = child; } - C.prototype = parent.prototype; - child.prototype = new C(); -} - -function peg$SyntaxError(message, expected, found, location) { - var self = Error.call(this, message); - // istanbul ignore next Check is a necessary evil to support older environments - if (Object.setPrototypeOf) { - Object.setPrototypeOf(self, peg$SyntaxError.prototype); - } - self.expected = expected; - self.found = found; - self.location = location; - self.name = "SyntaxError"; - return self; -} - -peg$subclass(peg$SyntaxError, Error); - -function peg$padEnd(str, targetLength, padString) { - padString = padString || " "; - if (str.length > targetLength) { return str; } - targetLength -= str.length; - padString += padString.repeat(targetLength); - return str + padString.slice(0, targetLength); -} - -peg$SyntaxError.prototype.format = function(sources) { - var str = "Error: " + this.message; - if (this.location) { - var src = null; - var k; - for (k = 0; k < sources.length; k++) { - if (sources[k].source === this.location.source) { - src = sources[k].text.split(/\r\n|\n|\r/g); - break; - } - } - var s = this.location.start; - var loc = this.location.source + ":" + s.line + ":" + s.column; - if (src) { - var e = this.location.end; - var filler = peg$padEnd("", s.line.toString().length, ' '); - var line = src[s.line - 1]; - var last = s.line === e.line ? e.column : line.length + 1; - var hatLen = (last - s.column) || 1; - str += "\n --> " + loc + "\n" - + filler + " |\n" - + s.line + " | " + line + "\n" - + filler + " | " + peg$padEnd("", s.column - 1, ' ') - + peg$padEnd("", hatLen, "^"); - } else { - str += "\n at " + loc; - } - } - return str; -}; - -peg$SyntaxError.buildMessage = function(expected, found) { - var DESCRIBE_EXPECTATION_FNS = { - literal: function(expectation) { - return "\"" + literalEscape(expectation.text) + "\""; - }, - - class: function(expectation) { - var escapedParts = expectation.parts.map(function(part) { - return Array.isArray(part) - ? classEscape(part[0]) + "-" + classEscape(part[1]) - : classEscape(part); - }); - - return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]"; - }, - - any: function() { - return "any character"; - }, - - end: function() { - return "end of input"; - }, - - other: function(expectation) { - return expectation.description; - } - }; - - function hex(ch) { - return ch.charCodeAt(0).toString(16).toUpperCase(); - } - - function literalEscape(s) { - return s - .replace(/\\/g, "\\\\") - .replace(/"/g, "\\\"") - .replace(/\0/g, "\\0") - .replace(/\t/g, "\\t") - .replace(/\n/g, "\\n") - .replace(/\r/g, "\\r") - .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); - } - - function classEscape(s) { - return s - .replace(/\\/g, "\\\\") - .replace(/\]/g, "\\]") - .replace(/\^/g, "\\^") - .replace(/-/g, "\\-") - .replace(/\0/g, "\\0") - .replace(/\t/g, "\\t") - .replace(/\n/g, "\\n") - .replace(/\r/g, "\\r") - .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); - } - - function describeExpectation(expectation) { - return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); - } - - function describeExpected(expected) { - var descriptions = expected.map(describeExpectation); - var i, j; - - descriptions.sort(); - - if (descriptions.length > 0) { - for (i = 1, j = 1; i < descriptions.length; i++) { - if (descriptions[i - 1] !== descriptions[i]) { - descriptions[j] = descriptions[i]; - j++; - } - } - descriptions.length = j; - } - - switch (descriptions.length) { - case 1: - return descriptions[0]; - - case 2: - return descriptions[0] + " or " + descriptions[1]; - - default: - return descriptions.slice(0, -1).join(", ") - + ", or " - + descriptions[descriptions.length - 1]; - } - } - - function describeFound(found) { - return found ? "\"" + literalEscape(found) + "\"" : "end of input"; - } - - return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; -}; - -function peg$parse(input, options) { - options = options !== undefined ? options : {}; - - var peg$FAILED = {}; - var peg$source = options.grammarSource; - - var peg$startRuleFunctions = { start: peg$parsestart }; - var peg$startRuleFunction = peg$parsestart; - - var peg$c0 = "#"; - var peg$c1 = "#include"; - var peg$c2 = "as"; - var peg$c3 = "'"; - var peg$c4 = "\""; - var peg$c5 = "//"; - var peg$c6 = "/*"; - var peg$c7 = "*/"; - - var peg$r0 = /^[^']/; - var peg$r1 = /^[^"]/; - var peg$r2 = /^[^*]/; - var peg$r3 = /^[ \t]/; - var peg$r4 = /^[\n\r]/; - var peg$r5 = /^[^\r\n]/; - var peg$r6 = /^[_a-z]/; - var peg$r7 = /^[_a-z0-9]/i; - - var peg$e0 = peg$literalExpectation("#", false); - var peg$e1 = peg$literalExpectation("#include", false); - var peg$e2 = peg$literalExpectation("as", false); - var peg$e3 = peg$otherExpectation("string"); - var peg$e4 = peg$literalExpectation("'", false); - var peg$e5 = peg$classExpectation(["'"], true, false); - var peg$e6 = peg$literalExpectation("\"", false); - var peg$e7 = peg$classExpectation(["\""], true, false); - var peg$e8 = peg$otherExpectation("comment"); - var peg$e9 = peg$literalExpectation("//", false); - var peg$e10 = peg$literalExpectation("/*", false); - var peg$e11 = peg$classExpectation(["*"], true, false); - var peg$e12 = peg$literalExpectation("*/", false); - var peg$e13 = peg$otherExpectation("white space"); - var peg$e14 = peg$classExpectation([" ", "\t"], false, false); - var peg$e15 = peg$otherExpectation("newline"); - var peg$e16 = peg$classExpectation(["\n", "\r"], false, false); - var peg$e17 = peg$otherExpectation("code"); - var peg$e18 = peg$classExpectation(["\r", "\n"], true, false); - var peg$e19 = peg$otherExpectation("identifier"); - var peg$e20 = peg$classExpectation(["_", ["a", "z"]], false, false); - var peg$e21 = peg$classExpectation(["_", ["a", "z"], ["0", "9"]], false, true); - - var peg$f0 = function(head, tail) {return [head, ...tail].filter( e => e != '');}; - var peg$f1 = function() {return [];}; - var peg$f2 = function(file, variable) {return [!variable ? '' : variable, file]}; - var peg$f3 = function(characters) {return characters.join('');}; - var peg$f4 = function(characters) {return characters.join('');}; - var peg$f5 = function() { return '';}; - var peg$f6 = function() { return '';}; - var peg$f7 = function() {return text();}; - var peg$currPos = 0; - var peg$savedPos = 0; - var peg$posDetailsCache = [{ line: 1, column: 1 }]; - var peg$maxFailPos = 0; - var peg$maxFailExpected = []; - var peg$silentFails = 0; - - var peg$resultsCache = {}; - - var peg$result; - - if ("startRule" in options) { - if (!(options.startRule in peg$startRuleFunctions)) { - throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); - } - - peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; - } - - function text() { - return input.substring(peg$savedPos, peg$currPos); - } - - function offset() { - return peg$savedPos; - } - - function range() { - return { - source: peg$source, - start: peg$savedPos, - end: peg$currPos - }; - } - - function location() { - return peg$computeLocation(peg$savedPos, peg$currPos); - } - - function expected(description, location) { - location = location !== undefined - ? location - : peg$computeLocation(peg$savedPos, peg$currPos); - - throw peg$buildStructuredError( - [peg$otherExpectation(description)], - input.substring(peg$savedPos, peg$currPos), - location - ); - } - - function error(message, location) { - location = location !== undefined - ? location - : peg$computeLocation(peg$savedPos, peg$currPos); - - throw peg$buildSimpleError(message, location); - } - - function peg$literalExpectation(text, ignoreCase) { - return { type: "literal", text: text, ignoreCase: ignoreCase }; - } - - function peg$classExpectation(parts, inverted, ignoreCase) { - return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; - } - - function peg$anyExpectation() { - return { type: "any" }; - } - - function peg$endExpectation() { - return { type: "end" }; - } - - function peg$otherExpectation(description) { - return { type: "other", description: description }; - } - - function peg$computePosDetails(pos) { - var details = peg$posDetailsCache[pos]; - var p; - - if (details) { - return details; - } else { - p = pos - 1; - while (!peg$posDetailsCache[p]) { - p--; - } - - details = peg$posDetailsCache[p]; - details = { - line: details.line, - column: details.column - }; - - while (p < pos) { - if (input.charCodeAt(p) === 10) { - details.line++; - details.column = 1; - } else { - details.column++; - } - - p++; - } - - peg$posDetailsCache[pos] = details; - - return details; - } - } - - function peg$computeLocation(startPos, endPos) { - var startPosDetails = peg$computePosDetails(startPos); - var endPosDetails = peg$computePosDetails(endPos); - - return { - source: peg$source, - start: { - offset: startPos, - line: startPosDetails.line, - column: startPosDetails.column - }, - end: { - offset: endPos, - line: endPosDetails.line, - column: endPosDetails.column - } - }; - } - - function peg$fail(expected) { - if (peg$currPos < peg$maxFailPos) { return; } - - if (peg$currPos > peg$maxFailPos) { - peg$maxFailPos = peg$currPos; - peg$maxFailExpected = []; - } - - peg$maxFailExpected.push(expected); - } - - function peg$buildSimpleError(message, location) { - return new peg$SyntaxError(message, null, null, location); - } - - function peg$buildStructuredError(expected, found, location) { - return new peg$SyntaxError( - peg$SyntaxError.buildMessage(expected, found), - expected, - found, - location - ); - } - - function peg$parsestart() { - var s0, s1, s2, s3, s4; - - var key = peg$currPos * 12 + 0; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - s0 = peg$currPos; - s1 = []; - s2 = peg$parsenewLine(); - if (s2 === peg$FAILED) { - s2 = peg$parse_(); - if (s2 === peg$FAILED) { - s2 = peg$parsecomment(); - if (s2 === peg$FAILED) { - s2 = peg$parsedelimitedComment(); - } - } - } - while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$parsenewLine(); - if (s2 === peg$FAILED) { - s2 = peg$parse_(); - if (s2 === peg$FAILED) { - s2 = peg$parsecomment(); - if (s2 === peg$FAILED) { - s2 = peg$parsedelimitedComment(); - } - } - } - } - s2 = peg$parseincludes(); - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$parsenewLine(); - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parsenewLine(); - } - s4 = peg$parseignore(); - s0 = s2; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parseincludes() { - var s0, s1, s2, s3, s4, s5; - - var key = peg$currPos * 12 + 1; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - s0 = peg$currPos; - s1 = peg$parsedependencyStatement(); - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$currPos; - s4 = []; - s5 = peg$parsenewLine(); - if (s5 !== peg$FAILED) { - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$parsenewLine(); - } - } else { - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - s5 = peg$parsedependencyStatement(); - if (s5 !== peg$FAILED) { - s3 = s5; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$currPos; - s4 = []; - s5 = peg$parsenewLine(); - if (s5 !== peg$FAILED) { - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$parsenewLine(); - } - } else { - s4 = peg$FAILED; - } - if (s4 !== peg$FAILED) { - s5 = peg$parsedependencyStatement(); - if (s5 !== peg$FAILED) { - s3 = s5; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } - peg$savedPos = s0; - s0 = peg$f0(s1, s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$currPos; - peg$silentFails++; - if (input.charCodeAt(peg$currPos) === 35) { - s2 = peg$c0; - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e0); } - } - peg$silentFails--; - if (s2 === peg$FAILED) { - s1 = undefined; - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f1(); - } - s0 = s1; - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parsedependencyStatement() { - var s0; - - var key = peg$currPos * 12 + 2; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - s0 = peg$parseincludeStatement(); - if (s0 === peg$FAILED) { - s0 = peg$parsecomment(); - if (s0 === peg$FAILED) { - s0 = peg$parsedelimitedComment(); - } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parseincludeStatement() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; - - var key = peg$currPos * 12 + 3; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - s0 = peg$currPos; - s1 = []; - s2 = peg$parse_(); - while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$parse_(); - } - if (input.substr(peg$currPos, 8) === peg$c1) { - s2 = peg$c1; - peg$currPos += 8; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } - } - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parse_(); - } - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - s4 = peg$parsestring(); - if (s4 !== peg$FAILED) { - s5 = peg$currPos; - s6 = []; - s7 = peg$parse_(); - if (s7 !== peg$FAILED) { - while (s7 !== peg$FAILED) { - s6.push(s7); - s7 = peg$parse_(); - } - } else { - s6 = peg$FAILED; - } - if (s6 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c2) { - s7 = peg$c2; - peg$currPos += 2; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e2); } - } - if (s7 !== peg$FAILED) { - s8 = []; - s9 = peg$parse_(); - if (s9 !== peg$FAILED) { - while (s9 !== peg$FAILED) { - s8.push(s9); - s9 = peg$parse_(); - } - } else { - s8 = peg$FAILED; - } - if (s8 !== peg$FAILED) { - s9 = peg$parseidentifier(); - if (s9 !== peg$FAILED) { - s5 = s9; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - if (s5 === peg$FAILED) { - s5 = null; - } - s6 = []; - s7 = peg$parse_(); - while (s7 !== peg$FAILED) { - s6.push(s7); - s7 = peg$parse_(); - } - s7 = peg$currPos; - peg$silentFails++; - s8 = peg$parsenewLine(); - peg$silentFails--; - if (s8 !== peg$FAILED) { - peg$currPos = s7; - s7 = undefined; - } else { - s7 = peg$FAILED; - } - if (s7 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f2(s4, s5); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parsestring() { - var s0, s1, s2, s3, s4; - - var key = peg$currPos * 12 + 4; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 39) { - s2 = peg$c3; - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e4); } - } - if (s2 !== peg$FAILED) { - s3 = []; - if (peg$r0.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e5); } - } - while (s4 !== peg$FAILED) { - s3.push(s4); - if (peg$r0.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e5); } - } - } - if (input.charCodeAt(peg$currPos) === 39) { - s4 = peg$c3; - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e4); } - } - if (s4 !== peg$FAILED) { - s1 = s3; - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f3(s1); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 34) { - s2 = peg$c4; - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e6); } - } - if (s2 !== peg$FAILED) { - s3 = []; - if (peg$r1.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e7); } - } - while (s4 !== peg$FAILED) { - s3.push(s4); - if (peg$r1.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e7); } - } - } - if (input.charCodeAt(peg$currPos) === 34) { - s4 = peg$c4; - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e6); } - } - if (s4 !== peg$FAILED) { - s1 = s3; - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f4(s1); - } - s0 = s1; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e3); } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parseignore() { - var s0, s1; - - var key = peg$currPos * 12 + 5; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - s0 = []; - s1 = peg$parseany(); - if (s1 === peg$FAILED) { - s1 = peg$parsenewLine(); - if (s1 === peg$FAILED) { - s1 = peg$parse_(); - } - } - while (s1 !== peg$FAILED) { - s0.push(s1); - s1 = peg$parseany(); - if (s1 === peg$FAILED) { - s1 = peg$parsenewLine(); - if (s1 === peg$FAILED) { - s1 = peg$parse_(); - } - } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parsecomment() { - var s0, s1, s2, s3, s4; - - var key = peg$currPos * 12 + 6; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - peg$silentFails++; - s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c5) { - s1 = peg$c5; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e9); } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseany(); - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseany(); - } - s3 = peg$currPos; - peg$silentFails++; - s4 = peg$parsenewLine(); - peg$silentFails--; - if (s4 !== peg$FAILED) { - peg$currPos = s3; - s3 = undefined; - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f5(); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e8); } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parsedelimitedComment() { - var s0, s1, s2, s3; - - var key = peg$currPos * 12 + 7; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - peg$silentFails++; - s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c6) { - s1 = peg$c6; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e10); } - } - if (s1 !== peg$FAILED) { - s2 = []; - if (peg$r2.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e11); } - } - while (s3 !== peg$FAILED) { - s2.push(s3); - if (peg$r2.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e11); } - } - } - if (input.substr(peg$currPos, 2) === peg$c7) { - s3 = peg$c7; - peg$currPos += 2; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e12); } - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f6(); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e8); } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parse_() { - var s0, s1; - - var key = peg$currPos * 12 + 8; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - peg$silentFails++; - if (peg$r3.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e14); } - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e13); } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parsenewLine() { - var s0, s1; - - var key = peg$currPos * 12 + 9; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - peg$silentFails++; - if (peg$r4.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e16); } - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e15); } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parseany() { - var s0, s1; - - var key = peg$currPos * 12 + 10; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - peg$silentFails++; - if (peg$r5.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e18); } - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e17); } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - function peg$parseidentifier() { - var s0, s1, s2, s3, s4; - - var key = peg$currPos * 12 + 11; - var cached = peg$resultsCache[key]; - - if (cached) { - peg$currPos = cached.nextPos; - - return cached.result; - } - - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$currPos; - s2 = []; - if (peg$r6.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e20); } - } - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - if (peg$r6.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e20); } - } - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - s3 = []; - if (peg$r7.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e21); } - } - while (s4 !== peg$FAILED) { - s3.push(s4); - if (peg$r7.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e21); } - } - } - s2 = [s2, s3]; - s1 = s2; - } else { - peg$currPos = s1; - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f7(); - } - s0 = s1; - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e19); } - } - - peg$resultsCache[key] = { nextPos: peg$currPos, result: s0 }; - - return s0; - } - - peg$result = peg$startRuleFunction(); - - if (peg$result !== peg$FAILED && peg$currPos === input.length) { - return peg$result; - } else { - if (peg$result !== peg$FAILED && peg$currPos < input.length) { - peg$fail(peg$endExpectation()); - } - - throw peg$buildStructuredError( - peg$maxFailExpected, - peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, - peg$maxFailPos < input.length - ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) - : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) - ); - } -} - -module.exports = { - SyntaxError: peg$SyntaxError, - parse: peg$parse -};