From 0c66bb057946e0573869159961040b46729c2ad8 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Fri, 24 Jun 2022 07:22:31 +0000 Subject: [PATCH] Add more options for distribution charts Changing title, color, tick format, and domain --- .../src/components/DistributionChart.tsx | 52 ++-- .../src/components/SquiggleChart.tsx | 24 ++ .../src/lib/distributionSpecBuilder.ts | 256 ++++++++++++++++++ 3 files changed, 299 insertions(+), 33 deletions(-) create mode 100644 packages/components/src/lib/distributionSpecBuilder.ts diff --git a/packages/components/src/components/DistributionChart.tsx b/packages/components/src/components/DistributionChart.tsx index 961178ff..af644d29 100644 --- a/packages/components/src/components/DistributionChart.tsx +++ b/packages/components/src/components/DistributionChart.tsx @@ -5,18 +5,15 @@ import { distributionError, distributionErrorToString, } from "@quri/squiggle-lang"; -import { Vega, VisualizationSpec } from "react-vega"; -import * as chartSpecification from "../vega-specs/spec-distributions.json"; +import { Vega } from "react-vega"; import { ErrorAlert } from "./Alert"; import { useSize } from "react-use"; import clsx from "clsx"; import { - linearXScale, - logXScale, - linearYScale, - expYScale, -} from "./DistributionVegaScales"; + buildVegaSpec, + DistributionChartSpecOptions, +} from "../lib/distributionSpecBuilder"; import { NumberShower } from "./NumberShower"; export type DistributionPlottingSettings = { @@ -24,27 +21,26 @@ export type DistributionPlottingSettings = { showSummary: boolean; /** Whether to show the user graph controls (scale etc) */ showControls: boolean; - /** Set the x scale to be logarithmic by deault */ - logX: boolean; - /** Set the y scale to be exponential by deault */ - expY: boolean; -}; +} & DistributionChartSpecOptions; export type DistributionChartProps = { distribution: Distribution; width?: number; height: number; + actions?: boolean; } & DistributionPlottingSettings; -export const DistributionChart: React.FC = ({ - distribution, - height, - showSummary, - width, - showControls, - logX, - expY, -}) => { +export const DistributionChart: React.FC = (props) => { + const { + distribution, + height, + showSummary, + width, + showControls, + logX, + expY, + actions = false, + } = props; const [isLogX, setLogX] = React.useState(logX); const [isExpY, setExpY] = React.useState(expY); @@ -64,7 +60,7 @@ export const DistributionChart: React.FC = ({ const massBelow0 = shape.value.continuous.some((x) => x.x <= 0) || shape.value.discrete.some((x) => x.x <= 0); - const spec = buildVegaSpec(isLogX, isExpY); + const spec = buildVegaSpec(props); let widthProp = width ? width : size.width; if (widthProp < 20) { @@ -82,7 +78,7 @@ export const DistributionChart: React.FC = ({ data={{ con: shape.value.continuous, dis: shape.value.discrete }} width={widthProp - 10} height={height} - actions={false} + actions={actions} /> ) : ( @@ -116,16 +112,6 @@ export const DistributionChart: React.FC = ({ return sized; }; -function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec { - return { - ...chartSpecification, - scales: [ - isLogX ? logXScale : linearXScale, - isExpY ? expYScale : linearYScale, - ], - } as VisualizationSpec; -} - interface CheckBoxProps { label: string; onChange: (x: boolean) => void; diff --git a/packages/components/src/components/SquiggleChart.tsx b/packages/components/src/components/SquiggleChart.tsx index f7bb7ace..e01d6920 100644 --- a/packages/components/src/components/SquiggleChart.tsx +++ b/packages/components/src/components/SquiggleChart.tsx @@ -41,6 +41,18 @@ export interface SquiggleChartProps { logX?: boolean; /** Set the y scale to be exponential by deault */ expY?: boolean; + /** How to format numbers on the x axis */ + tickFormat?: string; + /** Title of the graphed distribution */ + title?: string; + /** Color of the graphed distribution */ + color?: string; + /** Specify the lower bound of the x scale */ + minX?: number; + /** Specify the upper bound of the x scale */ + maxX?: number; + /** Whether to show vega actions to the user, so they can copy the chart spec */ + distributionChartActions?: boolean; } const defaultOnChange = () => {}; @@ -60,6 +72,12 @@ export const SquiggleChart: React.FC = ({ logX = false, expY = false, chartSettings = defaultChartSettings, + tickFormat, + minX, + maxX, + color, + title, + distributionChartActions, }) => { const { result } = useSquiggle({ code: squiggleString, @@ -78,6 +96,12 @@ export const SquiggleChart: React.FC = ({ showSummary, logX, expY, + format: tickFormat, + minX, + maxX, + color, + title, + actions: distributionChartActions, }; return ( diff --git a/packages/components/src/lib/distributionSpecBuilder.ts b/packages/components/src/lib/distributionSpecBuilder.ts new file mode 100644 index 00000000..b40a973d --- /dev/null +++ b/packages/components/src/lib/distributionSpecBuilder.ts @@ -0,0 +1,256 @@ +import { VisualizationSpec } from "react-vega"; +import type { LogScale, LinearScale, PowScale } 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 color of the chart */ + color?: string; + /** The title of the chart */ + title?: string; + /** The formatting of the ticks */ + format?: string; +}; + +export let linearXScale: LinearScale = { + name: "xscale", + clamp: true, + type: "linear", + range: "width", + zero: false, + nice: false, + domain: { + fields: [ + { + data: "con", + field: "x", + }, + { + data: "dis", + field: "x", + }, + ], + }, +}; +export let linearYScale: LinearScale = { + name: "yscale", + type: "linear", + range: "height", + zero: false, + domain: { + fields: [ + { + data: "con", + field: "y", + }, + { + data: "dis", + field: "y", + }, + ], + }, +}; + +export let logXScale: LogScale = { + name: "xscale", + type: "log", + range: "width", + zero: false, + base: 10, + nice: false, + clamp: true, + domain: { + fields: [ + { + data: "con", + field: "x", + }, + { + data: "dis", + field: "x", + }, + ], + }, +}; + +export let expYScale: PowScale = { + name: "yscale", + type: "pow", + exponent: 0.1, + range: "height", + zero: false, + nice: false, + domain: { + fields: [ + { + data: "con", + field: "y", + }, + { + data: "dis", + field: "y", + }, + ], + }, +}; + +export function buildVegaSpec( + specOptions: DistributionChartSpecOptions +): VisualizationSpec { + let { + format = ".9~s", + color = "#739ECC", + title, + minX, + maxX, + logX, + expY, + } = specOptions; + + let xScale = logX ? logXScale : linearXScale; + if (minX !== undefined) { + xScale = { ...xScale, domainMin: minX }; + } + + if (maxX !== undefined) { + xScale = { ...xScale, domainMax: maxX }; + } + + let spec: VisualizationSpec = { + $schema: "https://vega.github.io/schema/vega/v5.json", + description: "A basic area chart example", + width: 500, + height: 100, + padding: 5, + data: [ + { + name: "con", + }, + { + name: "dis", + }, + ], + signals: [], + scales: [xScale, expY ? expYScale : linearYScale], + axes: [ + { + orient: "bottom", + scale: "xscale", + labelColor: "#727d93", + tickColor: "#fff", + tickOpacity: 0.0, + domainColor: "#fff", + domainOpacity: 0.0, + format: format, + tickCount: 10, + }, + ], + marks: [ + { + type: "area", + from: { + data: "con", + }, + encode: { + update: { + interpolate: { value: "linear" }, + x: { + scale: "xscale", + field: "x", + }, + y: { + scale: "yscale", + field: "y", + }, + y2: { + scale: "yscale", + value: 0, + }, + fill: { + value: color, + }, + fillOpacity: { + value: 1, + }, + }, + }, + }, + { + type: "rect", + from: { + data: "dis", + }, + encode: { + enter: { + width: { + value: 1, + }, + }, + update: { + x: { + scale: "xscale", + field: "x", + }, + y: { + scale: "yscale", + field: "y", + }, + y2: { + scale: "yscale", + value: 0, + }, + fill: { + value: "#2f65a7", + }, + }, + }, + }, + { + type: "symbol", + from: { + data: "dis", + }, + encode: { + enter: { + shape: { + value: "circle", + }, + size: [{ value: 100 }], + tooltip: { + signal: "datum.y", + }, + }, + update: { + x: { + scale: "xscale", + field: "x", + }, + y: { + scale: "yscale", + field: "y", + }, + fill: { + value: "#1e4577", + }, + }, + }, + }, + ], + }; + if (title) { + spec = { + ...spec, + title: { + text: title, + }, + }; + } + + return spec; +}