d3-shape#line JavaScript Examples

The following examples show how to use d3-shape#line. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AccelerationsChart.js    From openeew-dashboard with Apache License 2.0 6 votes vote down vote up
renderPath = (data, axis) => {
  /**
   * * Merges all axis data into one big array
   */
  const rawAcc = data.map((d) => [...d[axis]])
  const mergedAcc = [].concat.apply([], rawAcc)

  /**
   * * Each second of data has 32 readings & there's a maximum 50 seconds
   * * of data sent from the mock API. X values are evenly spaced, based
   * * on array indices, so maximum value should be 32 x 50 === 1600
   */
  const x = scaleLinear()
    .domain([1, 32 * 50])
    .range([0, VIEWBOX_WIDTH])
  const y = scaleLinear().domain(Y_DOMAIN).range([VIEWBOX_HEIGHT, 0])

  const lineBuilder = line()
    .curve(curveBasis)
    .x((d, i) => {
      return x(i + 1)
    })
    .y((d) => y(d))

  const renderedPath = lineBuilder(mergedAcc)

  return renderedPath
}
Example #2
Source File: connectionCalculator.js    From flume with MIT License 6 votes vote down vote up
calculateCurve = (from, to) => {
  const length = to.x - from.x;
  const thirdLength = length / 3;
  const curve = line().curve(curveBasis)([
    [from.x, from.y],
    [from.x + thirdLength, from.y],
    [from.x + thirdLength * 2, to.y],
    [to.x, to.y]
  ]);
  return curve;
}
Example #3
Source File: sparkline.js    From ThreatMapper with Apache License 2.0 6 votes vote down vote up
constructor(props, context) {
    super(props, context);

    this.x = scaleLinear();
    this.y = scaleLinear();
    this.line = line()
      .x(d => this.x(d.date))
      .y(d => this.y(d.value));
  }
Example #4
Source File: sparkline.js    From ThreatMapper with Apache License 2.0 6 votes vote down vote up
render() {
    const dash = 5;
    const hasData = this.props.data && this.props.data.length > 0;
    const strokeColor = this.props.hovered && hasData
      ? this.props.hoverColor
      : this.props.strokeColor;
    const strokeWidth = this.props.strokeWidth * (this.props.hovered ? HOVER_STROKE_MULTIPLY : 1);
    const strokeDasharray = hasData ? undefined : `${dash}, ${dash}`;
    const radius = this.props.circleRadius * (this.props.hovered ? HOVER_RADIUS_MULTIPLY : 1);
    const fillOpacity = this.props.hovered ? 1 : 0.6;
    const circleColor = hasData && this.props.hovered ? strokeColor : strokeColor;
    const graph = hasData ? this.getGraphData() : this.getEmptyGraphData();

    return (
      <div title={graph.title}>
        <svg width={this.props.width} height={this.props.height}>
          <path
            className="sparkline"
            fill="none"
            stroke={strokeColor}
            strokeWidth={strokeWidth}
            strokeDasharray={strokeDasharray}
            d={this.line(graph.data)}
          />
          {hasData && (
          <circle
            className="sparkcircle"
            cx={graph.lastX}
            cy={graph.lastY}
            fill={circleColor}
            fillOpacity={fillOpacity}
            stroke="none"
            r={radius}
          />
          )}
        </svg>
      </div>
    );
  }
Example #5
Source File: sparkline.js    From ThreatMapper with Apache License 2.0 6 votes vote down vote up
initRanges(hasCircle) {
    // adjust scales and leave some room for the circle on the right, upper, and lower edge
    let circleSpace = MARGIN;
    if (hasCircle) {
      circleSpace += Math.ceil(this.props.circleRadius * HOVER_RADIUS_MULTIPLY);
    }

    this.x.range([MARGIN, this.props.width - circleSpace]);
    this.y.range([this.props.height - circleSpace, circleSpace]);
    this.line.curve(curveLinear);
  }
Example #6
Source File: sparklines.js    From website with Apache License 2.0 5 votes vote down vote up
StatisticSparkline = ({ data, field, color }) => {
  const height = 40
  const width = 120
  const marginTop = 5
  const marginRight = 20
  const dates = []
  const values = []
  const averages = []
  data.forEach((item, key) => {
    if (key < 14) {
      return
    }
    let sum = 0
    for (let i = key; i > key - 7; i -= 1) {
      sum += data[i][field]
    }
    averages.push({
      value: sum / 7,
      date: DateTime.fromISO(item.date).toJSDate(),
    })
  })

  averages.forEach(item => {
    dates.push(item.date)
    values.push(item.value)
  })

  const dateDomain = extent(dates)

  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([0, width])

  const yMaxEffective = max(values)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height, 0])

  const lineFn = line()
    .defined(d => !Number.isNaN(d.value) && d.value !== null)
    .curve(curveNatural)
    .x(d => xScaleTime(d.date))
    .y(d => yScale(d.value))

  return (
    <svg
      className={sparklineStyles.sparkline}
      viewBox={`0 0 ${width + marginRight} ${height - marginTop}`}
      aria-hidden
    >
      <g transform={`translate(0 ${marginTop})`}>
        <defs>
          <marker
            id={`triangle-${field}`}
            refX="10"
            refY="6"
            markerWidth="30"
            markerHeight="30"
            markerUnits="userSpaceOnUse"
            orient="auto"
          >
            <path d="M 0 0 12 6 0 12 3 6" style={{ fill: color }} />
          </marker>
        </defs>
        <path
          d={lineFn(averages)}
          stroke={color}
          strokeWidth={3}
          fill="none"
          markerEnd={`url(#triangle-${field})`}
        />
      </g>
    </svg>
  )
}
Example #7
Source File: connectionCalculator.js    From flume with MIT License 5 votes vote down vote up
deleteConnection = ({ id }) => {
  const line = document.querySelector(`[data-connection-id="${id}"]`);
  if (line) line.parentNode.remove();
}
Example #8
Source File: connectionCalculator.js    From flume with MIT License 5 votes vote down vote up
deleteConnectionsByNodeId = nodeId => {
  const lines = document.querySelectorAll(
    `[data-output-node-id="${nodeId}"], [data-input-node-id="${nodeId}"]`
  );
  for (const line of lines) {
    line.parentNode.remove();
  }
}
Example #9
Source File: connectionCalculator.js    From flume with MIT License 5 votes vote down vote up
updateConnection = ({ line, from, to }) => {
  line.setAttribute("d", calculateCurve(from, to));
}
Example #10
Source File: connectionCalculator.js    From flume with MIT License 5 votes vote down vote up
createConnections = (nodes, {scale, stageId}, editorId) => {
  const stageRef = getStageRef(editorId);
  if(stageRef){
    const stage = stageRef.getBoundingClientRect();
    const stageHalfWidth = stage.width / 2;
    const stageHalfHeight = stage.height / 2;

    const byScale = value => (1 / scale) * value;

    Object.values(nodes).forEach(node => {
      if (node.connections && node.connections.inputs) {
        Object.entries(node.connections.inputs).forEach(
          ([inputName, outputs], k) => {
            outputs.forEach(output => {
              const fromPort = getPortRect(
                output.nodeId,
                output.portName,
                "output"
              );
              const toPort = getPortRect(node.id, inputName, "input");
              const portHalf = fromPort ? fromPort.width / 2 : 0;
              if (fromPort && toPort) {
                const id = output.nodeId + output.portName + node.id + inputName;
                const existingLine = document.querySelector(
                  `[data-connection-id="${id}"]`
                );
                if (existingLine) {
                  updateConnection({
                    line: existingLine,
                    from: {
                      x: byScale(fromPort.x - stage.x + portHalf - stageHalfWidth),
                      y: byScale(fromPort.y - stage.y + portHalf - stageHalfHeight)
                    },
                    to: {
                      x: byScale(toPort.x - stage.x + portHalf - stageHalfWidth),
                      y: byScale(toPort.y - stage.y + portHalf - stageHalfHeight)
                    }
                  });
                } else {
                  createSVG({
                    id,
                    outputNodeId: output.nodeId,
                    outputPortName: output.portName,
                    inputNodeId: node.id,
                    inputPortName: inputName,
                    from: {
                      x: byScale(fromPort.x - stage.x + portHalf - stageHalfWidth),
                      y: byScale(fromPort.y - stage.y + portHalf - stageHalfHeight)
                    },
                    to: {
                      x: byScale(toPort.x - stage.x + portHalf - stageHalfWidth),
                      y: byScale(toPort.y - stage.y + portHalf - stageHalfHeight)
                    },
                    stage: stageRef
                  });
                }
              }
            });
          }
        );
      }
    });
  }
}
Example #11
Source File: edge-container.js    From ThreatMapper with Apache License 2.0 5 votes vote down vote up
spline = line()
  .curve(curveBasis)
  .x(d => d.x)
  .y(d => d.y)
