From 4d0a522e9610bb2fa626c0ad713592983b72594d Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Wed, 6 Apr 2022 17:51:24 -0400 Subject: [PATCH] Moved NumberShower to its own Storybook, and slight cleanup to other code. --- packages/components/src/NumberShower.tsx | 98 ++++++ packages/components/src/SquiggleChart.tsx | 93 +----- .../components/src/spec-distributions.json | 138 +++++++-- packages/components/src/spec-percentiles.json | 283 ++++++++++++++---- .../src/stories/Introduction.stories.mdx | 7 +- .../src/stories/NumberShower.stories.mdx | 60 ++++ .../src/stories/SquiggleChart.stories.mdx | 19 +- 7 files changed, 504 insertions(+), 194 deletions(-) create mode 100644 packages/components/src/NumberShower.tsx create mode 100644 packages/components/src/stories/NumberShower.stories.mdx diff --git a/packages/components/src/NumberShower.tsx b/packages/components/src/NumberShower.tsx new file mode 100644 index 00000000..ca9dc943 --- /dev/null +++ b/packages/components/src/NumberShower.tsx @@ -0,0 +1,98 @@ +import * as React from "react"; +import _ from "lodash"; + +const orderOfMagnitudeNum = (n: number) => { + return Math.pow(10, n); +}; + +// 105 -> 3 +const orderOfMagnitude = (n: number) => { + return Math.floor(Math.log(n) / Math.LN10 + 0.000000001); +}; + +function withXSigFigs(number: number, sigFigs: number) { + const withPrecision = number.toPrecision(sigFigs); + const formatted = Number(withPrecision); + return `${formatted}`; +} + +class NumberShowerBuilder { + number: number; + precision: number; + + constructor(number: number, precision = 2) { + this.number = number; + this.precision = precision; + } + + convert() { + const number = Math.abs(this.number); + const response = this.evaluate(number); + if (this.number < 0) { + response.value = "-" + response.value; + } + return response; + } + + metricSystem(number: number, order: number) { + const newNumber = number / orderOfMagnitudeNum(order); + const precision = this.precision; + return `${withXSigFigs(newNumber, precision)}`; + } + + evaluate(number: number) { + if (number === 0) { + return { value: this.metricSystem(0, 0) }; + } + + const order = orderOfMagnitude(number); + if (order < -2) { + return { value: this.metricSystem(number, order), power: order }; + } else if (order < 4) { + return { value: this.metricSystem(number, 0) }; + } else if (order < 6) { + return { value: this.metricSystem(number, 3), symbol: "K" }; + } else if (order < 9) { + return { value: this.metricSystem(number, 6), symbol: "M" }; + } else if (order < 12) { + return { value: this.metricSystem(number, 9), symbol: "B" }; + } else if (order < 15) { + return { value: this.metricSystem(number, 12), symbol: "T" }; + } else { + return { value: this.metricSystem(number, order), power: order }; + } + } +} + +export function numberShow(number: number, precision = 2) { + const ns = new NumberShowerBuilder(number, precision); + return ns.convert(); +} + +export interface NumberShowerProps { + number: number; + precision?: number +} + +export let NumberShower: React.FC = ({ + number, + precision = 2 +}: NumberShowerProps) => { + let numberWithPresentation = numberShow(number, precision); + return ( + + {numberWithPresentation.value} + {numberWithPresentation.symbol} + {numberWithPresentation.power ? ( + + {"\u00b710"} + + {numberWithPresentation.power} + + + ) : ( + <> + )} + + ); +} diff --git a/packages/components/src/SquiggleChart.tsx b/packages/components/src/SquiggleChart.tsx index 2e4234d9..bb9294af 100644 --- a/packages/components/src/SquiggleChart.tsx +++ b/packages/components/src/SquiggleChart.tsx @@ -11,6 +11,7 @@ import type { import { createClassFromSpec } from "react-vega"; import * as chartSpecification from "./spec-distributions.json"; import * as percentilesSpec from "./spec-percentiles.json"; +import { NumberShower } from "./NumberShower"; let SquiggleVegaChart = createClassFromSpec({ spec: chartSpecification as Spec, @@ -71,7 +72,7 @@ export const SquiggleChart: React.FC = ({ onEnvChange(environment); let chartResults = exports.map((chartResult: exportDistribution) => { if (chartResult["NAME"] === "Float") { - return ; + return ; } else if (chartResult["NAME"] === "DistPlus") { let shape = chartResult.VAL.pointSetDist; if (shape.tag === "Continuous") { @@ -316,92 +317,4 @@ function getPercentiles(percentiles: number[], t: DistPlus) { }); return bounds; } -} - -function MakeNumberShower(props: { number: number; precision: number }) { - let numberWithPresentation = numberShow(props.number, props.precision); - return ( - - {numberWithPresentation.value} - {numberWithPresentation.symbol} - {numberWithPresentation.power ? ( - - {"\u00b710"} - - {numberWithPresentation.power} - - - ) : ( - <> - )} - - ); -} - -const orderOfMagnitudeNum = (n: number) => { - return Math.pow(10, n); -}; - -// 105 -> 3 -const orderOfMagnitude = (n: number) => { - return Math.floor(Math.log(n) / Math.LN10 + 0.000000001); -}; - -function withXSigFigs(number: number, sigFigs: number) { - const withPrecision = number.toPrecision(sigFigs); - const formatted = Number(withPrecision); - return `${formatted}`; -} - -class NumberShower { - number: number; - precision: number; - - constructor(number: number, precision = 2) { - this.number = number; - this.precision = precision; - } - - convert() { - const number = Math.abs(this.number); - const response = this.evaluate(number); - if (this.number < 0) { - response.value = "-" + response.value; - } - return response; - } - - metricSystem(number: number, order: number) { - const newNumber = number / orderOfMagnitudeNum(order); - const precision = this.precision; - return `${withXSigFigs(newNumber, precision)}`; - } - - evaluate(number: number) { - if (number === 0) { - return { value: this.metricSystem(0, 0) }; - } - - const order = orderOfMagnitude(number); - if (order < -2) { - return { value: this.metricSystem(number, order), power: order }; - } else if (order < 4) { - return { value: this.metricSystem(number, 0) }; - } else if (order < 6) { - return { value: this.metricSystem(number, 3), symbol: "K" }; - } else if (order < 9) { - return { value: this.metricSystem(number, 6), symbol: "M" }; - } else if (order < 12) { - return { value: this.metricSystem(number, 9), symbol: "B" }; - } else if (order < 15) { - return { value: this.metricSystem(number, 12), symbol: "T" }; - } else { - return { value: this.metricSystem(number, order), power: order }; - } - } -} - -export function numberShow(number: number, precision = 2) { - const ns = new NumberShower(number, precision); - return ns.convert(); -} +} \ No newline at end of file diff --git a/packages/components/src/spec-distributions.json b/packages/components/src/spec-distributions.json index 8a5bbff9..0c488441 100644 --- a/packages/components/src/spec-distributions.json +++ b/packages/components/src/spec-distributions.json @@ -4,14 +4,25 @@ "width": 500, "height": 200, "padding": 5, - "data": [{ "name": "con" }, { "name": "dis" }], - + "data": [ + { + "name": "con" + }, + { + "name": "dis" + } + ], "signals": [ { "name": "mousex", "description": "x position of mouse", "update": "0", - "on": [{ "events": "mousemove", "update": "1-x()/width" }] + "on": [ + { + "events": "mousemove", + "update": "1-x()/width" + } + ] }, { "name": "xscale", @@ -32,89 +43,152 @@ } } ], - "scales": [ { "name": "xscale", "type": "pow", - "exponent": { "signal": "xscale ? 0.1 : 1" }, + "exponent": { + "signal": "xscale ? 0.1 : 1" + }, "range": "width", "zero": false, "nice": false, "domain": { "fields": [ - { "data": "con", "field": "x" }, - { "data": "dis", "field": "x" } + { + "data": "con", + "field": "x" + }, + { + "data": "dis", + "field": "x" + } ] } }, { "name": "yscale", "type": "pow", - "exponent": { "signal": "yscale ? 0.1 : 1" }, + "exponent": { + "signal": "yscale ? 0.1 : 1" + }, "range": "height", "nice": true, "zero": true, "domain": { "fields": [ - { "data": "con", "field": "y" }, - { "data": "dis", "field": "y" } + { + "data": "con", + "field": "y" + }, + { + "data": "dis", + "field": "y" + } ] } } ], - - "axes": [{ "orient": "bottom", "scale": "xscale", "tickCount": 20 }], - + "axes": [ + { + "orient": "bottom", + "scale": "xscale", + "tickCount": 20 + } + ], "marks": [ { "type": "area", - "from": { "data": "con" }, + "from": { + "data": "con" + }, "encode": { "enter": { - "tooltip": { "signal": "datum.cdf" } + "tooltip": { + "signal": "datum.cdf" + } }, "update": { - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "y" }, - "y2": { "scale": "yscale", "value": 0 }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "y" + }, + "y2": { + "scale": "yscale", + "value": 0 + }, "fill": { "signal": "{gradient: 'linear', x1: 1, y1: 1, x2: 0, y2: 1, stops: [ {offset: 0.0, color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#11ac8f'}, {offset: clamp(mousex, 0, 1), color: '#1b6fac'}, {offset: 1.0, color: '#1b6fac'} ] }", "color": "#000" }, - "interpolate": { "value": "monotone" }, - "fillOpacity": { "value": 1 } + "interpolate": { + "value": "monotone" + }, + "fillOpacity": { + "value": 1 + } } } }, { "type": "rect", - "from": { "data": "dis" }, + "from": { + "data": "dis" + }, "encode": { "enter": { - "y2": { "scale": "yscale", "value": 0 }, - "width": { "value": 1 } + "y2": { + "scale": "yscale", + "value": 0 + }, + "width": { + "value": 1 + } }, "update": { - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "y" } + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "y" + } } } }, { "type": "symbol", - "from": { "data": "dis" }, + "from": { + "data": "dis" + }, "encode": { "enter": { - "shape": { "value": "circle" }, - "width": { "value": 5 }, - "tooltip": { "signal": "datum.y" } + "shape": { + "value": "circle" + }, + "width": { + "value": 5 + }, + "tooltip": { + "signal": "datum.y" + } }, "update": { - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "y" } + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "y" + } } } } ] -} +} \ No newline at end of file diff --git a/packages/components/src/spec-percentiles.json b/packages/components/src/spec-percentiles.json index c3b0a21f..6afa5c0b 100644 --- a/packages/components/src/spec-percentiles.json +++ b/packages/components/src/spec-percentiles.json @@ -7,7 +7,12 @@ { "name": "facet", "values": [], - "format": { "type": "json", "parse": { "timestamp": "date" } } + "format": { + "type": "json", + "parse": { + "timestamp": "date" + } + } }, { "name": "table", @@ -15,7 +20,9 @@ "transform": [ { "type": "aggregate", - "groupby": ["x"], + "groupby": [ + "x" + ], "ops": [ "mean", "mean", @@ -70,7 +77,10 @@ "name": "xscale", "type": "linear", "nice": true, - "domain": { "data": "facet", "field": "x" }, + "domain": { + "data": "facet", + "field": "x" + }, "range": "width" }, { @@ -79,7 +89,10 @@ "range": "height", "nice": true, "zero": true, - "domain": { "data": "facet", "field": "p99" } + "domain": { + "data": "facet", + "field": "p99" + } } ], "axes": [ @@ -89,8 +102,20 @@ "grid": false, "tickSize": 2, "encode": { - "grid": { "enter": { "stroke": { "value": "#ccc" } } }, - "ticks": { "enter": { "stroke": { "value": "#ccc" } } } + "grid": { + "enter": { + "stroke": { + "value": "#ccc" + } + } + }, + "ticks": { + "enter": { + "stroke": { + "value": "#ccc" + } + } + } } }, { @@ -100,109 +125,251 @@ "domain": false, "tickSize": 2, "encode": { - "grid": { "enter": { "stroke": { "value": "#ccc" } } }, - "ticks": { "enter": { "stroke": { "value": "#ccc" } } } + "grid": { + "enter": { + "stroke": { + "value": "#ccc" + } + } + }, + "ticks": { + "enter": { + "stroke": { + "value": "#ccc" + } + } + } } } ], "marks": [ { "type": "area", - "from": { "data": "table" }, + "from": { + "data": "table" + }, "encode": { - "enter": { "fill": { "value": "#4C78A8" } }, + "enter": { + "fill": { + "value": "#4C78A8" + } + }, "update": { - "interpolate": { "value": "monotone" }, - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "p1" }, - "y2": { "scale": "yscale", "field": "p99" }, - "opacity": { "value": 0.05 } + "interpolate": { + "value": "monotone" + }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "p1" + }, + "y2": { + "scale": "yscale", + "field": "p99" + }, + "opacity": { + "value": 0.05 + } } } }, { "type": "area", - "from": { "data": "table" }, + "from": { + "data": "table" + }, "encode": { - "enter": { "fill": { "value": "#4C78A8" } }, + "enter": { + "fill": { + "value": "#4C78A8" + } + }, "update": { - "interpolate": { "value": "monotone" }, - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "p5" }, - "y2": { "scale": "yscale", "field": "p95" }, - "opacity": { "value": 0.1 } + "interpolate": { + "value": "monotone" + }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "p5" + }, + "y2": { + "scale": "yscale", + "field": "p95" + }, + "opacity": { + "value": 0.1 + } } } }, { "type": "area", - "from": { "data": "table" }, + "from": { + "data": "table" + }, "encode": { - "enter": { "fill": { "value": "#4C78A8" } }, + "enter": { + "fill": { + "value": "#4C78A8" + } + }, "update": { - "interpolate": { "value": "monotone" }, - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "p10" }, - "y2": { "scale": "yscale", "field": "p90" }, - "opacity": { "value": 0.15 } + "interpolate": { + "value": "monotone" + }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "p10" + }, + "y2": { + "scale": "yscale", + "field": "p90" + }, + "opacity": { + "value": 0.15 + } } } }, { "type": "area", - "from": { "data": "table" }, + "from": { + "data": "table" + }, "encode": { - "enter": { "fill": { "value": "#4C78A8" } }, + "enter": { + "fill": { + "value": "#4C78A8" + } + }, "update": { - "interpolate": { "value": "monotone" }, - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "p20" }, - "y2": { "scale": "yscale", "field": "p80" }, - "opacity": { "value": 0.2 } + "interpolate": { + "value": "monotone" + }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "p20" + }, + "y2": { + "scale": "yscale", + "field": "p80" + }, + "opacity": { + "value": 0.2 + } } } }, { "type": "area", - "from": { "data": "table" }, + "from": { + "data": "table" + }, "encode": { - "enter": { "fill": { "value": "#4C78A8" } }, + "enter": { + "fill": { + "value": "#4C78A8" + } + }, "update": { - "interpolate": { "value": "monotone" }, - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "p30" }, - "y2": { "scale": "yscale", "field": "p70" }, - "opacity": { "value": 0.2 } + "interpolate": { + "value": "monotone" + }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "p30" + }, + "y2": { + "scale": "yscale", + "field": "p70" + }, + "opacity": { + "value": 0.2 + } } } }, { "type": "area", - "from": { "data": "table" }, + "from": { + "data": "table" + }, "encode": { - "enter": { "fill": { "value": "#4C78A8" } }, + "enter": { + "fill": { + "value": "#4C78A8" + } + }, "update": { - "interpolate": { "value": "monotone" }, - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "p40" }, - "y2": { "scale": "yscale", "field": "p60" }, - "opacity": { "value": 0.2 } + "interpolate": { + "value": "monotone" + }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "p40" + }, + "y2": { + "scale": "yscale", + "field": "p60" + }, + "opacity": { + "value": 0.2 + } } } }, { "type": "line", - "from": { "data": "table" }, + "from": { + "data": "table" + }, "encode": { "update": { - "interpolate": { "value": "monotone" }, - "stroke": { "value": "#4C78A8" }, - "strokeWidth": { "value": 2 }, - "opacity": { "value": 0.8 }, - "x": { "scale": "xscale", "field": "x" }, - "y": { "scale": "yscale", "field": "p50" } + "interpolate": { + "value": "monotone" + }, + "stroke": { + "value": "#4C78A8" + }, + "strokeWidth": { + "value": 2 + }, + "opacity": { + "value": 0.8 + }, + "x": { + "scale": "xscale", + "field": "x" + }, + "y": { + "scale": "yscale", + "field": "p50" + } } } } ] -} +} \ No newline at end of file diff --git a/packages/components/src/stories/Introduction.stories.mdx b/packages/components/src/stories/Introduction.stories.mdx index db08d6f5..525c12bb 100644 --- a/packages/components/src/stories/Introduction.stories.mdx +++ b/packages/components/src/stories/Introduction.stories.mdx @@ -2,8 +2,5 @@ import { Meta } from "@storybook/addon-docs"; -This is the component library for Squiggle. All of these components are react -components, and can be used in any application that you see fit. - -Currently, the only component that is provided is the SquiggleChart component. -This component allows you to render the result of a squiggle expression. +This is the component library for Squiggle. These are React +components, and can be used in any application that you see fit. \ No newline at end of file diff --git a/packages/components/src/stories/NumberShower.stories.mdx b/packages/components/src/stories/NumberShower.stories.mdx new file mode 100644 index 00000000..5f040be5 --- /dev/null +++ b/packages/components/src/stories/NumberShower.stories.mdx @@ -0,0 +1,60 @@ +import { NumberShower } from "../NumberShower"; +import { Canvas, Meta, Story, Props } from "@storybook/addon-docs"; + + + +# Number Shower + +The number shower is a simple component to display a number. + +It uses the symbols "K", "M", "B", and "T", to represent thousands, millions, billions, and trillions. Outside of that range, it uses scientific notation. + + + + {args => } + + + + + + {args => } + + + + + + {args => } + + + + + + {args => } + + + + diff --git a/packages/components/src/stories/SquiggleChart.stories.mdx b/packages/components/src/stories/SquiggleChart.stories.mdx index ad76b880..4c193b84 100644 --- a/packages/components/src/stories/SquiggleChart.stories.mdx +++ b/packages/components/src/stories/SquiggleChart.stories.mdx @@ -18,7 +18,7 @@ could be continuous, discrete or mixed. ## Distributions -An example of a normal distribution is: +### Continuous Distributions -An example of a Discrete distribution is: +### Discrete Distributions {Template.bind({})} -An example of a Mixed distribution is: +## Mixed distributions {Template.bind({})} @@ -66,7 +66,7 @@ to allow large and small numbers being printed cleanly. {Template.bind({})} @@ -75,14 +75,15 @@ to allow large and small numbers being printed cleanly. ## Functions -Finally, a function can be returned, and this shows how the distribution changes -over the axis between x = 0 and 10. +Full functions can be returned. These plot out the results of distributions between a set of x-coordinates. + +The default is show 10 points between 0 and 10. {Template.bind({})}