diff --git a/showcase/entries/Continuous.re b/showcase/entries/Continuous.re index 832d6789..4d56c583 100644 --- a/showcase/entries/Continuous.re +++ b/showcase/entries/Continuous.re @@ -1,5 +1,3 @@ -open ForetoldComponents.Base; - let data: DistributionTypes.xyShape = { xs: [|1., 10., 10., 200., 250., 292., 330.|], ys: [|0.0, 0.0, 0.1, 0.3, 0.5, 0.2, 0.1|], diff --git a/src/components/charts/CdfChart__Base.re b/src/components/charts/CdfChart__Base.re index 273bc028..ffcaf2f0 100644 --- a/src/components/charts/CdfChart__Base.re +++ b/src/components/charts/CdfChart__Base.re @@ -11,16 +11,17 @@ type primaryDistribution = { let make = ( ~height=?, - ~verticalLine=?, - ~showVerticalLine=?, ~marginBottom=?, ~marginTop=?, - ~showDistributionLines=?, ~maxX=?, ~minX=?, ~onHover=(f: float) => (), ~primaryDistribution=?, ~scale=?, + ~showDistributionLines=?, + ~showVerticalLine=?, + ~timeScale=?, + ~verticalLine=?, ~children=[||], ) => ReasonReact.wrapJsForReason( @@ -28,16 +29,17 @@ let make = ~props= makeProps( ~height?, - ~verticalLine?, ~marginBottom?, ~marginTop?, - ~onHover, - ~showVerticalLine?, - ~showDistributionLines?, ~maxX?, ~minX?, + ~onHover, ~primaryDistribution?, ~scale?, + ~showDistributionLines?, + ~showVerticalLine?, + ~timeScale?, + ~verticalLine?, (), ), children, diff --git a/src/components/charts/CdfChart__Plain.re b/src/components/charts/CdfChart__Plain.re index 8f4859de..16d3b37a 100644 --- a/src/components/charts/CdfChart__Plain.re +++ b/src/components/charts/CdfChart__Plain.re @@ -18,26 +18,28 @@ module Styles = { [@react.component] let make = ( - ~data, - ~minX=?, - ~maxX=?, - ~scale=?, - ~height=200, ~color=`hex("111"), + ~data, + ~height=200, + ~maxX=?, + ~minX=?, ~onHover: float => unit, + ~scale=?, + ~timeScale=?, ) => {
Shape.XYShape.toJs} + showDistributionLines=false + showVerticalLine=false />
; }; \ No newline at end of file diff --git a/src/components/charts/GenericDistributionChart.re b/src/components/charts/GenericDistributionChart.re index 3e0b96f7..f7402f8b 100644 --- a/src/components/charts/GenericDistributionChart.re +++ b/src/components/charts/GenericDistributionChart.re @@ -2,16 +2,19 @@ module Continuous = { [@react.component] let make = (~data, ~unit) => { let (x, setX) = React.useState(() => 0.); + let timeScale = unit |> DistributionTypes.DistributionUnit.toJson; let chart = React.useMemo1( () => setX(_ => r)} />, [|data|], ); +
chart diff --git a/src/components/charts/cdfChartD3.js b/src/components/charts/cdfChartD3.js index a341cba0..417ca247 100644 --- a/src/components/charts/cdfChartD3.js +++ b/src/components/charts/cdfChartD3.js @@ -1,7 +1,8 @@ +const _ = require('lodash'); const d3 = require('d3'); const moment = require('moment'); -class Chart { +export class CdfChartD3 { constructor() { this.attrs = { @@ -13,10 +14,11 @@ class Chart { marginRight: 50, marginLeft: 5, - container: 'body', + container: null, minX: false, maxX: false, scale: 'linear', + timeScale: null, showDistributionLines: true, areaColors: ['#E1E5EC', '#E1E5EC'], logBase: 10, @@ -28,6 +30,7 @@ class Chart { }; this.hoverLine = null; this.xScale = null; + this.xScaleTime = null; this.dataPoints = null; this.mouseover = this.mouseover.bind(this); this.mouseout = this.mouseout.bind(this); @@ -59,6 +62,11 @@ class Chart { return this; } + timeScale(timeScale) { + this.attrs.timeScale = timeScale; + return this; + } + onHover(onHover) { this.attrs.onHover = onHover; return this; @@ -119,10 +127,7 @@ class Chart { const len = data.xs.length; for (let i = 0; i < len; i++) { - dt.push({ - x: data.xs[i], - y: data.ys[i] - }) + dt.push({ x: data.xs[i], y: data.ys[i] }); } return dt; @@ -133,6 +138,7 @@ class Chart { const container = d3.select(attrs.container); if (container.node() === null) return; + // Sets the width from the DOM element. const containerRect = container.node().getBoundingClientRect(); if (containerRect.width > 0) { attrs.svgWidth = containerRect.width; @@ -159,10 +165,6 @@ class Chart { this.xScale = d3.scaleLinear() .domain([attrs.minX || xMin, attrs.maxX || xMax]) .range([0, calc.chartWidth]); - } else if (attrs.scale === 'time') { - this.xScale = d3.scaleLinear() - .domain([new Date(2012, 0, 1), new Date(2020, 0, 31)]) - .range([0, calc.chartWidth]); } else { this.xScale = d3.scaleLog() .base(attrs.logBase) @@ -178,9 +180,17 @@ class Chart { .range([calc.chartHeight, 0]); // Axis generator. - if (attrs.scale === 'time') { + if (!!this.attrs.timeScale) { + const zero = _.get(this.attrs.timeScale, 'zero', moment()); + const unit = _.get(this.attrs.timeScale, 'unit', null); + const length = zero.clone().add('year', 5); + + this.xScaleTime = d3.scaleLinear() + .domain([zero.toDate(), length.toDate()]) + .range([0, calc.chartWidth]); + this.xAxis = d3.axisBottom() - .scale(this.xScale) + .scale(this.xScaleTime) .ticks(5) .tickFormat(this.formatDates); } else { @@ -329,16 +339,16 @@ class Chart { } } - d3.selection.prototype.patternify = function patternify(params) { const selector = params.selector; const elementTag = params.tag; const data = params.data || [selector]; - const selection = this.selectAll('.' + selector).data(data, (d, i) => { - if (typeof d === 'object' && d.id) return d.id; - return i; - }); + const selection = this.selectAll('.' + selector) + .data(data, (d, i) => { + if (typeof d === 'object' && d.id) return d.id; + return i; + }); selection.exit().remove(); @@ -348,9 +358,3 @@ d3.selection.prototype.patternify = function patternify(params) { .merge(selection) .attr('class', selector); }; - -function chart() { - return new Chart(); -} - -export default chart; diff --git a/src/components/charts/cdfChartReact.js b/src/components/charts/cdfChartReact.js index 95b2a61b..e2c48952 100644 --- a/src/components/charts/cdfChartReact.js +++ b/src/components/charts/cdfChartReact.js @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import { useSize } from 'react-use'; -import chart from './cdfChartD3'; +import { CdfChartD3 } from './cdfChartD3'; /** * @param min @@ -19,8 +19,9 @@ function getRandomInt(min, max) { * @returns {*} * @constructor */ -function CdfChart(props) { - const id = "chart-" + getRandomInt(0, 100000); +function CdfChartReact(props) { + const containerRef = React.createRef(); + const key = "cdf-chart-react-" + getRandomInt(0, 1000); const scale = props.scale || 'linear'; const style = !!props.width ? { width: props.width + "px" } : {}; @@ -33,7 +34,7 @@ function CdfChart(props) { }); useEffect(() => { - chart() + new CdfChartD3() .svgWidth(width) .svgHeight(props.height) .maxX(props.maxX) @@ -46,9 +47,10 @@ function CdfChart(props) { .showDistributionLines(props.showDistributionLines) .verticalLine(props.verticalLine) .showVerticalLine(props.showVerticalLine) - .container("#" + id) + .container(containerRef.current) .data({ primary: props.primaryDistribution }) .scale(scale) + .timeScale(props.timeScale) .render(); }); @@ -59,8 +61,12 @@ function CdfChart(props) { }, }, [ sized, - React.createElement("div", { id, style, key: id }), + React.createElement("div", { + key, + style, + ref: containerRef, + }), ]); } -export default CdfChart; +export default CdfChartReact; diff --git a/src/core/DistributionTypes.re b/src/core/DistributionTypes.re index d19070df..cfc5cf29 100644 --- a/src/core/DistributionTypes.re +++ b/src/core/DistributionTypes.re @@ -46,4 +46,15 @@ type genericDistribution = { probabilityType, domain, unit: distributionUnit, +}; + +module DistributionUnit = { + let toJson = (distributionUnit: distributionUnit) => + switch (distributionUnit) { + | Time({zero, unit}) => + Js.Null.fromOption( + Some({"zero": zero, "unit": unit |> TimeTypes.TimeUnit.toString}), + ) + | _ => Js.Null.fromOption(None) + }; }; \ No newline at end of file diff --git a/src/core/TimeTypes.re b/src/core/TimeTypes.re index db08c5bd..75e158e2 100644 --- a/src/core/TimeTypes.re +++ b/src/core/TimeTypes.re @@ -20,6 +20,21 @@ type timePoint = { value: float, }; +module TimeUnit = { + let toString = (timeUnit: timeUnit) => + switch (timeUnit) { + | `days => "days" + | `hours => "hours" + | `milliseconds => "milliseconds" + | `minutes => "minutes" + | `months => "months" + | `quarters => "quarters" + | `seconds => "seconds" + | `weeks => "weeks" + | `years => "years" + }; +}; + module TimePoint = { let fromTimeVector = (timeVector, value): timePoint => {timeVector, value};