Example #12
Source File: CurvaturePlot.js    From likelihood with MIT License 4 votes vote down vote up
CurvatureChart = props => {
  const vizRef = useRef(null);

  // Stuff
  const margin = { top: 20, right: 20, bottom: 30, left: 50 };
  const durationTime = 200;
  const w = props.width - margin.left - margin.right;
  const h = props.width * 0.5 - margin.top - margin.bottom;
  const deriv = props.deriv;
  const llThetaMLE = props.llThetaMLE;
  const llThetaNull = props.llThetaNull;
  const test = props.test;
  const n = props.n;
  const muNull = props.muNull;

  // Axes min and max
  var xMax, xMin, llTheta;

  const sigmaTheta = Math.sqrt(props.sigma2Theta);
  xMax = props.muTheta + sigmaTheta * 5;
  xMin = props.muTheta - sigmaTheta * 5;
  llTheta = 0;

  const data1 = useMemo(
    () =>
      genEstLogLikCurve(
        10,
        props.muHat,
        props.sigma2Hat,
        props.muTheta,
        props.sigma2Theta
      ),
    [props.width, props.sigma2Hat, props.muHat]
  );

  const data2 = useMemo(
    () =>
      genEstLogLikCurve(
        n,
        props.muHat,
        props.sigma2Hat,
        props.muTheta,
        props.sigma2Theta
      ),
    [n, props.width, props.sigma2Hat, props.muHat]
  );

  const yMin = min(data1.y.filter(y => isFinite(y)));
  const yMax = max(data1.y);
  //const yMax = 0.05;
  // Create scales
  const yScale = scaleLinear()
    .domain([yMin, yMax])
    .range([h, 0]);

  const xScale = scaleLinear()
    .domain([xMin, xMax])
    .range([0, w]);

  // Scales and Axis
  const xAxis = axisBottom(xScale);

  // Line function
  const linex = line()
    .x(d => xScale(d[0]))
    .y(d => yScale(d[1]));

  // Update
  useEffect(() => {
    createChart(durationTime);
  }, [n, props.width]);

 
  const createChart = () => {
    const node = vizRef.current;

    const gOuter = select(node).attr(
      "transform",
      "translate(" + margin.left + "," + margin.top + ")"
    );

    // x Axis
    gOuter
      .selectAll("g.xAxis")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "xAxis");

    select(node)
      .select("g.xAxis")
      .attr("transform", "translate(" + 0 + "," + h + ")")
      .call(xAxis);

    // x label
    gOuter
      .selectAll("#x-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("class", "x-label");

    select(node)
      .selectAll(".x-label")
      .attr(
        "transform",
        "translate(" + w / 2 + " ," + (h + margin.bottom) + ")"
      )
      .text("μ");

    // y label
    gOuter
      .selectAll("#y-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("id", "y-label");

    select(node)
      .selectAll("#y-label")
      .attr("transform", "rotate(-90)")
      .attr("text-anchor", "middle")
      .attr("x", -(h / 2))
      .attr("y", -40)
      .text("Log-Likelihood");
  };
  const delta = xMax - xMin;
  return (
    <svg width={props.width} height={props.width * 0.5}>
      <g id="outer" ref={vizRef}>
        <g className="viz" clipPath="url(#clip)">
          <path d={linex(data1.data)} id="logLikReferenceCurve" />
          <path d={linex(data2.data)} id="logLikNCurve" />
          <line
            className={clsx("LRT", test == "LRT" && "highlight")}
            x1={xScale(xMin)}
            x2={xScale(xMax)}
            y1={yScale(llThetaMLE)}
            y2={yScale(llThetaMLE)}
          />
          <line
            className={clsx("LRT", test == "LRT" && "highlight")}
            x1={xScale(xMin)}
            x2={xScale(xMax)}
            y1={yScale(llThetaNull)}
            y2={yScale(llThetaNull)}
          />
          <line
            className={clsx("wald", test == "wald" && "highlight")}
            x1={xScale(props.muHat)}
            x2={xScale(props.muHat)}
            y1={yScale(yMin)}
            y2={yScale(yMax)}
          />

          <circle
            cx={xScale(muNull)}
            cy={yScale(llThetaNull)}
            r="5"
            fill="red"
            className="testPointMuNull"
          />
          <circle
            cx={xScale(props.muHat)}
            cy={yScale(llTheta)}
            r="5"
            className="testPointMu"
          />
        </g>
        <line
          className={clsx("wald", test == "wald" && "highlight")}
          x1={xScale(props.muNull)}
          x2={xScale(props.muNull)}
          y1={yScale(yMin)}
          y2={yScale(yMax)}
        />
        <line
          className={clsx("score", test == "score" && "highlight")}
          x1={xScale(props.muNull - delta)}
          x2={xScale(props.muNull + delta)}
          y1={yScale(llThetaNull - delta * deriv)}
          y2={yScale(llThetaNull + delta * deriv)}
        />
      </g>
      <defs>
        <clipPath id="clip">
          <rect id="clip-rect" x="0" y="-10" width={w} height={h + 10} />
        </clipPath>
      </defs>
    </svg>
  );
}
Example #13
Source File: renderLines.js    From react-orgchart with MIT License 4 votes vote down vote up
export function renderLines(config) {
  const {
    svg,
    links,
    nodeWidth,
    nodeHeight,
    borderColor,
    sourceNode,
    treeData,
    animationDuration,
  } = config;

  const parentNode = sourceNode || treeData;

  // Select all the links to render the lines
  const link = svg.selectAll('path.link').data(links, ({ source, target }) => {
    return `${source.data.id}-${target.data.id}`;
  });

  // Define the angled line function
  const angle = line()
    .x(d => d.x)
    .y(d => d.y)
    .curve(curveLinear);

  // Enter any new links at the parent's previous position.
  const linkEnter = link
    .enter()
    .insert('path', 'g')
    .attr('class', 'link')
    .attr('fill', 'none')
    .attr('stroke', borderColor)
    .attr('stroke-opacity', 1)
    .attr('stroke-width', 1.25)
    .attr('d', d => {
      const linePoints = [
        {
          x: d.source.x + parseInt(nodeWidth / 2, 10),
          y: d.source.y + margin,
        },
        {
          x: d.source.x + parseInt(nodeWidth / 2, 10),
          y: d.source.y + margin,
        },
        {
          x: d.source.x + parseInt(nodeWidth / 2, 10),
          y: d.source.y + margin,
        },
        {
          x: d.source.x + parseInt(nodeWidth / 2, 10),
          y: d.source.y + margin,
        },
      ];

      return angle(linePoints);
    });

  const linkUpdate = linkEnter.merge(link);

  // Transition links to their new position.
  linkUpdate
    .transition()
    .duration(animationDuration)
    .attr('d', d => {
      const linePoints = [
        {
          x: d.source.x + parseInt(nodeWidth / 2, 10),
          y: d.source.y + nodeHeight,
        },
        {
          x: d.source.x + parseInt(nodeWidth / 2, 10),
          y: d.target.y - margin,
        },
        {
          x: d.target.x + parseInt(nodeWidth / 2, 10),
          y: d.target.y - margin,
        },
        {
          x: d.target.x + parseInt(nodeWidth / 2, 10),
          y: d.target.y,
        },
      ];

      return angle(linePoints);
    });

  // Animate the existing links to the parent's new position
  link
    .exit()
    .transition()
    .duration(animationDuration)
    .attr('d', () => {
      const lineNode = config.sourceNode ? config.sourceNode : parentNode;
      const linePoints = [
        {
          x: lineNode.x + parseInt(nodeWidth / 2, 10),
          y: lineNode.y + nodeHeight + 2,
        },
        {
          x: lineNode.x + parseInt(nodeWidth / 2, 10),
          y: lineNode.y + nodeHeight + 2,
        },
        {
          x: lineNode.x + parseInt(nodeWidth / 2, 10),
          y: lineNode.y + nodeHeight + 2,
        },
        {
          x: lineNode.x + parseInt(nodeWidth / 2, 10),
          y: lineNode.y + nodeHeight + 2,
        },
      ];

      return angle(linePoints);
    });
}
Example #14
Source File: LogLikPlot.js    From likelihood with MIT License 4 votes vote down vote up
logLikCart = props => {
  const vizRef = useRef(null);
  const dispatch = useContext(VizDispatch);
  // Stuff
  const margin = { top: 60, right: 20, bottom: 40, left: 50 };
  const durationTime = 200;
  const w = props.width - margin.left - margin.right;
  const h = props.width * 0.4 - margin.top - margin.bottom;
  const sample = props.sample;
  const sigmaTheta = Math.sqrt(props.sigma2Theta);
  const deriv = props.deriv;
  const data1 = props.data;
  // Axes min and max
  var xMax, xMin, llTheta;
  if (props.thetaLab == "mu") {
    xMax = props.muTheta + sigmaTheta * 5;
    xMin = props.muTheta - sigmaTheta * 5;
    llTheta = useMemo(() => logLikSum(sample, props.mu, props.sigma2), [
      props.mu,
      props.sigma2,
      props.sample
    ]);
  } else if (props.thetaLab == "sigma") {
    const sigma2MLE = props.sigma2Theta;
    xMax = sigma2MLE + sigma2MLE * 2;
    xMin = sigma2MLE - sigma2MLE * 5;
    xMin = xMin < 0 ? 0.1 : xMin;
    llTheta = useMemo(() =>
      logLikSum(sample, props.mu, props.sigma2, [
        props.mu,
        props.sigma2,
        props.sample
      ])
    );
  }

  const x_range = range(xMin, xMax, Math.abs(xMax - xMin) / 50);
  const newtonParabola = x_range.map(x1 => {
    return [
      x1,
      quadraticApprox(x1 - props.mu, 1, llTheta, deriv, -10 / props.sigma2)
    ];
  });
  const yMin = -100;
  const yMax = -20;

  const [spring, set] = useSpring(() => ({
    xy: [props.mu, props.sigma2],
    immediate: false,
    config: { duration: 500 }
  }));

  set({ xy: [props.mu, props.sigma2], immediate: !props.animating });

  const bind = useDrag(({ movement: [mx, my], first, memo }) => {
    const muStart = first ? props.mu : memo[0];
    const sigma2Start = first ? props.sigma2 : memo[1];
    const mu = xScale.invert(xScale(muStart) + mx);
    const sigma2 = yScale.invert(yScale(sigma2Start) + my);
    dispatch({
      name: "contourDrag",
      value: { mu: mu, sigma2: props.sigma2 }
    });
    return [muStart, sigma2Start];
  });

  //const yMax = 0.05;
  // Create scales
  const yScale = scaleLinear()
    .domain([yMin, yMax])
    .range([h, 0]);

  const xScale = scaleLinear()
    .domain([xMin, xMax])
    .range([0, w]);

  // Scales and Axis
  const xAxis = axisBottom(xScale);
  const yAxis = axisLeft(yScale).ticks(4);

  // Line function
  const linex = line()
    .x(d => xScale(d[0]))
    .y(d => yScale(d[1]));

  // Update
  useEffect(() => {
    createChart(durationTime);
  }, [props.mu, props.sigma2, w, props.sample]);

  const gradientNext = gradientStep(props);
  const gradientNextLL = logLikSum(
    sample,
    gradientNext.points.mu,
    props.sigma2
  );

  // Tooltip
  const Tooltip = ({ theta, thetaLab, ll, deriv }) => {
    const x = 0;
    const y = 0;
    const width = 100;
    const path = topTooltipPath(width, 40, 10, 10);
    const thetaSymb = thetaLab == "mu" ? "mu" : "sigma^2";
    const eqLogLik = katex.renderToString(
      `\\frac{\\partial}{\\partial \\${thetaSymb}}\\ell = `,
      {
        displayMode: false,
        throwOnError: false
      }
    );
    return (
      <g>
        <path
          d={path}
          className="polygonTip"
          transform={`translate(${x + margin.left}, ${y + margin.top - 5})`}
        />
        <foreignObject
          x={x - width / 2 + margin.left}
          y={y}
          width={width}
          height={50}
        >
          <div className="vizTooltip">
            <p>
              <span dangerouslySetInnerHTML={{ __html: eqLogLik }} />
              {format(".2f")(deriv)}
            </p>
          </div>
        </foreignObject>
      </g>
    );
  };

  const createChart = () => {
    const node = vizRef.current;

    select(node)
      .selectAll("g.viz")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // x Axis
    select(node)
      .selectAll("g.xAxis")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "xAxis");

    select(node)
      .select("g.xAxis")
      .attr(
        "transform",
        "translate(" + margin.left + "," + (h + margin.top) + ")"
      )
      .call(xAxis);

    // y Axis
    select(node)
      .selectAll("g.yAxis")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "yAxis");

    select(node)
      .select("g.yAxis")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .call(yAxis);

    const gViz = select(node)
      .selectAll("g.viz")
      .data([0])
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // x label
    gViz
      .selectAll(".x-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("class", "x-label MuiTypography-body1");

    select(node)
      .selectAll(".x-label")
      .attr(
        "transform",
        "translate(" + w / 2 + " ," + (h + margin.bottom - 5) + ")"
      )
      .text(props.thetaLab == "mu" ? "μ" : "σ²");

    // y label
    gViz
      .selectAll(".y-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("class", "y-label MuiTypography-body2");

    select(node)
      .selectAll(".y-label")
      .attr("transform", "rotate(-90)")
      .attr("text-anchor", "middle")
      .attr("x", -(h / 2))
      .attr("y", -40)
      .text(`ℓ(μ, σ² = ${format(".2f")(props.sigma2)})`);
  };
  const delta = xMax - xMin;
  return (
    <svg width={props.width} height={props.width * 0.4}>
      <g ref={vizRef}>
        <g className="viz">
          <g clipPath="url(#clipMu)">
            <AnimatedPath
              data={data1.data}
              x={100}
              sigma2={props.sigma2}
              xScale={xScale}
              yScale={yScale}
              linex={linex}
              mu={props.mu}
              sample={sample}
              animating={props.animating}
              className="LogLikMu"
            />
            {props.algo == "newtonRaphson" && (
              <AnimatedPath
                data={newtonParabola}
                x={100}
                sigma2={props.sigma2}
                xScale={xScale}
                yScale={yScale}
                linex={linex}
                mu={props.mu}
                sample={sample}
                animating={props.animating}
                className="LogLikNewton"
              />
            )}
            {props.algo == "gradientAscent" && (
              <>
                <circle
                  cx={xScale(gradientNext.points.mu)}
                  cy={yScale(gradientNextLL)}
                  r="5"
                  className="logLikNewtonX--approx"
                />
                <line
                  className="LogLikNewton--maxima"
                  y1={yScale(yMin)}
                  y2={yScale(gradientNextLL)}
                  x1={xScale(gradientNext.points.mu)}
                  x2={xScale(gradientNext.points.mu)}
                />
              </>
            )}
          </g>
        </g>
      </g>
      <g clipPath="url(#clipMu2)">
        <animated.g
          {...bind()}
          transform={spring.xy.interpolate(
            (x, y) =>
              `translate(${xScale(x)}, ${yScale(logLikSum(sample, x, y))})`
          )}
          className="draggable"
        >
          <circle
            cx={margin.left}
            cy={margin.top}
            r="5"
            className="logLikX"
          />
          <animated.line
            className="deriv"
            y1={spring.xy.interpolate(
              (x, y) =>
                margin.top + yScale(yMax - delta * dMu(10, x, props.muHat, y))
            )}
            y2={spring.xy.interpolate(
              (x, y) =>
                margin.top + yScale(yMax + delta * dMu(10, x, props.muHat, y))
            )}
            x1={margin.left + xScale(xMin - delta)}
            x2={margin.left + xScale(xMin + delta)}
          />

          <Tooltip
            theta={props.theta}
            thetaLab={props.thetaLab}
            ll={llTheta}
            deriv={deriv}
          />
        </animated.g>
      </g>
      <defs>
        <clipPath id="clipMu">
          <rect id="clip-rectMu" x="0" y="-10" width={w} height={h + 10} />
        </clipPath>
        <clipPath id="clipMu2">
          <rect
            id="clip-rectMu"
            x={margin.left}
            y={-margin.bottom}
            width={w}
            height={h + 100}
          />
        </clipPath>
      </defs>
    </svg>
  );
}
Example #15
Source File: LogLikPlotSigma.js    From likelihood with MIT License 4 votes vote down vote up
OverlapChart = props => {
  const vizRef = useRef(null);
  const dispatch = useContext(VizDispatch);
  // Stuff
  const margin = { top: 0, right: 20, bottom: 40, left: 50 };
  const durationTime = 200;
  const w = props.width * 0.4 - margin.left - margin.right;
  const h = props.width * 0.75 - margin.top - margin.bottom;
  const sample = props.sample;
  const deriv = props.deriv;
  const data1 = props.data;
  // Axes min and max
  var yMin, yMax, llTheta;

  yMax = 1500;
  yMin = 1;
  llTheta = useMemo(() => logLikSum(sample, props.mu, props.sigma2), [
    props.mu,
    props.sigma2,
    props.sample
  ]);

  const [spring, set] = useSpring(() => ({
    xy: [props.mu, props.sigma2],
    immediate: false,
    config: { duration: 500 }
  }));

  set({ xy: [props.mu, props.sigma2], immediate: !props.animating });

  const bind = useDrag(({ movement: [mx, my], first, memo }) => {
    const muStart = first ? props.mu : memo[0];
    const sigma2Start = first ? props.sigma2 : memo[1];
    const mu = xScale.invert(xScale(muStart) + mx);
    const sigma2 = yScale.invert(yScale(sigma2Start) + my);
    dispatch({
      name: "contourDrag",
      value: { mu: props.mu, sigma2: sigma2 }
    });
    return [muStart, sigma2Start];
  });

  const xMin = -100;
  const xMax = -20;

  const hessian = -10 / (2 * props.sigma2 * props.sigma2);

  //const y_max = 0.05;
  // Create scales
  const yScale = scaleLinear()
    .domain([yMin, yMax])
    .range([h, 0]);

  const xScale = scaleLinear()
    .domain([xMin, xMax])
    .range([0, w]);

  // Scales and Axis
  const xAxis = axisBottom(xScale).ticks(3);
  const yAxis = axisLeft(yScale);

  // Line function
  const linex = line()
    .x(d => xScale(d[1]))
    .y(d => yScale(d[0]));

  // Update
  useEffect(() => {
    createChart(durationTime);
  }, [props.mu, props.sigma2, w, props.sample]);

  const gradientNext = gradientStep(props);
  const gradientNextLL = logLikSum(
    sample,
    props.mu,
    gradientNext.points.sigma2
  );

  // Tooltip
  const Tooltip = ({ theta, thetaLab, ll, deriv }) => {
    const x = 0;
    const y = 0;
    const width = 40;
    const path = topTooltipPath(width, 100, 10, 10);
    const thetaSymb = thetaLab == "mu" ? "mu" : "sigma^2";
    const eqLogLik = katex.renderToString(
      `\\frac{\\partial}{\\partial \\${thetaSymb}}\\ell = `,
      {
        displayMode: false,
        throwOnError: false
      }
    );
    return (
      <g>
        <path
          d={path}
          className="polygonTip"
          transform={`translate(${x + margin.left + 5}, ${y +
            margin.top}) rotate(90)`}
        />
        <foreignObject
          x={x + margin.right / 2 + margin.left}
          y={y - margin.bottom + 15}
          width={100}
          height={50}
        >
          <div className="vizTooltip">
            <p>
              <span dangerouslySetInnerHTML={{ __html: eqLogLik }} />
              {format(".2f")(deriv)}
            </p>
          </div>
        </foreignObject>
      </g>
    );
  };

  const createChart = () => {
    const node = vizRef.current;

    select(node)
      .selectAll("g.viz")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // x Axis
    select(node)
      .selectAll("g.xAxis")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "xAxis");

    select(node)
      .select("g.xAxis")
      .attr(
        "transform",
        "translate(" + margin.left + "," + (h + margin.top) + ")"
      )
      .call(xAxis);

    // y Axis
    select(node)
      .selectAll("g.yAxis")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "yAxis");

    select(node)
      .select("g.yAxis")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .call(yAxis);

    const gViz = select(node)
      .selectAll("g.viz")
      .data([0])
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // x label
    gViz
      .selectAll(".x-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("class", "x-label MuiTypography-body2");

    select(node)
      .selectAll(".x-label")
      .attr(
        "transform",
        "translate(" + w / 2 + " ," + (h + margin.bottom - 5) + ")"
      )
      .text(`ℓ(μ = ${format(".2f")(props.mu)}, σ²)`);

    // y label
    gViz
      .selectAll(".y-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("class", "y-label MuiTypography-body2");

    select(node)
      .selectAll(".y-label")
      .attr("transform", "rotate(-90)")
      .attr("text-anchor", "middle")
      .attr("x", -(h / 2))
      .attr("y", -40)
      .text("σ²");
  };
  const delta = yMax - yMin;

  return (
    <svg width={props.width} height={h + margin.bottom}>
      <g ref={vizRef}>
        <g className="viz">
          <g clipPath="url(#clipQuadApprox)">
            <AnimatedPath
              data={data1.data}
              x={100}
              sigma2={props.sigma2}
              xScale={xScale}
              yScale={yScale}
              linex={linex}
              mu={props.mu}
              sample={sample}
              animating={props.animating}
            />
            {props.algo == "newtonRaphson" && (
              <NewtonParabola
                mu={props.mu}
                sigma2={props.sigma2}
                yMin={yMin}
                yMax={yMax}
                xMin={xMin}
                xScale={xScale}
                yScale={yScale}
                linex={linex}
                llTheta={llTheta}
                deriv={deriv}
                hessian={hessian}
                count={props.count}
              />
            )}
            {props.algo == "gradientAscent" && (
              <>
                <circle
                  cy={yScale(gradientNext.points.sigma2)}
                  cx={xScale(gradientNextLL)}
                  r="5"
                  className="logLikNewtonX--approx"
                />
                <line
                  className="LogLikNewton--maxima"
                  x1={xScale(xMin)}
                  x2={xScale(gradientNextLL)}
                  y1={yScale(gradientNext.points.sigma2)}
                  y2={yScale(gradientNext.points.sigma2)}
                />
              </>
            )}
          </g>
        </g>
        <g clipPath="url(#clipSigma2)">
          <animated.g
            {...bind()}
            transform={spring.xy.interpolate(
              (x, y) =>
                `translate(${xScale(logLikSum(sample, x, y))}, ${yScale(y)})`
            )}
            className="draggable"
          >
            <circle cx={margin.left} cy={0} r="5" className="logLikX" />
            <animated.line
              className="deriv"
              x1={spring.xy.interpolate(
                (x, y) =>
                  margin.left + xScale(xMin - delta * dSigma2(sample, x, y))
              )}
              x2={spring.xy.interpolate(
                (x, y) =>
                  margin.left + xScale(xMin + delta * dSigma2(sample, x, y))
              )}
              y1={yScale(yMax - delta)}
              y2={yScale(yMax + delta)}
            />

            <Tooltip
              theta={props.theta}
              thetaLab={props.thetaLab}
              ll={llTheta}
              deriv={deriv}
            />
          </animated.g>
        </g>
      </g>

      <defs>
        <clipPath id="clipSigma">
          <rect id="clip-rect2" x="0" y="-10" width={w} height={h + 10} />
        </clipPath>
        <clipPath id="clipSigma2">
          <rect
            id="clip-rect2"
            x={margin.left}
            y={-10}
            width={w + 100}
            height={h + 10}
          />
        </clipPath>
        <clipPath id="clipQuadApprox">
          <rect id="clip-rect2" x="0" y="-10" width={h + 100} height={h + 10} />
        </clipPath>
      </defs>
    </svg>
  );
}
Example #16
Source File: SamplePlot.js    From likelihood with MIT License 4 votes vote down vote up
SampleChart = props => {
  const vizRef = useRef(null);

  // Stuff
  const margin = { top: 60, right: 20, bottom: 30, left: 50 };
 
  const durationTime = 200;
  const w = props.width - margin.left - margin.right;
  const h = props.width * aspect - margin.top - margin.bottom;
  const sample = props.sample;
  const sigma = Math.sqrt(props.sigma2)
  const sigmaTheta = Math.sqrt(props.sigma2Theta)
  // x.values
  const x_start = props.muTheta - 10 * sigmaTheta;
  const x_end = props.muTheta + 10 * sigmaTheta;

  const x_start2 = props.mu - 3 * sigma;
  const x_end2 = props.mu + 3 * sigma;
  const x = range( props.mu - 3 * sigma,
     props.mu + 3 * sigma, Math.abs(x_start2 - x_end2) / 50);
  x.push(x_end)
  x.unshift(x_start)
  

  // Data sets
  const data1 = genData(props.mu, sigma, x);

  // Axes min and max
  const x_max = props.muTheta + sigmaTheta * 5;
  const x_min = props.muTheta - sigmaTheta * 5;
  //const y_max = max(data1.y);
  const y_max = 0.04;
  // Create scales
  const yScale = scaleLinear()
    .domain([0, y_max])
    .range([h, 0]);

  const xScale = scaleLinear()
    .domain([x_min, x_max])
    .range([0, w]);

  // Scales and Axis
  const xAxis = axisBottom(xScale);
  const yAxis = axisLeft(yScale);

  // Update
  useEffect(() => {
    createSampleChart(durationTime);
  }, [props.mu, props.sigma2, props.highlight, props.sample, w]);

  // Tooltip
  const Tooltip = ({ d, i }) => {
    const x = xScale(d);
    const L = normal.pdf(d, props.mu, sigma);
    const y = yScale(L);
    const path = topTooltipPath(150, 50, 10, 10);
    const width = 150;
    const eqLogLik = katex.renderToString(`\\ell_{${i + 1}} = `, {
      displayMode: false,
      throwOnError: false
    });
    return (
      <g>
        <path
          d={path}
          className="polygonTip1"
          transform={`translate(${x + margin.left}, ${y + margin.top })`}
        />
        <foreignObject
          x={x - width / 2 + margin.left}
          y={y }
          width={width}
          height={50}
        >
          <div className="vizTooltip">
            <p>
              <span dangerouslySetInnerHTML={{ __html: eqLogLik }} />
              log({format(".2n")(L)})
            </p>
          </div>
        </foreignObject>
      </g>
    );
  };

  const createSampleChart = () => {
    const node = vizRef.current;

    // Line function
    const linex = line()
      .x(d => xScale(d[0]))
      .y(d => yScale(d[1]));

    select(node)
      .selectAll("g.viz")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // x Axis
    select(node)
      .selectAll("g.xAxis")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "xAxis");

    select(node)
      .select("g.xAxis")
      .attr(
        "transform",
        "translate(" + margin.left + "," + (h + margin.top) + ")"
      )
      .call(xAxis);

    // y Axis
    select(node)
      .selectAll("g.yAxis")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "yAxis");

    select(node)
      .select("g.yAxis")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .call(yAxis);

    const gViz = select(node)
      .selectAll("g.viz")
      .data([0])
      .enter()
      .append("g")
      .attr("class", "viz")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // x label
    gViz
      .selectAll(".x-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("class", "x-label");

    select(node)
      .selectAll(".x-label")
      .attr(
        "transform",
        "translate(" + w / 2 + " ," + (h + margin.bottom) + ")"
      )
      .text(props.xLabel);

    // y label
    gViz
      .selectAll("#y-label")
      .data([0])
      .enter()
      .append("text")
      .style("text-anchor", "middle")
      .attr("id", "y-label");

    select(node)
      .selectAll("#y-label")
      .attr("transform", "rotate(-90)")
      .attr("text-anchor", "middle")
      .attr("x", -(h / 2))
      .attr("y", -40)
      .text("Density");

    // Append dists

    // DIST1
    gViz
      .selectAll("#dist1")
      .data([data1.data])
      .enter()
      .append("svg:path")
      .attr("d", linex)
      .attr("id", "dist1");

    select(node)
      .selectAll("#dist1")
      .data([data1.data])
      .attr("d", linex);


    // mu vertical lines
    const sampleLines = (mu, id) => {
      gViz
        .selectAll("#" + id)
        .data([0])
        .enter()
        .append("line")
        .attr("class", "logLikLines")
        .attr("id", id);

      select(node)
        .selectAll("#" + id)
        .data([0])
        .attr("x1", xScale(mu))
        .attr("x2", xScale(mu))
        .attr("y1", yScale(normal.pdf(mu, props.mu, sigma)))
        .attr("y2", yScale(0));
    };

    //muLines(props.mu0, "mu0");
    sample.map((x, i) => sampleLines(x, `sample${i}`));
        // Points
        gViz
        .selectAll("circle")
        .data(sample)
        .enter()
        .append("svg:circle")
        .attr("cy", d => yScale(normal.pdf(d, props.mu, sigma)))
        .attr("cx", d => xScale(d));
  
      select(node)
        .selectAll("circle")
        .data(sample)
        .attr("cy", d => yScale(normal.pdf(d, props.mu, sigma)))
        .attr("cx", d => xScale(d))
        .attr("class", "sampleCircles")
        .on("mouseover", function(d, i) {
          props.setHighlight(i);
          select(this).attr("r", 10)
        })
        .on("mouseout", function() {
          props.setHighlight();
          select(this).attr("r", 5)
        })
        .attr("r", (d, i) => {
          const r = props.highlight == i ? 10 : 5;
          return r;
        });
  
  };

  return (
    <svg width={props.width} height={props.width * aspect}>
      <g ref={vizRef} />
      {props.highlight >= 0 && (
        <Tooltip d={sample[props.highlight]} i={props.highlight} />
      )}
    </svg>
  );
}
Example #17
Source File: bar-chart.js    From website with Apache License 2.0 4 votes vote down vote up
BarChart = ({
  data,
  lineData,
  refLineData,
  annotations,
  handleAnnotationClick,
  fill,
  lineColor,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop,
  showTicks,
  width,
  height,
  yMax,
  yTicks,
  lastXTick,
  renderTooltipContents,
  perCapLabel,
}) => {
  const chartRef = useRef()
  const [tooltip, setTooltip] = useState(null)
  // Used for tooltip optimization
  const [timeoutRef, setTimeoutRef] = useState(null)
  const [keyboardFocus, setKeyboardFocus] = useState(false)

  // Used when placing annotations
  const getValueForDate = date => {
    const dateData = data.find(d => d.date.getTime() === date.getTime())
    return dateData && dateData.value
  }
  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom
  // The x range is over the area in which the chart should be displaying.
  // We don't use an X transform to place it in the correct spot, we use range
  // instead
  const xScale = scaleBand()
    .domain(data.map(d => d.date))
    .range([marginLeft, width - marginRight])
    .padding(0.1)
  const dateDomain = extent(data, d => d.date)
  // Should probably refactor to use a single x-axis scale
  // but the bars make use of the band.
  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([marginLeft, width - marginRight])

  const yMaxEffective =
    yMax || max([...data, ...(refLineData || [])], d => d.value)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height - totalYMargin, 0])

  const msInOneMonth = 2628000000
  const monthlyTickInterval = Math.ceil(
    Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
  )

  const xTickAmount = timeMonth.every(monthlyTickInterval)

  const yTicksThreshold = 4
  const yTicksEffective =
    yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold

  const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()

  let lineFn = null
  if (lineData) {
    lineFn = line()
      .defined(d => !Number.isNaN(d.value) && d.value !== null)
      .curve(curveCardinal)
      .x(d => xScaleTime(d.date))
      .y(d => yScale(d.value))
  }
  const hover = (event, d) => {
    // Ensure that tooltip doesn't flash when transitioning between bars
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    const isTouchEvent = !event.clientX
    const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
    const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
    setTooltip({
      top: isTouchEvent ? eventY - 130 : eventY + 10,
      left: isTouchEvent ? eventX - 80 : eventX + 5,
      d,
    })
  }

  const mouseOut = () => {
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    setTimeoutRef(setTimeout(() => setTooltip(null), 200))
  }

  useEffect(() => {
    if (keyboardFocus === false || typeof data[keyboardFocus] === 'undefined') {
      return
    }
    const column = data[keyboardFocus]
    setTooltip({
      top: chartRef.current.getBoundingClientRect().top,
      left: chartRef.current.getBoundingClientRect().left,
      d: column,
    })
  }, [keyboardFocus])

  return (
    <>
      <svg
        className={classnames(chartStyles.chart, styles.chart)}
        viewBox={`0 0 ${width} ${height}`}
        tabIndex="0"
        aria-hidden
        ref={chartRef}
        onBlur={() => {
          setTooltip(null)
          setKeyboardFocus(false)
        }}
        onKeyDown={event => {
          if (event.key === 'Escape') {
            setTooltip(null)
            setKeyboardFocus(false)
            chartRef.current.blur()
          }
          if (event.key === 'ArrowRight') {
            setKeyboardFocus(
              keyboardFocus < data.length ? keyboardFocus + 1 : data.length,
            )
          }
          if (
            (event.shiftKey && event.key === 'Tab') ||
            event.key === 'ArrowLeft'
          ) {
            setKeyboardFocus(keyboardFocus > 0 ? keyboardFocus - 1 : 0)
          }
        }}
      >
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          <text className={classnames(chartStyles.label, styles.directions)}>
            Use arrows to move, Escape to leave.
          </text>
        </g>
        {/* y ticks */}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {yScale.ticks(yTicksEffective).map(
            (tick, i) =>
              i < showTicks && (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 6}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {formatNumber(tick)}
                      {tick > 0 &&
                        perCapLabel /* this only displays if passed */}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ),
          )}
        </g>
        {/* x ticks (dates) */}
        <g transform={`translate(0, ${height - marginBottom})`}>
          {xScaleTime.ticks(xTickAmount).map(d => (
            <Fragment key={`x-${d}`}>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                key={d}
                x={xScaleTime(d)}
                y="20"
              >{`${formatDate(d)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(d)}
                y1="0"
                x2={xScaleTime(d)}
                y2="5"
              />
            </Fragment>
          ))}
          {lastXTick && (
            <>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                x={xScaleTime(lastTime)}
                y="20"
              >{`${formatDate(lastTime)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(lastTime)}
                y1="0"
                x2={xScaleTime(lastTime)}
                y2="5"
              />
            </>
          )}
        </g>

        <mask id="dataMask">
          <rect
            x="0"
            y="0"
            width={width - marginRight}
            height={height - totalYMargin}
            fill="white"
          />
        </mask>

        {/* data */}
        <g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
          {/* bars (data) */}
          {data.map((d, key) => (
            <rect
              key={d.date + d.value}
              x={xScale(d.date)}
              y={yScale(d.value)}
              height={yScale(0) - yScale(d.value)}
              width={xScale.bandwidth()}
              fillOpacity={lineData ? 1 : 0.8}
              fill={fill}
              className={classnames(
                renderTooltipContents && styles.interactiveBar,
                key === keyboardFocus && styles.selected,
              )}
              onMouseOver={event => hover(event, d)}
              onFocus={event => hover(event, d)}
              onMouseOut={mouseOut}
              onBlur={mouseOut}
            />
          ))}
          {/* line */}
          {lineData && (
            <path
              d={lineFn(lineData)}
              stroke={lineColor}
              strokeWidth="3"
              fill="none"
            />
          )}
          {/* reference line */}
          {refLineData && (
            <path
              d={lineFn(refLineData)}
              stroke="black"
              strokeWidth="2"
              strokeDasharray="4"
              fill="none"
            />
          )}
        </g>
        {/* annotations */}
        {annotations && (
          <g transform={`translate(0 ${marginTop})`}>
            {annotations
              .filter(
                annotation =>
                  xScaleTime(annotation.date) >= xScaleTime(dateDomain[0]) &&
                  xScaleTime(annotation.date) <= xScaleTime(dateDomain[1]),
              )
              .map(d => (
                <AnnotationBubble
                  content={d}
                  xScaleTime={xScaleTime}
                  yScale={yScale}
                  handleAnnotationClick={handleAnnotationClick}
                  getValueForDate={getValueForDate}
                />
              ))}
          </g>
        )}
      </svg>
      {renderTooltipContents && tooltip && (
        <Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
      )}
    </>
  )
}
Example #18
Source File: ContourLogLik.js    From likelihood with MIT License 4 votes vote down vote up
ContourChart = props => {
  const vizRef = useRef(null);
  const dispatch = useContext(VizDispatch);
  // Stuff
  const margin = { top: 0, right: 20, bottom: 40, left: 50 };
  const w = props.width - margin.left - margin.right;
  const h = props.width * 0.75 - margin.top - margin.bottom;
  const sample = props.sample;
  const sigmaTheta = Math.sqrt(props.sigma2Theta);
  const muMax = props.muTheta + sigmaTheta * 5;
  const muMin = props.muTheta - sigmaTheta * 5;
  const sigma2MLE = props.sigma2Theta;
  const sigma2Max = 1500;
  const sigma2Min = 1;

  // For gradient ascent illustration
  const [spring, set] = useSpring(() => ({
    xy: [props.mu, props.sigma2],
    immediate: false,
    config: { duration: 500 }
  }));

  const bind = useDrag(({ movement: [mx, my], first, memo }) => {
    const muStart = first ? props.mu : memo[0];
    const sigma2Start = first ? props.sigma2 : memo[1];
    const mu = xScale.invert(xScale(muStart) + mx);
    const sigma2 = yScale.invert(yScale(sigma2Start) + my);
    dispatch({
      name: "contourDrag",
      value: { mu: mu, sigma2: sigma2 }
    });
    return [muStart, sigma2Start];
  });

  const iterate = () => {
    dispatch({
      name: "algoIterate",
      value: {
        increment: 1,
      }
    });
  };

  useInterval(() => {
    iterate();
  }, props.algoDelay);

  set({ xy: [props.mu, props.sigma2], immediate: !props.animating });

  const llMin = -300;
  const llMax = -20;
  const thresholds = useMemo(
    () => range(llMin, llMax, (llMax - llMin) / 100),
    []
  );

  const yScale = scaleLinear([sigma2Min, sigma2Max], [h, 0]);

  const xScale = scaleLinear([muMin, muMax], [0, w]);

  const linex = useMemo(
    () =>
      line()
        .x(d => xScale(d.mu))
        .y(d => yScale(d.sigma2)),
    [w]
  );

  const grid = useMemo(
    () => createGrid(muMin, muMax, sigma2Min, sigma2Max, sample),
    [props.sample]
  );

  const color = useMemo(
    () =>
      scaleLinear()
        .domain([-100, max(grid)])
        .range(["#82b3aa", "#fff"])
        .interpolate(interpolateRgb.gamma(0.6)),
    [props.sample]
  );

  const contour = useMemo(
    () =>
      contours()
        .size([grid.n, grid.m])
        .thresholds(thresholds)(grid)
        .map(({ type, value, coordinates }) => {
          return {
            type,
            value,
            coordinates: coordinates.map(rings => {
              return rings.map(points => {
                return points.map(([mu, sigma2]) => [
                  xScale(muMin + mu * grid.muStep),
                  yScale(sigma2Min + sigma2 * grid.sigmaStep)
                ]);
              });
            })
          };
        }),
    [props.sample, w]
  );

  const contourPaths = useMemo(
    () =>
      contour.map((d, i) => {
        return (
          <path
            d={geoPath()(d)}
            className="contour"
            fill={color(d.value)}
            fillOpacity={1}
            stroke="#485460"
            strokeWidth={i % 5 ? 0.5 : 1.5}
            strokeOpacity={0.75}
            strokeLinejoin="round"
            key={i}
          />
        );
      }),
    [props.sample, w]
  );

  const ll = useMemo(
    () => format(".2f")(logLikSum(sample, props.mu, props.sigma2)),
    [sample, props.mu, props.sigma2]
  );

  return (
    <svg width={props.width} height={h + margin.bottom}>
      <g ref={vizRef}>
        <g
          className="viz"
          transform={"translate(" + margin.left + "," + 0 + ")"}
        >
          {contourPaths}
          <animated.line
            x1={xScale(muMin)}
            x2={xScale(muMax)}
            y1={0}
            y2={0}
            className="LogLikMu"
            transform={spring.xy.interpolate(
              (x, y) => `translate(0, ${yScale(y)})`
            )}
          />
          <animated.line
            y1={yScale(sigma2Min)}
            y2={yScale(sigma2Max)}
            x1={0}
            x2={0}
            transform={spring.xy.interpolate(
              (x, y) => `translate(${xScale(x)}, 0)`
            )}
            className="LogLikSigma"
          />

          <animated.g
            {...bind()}
            transform={spring.xy.interpolate(
              (x, y) => `translate(${xScale(x)}, ${yScale(y)})`
            )}
            className="draggable"
          >
            <circle cx={0} cy={0} r="5" className="logLikX" />
            <Tooltip x={0} y={0} equation={eqLogLik(ll)} margin={margin} />
          </animated.g>
          <path d={linex(props.drawGradientPath)} className="gradientDescent" />
          <rect
            id="clip-rect"
            x="0"
            y="0"
            width={w}
            height={h}
            fill="none"
            stroke="#fff"
            strokeWidth="3px"
          />
        </g>
      </g>
    </svg>
  );
}
Example #19
Source File: Timeseries.js    From covid19india-react with MIT License 4 votes vote down vote up
function Timeseries({
  statistics,
  timeseries,
  dates,
  endDate,
  chartType,
  isUniform,
  isLog,
  isMovingAverage,
  noRegionHighlightedDistrictData,
}) {
  const {t} = useTranslation();
  const refs = useRef([]);
  const [wrapperRef, {width, height}] = useMeasure();

  const [highlightedDate, setHighlightedDate] = useState(
    dates[dates.length - 1]
  );
  useEffect(() => {
    setHighlightedDate(dates[dates.length - 1]);
  }, [dates]);

  const getTimeseriesStatistic = useCallback(
    (date, statistic, movingAverage = isMovingAverage) => {
      return getStatistic(timeseries?.[date], chartType, statistic, {
        movingAverage,
      });
    },
    [chartType, isMovingAverage, timeseries]
  );

  const condenseChart = useMemo(() => {
    const T = dates.length;
    const days = differenceInDays(
      parseIndiaDate(dates[T - 1]),
      parseIndiaDate(dates[0])
    );
    // Chart extremes
    const chartRight = width - margin.right;
    // Bar widths
    const axisWidth = Math.max(0, chartRight - margin.left) / (1.25 * days);
    return axisWidth < 4;
  }, [width, dates]);

  const xScale = useMemo(() => {
    const T = dates.length;
    const chartRight = width - margin.right;

    return scaleTime()
      .clamp(true)
      .domain([
        parseIndiaDate(dates[0] || endDate),
        parseIndiaDate(dates[T - 1] || endDate),
      ])
      .range([margin.left, chartRight]);
  }, [width, endDate, dates]);

  const yScales = useMemo(() => {
    const chartBottom = height - margin.bottom;

    const addScaleBuffer = (scale, log = false) => {
      const domain = scale.domain();
      if (log) {
        scale.domain([
          domain[0],
          domain[0] * Math.pow(domain[1] / domain[0], 1 / yScaleShrinkFactor),
        ]);
      } else {
        scale.domain([
          domain[0],
          domain[0] + (domain[1] - domain[0]) / yScaleShrinkFactor,
        ]);
      }
      return scale;
    };

    const [uniformScaleMin, uniformScaleMax] = extent(
      dates.reduce((res, date) => {
        res.push(
          ...PRIMARY_STATISTICS.map((statistic) =>
            getTimeseriesStatistic(date, statistic)
          )
        );
        return res;
      }, [])
    );

    const yScaleUniformLinear = addScaleBuffer(
      scaleLinear()
        .clamp(true)
        .domain([Math.min(0, uniformScaleMin), Math.max(1, uniformScaleMax)])
        .range([chartBottom, margin.top])
        .nice(4)
    );

    const yScaleUniformLog = addScaleBuffer(
      scaleLog()
        .clamp(true)
        .domain([1, Math.max(10, uniformScaleMax)])
        .range([chartBottom, margin.top])
        .nice(4),
      true
    );

    return statistics.map((statistic) => {
      if (isUniform) {
        if (
          chartType === 'total' &&
          isLog &&
          PRIMARY_STATISTICS.includes(statistic)
        ) {
          return yScaleUniformLog;
        } else if (PRIMARY_STATISTICS.includes(statistic)) {
          return yScaleUniformLinear;
        }
      }

      const [scaleMin, scaleMax] = extent(dates, (date) =>
        getTimeseriesStatistic(date, statistic)
      );

      if (chartType === 'total' && isLog) {
        return addScaleBuffer(
          scaleLog()
            .clamp(true)
            .domain([1, Math.max(10, scaleMax)])
            .range([chartBottom, margin.top])
            .nice(4),
          true
        );
      } else {
        return addScaleBuffer(
          scaleLinear()
            .clamp(true)
            .domain([
              Math.min(0, scaleMin),
              STATISTIC_CONFIGS[statistic].format === '%'
                ? Math.min(100, Math.max(1, scaleMax))
                : Math.max(1, scaleMax),
            ])
            .range([chartBottom, margin.top])
            .nice(4)
        );
      }
    });
  }, [
    height,
    chartType,
    isUniform,
    isLog,
    statistics,
    dates,
    getTimeseriesStatistic,
  ]);

  useEffect(() => {
    if (!width || !height) return;

    const T = dates.length;
    // Chart extremes
    const chartRight = width - margin.right;
    const chartBottom = height - margin.bottom;

    const isDiscrete = chartType === 'delta' && !isMovingAverage;

    const xAxis = (g) =>
      g
        .attr('class', 'x-axis')
        .call(axisBottom(xScale).ticks(numTicksX(width)));

    const xAxis2 = (g, yScale) => {
      g.attr('class', 'x-axis2')
        .call(axisBottom(xScale).tickValues([]).tickSize(0))
        .select('.domain')
        .style('transform', `translate3d(0, ${yScale(0)}px, 0)`);

      if (yScale(0) !== chartBottom) g.select('.domain').attr('opacity', 0.4);
      else g.select('.domain').attr('opacity', 0);
    };

    const yAxis = (g, yScale, format) =>
      g.attr('class', 'y-axis').call(
        axisRight(yScale)
          .ticks(4)
          .tickFormat((num) => formatNumber(num, format))
          .tickPadding(4)
      );

    function mousemove(event) {
      const xm = pointer(event)[0];
      const date = xScale.invert(xm);
      if (!isNaN(date)) {
        const bisectDate = bisector((date) => parseIndiaDate(date)).left;
        const index = bisectDate(dates, date, 1);
        const dateLeft = dates[index - 1];
        const dateRight = dates[index];
        setHighlightedDate(
          date - parseIndiaDate(dateLeft) < parseIndiaDate(dateRight) - date
            ? dateLeft
            : dateRight
        );
      }
    }

    function mouseout(event) {
      setHighlightedDate(dates[T - 1]);
    }

    /* Begin drawing charts */
    statistics.forEach((statistic, i) => {
      const ref = refs.current[i];
      const svg = select(ref);
      const t = svg.transition().duration(D3_TRANSITION_DURATION);

      const yScale = yScales[i];
      const statisticConfig = STATISTIC_CONFIGS[statistic];
      const format =
        STATISTIC_CONFIGS[statistic].format === '%' ? '%' : 'short';
      const isNonLinear = !!STATISTIC_CONFIGS[statistic]?.nonLinear;

      /* X axis */
      svg
        .select('.x-axis')
        .style('transform', `translate3d(0, ${chartBottom}px, 0)`)
        .transition(t)
        .call(xAxis);

      svg.select('.x-axis2').transition(t).call(xAxis2, yScale);

      /* Y axis */
      svg
        .select('.y-axis')
        .style('transform', `translate3d(${chartRight}px, 0, 0)`)
        .transition(t)
        .call(yAxis, yScale, format);

      /* Path dots */
      svg
        .selectAll('circle.normal')
        .data(condenseChart ? [] : dates, (date) => date)
        .join((enter) =>
          enter
            .append('circle')
            .attr('class', 'normal')
            .attr('fill', statisticConfig?.color)
            .attr('stroke', statisticConfig?.color)
            .attr('cx', (date) => xScale(parseIndiaDate(date)))
            .attr('cy', (date) =>
              yScale(isDiscrete ? 0 : getTimeseriesStatistic(date, statistic))
            )
            .attr('r', 2)
        )
        .transition(t)
        .attr('cx', (date) => xScale(parseIndiaDate(date)))
        .attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));

      const areaPath = (dates, allZero = false) =>
        area()
          .curve(curveStep)
          .x((date) => xScale(parseIndiaDate(date)))
          .y0(yScale(0))
          .y1(
            allZero
              ? yScale(0)
              : (date) => yScale(getTimeseriesStatistic(date, statistic, false))
          )(dates);

      svg
        .selectAll('.trend-area')
        .data(
          T && chartType === 'delta' && !isNonLinear && condenseChart
            ? [dates]
            : []
        )
        .join(
          (enter) =>
            enter
              .append('path')
              .attr('class', 'trend-area')
              .call((enter) =>
                enter
                  .attr('d', (dates) => areaPath(dates, true))
                  .transition(t)
                  .attr('d', areaPath)
              ),
          (update) =>
            update.call((update) =>
              update.transition(t).attrTween('d', function (dates) {
                const previous = select(this).attr('d');
                const current = areaPath(dates);
                return interpolatePath(previous, current);
              })
            ),
          (exit) =>
            exit.call((exit) =>
              exit
                .transition(t)
                .attr('d', (dates) => areaPath(dates, true))
                .remove()
            )
        )
        .transition(t)
        .attr('opacity', isMovingAverage ? 0.3 : 1);

      svg
        .selectAll('.stem')
        .data(
          T && chartType === 'delta' && !isNonLinear && !condenseChart
            ? dates
            : [],
          (date) => date
        )
        .join(
          (enter) =>
            enter
              .append('line')
              .attr('class', 'stem')
              .attr('stroke-width', 4)
              .attr('x1', (date) => xScale(parseIndiaDate(date)))
              .attr('y1', yScale(0))
              .attr('x2', (date) => xScale(parseIndiaDate(date)))
              .attr('y2', yScale(0)),
          (update) => update,
          (exit) =>
            exit.call((exit) =>
              exit
                .transition(t)
                .attr('x1', (date) => xScale(parseIndiaDate(date)))
                .attr('x2', (date) => xScale(parseIndiaDate(date)))
                .attr('y1', yScale(0))
                .attr('y2', yScale(0))
                .remove()
            )
        )
        .transition(t)
        .attr('x1', (date) => xScale(parseIndiaDate(date)))
        .attr('y1', yScale(0))
        .attr('x2', (date) => xScale(parseIndiaDate(date)))
        .attr('y2', (date) =>
          yScale(getTimeseriesStatistic(date, statistic, false))
        )
        .attr('opacity', isMovingAverage ? 0.2 : 1);

      const linePath = (
        movingAverage = isMovingAverage,
        curve = curveMonotoneX
      ) =>
        line()
          .curve(curve)
          .x((date) => xScale(parseIndiaDate(date)))
          .y((date) =>
            yScale(getTimeseriesStatistic(date, statistic, movingAverage))
          );

      svg
        .select('.trend')
        .selectAll('path')
        .data(
          T && (chartType === 'total' || isNonLinear || isMovingAverage)
            ? [dates]
            : []
        )
        .join(
          (enter) =>
            enter
              .append('path')
              .attr('class', 'trend')
              .attr('fill', 'none')
              .attr('stroke-width', 4)
              .attr('d', linePath())
              .call((enter) => enter.transition(t).attr('opacity', 1)),
          (update) =>
            update.call((update) =>
              update
                .transition(t)
                .attrTween('d', function (date) {
                  const previous = select(this).attr('d');
                  const current = linePath()(date);
                  return interpolatePath(previous, current);
                })
                .attr('opacity', 1)
            ),
          (exit) =>
            exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
        )
        .attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));

      svg
        .select('.trend-background')
        .selectAll('path')
        .data(
          T && chartType === 'delta' && isNonLinear && isMovingAverage
            ? [dates]
            : []
        )
        .join(
          (enter) =>
            enter
              .append('path')
              .attr('class', 'trend-background')
              .attr('fill', 'none')
              .attr('stroke-width', 4)
              .attr('d', linePath(false, curveLinear))
              .call((enter) => enter.transition(t).attr('opacity', 0.2)),
          (update) =>
            update.call((update) =>
              update
                .transition(t)
                .attrTween('d', function (date) {
                  const previous = select(this).attr('d');
                  const current = linePath(false, curveLinear)(date);
                  return interpolatePath(previous, current);
                })
                .attr('opacity', 0.2)
            ),
          (exit) =>
            exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
        )
        .attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));

      svg.selectAll('*').attr('pointer-events', 'none');
      svg
        .on('mousemove', mousemove)
        .on('touchmove', (event) => mousemove(event.touches[0]))
        .on('mouseout touchend', mouseout);
    });
  }, [
    width,
    height,
    chartType,
    isMovingAverage,
    condenseChart,
    xScale,
    yScales,
    statistics,
    getTimeseriesStatistic,
    dates,
  ]);

  useEffect(() => {
    statistics.forEach((statistic, i) => {
      const ref = refs.current[i];
      const svg = select(ref);
      const statisticConfig = STATISTIC_CONFIGS[statistic];
      const yScale = yScales[i];
      const t = svg.transition().duration(D3_TRANSITION_DURATION);

      svg
        .selectAll('circle.condensed')
        .data(
          condenseChart && highlightedDate ? [highlightedDate] : [],
          (date) => date
        )
        .join((enter) =>
          enter
            .append('circle')
            .attr('class', 'condensed')
            .attr('fill', statisticConfig?.color)
            .attr('stroke', statisticConfig?.color)
            .attr('pointer-events', 'none')
            .attr('cx', (date) => xScale(parseIndiaDate(date)))
            .attr('cy', (date) =>
              yScale(getTimeseriesStatistic(date, statistic))
            )
            .attr('r', 4)
        )
        .transition(t)
        .attr('cx', (date) => xScale(parseIndiaDate(date)))
        .attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));

      if (!condenseChart) {
        svg
          .selectAll('circle')
          .attr('r', (date) => (date === highlightedDate ? 4 : 2));
      }
    });
  }, [
    condenseChart,
    highlightedDate,
    xScale,
    yScales,
    statistics,
    getTimeseriesStatistic,
  ]);

  const getTimeseriesStatisticDelta = useCallback(
    (statistic) => {
      if (!highlightedDate) return;

      const currCount = getTimeseriesStatistic(highlightedDate, statistic);
      if (STATISTIC_CONFIGS[statistic]?.hideZero && currCount === 0) return;

      const prevDate =
        dates[dates.findIndex((date) => date === highlightedDate) - 1];

      const prevCount = getTimeseriesStatistic(prevDate, statistic);
      return currCount - prevCount;
    },
    [dates, highlightedDate, getTimeseriesStatistic]
  );

  const trail = useMemo(
    () =>
      statistics.map((statistic, index) => ({
        animationDelay: `${index * 250}ms`,
      })),
    [statistics]
  );

  return (
    <div className="Timeseries">
      {statistics.map((statistic, index) => {
        const total = getTimeseriesStatistic(highlightedDate, statistic);
        const delta = getTimeseriesStatisticDelta(statistic, index);
        const statisticConfig = STATISTIC_CONFIGS[statistic];
        return (
          <div
            key={statistic}
            className={classnames('svg-parent fadeInUp', `is-${statistic}`)}
            style={trail[index]}
            ref={index === 0 ? wrapperRef : null}
          >
            {highlightedDate && (
              <div className={classnames('stats', `is-${statistic}`)}>
                <h5 className="title">
                  {t(capitalize(statisticConfig.displayName))}
                </h5>
                <h5>{formatDate(highlightedDate, 'dd MMMM')}</h5>
                <div className="stats-bottom">
                  <h2>
                    {!noRegionHighlightedDistrictData ||
                    !statisticConfig?.hasPrimary
                      ? formatNumber(
                          total,
                          statisticConfig.format !== 'short'
                            ? statisticConfig.format
                            : 'long',
                          statistic
                        )
                      : '-'}
                  </h2>
                  <h6>
                    {!noRegionHighlightedDistrictData ||
                    !statisticConfig?.hasPrimary
                      ? `${delta > 0 ? '+' : ''}${formatNumber(
                          delta,
                          statisticConfig.format !== 'short'
                            ? statisticConfig.format
                            : 'long',
                          statistic
                        )}`
                      : ''}
                  </h6>
                </div>
              </div>
            )}
            <svg
              ref={(element) => {
                refs.current[index] = element;
              }}
              preserveAspectRatio="xMidYMid meet"
            >
              <g className="x-axis" />
              <g className="x-axis2" />
              <g className="y-axis" />
              <g className="trend-background" />
              <g className="trend" />
            </svg>
          </div>
        );
      })}
    </div>
  );
}
Example #20
Source File: Minigraphs.js    From covid19india-react with MIT License 4 votes vote down vote up
function Minigraphs({timeseries, date: timelineDate}) {
  const refs = useRef([]);
  const endDate = timelineDate || getIndiaDateYesterdayISO();

  let [wrapperRef, {width}] = useMeasure();
  width = Math.min(width, maxWidth);

  const dates = useMemo(() => {
    const pastDates = Object.keys(timeseries || {}).filter(
      (date) => date <= endDate
    );
    const lastDate = pastDates[pastDates.length - 1];

    const cutOffDateLower = formatISO(
      subDays(parseIndiaDate(lastDate), MINIGRAPH_LOOKBACK_DAYS),
      {representation: 'date'}
    );
    return pastDates.filter((date) => date >= cutOffDateLower);
  }, [endDate, timeseries]);

  const getMinigraphStatistic = useCallback(
    (date, statistic) => {
      return getStatistic(timeseries?.[date], 'delta', statistic);
    },
    [timeseries]
  );

  useEffect(() => {
    if (!width) return;

    const T = dates.length;

    const chartRight = width - margin.right;
    const chartBottom = height - margin.bottom;

    const xScale = scaleTime()
      .clamp(true)
      .domain([
        parseIndiaDate(dates[0] || endDate),
        parseIndiaDate(dates[T - 1]) || endDate,
      ])
      .range([margin.left, chartRight]);

    refs.current.forEach((ref, index) => {
      const svg = select(ref);
      const statistic = LEVEL_STATISTICS[index];
      const color = STATISTIC_CONFIGS[statistic].color;

      const dailyMaxAbs = max(dates, (date) =>
        Math.abs(getMinigraphStatistic(date, statistic))
      );

      const yScale = scaleLinear()
        .clamp(true)
        .domain([-dailyMaxAbs, dailyMaxAbs])
        .range([chartBottom, margin.top]);

      const linePath = line()
        .curve(curveMonotoneX)
        .x((date) => xScale(parseIndiaDate(date)))
        .y((date) => yScale(getMinigraphStatistic(date, statistic)));

      let pathLength;
      svg
        .selectAll('path')
        .data(T ? [dates] : [])
        .join(
          (enter) =>
            enter
              .append('path')
              .attr('fill', 'none')
              .attr('stroke', color + '99')
              .attr('stroke-width', 2.5)
              .attr('d', linePath)
              .attr('stroke-dasharray', function () {
                return (pathLength = this.getTotalLength());
              })
              .call((enter) =>
                enter
                  .attr('stroke-dashoffset', pathLength)
                  .transition()
                  .delay(100)
                  .duration(2500)
                  .attr('stroke-dashoffset', 0)
              ),
          (update) =>
            update
              .attr('stroke-dasharray', null)
              .transition()
              .duration(500)
              .attrTween('d', function (date) {
                const previous = select(this).attr('d');
                const current = linePath(date);
                return interpolatePath(previous, current);
              })
              .selection()
        );

      svg
        .selectAll('circle')
        .data(T ? [dates[T - 1]] : [])
        .join(
          (enter) =>
            enter
              .append('circle')
              .attr('fill', color)
              .attr('r', 2.5)
              .attr('cx', (date) => xScale(parseIndiaDate(date)))
              .attr('cy', (date) =>
                yScale(getMinigraphStatistic(date, statistic))
              )
              .style('opacity', 0)
              .call((enter) =>
                enter
                  .transition()
                  .delay(2100)
                  .duration(500)
                  .style('opacity', 1)
                  .attr('cx', (date) => xScale(parseIndiaDate(date)))
                  .attr('cy', (date) =>
                    yScale(getMinigraphStatistic(date, statistic))
                  )
              ),
          (update) =>
            update
              .transition()
              .duration(500)
              .attr('cx', (date) => xScale(parseIndiaDate(date)))
              .attr('cy', (date) =>
                yScale(getMinigraphStatistic(date, statistic))
              )
              .style('opacity', 1)
              .selection()
        );
    });
  }, [endDate, dates, width, getMinigraphStatistic]);

  return (
    <div className="Minigraph">
      {LEVEL_STATISTICS.map((statistic, index) => (
        <div
          key={statistic}
          className={classnames('svg-parent')}
          ref={index === 0 ? wrapperRef : null}
          style={{width: `calc(${100 / LEVEL_STATISTICS.length}%)`}}
        >
          <svg
            ref={(el) => {
              refs.current[index] = el;
            }}
            preserveAspectRatio="xMidYMid meet"
            width={width}
            height={height}
          />
        </div>
      ))}
    </div>
  );
}
Example #21
Source File: multi-line-chart.js    From website with Apache License 2.0 4 votes vote down vote up
MultiLineChart = ({
  data,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop = 0,
  showTicks,
  width,
  height,
  yMax,
  yTicks,
  lastXTick,
  perCapLabel,
}) => {
  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom

  const dates = []
  const values = []

  data.forEach(item => {
    Object.keys(item.data).forEach(category => {
      item.data[category].forEach(row => {
        dates.push(row.date)
        values.push(row.value)
      })
    })
  })

  const dateDomain = extent(dates)

  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([marginLeft, width - marginRight])

  const yMaxEffective = yMax || max(values)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height - totalYMargin, 0])

  const msInOneMonth = 2628000000
  const monthlyTickInterval = Math.ceil(
    Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
  )

  const xTickAmount = timeMonth.every(monthlyTickInterval)

  const yTicksThreshold = 4
  const yTicksEffective =
    yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold

  const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()

  const lineFn = line()
    .defined(d => !Number.isNaN(d.value) && d.value !== null)
    .curve(curveCardinal)
    .x(d => xScaleTime(d.date))
    .y(d => yScale(d.value))

  // TODO make this dry-er w/r/t the single line chart component

  return (
    <>
      <svg
        className={chartStyles.chart}
        viewBox={`0 0 ${width} ${height}`}
        aria-hidden
      >
        {/* y ticks */}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {yScale.ticks(yTicksEffective).map(
            (tick, i) =>
              i < showTicks && (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 6}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {formatNumber(tick)}
                      {tick > 0 &&
                        perCapLabel /* this only displays if passed */}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ),
          )}
        </g>
        {/* x ticks (dates) */}
        <g transform={`translate(0, ${height - marginBottom})`}>
          {xScaleTime.ticks(xTickAmount).map(d => (
            <Fragment key={`x-${d}`}>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                key={d}
                x={xScaleTime(d)}
                y="20"
              >{`${formatDate(d)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(d)}
                y1="0"
                x2={xScaleTime(d)}
                y2="5"
              />
            </Fragment>
          ))}
          {lastXTick && (
            <>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                x={xScaleTime(lastTime)}
                y="20"
              >{`${formatDate(lastTime)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(lastTime)}
                y1="0"
                x2={xScaleTime(lastTime)}
                y2="5"
              />
            </>
          )}
        </g>

        <mask id="dataMask">
          <rect
            x="0"
            y="0"
            width={width - marginRight}
            height={height - totalYMargin}
            fill="white"
          />
        </mask>

        {/* data */}
        <g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
          {data && (
            <>
              {data.map(item => (
                <>
                  {Object.keys(item.data).map(category => (
                    <path
                      d={lineFn(item.data[category])}
                      stroke={item.colorMap[category]}
                      strokeWidth="1px"
                      fill="none"
                    />
                  ))}
                </>
              ))}
            </>
          )}
        </g>
      </svg>
    </>
  )
}
Example #22
Source File: line-chart.js    From website with Apache License 2.0 4 votes vote down vote up
LineChart = ({
  data,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop = 0,
  showTicks,
  width,
  height,
  yMax,
  yTicks,
  lastXTick,
  perCapLabel,
  renderTooltipContents,
}) => {
  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom

  const dates = []
  const values = []
  data.forEach(item => {
    item.data.forEach(row => {
      dates.push(row.date)
      values.push(row.value)
    })
  })

  const [tooltip, setTooltip] = useState(null)
  const [timeoutRef, setTimeoutRef] = useState(null)

  const hover = (event, dataLine) => {
    // Ensure that tooltip doesn't flash when transitioning between bars
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    const isTouchEvent = !event.clientX
    const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
    const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY

    setTooltip({
      top: isTouchEvent ? eventY - 130 : eventY + 10,
      left: isTouchEvent ? eventX - 80 : eventX + 5,
      d: dataLine,
    })
  }

  const mouseOut = () => {
    if (timeoutRef) {
      clearTimeout(timeoutRef)
    }
    setTimeoutRef(setTimeout(() => setTooltip(null), 200))
  }

  const dateDomain = extent(dates)

  const xScaleTime = scaleTime()
    .domain(dateDomain)
    .range([marginLeft, width - marginRight])

  const yMaxEffective = yMax || max(values)

  const yScale = scaleLinear()
    .domain([0, yMaxEffective])
    .nice()
    .range([height - totalYMargin, 0])

  const msInOneMonth = 2628000000
  const monthlyTickInterval = Math.ceil(
    Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
  )

  const xTickAmount = timeMonth.every(monthlyTickInterval)

  const yTicksThreshold = 4
  const yTicksEffective =
    yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold

  const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()

  const lineFn = line()
    .defined(d => !Number.isNaN(d.value) && d.value !== null)
    .curve(curveCardinal)
    .x(d => xScaleTime(d.date))
    .y(d => yScale(d.value))

  return (
    <>
      <svg
        className={chartStyles.chart}
        viewBox={`0 0 ${width} ${height}`}
        aria-hidden
      >
        {/* y ticks */}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {yScale.ticks(yTicksEffective).map(
            (tick, i) =>
              i < showTicks && (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 6}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {formatNumber(tick)}
                      {tick > 0 &&
                        perCapLabel /* this only displays if passed */}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ),
          )}
        </g>
        {/* x ticks (dates) */}
        <g transform={`translate(0, ${height - marginBottom})`}>
          {xScaleTime.ticks(xTickAmount).map(d => (
            <Fragment key={`x-${d}`}>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                key={d}
                x={xScaleTime(d)}
                y="20"
              >{`${formatDate(d)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(d)}
                y1="0"
                x2={xScaleTime(d)}
                y2="5"
              />
            </Fragment>
          ))}
          {lastXTick && (
            <>
              <text
                className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                x={xScaleTime(lastTime)}
                y="20"
              >{`${formatDate(lastTime)}`}</text>
              <line
                className={chartStyles.label}
                stroke={colors.colorSlate500}
                x1={xScaleTime(lastTime)}
                y1="0"
                x2={xScaleTime(lastTime)}
                y2="5"
              />
            </>
          )}
        </g>

        <mask id="dataMask">
          <rect
            x="0"
            y="0"
            width={width - marginRight}
            height={height - totalYMargin}
            fill="white"
          />
        </mask>

        {/* data */}
        <g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
          {data && (
            <>
              {data.map(dataLine => (
                <>
                  <path
                    d={lineFn(dataLine.data)}
                    stroke={dataLine.color}
                    strokeWidth={dataLine.stroke}
                    fill="none"
                  />
                  {/* Add a wider hidden path for tooltips. */}
                  <path
                    d={lineFn(dataLine.data)}
                    stroke="transparent"
                    strokeWidth={6}
                    fill="none"
                    onMouseOver={event => hover(event, dataLine)}
                    onFocus={event => hover(event, dataLine)}
                    onMouseOut={mouseOut}
                    onBlur={mouseOut}
                  />
                </>
              ))}
            </>
          )}
        </g>
      </svg>
      {renderTooltipContents && tooltip && (
        <Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
      )}
    </>
  )
}