Add functions to SquiggleChart
This commit is contained in:
parent
fa37da66ff
commit
5dc733fb56
|
@ -2,138 +2,16 @@ import * as React from 'react';
|
|||
import * as PropTypes from 'prop-types';
|
||||
import * as _ from 'lodash';
|
||||
import './button.css';
|
||||
import type { PowScale, Spec } from 'vega';
|
||||
import type { Spec } from 'vega';
|
||||
import { run } from '@squiggle/squiggle-lang';
|
||||
import type { DistPlus } from '@squiggle/squiggle-lang';
|
||||
import { createClassFromSpec } from 'react-vega';
|
||||
import * as chartSpecification from './spec-distributions.json'
|
||||
import * as percentilesSpec from './spec-pertentiles.json'
|
||||
|
||||
let scales : PowScale[] = [{
|
||||
"name": "xscale",
|
||||
"type": "pow",
|
||||
"exponent": {"signal": "xscale"},
|
||||
"range": "width",
|
||||
"zero": false,
|
||||
"nice": false,
|
||||
"domain": {
|
||||
"fields": [
|
||||
{ "data": "con", "field": "x"},
|
||||
{ "data": "dis", "field": "x"}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "yscale",
|
||||
"type": "pow",
|
||||
"exponent": {"signal": "yscale"},
|
||||
"range": "height",
|
||||
"nice": true,
|
||||
"zero": true,
|
||||
"domain": {
|
||||
"fields": [
|
||||
{ "data": "con", "field": "y"},
|
||||
{ "data": "dis", "field": "y"}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
let SquiggleVegaChart = createClassFromSpec({'spec': chartSpecification as Spec});
|
||||
|
||||
|
||||
let specification : Spec = {
|
||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||
"description": "A basic area chart example.",
|
||||
"width": 500,
|
||||
"height": 200,
|
||||
"padding": 5,
|
||||
"data": [{"name": "con"}, {"name": "dis"}],
|
||||
|
||||
"signals": [
|
||||
{
|
||||
"name": "mousex",
|
||||
"description": "x position of mouse",
|
||||
"update": "0",
|
||||
"on": [{"events": "mousemove", "update": "1-x()/width"}]
|
||||
},
|
||||
{
|
||||
"name": "xscale",
|
||||
"description": "The transform of the x scale",
|
||||
"value": 1.0,
|
||||
"bind": {
|
||||
"input": "range",
|
||||
"min": 0.1,
|
||||
"max": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "yscale",
|
||||
"description": "The transform of the y scale",
|
||||
"value": 1.0,
|
||||
"bind": {
|
||||
"input": "range",
|
||||
"min": 0.1,
|
||||
"max": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"scales": scales,
|
||||
|
||||
"axes": [
|
||||
{"orient": "bottom", "scale": "xscale", "tickCount": 20},
|
||||
{"orient": "left", "scale": "yscale"}
|
||||
],
|
||||
|
||||
"marks": [
|
||||
{
|
||||
"type": "area",
|
||||
"from": {"data": "con"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"tooltip": {"signal": "datum.cdf"}
|
||||
},
|
||||
"update": {
|
||||
"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: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'blue'}, {offset: 1.0, color: 'blue'} ] }"
|
||||
},
|
||||
"interpolate": {"value": "monotone"},
|
||||
"fillOpacity": {"value": 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"from": {"data": "dis"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"y2": {"scale": "yscale", "value": 0},
|
||||
"width": {"value": 1}
|
||||
},
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "x"},
|
||||
"y": {"scale": "yscale", "field": "y"},
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "symbol",
|
||||
"from": {"data": "dis"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"shape": {"value": "circle"},
|
||||
"width": {"value": 5},
|
||||
"tooltip": {"signal": "datum.y"},
|
||||
},
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "x"},
|
||||
"y": {"scale": "yscale", "field": "y"},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let SquiggleVegaChart = createClassFromSpec({'spec': specification});
|
||||
let SquigglePercentilesChart = createClassFromSpec({'spec': percentilesSpec as Spec});
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction
|
||||
|
@ -235,8 +113,58 @@ export const SquiggleChart = ({ squiggleString }: { squiggleString: string}) =>
|
|||
}
|
||||
}
|
||||
else if(chartResult.NAME === "Function"){
|
||||
console.log("Function time")
|
||||
// We are looking at a function. In this case, we draw a Percentiles chart
|
||||
let data = _.range(0,10,0.1).map((_,i) => {
|
||||
let x = i /10;
|
||||
console.log(chartResult);
|
||||
console.log("Run thing")
|
||||
if(chartResult.NAME=="Function"){
|
||||
let result = chartResult.VAL(x);
|
||||
console.log(result);
|
||||
if(result.tag == "Ok"){
|
||||
let percentileArray = [
|
||||
0.01,
|
||||
0.05,
|
||||
0.1,
|
||||
0.2,
|
||||
0.3,
|
||||
0.4,
|
||||
0.5,
|
||||
0.6,
|
||||
0.7,
|
||||
0.8,
|
||||
0.9,
|
||||
0.95,
|
||||
0.99
|
||||
]
|
||||
|
||||
let percentiles = getPercentiles(percentileArray, result.value);
|
||||
return {
|
||||
"x": x,
|
||||
"p1": percentiles[0],
|
||||
"p5": percentiles[1],
|
||||
"p10": percentiles[2],
|
||||
"p20": percentiles[3],
|
||||
"p30": percentiles[4],
|
||||
"p40": percentiles[5],
|
||||
"p50": percentiles[6],
|
||||
"p60": percentiles[7],
|
||||
"p70": percentiles[8],
|
||||
"p80": percentiles[9],
|
||||
"p90": percentiles[10],
|
||||
"p95": percentiles[11],
|
||||
"p99": percentiles[12]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
console.log(data);
|
||||
return <SquigglePercentilesChart data={{"facet": data}} />
|
||||
}
|
||||
}
|
||||
else if(result.tag == "Error") {
|
||||
// At this point, we came across an error. What was our error?
|
||||
|
@ -246,6 +174,79 @@ export const SquiggleChart = ({ squiggleString }: { squiggleString: string}) =>
|
|||
return (<p>{"Invalid Response"}</p>)
|
||||
};
|
||||
|
||||
function getPercentiles(percentiles:number[], t : DistPlus) {
|
||||
if(t.shape.tag == "Discrete") {
|
||||
let total = 0;
|
||||
let maxX = _.max(t.shape.value.xyShape.xs)
|
||||
let bounds = percentiles.map(_ => maxX);
|
||||
_.zipWith(t.shape.value.xyShape.xs,t.shape.value.xyShape.ys, (x,y) => {
|
||||
total += y
|
||||
percentiles.forEach((v, i) => {
|
||||
if(total > v && bounds[i] == maxX){
|
||||
bounds[i] = x
|
||||
}
|
||||
})
|
||||
});
|
||||
return bounds;
|
||||
}
|
||||
else if(t.shape.tag == "Continuous"){
|
||||
let total = 0;
|
||||
let maxX = _.max(t.shape.value.xyShape.xs)
|
||||
let totalY = _.sum(t.shape.value.xyShape.ys)
|
||||
let bounds = percentiles.map(_ => maxX);
|
||||
_.zipWith(t.shape.value.xyShape.xs,t.shape.value.xyShape.ys, (x,y) => {
|
||||
total += y / totalY;
|
||||
percentiles.forEach((v, i) => {
|
||||
if(total > v && bounds[i] == maxX){
|
||||
bounds[i] = x
|
||||
}
|
||||
})
|
||||
});
|
||||
return bounds;
|
||||
}
|
||||
else if(t.shape.tag == "Mixed"){
|
||||
let discreteShape = t.shape.value.discrete.xyShape;
|
||||
let totalDiscrete = discreteShape.ys.reduce((a, b) => a + b);
|
||||
|
||||
let discretePoints = _.zip(discreteShape.xs, discreteShape.ys);
|
||||
let continuousShape = t.shape.value.continuous.xyShape;
|
||||
let continuousPoints = _.zip(continuousShape.xs, continuousShape.ys);
|
||||
|
||||
interface labeledPoint {
|
||||
x: number,
|
||||
y: number,
|
||||
type: "discrete" | "continuous"
|
||||
};
|
||||
|
||||
let markedDisPoints : labeledPoint[] = discretePoints.map(([x,y]) => ({x: x, y: y, type: "discrete"}))
|
||||
let markedConPoints : labeledPoint[] = continuousPoints.map(([x,y]) => ({x: x, y: y, type: "continuous"}))
|
||||
|
||||
let sortedPoints = _.sortBy(markedDisPoints.concat(markedConPoints), 'x')
|
||||
|
||||
let totalContinuous = 1 - totalDiscrete;
|
||||
let totalY = continuousShape.ys.reduce((a:number, b:number) => a + b);
|
||||
|
||||
let total = 0;
|
||||
let maxX = _.max(sortedPoints.map(x => x.x));
|
||||
let bounds = percentiles.map(_ => maxX);
|
||||
sortedPoints.map((point: labeledPoint) => {
|
||||
if(point.type == "discrete") {
|
||||
total += point.y;
|
||||
}
|
||||
else if (point.type == "continuous") {
|
||||
total += point.y / totalY * totalContinuous;
|
||||
}
|
||||
percentiles.forEach((v,i) => {
|
||||
if(total > v && bounds[i] == maxX){
|
||||
bounds[i] = total;
|
||||
}
|
||||
})
|
||||
return total;
|
||||
});
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
SquiggleChart.propTypes = {
|
||||
/**
|
||||
* Squiggle String
|
||||
|
|
122
packages/components/src/stories/spec-distributions.json
Normal file
122
packages/components/src/stories/spec-distributions.json
Normal file
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||
"description": "A basic area chart example.",
|
||||
"width": 500,
|
||||
"height": 200,
|
||||
"padding": 5,
|
||||
"data": [{"name": "con"}, {"name": "dis"}],
|
||||
|
||||
"signals": [
|
||||
{
|
||||
"name": "mousex",
|
||||
"description": "x position of mouse",
|
||||
"update": "0",
|
||||
"on": [{"events": "mousemove", "update": "1-x()/width"}]
|
||||
},
|
||||
{
|
||||
"name": "xscale",
|
||||
"description": "The transform of the x scale",
|
||||
"value": 1.0,
|
||||
"bind": {
|
||||
"input": "range",
|
||||
"min": 0.1,
|
||||
"max": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "yscale",
|
||||
"description": "The transform of the y scale",
|
||||
"value": 1.0,
|
||||
"bind": {
|
||||
"input": "range",
|
||||
"min": 0.1,
|
||||
"max": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"scales": [{
|
||||
"name": "xscale",
|
||||
"type": "pow",
|
||||
"exponent": {"signal": "xscale"},
|
||||
"range": "width",
|
||||
"zero": false,
|
||||
"nice": false,
|
||||
"domain": {
|
||||
"fields": [
|
||||
{ "data": "con", "field": "x"},
|
||||
{ "data": "dis", "field": "x"}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "yscale",
|
||||
"type": "pow",
|
||||
"exponent": {"signal": "yscale"},
|
||||
"range": "height",
|
||||
"nice": true,
|
||||
"zero": true,
|
||||
"domain": {
|
||||
"fields": [
|
||||
{ "data": "con", "field": "y"},
|
||||
{ "data": "dis", "field": "y"}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"axes": [
|
||||
{"orient": "bottom", "scale": "xscale", "tickCount": 20},
|
||||
{"orient": "left", "scale": "yscale"}
|
||||
],
|
||||
|
||||
"marks": [
|
||||
{
|
||||
"type": "area",
|
||||
"from": {"data": "con"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"tooltip": {"signal": "datum.cdf"}
|
||||
},
|
||||
"update": {
|
||||
"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: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'steelblue'}, {offset: clamp(mousex, 0, 1), color: 'blue'}, {offset: 1.0, color: 'blue'} ] }"
|
||||
},
|
||||
"interpolate": {"value": "monotone"},
|
||||
"fillOpacity": {"value": 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "rect",
|
||||
"from": {"data": "dis"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"y2": {"scale": "yscale", "value": 0},
|
||||
"width": {"value": 1}
|
||||
},
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "x"},
|
||||
"y": {"scale": "yscale", "field": "y"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "symbol",
|
||||
"from": {"data": "dis"},
|
||||
"encode": {
|
||||
"enter": {
|
||||
"shape": {"value": "circle"},
|
||||
"width": {"value": 5},
|
||||
"tooltip": {"signal": "datum.y"}
|
||||
},
|
||||
"update": {
|
||||
"x": {"scale": "xscale", "field": "x"},
|
||||
"y": {"scale": "yscale", "field": "y"}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
208
packages/components/src/stories/spec-pertentiles.json
Normal file
208
packages/components/src/stories/spec-pertentiles.json
Normal file
|
@ -0,0 +1,208 @@
|
|||
{
|
||||
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||
"width": 500,
|
||||
"height": 400,
|
||||
"padding": 5,
|
||||
"data": [
|
||||
{
|
||||
"name": "facet",
|
||||
"values": [],
|
||||
"format": { "type": "json", "parse": { "timestamp": "date" } }
|
||||
},
|
||||
{
|
||||
"name": "table",
|
||||
"source": "facet",
|
||||
"transform": [
|
||||
{
|
||||
"type": "aggregate",
|
||||
"groupby": ["x"],
|
||||
"ops": [
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean",
|
||||
"mean"
|
||||
],
|
||||
"fields": [
|
||||
"p1",
|
||||
"p5",
|
||||
"p10",
|
||||
"p20",
|
||||
"p30",
|
||||
"p40",
|
||||
"p50",
|
||||
"p60",
|
||||
"p70",
|
||||
"p80",
|
||||
"p90",
|
||||
"p95",
|
||||
"p99"
|
||||
],
|
||||
"as": [
|
||||
"p1",
|
||||
"p5",
|
||||
"p10",
|
||||
"p20",
|
||||
"p30",
|
||||
"p40",
|
||||
"p50",
|
||||
"p60",
|
||||
"p70",
|
||||
"p80",
|
||||
"p90",
|
||||
"p95",
|
||||
"p99"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scales": [
|
||||
{
|
||||
"name": "xscale",
|
||||
"type": "linear",
|
||||
"nice": true,
|
||||
"domain": { "data": "facet", "field": "x" },
|
||||
"range": "width"
|
||||
},
|
||||
{
|
||||
"name": "yscale",
|
||||
"type": "linear",
|
||||
"range": "height",
|
||||
"nice": true,
|
||||
"zero": true,
|
||||
"domain": { "data": "facet", "field": "p99" }
|
||||
}
|
||||
],
|
||||
"axes": [
|
||||
{
|
||||
"orient": "bottom",
|
||||
"scale": "xscale",
|
||||
"grid": false,
|
||||
"tickSize": 2,
|
||||
"encode": {
|
||||
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
||||
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
||||
}
|
||||
},
|
||||
{
|
||||
"orient": "left",
|
||||
"scale": "yscale",
|
||||
"grid": false,
|
||||
"domain": false,
|
||||
"tickSize": 2,
|
||||
"encode": {
|
||||
"grid": { "enter": { "stroke": { "value": "#ccc" } } },
|
||||
"ticks": { "enter": { "stroke": { "value": "#ccc" } } }
|
||||
}
|
||||
}
|
||||
],
|
||||
"marks": [
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "area",
|
||||
"from": { "data": "table" },
|
||||
"encode": {
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
|
|
Loading…
Reference in New Issue
Block a user