diff --git a/bsconfig.json b/bsconfig.json index 359646a4..81ef6fe0 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -3,10 +3,16 @@ "reason": { "react-jsx": 3 }, - "sources": { - "dir": "src", - "subdirs": true - }, + "sources": [{ + "dir": "src", + "subdirs": true + }, + { + "dir": "showcase", + "type": "dev", + "subdirs": true + } + ], "bsc-flags": ["-bs-super-errors", "-bs-no-version-header"], "package-specs": [{ "module": "commonjs", diff --git a/package.json b/package.json index 90305344..59806ab0 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "clean": "bsb -clean-world", "parcel": "parcel ./src/index.html --public-url / --no-autoinstall -- watch", "parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall", + "showcase": "PORT=12345 parcel showcase/index.html", "server": "moduleserve ./ --port 8000", "predeploy": "parcel build ./src/index.html --no-source-maps --no-autoinstall", "deploy": "gh-pages -d dist", @@ -31,6 +32,7 @@ "bs-css": "^11.0.0", "bs-moment": "0.4.4", "bs-reform": "9.7.1", + "d3": "^5.15.0", "lenses-ppx": "4.0.0", "less": "^3.10.3", "lodash": "^4.17.15", diff --git a/showcase/Entries.re b/showcase/Entries.re new file mode 100644 index 00000000..ae4cef64 --- /dev/null +++ b/showcase/Entries.re @@ -0,0 +1 @@ +let entries = EntryTypes.[Continuous.entry]; \ No newline at end of file diff --git a/showcase/EntryTypes.re b/showcase/EntryTypes.re new file mode 100644 index 00000000..49a61a33 --- /dev/null +++ b/showcase/EntryTypes.re @@ -0,0 +1,30 @@ +type compEntry = { + mutable id: string, + title: string, + render: unit => React.element, + container: containerType, +} +and folderEntry = { + mutable id: string, + title: string, + children: list(navEntry), +} +and navEntry = + | CompEntry(compEntry) + | FolderEntry(folderEntry) +and containerType = + | FullWidth + | Sidebar; + +let entry = (~title, ~render): navEntry => { + CompEntry({id: "", title, render, container: FullWidth}); +}; + +// Maybe different api, this avoids breaking changes +let sidebar = (~title, ~render): navEntry => { + CompEntry({id: "", title, render, container: Sidebar}); +}; + +let folder = (~title, ~children): navEntry => { + FolderEntry({id: "", title, children}); +}; diff --git a/showcase/Lib.re b/showcase/Lib.re new file mode 100644 index 00000000..60c27f79 --- /dev/null +++ b/showcase/Lib.re @@ -0,0 +1,200 @@ +open EntryTypes; + +module HS = Belt.HashMap.String; + +let entriesByPath: HS.t(navEntry) = HS.make(~hintSize=100); + +/* Creates unique id's per scope based on title */ +let buildIds = entries => { + let genId = (title, path) => { + let noSpaces = Js.String.replaceByRe([%bs.re "/\\s+/g"], "-", title); + if (!HS.has(entriesByPath, path ++ "/" ++ noSpaces)) { + noSpaces; + } else { + let rec loop = num => { + let testId = noSpaces ++ "-" ++ string_of_int(num); + if (!HS.has(entriesByPath, path ++ "/" ++ testId)) { + testId; + } else { + loop(num + 1); + }; + }; + loop(2); + }; + }; + let rec processFolder = (f: folderEntry, curPath) => { + f.id = curPath ++ "/" ++ genId(f.title, curPath); + HS.set(entriesByPath, f.id, FolderEntry(f)); + f.children + |> E.L.iter(e => + switch (e) { + | CompEntry(c) => processEntry(c, f.id) + | FolderEntry(f) => processFolder(f, f.id) + } + ); + } + and processEntry = (c: compEntry, curPath) => { + c.id = curPath ++ "/" ++ genId(c.title, curPath); + HS.set(entriesByPath, c.id, CompEntry(c)); + }; + entries + |> E.L.iter(e => + switch (e) { + | CompEntry(c) => processEntry(c, "") + | FolderEntry(f) => processFolder(f, "") + } + ); +}; + +let entries = Entries.entries; +buildIds(entries); + +module Styles = { + open Css; + let pageContainer = style([display(`flex), height(`vh(100.))]); + let leftNav = + style([ + padding(`em(2.)), + flexBasis(`px(200)), + flexShrink(0.), + backgroundColor(`hex("eaeff3")), + boxShadows([ + Shadow.box( + ~x=px(-1), + ~blur=px(1), + ~inset=true, + rgba(0, 0, 0, 0.1), + ), + ]), + ]); + + let folderNav = + style([ + selector( + ">h4", + [ + cursor(`pointer), + margin2(~v=`em(0.3), ~h=`zero), + hover([color(`hex("7089ad"))]), + ], + ), + ]); + let folderChildren = style([paddingLeft(`px(7))]); + let compNav = + style([ + cursor(`pointer), + paddingBottom(`px(3)), + hover([color(`hex("7089ad"))]), + ]); + let compContainer = style([padding(`em(2.)), flexGrow(1.)]); + // Approximate sidebar container for entry + let sidebarContainer = style([maxWidth(`px(430))]); + let folderChildContainer = style([marginBottom(`em(2.))]); +}; + +let baseUrl = "/showcase/index.html"; + +module Index = { + type state = {route: ReasonReactRouter.url}; + + type action = + | ItemClick(string) + | ChangeRoute(ReasonReactRouter.url); + + let changeId = (id: string) => { + ReasonReactRouter.push(baseUrl ++ "#" ++ id); + (); + }; + + let buildNav = setRoute => { + let rec buildFolder = (f: folderEntry) => { +
+

changeId(f.id)}> f.title->React.string

+
+ {( + f.children + |> E.L.fmap(e => + switch (e) { + | FolderEntry(folder) => buildFolder(folder) + | CompEntry(entry) => buildEntry(entry) + } + ) + |> E.L.toArray + ) + ->React.array} +
+
; + } + and buildEntry = (e: compEntry) => { +
changeId(e.id)}> + e.title->React.string +
; + }; + ( + entries + |> E.L.fmap(e => + switch (e) { + | FolderEntry(folder) => buildFolder(folder) + | CompEntry(entry) => buildEntry(entry) + } + ) + |> E.L.toArray + ) + ->React.array; + }; + + let renderEntry = e => { + switch (e.container) { + | FullWidth => e.render() + | Sidebar =>
{e.render()}
+ }; + }; + + [@react.component] + let make = () => { + let (route, setRoute) = + React.useState(() => { + let url: ReasonReactRouter.url = {path: [], hash: "", search: ""}; + url; + }); + + React.useState(() => { + ReasonReactRouter.watchUrl(url => setRoute(_ => url)); + (); + }) + |> ignore; + +
+
{buildNav(setRoute)}
+
+ {if (route.hash == "") { + React.null; + } else { + switch (HS.get(entriesByPath, route.hash)) { + | Some(navEntry) => + switch (navEntry) { + | CompEntry(c) => renderEntry(c) + | FolderEntry(f) => + /* Rendering immediate children */ + ( + f.children + |> E.L.fmap(child => + switch (child) { + | CompEntry(c) => +
+ {renderEntry(c)} +
+ | _ => React.null + } + ) + |> E.L.toArray + ) + ->React.array + } + | None =>
"Component not found"->React.string
+ }; + }} +
+
; + }; +}; \ No newline at end of file diff --git a/showcase/ShowcaseIndex.re b/showcase/ShowcaseIndex.re new file mode 100644 index 00000000..e3912686 --- /dev/null +++ b/showcase/ShowcaseIndex.re @@ -0,0 +1,2 @@ +ReactDOMRe.renderToElementWithId(
, "main"); +ReasonReactRouter.push(""); \ No newline at end of file diff --git a/showcase/entries/Continuous.re b/showcase/entries/Continuous.re new file mode 100644 index 00000000..5475fd92 --- /dev/null +++ b/showcase/entries/Continuous.re @@ -0,0 +1,19 @@ +open ForetoldComponents.Base; + +let data: DistributionTypes.xyShape = { + xs: [|0.2, 20., 80., 212., 330.|], + ys: [|0.0, 0.3, 0.5, 0.2, 0.1|], +}; + +let alerts = () => +
+
+
+ Shape.XYShape.integral} + color={`hex("333")} + /> +
+
; + +let entry = EntryTypes.(entry(~title="Pdf", ~render=alerts)); \ No newline at end of file diff --git a/showcase/index.html b/showcase/index.html new file mode 100644 index 00000000..7879deec --- /dev/null +++ b/showcase/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + Showcase + + + +
+ + + + \ No newline at end of file diff --git a/src/components/charts/CdfChart__Base.re b/src/components/charts/CdfChart__Base.re new file mode 100644 index 00000000..6abb50bc --- /dev/null +++ b/src/components/charts/CdfChart__Base.re @@ -0,0 +1,43 @@ +[@bs.module "./cdfChartReact.js"] +external cdfChart: ReasonReact.reactClass = "default"; + +type primaryDistribution = { + . + "xs": array(float), + "ys": array(float), +}; + +[@react.component] +let make = + ( + ~height=?, + ~verticalLine=?, + ~showVerticalLine=?, + ~marginBottom=?, + ~marginTop=?, + ~showDistributionLines=?, + ~maxX=?, + ~minX=?, + ~onHover=(f: float) => (), + ~primaryDistribution=?, + ~children=[||], + ) => + ReasonReact.wrapJsForReason( + ~reactClass=cdfChart, + ~props= + makeProps( + ~height?, + ~verticalLine?, + ~marginBottom?, + ~marginTop?, + ~onHover, + ~showVerticalLine?, + ~showDistributionLines?, + ~maxX?, + ~minX?, + ~primaryDistribution?, + (), + ), + children, + ) + |> ReasonReact.element; \ No newline at end of file diff --git a/src/components/charts/CdfChart__Plain.re b/src/components/charts/CdfChart__Plain.re new file mode 100644 index 00000000..72067a3e --- /dev/null +++ b/src/components/charts/CdfChart__Plain.re @@ -0,0 +1,41 @@ +module Styles = { + open Css; + let textOverlay = style([position(`absolute)]); + let mainText = style([fontSize(`em(1.1))]); + let secondaryText = style([fontSize(`em(0.9))]); + + let graph = chartColor => + style([ + position(`relative), + selector(".axis", [fontSize(`px(9))]), + selector(".domain", [display(`none)]), + selector(".tick line", [display(`none)]), + selector(".tick text", [color(`hex("bfcad4"))]), + selector(".chart .area-path", [SVG.fill(chartColor)]), + ]); +}; + +[@react.component] +let make = + ( + ~data, + ~minX=?, + ~maxX=?, + ~height=200, + ~color=`hex("111"), + ~onHover: float => unit, + ) => { +
+ Shape.XYShape.toJs} + /> +
; +}; \ No newline at end of file diff --git a/src/components/charts/ChartSimple.re b/src/components/charts/ChartSimple.re new file mode 100644 index 00000000..37f8697c --- /dev/null +++ b/src/components/charts/ChartSimple.re @@ -0,0 +1,24 @@ +module Styles = { + open Css; + let graph = chartColor => + style([ + selector(".axis", [fontSize(`px(9))]), + selector(".domain", [display(`none)]), + selector(".tick line", [display(`none)]), + selector(".tick text", [color(`hex("bfcad4"))]), + selector(".chart .area-path", [SVG.fill(chartColor)]), + ]); +}; + +[@react.component] +let make = (~minX=None, ~maxX=None, ~height=50, ~color=`hex("7e9db7")) => +
+ +
; \ No newline at end of file diff --git a/src/components/charts/ChartWithNumber.re b/src/components/charts/ChartWithNumber.re new file mode 100644 index 00000000..b8ac5bfb --- /dev/null +++ b/src/components/charts/ChartWithNumber.re @@ -0,0 +1,23 @@ +[@react.component] +let make = (~data, ~color=?) => { + let (x, setX) = React.useState(() => 0.); + let chart = + React.useMemo1( + () => setX(_ => r)} />, + [|data|], + ); +
+ chart +
{x |> E.Float.toString |> ReasonReact.string}
+
+ {Shape.Continuous.findY(x, data) + |> E.Float.toString + |> ReasonReact.string} +
+
+ {Shape.Continuous.findY(x, Shape.XYShape.integral(data)) + |> E.Float.toString + |> ReasonReact.string} +
+
; +}; \ No newline at end of file diff --git a/src/components/charts/cdfChartReact.js b/src/components/charts/cdfChartReact.js new file mode 100644 index 00000000..f72e5db7 --- /dev/null +++ b/src/components/charts/cdfChartReact.js @@ -0,0 +1,66 @@ +import React, { useEffect } from 'react'; +import { useSize } from 'react-use'; + +import chart from './cdfChartd3'; + +/** + * @param min + * @param max + * @returns {number} + */ +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +/** + * Example input: + * { + * xs: [50,100,300,400,500,600], + * ys: [0.1, 0.4, 0.6, 0.7,0.8, 0.9]} + * } + */ +function CdfChart(props) { + const id = "chart-" + getRandomInt(0, 100000); + const [sized, { width }] = useSize(() => { + return React.createElement("div", { + key: "resizable-div", + }); + }, { + width: props.width, + }); + + useEffect(() => { + chart() + .svgWidth(width) + .svgHeight(props.height) + .maxX(props.maxX) + .minX(props.minX) + .onHover(props.onHover) + .marginBottom(props.marginBottom || 15) + .marginLeft(5) + .marginRight(5) + .marginTop(5) + .showDistributionLines(props.showDistributionLines) + .verticalLine(props.verticalLine) + .showVerticalLine(props.showVerticalLine) + .container("#" + id) + .data({ primary: props.primaryDistribution }).render(); + }); + + const style = !!props.width ? { width: props.width + "px" } : {}; + const key = id; + + return React.createElement("div", { + style: { + paddingLeft: "10px", + paddingRight: "10px", + }, + }, [ + sized, + React.createElement("div", { id, style, key }), + ]); +} + +export default CdfChart; diff --git a/src/components/charts/cdfChartd3.js b/src/components/charts/cdfChartd3.js new file mode 100644 index 00000000..a57b8cb1 --- /dev/null +++ b/src/components/charts/cdfChartd3.js @@ -0,0 +1,302 @@ +import * as d3 from 'd3'; + +function chart() { + // Id for event handlings. + var attrs = { + id: 'ID' + Math.floor(Math.random() * 1000000), + svgWidth: 400, + svgHeight: 400, + + marginTop: 5, + marginBottom: 5, + marginRight: 5, + marginLeft: 5, + + container: 'body', + minX: false, + maxX: false, + scale: 'linear', + showDistributionLines: true, + areaColors: ['#E1E5EC', '#E1E5EC'], + logBase: 10, + verticalLine: 110, + showVerticalLine: true, + data: null, + onHover: (e) => {}, + }; + + var main = function main() { + // Drawing containers. + var container = d3.select(attrs.container); + + if (container.node() === null) { + return; + } + + var containerRect = container.node().getBoundingClientRect(); + if (containerRect.width > 0) { + attrs.svgWidth = containerRect.width; + } + + // Calculated properties. + // id for event handlings. + var calc = {}; + calc.id = 'ID' + Math.floor(Math.random() * 1000000); + calc.chartLeftMargin = attrs.marginLeft; + calc.chartTopMargin = attrs.marginTop; + calc.chartWidth = attrs.svgWidth - attrs.marginRight - attrs.marginLeft; + calc.chartHeight = attrs.svgHeight - attrs.marginBottom - attrs.marginTop; + + var areaColor = d3.scaleOrdinal().range(attrs.areaColors); + + var dataPoints = [getDatapoints('primary')]; + + // Scales. + var xScale; + + var xMin = d3.min(attrs.data.primary.xs); + var xMax = d3.max(attrs.data.primary.xs); + + if (attrs.scale === 'linear') { + xScale = d3.scaleLinear() + .domain([ + attrs.minX || xMin, + attrs.maxX || xMax + ]) + .range([0, calc.chartWidth]); + } else { + xScale = d3.scaleLog() + .base(attrs.logBase) + .domain([ + attrs.minX, + attrs.maxX, + ]) + .range([0, calc.chartWidth]); + } + + var yMin = d3.min(attrs.data.primary.ys); + var yMax = d3.max(attrs.data.primary.ys); + + var yScale = d3.scaleLinear() + .domain([ + yMin, + yMax, + ]) + .range([calc.chartHeight, 0]); + + // Axis generator. + var xAxis = d3.axisBottom(xScale) + .ticks(3) + .tickFormat(d => { + if (Math.abs(d) < 1) { + return d3.format(".2")(d); + } else if (xMin > 1000 && xMax < 3000) { + // Condition which identifies years; 2019, 2020, 2021. + return d3.format(".0")(d); + } else { + var prefix = d3.formatPrefix(".0", d); + var output = prefix(d); + return output.replace("G", "B"); + } + }); + + // Line generator. + var line = d3.line() + .x(function (d, i) { + return xScale(d.x); + }) + .y(function (d, i) { + return yScale(d.y); + }); + + var area = d3.area() + .x(function (d, i) { + return xScale(d.x); + }) + .y1(function (d, i) { + return yScale(d.y); + }) + .y0(calc.chartHeight); + + // Add svg. + var svg = container + .patternify({ tag: 'svg', selector: 'svg-chart-container' }) + .attr('width', "100%") + .attr('height', attrs.svgHeight) + .attr('pointer-events', 'none'); + + // Add container g element. + var chart = svg + .patternify({ tag: 'g', selector: 'chart' }) + .attr( + 'transform', + 'translate(' + calc.chartLeftMargin + ',' + calc.chartTopMargin + ')', + ); + + // Add axis. + chart.patternify({ tag: 'g', selector: 'axis' }) + .attr('transform', 'translate(' + 0 + ',' + calc.chartHeight + ')') + .call(xAxis); + + // Draw area. + chart + .patternify({ + tag: 'path', + selector: 'area-path', + data: dataPoints + }) + .attr('d', area) + .attr('fill', (d, i) => areaColor(i)) + .attr('opacity', (d, i) => i === 0 ? 0.7 : 1); + + // Draw line. + if (attrs.showDistributionLines) { + chart + .patternify({ + tag: 'path', + selector: 'line-path', + data: dataPoints + }) + .attr('d', line) + .attr('id', (d, i) => 'line-' + (i + 1)) + .attr('opacity', (d, i) => { + return i === 0 ? 0.7 : 1 + }) + .attr('fill', 'none'); + } + + if (attrs.showVerticalLine) { + chart.patternify({ tag: 'line', selector: 'v-line' }) + .attr('x1', xScale(attrs.verticalLine)) + .attr('x2', xScale(attrs.verticalLine)) + .attr('y1', 0) + .attr('y2', calc.chartHeight) + .attr('stroke-width', 1.5) + .attr('stroke-dasharray', '6 6') + .attr('stroke', 'steelblue'); + } + + var hoverLine = chart.patternify({ tag: 'line', selector: 'hover-line' }) + .attr('x1', 0) + .attr('x2', 0) + .attr('y1', 0) + .attr('y2', calc.chartHeight) + .attr('opacity', 0) + .attr('stroke-width', 1.5) + .attr('stroke-dasharray', '6 6') + .attr('stroke', '#22313F'); + + // Add drawing rectangle. + chart.patternify({ tag: 'rect', selector: 'mouse-rect' }) + .attr('width', calc.chartWidth) + .attr('height', calc.chartHeight) + .attr('fill', 'transparent') + .attr('pointer-events', 'all') + .on('mouseover', mouseover) + .on('mousemove', mouseover) + .on('mouseout', mouseout); + + function mouseover() { + var mouse = d3.mouse(this); + + hoverLine.attr('opacity', 1) + .attr('x1', mouse[0]) + .attr('x2', mouse[0]); + + var range = [ + xScale(dataPoints[dataPoints.length - 1][0].x), + xScale( + dataPoints + [dataPoints.length - 1] + [dataPoints[dataPoints.length - 1].length - 1].x, + ), + ]; + + var xValue = xScale.invert(mouse[0]).toFixed(2); + + if (mouse[0] > range[0] && mouse[0] < range[1]) { + attrs.onHover(xValue); + } else { + attrs.onHover(0.0); + } + } + + function mouseout() { + hoverLine.attr('opacity', 0) + } + + /** + * @param key + * @returns {[]} + */ + function getDatapoints(key) { + var dt = []; + var data = attrs.data[key]; + var len = data.xs.length; + + for (let i = 0; i < len; i++) { + dt.push({ + x: data.xs[i], + y: data.ys[i] + }) + } + + return dt; + } + }; + + d3.selection.prototype.patternify = function patternify(params) { + var container = this; + var selector = params.selector; + var elementTag = params.tag; + var data = params.data || [selector]; + + // Pattern in action. + var selection = container.selectAll('.' + selector).data(data, (d, i) => { + if (typeof d === 'object') { + if (d.id) { + return d.id; + } + } + return i; + }); + + selection.exit().remove(); + selection = selection.enter().append(elementTag).merge(selection); + selection.attr('class', selector); + return selection; + }; + + // @todo: Do not do like that. + // Dynamic keys functions. + // Attach variables to main function. + Object.keys(attrs).forEach((key) => { + main[key] = function (_) { + if (!arguments.length) { + return attrs[key]; + } + attrs[key] = _; + return main; + }; + }); + + //Set attrs as property. + main.attrs = attrs; + + //Exposed update functions. + main.data = function data(value) { + if (!arguments.length) return attrs.data; + attrs.data = value; + return main; + }; + + // Run visual. + main.render = function render() { + main(); + return main; + }; + + return main; +} + +export default chart; diff --git a/src/core/MixedCdf.re b/src/core/MixedCdf.re index 8ffb7b31..2006df48 100644 --- a/src/core/MixedCdf.re +++ b/src/core/MixedCdf.re @@ -8,8 +8,7 @@ type yPdfPoint = { let getY = (t: t, x: float): yPdfPoint => { continuous: Shape.Continuous.findY(x, t.continuous), discrete: Shape.Discrete.findY(x, t.discrete), -} /* }*/; +} /* discrete: Shape.Discrete.findY(x, t.discrete)*/; // let getIntegralY = (t: t, x: float): float => { -// continuous: Shape.Continuous.findY(x, t.continuous), -// discrete: Shape.Discrete.findY(x, t.discrete), \ No newline at end of file +// continuous: Shape.Continuous.findY(x, t.continuous), \ No newline at end of file diff --git a/src/core/Shape.re b/src/core/Shape.re index 507e05a7..7d0ebe06 100644 --- a/src/core/Shape.re +++ b/src/core/Shape.re @@ -3,7 +3,7 @@ open DistributionTypes; let _lastElement = (a: array('a)) => switch (Belt.Array.size(a)) { | 0 => None - | n => Belt.Array.get(a, n) + | n => Belt.Array.get(a, n - 1) }; module XYShape = { @@ -28,7 +28,8 @@ module XYShape = { Belt.Array.zip(p.xs, p.ys) ->Belt.Array.reduce([||], (items, (x, y)) => switch (_lastElement(items)) { - | Some((_, yLast)) => [|(x, fn(y, yLast))|] + | Some((_, yLast)) => + Belt.Array.concat(items, [|(x, fn(y, yLast))|]) | None => [|(x, y)|] } ) diff --git a/src/utility/CdfLibrary.js b/src/utility/CdfLibrary.js index 528e2dda..ab9134de 100644 --- a/src/utility/CdfLibrary.js +++ b/src/utility/CdfLibrary.js @@ -1,5 +1,6 @@ const { Cdf, + Pdf, ContinuousDistribution, ContinuousDistributionCombination, scoringFunctions, diff --git a/src/utility/CdfLibrary.re b/src/utility/CdfLibrary.re index fdc08392..2354331c 100644 --- a/src/utility/CdfLibrary.re +++ b/src/utility/CdfLibrary.re @@ -38,7 +38,7 @@ module JS = { module Distribution = { let toPdf = dist => dist |> JS.doAsDist(JS.cdfToPdf); - let toCdf = dist => dist |> JS.doAsDist(JS.cdfToPdf); + let toCdf = dist => dist |> JS.doAsDist(JS.pdfToCdf); let findX = (y, dist) => dist |> JS.distToJs |> JS.findX(y); let findY = (x, dist) => dist |> JS.distToJs |> JS.findY(x); let integral = dist => dist |> JS.distToJs |> JS.integral; diff --git a/yarn.lock b/yarn.lock index fa7acb48..2e276981 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2967,6 +2967,43 @@ d3@5.9.2: d3-voronoi "1" d3-zoom "1" +d3@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-5.15.0.tgz#ffd44958e6a3cb8a59a84429c45429b8bca5677a" + integrity sha512-C+E80SL2nLLtmykZ6klwYj5rPqB5nlfN5LdWEAVdWPppqTD8taoJi2PxLZjPeYT8FFRR2yucXq+kBlOnnvZeLg== + dependencies: + d3-array "1" + d3-axis "1" + d3-brush "1" + d3-chord "1" + d3-collection "1" + d3-color "1" + d3-contour "1" + d3-dispatch "1" + d3-drag "1" + d3-dsv "1" + d3-ease "1" + d3-fetch "1" + d3-force "1" + d3-format "1" + d3-geo "1" + d3-hierarchy "1" + d3-interpolate "1" + d3-path "1" + d3-polygon "1" + d3-quadtree "1" + d3-random "1" + d3-scale "2" + d3-scale-chromatic "1" + d3-selection "1" + d3-shape "1" + d3-time "1" + d3-time-format "2" + d3-timer "1" + d3-transition "1" + d3-voronoi "1" + d3-zoom "1" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"