d3-scale#scaleLinear JavaScript Examples

The following examples show how to use d3-scale#scaleLinear. 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: data-generator-utils.js    From ThreatMapper with Apache License 2.0 7 votes vote down vote up
// Inspired by Lee Byron's test data generator.
/* eslint-disable */
function bumpLayer(n, maxValue) {
  function bump(a) {
    const x = 1 / (0.1 + Math.random());
    const y = 2 * Math.random() - 0.5;
    const z = 10 / (0.1 + Math.random());
    for (let i = 0; i < n; i++) {
      const w = (i / n - y) * z;
      a[i] += x * Math.exp(-w * w);
    }
  }

  const a = [];
  let i;
  for (i = 0; i < n; ++i) a[i] = 0;
  for (i = 0; i < 5; ++i) bump(a);
  const values = a.map(function(d) { return Math.max(0, d * maxValue); });
  const s = scaleLinear().domain(extent(values)).range([0, maxValue]);
  return values.map(s);
}
Example #2
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 #3
Source File: color-utils.js    From ThreatMapper with Apache License 2.0 6 votes vote down vote up
hueScale = scaleLinear().range(hueRange)
Example #4
Source File: color-utils.js    From ThreatMapper with Apache License 2.0 6 votes vote down vote up
lightnessScale = scaleLinear().domain(hueRange).range([0.5, 0.7])
Example #5
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 #6
Source File: graph.js    From plant-3d-explorer with GNU Affero General Public License v3.0 5 votes vote down vote up
horizontalScale = scaleLinear()
Example #7
Source File: Gauge.jsx    From pooltogether-community-ui with MIT License 5 votes vote down vote up
export function Gauge({ value = 50, min = 0, max = 100, label, units }) {
  const backgroundFillColor = '#222B45'

  const startAngle = -Math.PI / 2 - 0.6
  const endAngle = Math.PI / 2 + 0.6

  const backgroundArc = arc()
    .innerRadius(0.85)
    .outerRadius(1)
    .startAngle(startAngle)
    .endAngle(endAngle)
    .cornerRadius(1)()

  const percentScale = scaleLinear().domain([min, max]).range([0, 1])
  const percent = percentScale(value)

  const angleScale = scaleLinear().domain([0, 1]).range([startAngle, endAngle]).clamp(true)

  const angle = angleScale(percent)

  const filledArc = arc()
    .innerRadius(0.85)
    .outerRadius(1)
    .startAngle(startAngle)
    .endAngle(angle)
    .cornerRadius(1)()

  const colorScale = scaleLinear().domain([0, 1]).range(['#EF2751', '#6CE988'])

  const gradientSteps = colorScale.ticks(10).map((value) => colorScale(value))

  return (
    <div className='text-center'>
      <svg className='mx-auto overflow-visible' width='15em' viewBox={[-1, -1, 2, 1].join(' ')}>
        <defs>
          <linearGradient id='Gauge__gradient' gradientUnits='userSpaceOnUse' x1='-1' x2='1' y2='0'>
            {gradientSteps.map((color, index) => (
              <stop
                key={color}
                stopColor={color}
                offset={`${index / (gradientSteps.length - 1)}`}
              />
            ))}
          </linearGradient>
        </defs>
        <path d={backgroundArc} fill={backgroundFillColor} />
        <path d={filledArc} fill='url(#Gauge__gradient)' />
      </svg>

      <div
        className='relative'
        style={{
          top: '-5.5rem'
        }}
      >
        {label}
      </div>
    </div>
  )
}
Example #8
Source File: graph.js    From plant-3d-explorer with GNU Affero General Public License v3.0 5 votes vote down vote up
verticalScale = scaleLinear()
Example #9
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 #10
Source File: horizontal-bar-chart.js    From website with Apache License 2.0 5 votes vote down vote up
HorizontalBarChart = ({
  data,
  fill,
  height,
  marginBottom = 0,
  marginLeft = 0,
  marginRight = 0,
  marginTop = 0,
  xTicks,
  width,
  xMax = null,
}) => {
  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom
  const yScale = scaleBand()
    .domain(data.map(d => d.name))
    .range([0, height - totalYMargin])
    .padding(0.2)
  const formatTick = format('~s')
  const xScale = scaleLinear()
    .domain([120, xMax || max(data, d => d.value)])
    .nice()
    .range([0, width - totalXMargin])

  return (
    <svg
      className={chartStyles.chart}
      viewBox={`0 0 ${width} ${height}`}
      aria-hidden
    >
      <g transform={`translate(${marginLeft} ${marginTop})`}>
        {xScale.ticks(xTicks).map(tick => (
          <g key={tick}>
            <text
              className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
              x={xScale(tick)}
              y={height - marginBottom}
            >
              {formatTick(tick)}
            </text>
            <line
              className={chartStyles.gridLine}
              x1={xScale(tick)}
              x2={xScale(tick)}
              y1={0}
              y2={height - totalYMargin}
            />
          </g>
        ))}
      </g>

      <g transform={`translate(0, ${marginTop})`}>
        {data.map(d => (
          /* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */
          <svg
            y={yScale(d.name) + 20}
            x={marginLeft - 10}
            className={chartStyles.yTickLabel}
            key={d.name}
          >
            <text className={chartStyles.label}>{`${d.name}`}</text>
          </svg>
        ))}
      </g>

      <g transform={`translate(${marginLeft}, ${marginTop})`}>
        {data.map(d => (
          <rect
            key={d.name}
            x={0}
            y={yScale(d.name)}
            height={yScale.bandwidth()}
            width={xScale(d.value)}
            fill={fill}
          />
        ))}
      </g>
    </svg>
  )
}
Example #11
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 #12
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 #13
Source File: Gauge.jsx    From v3-ui with MIT License 4 votes vote down vote up
// import { format } from 'd3-format'

export function Gauge({ value = 50, min = 0, max = 100, label, units }) {
  const { theme } = useContext(ThemeContext)

  const backgroundFillColor = theme === 'light' ? '#9f82d7' : '#222B45'

  const startAngle = -Math.PI / 2 - 0.6
  const endAngle = Math.PI / 2 + 0.6

  const backgroundArc = arc()
    .innerRadius(0.85)
    .outerRadius(1)
    .startAngle(startAngle)
    .endAngle(endAngle)
    .cornerRadius(1)()

  const percentScale = scaleLinear().domain([min, max]).range([0, 1])
  const percent = percentScale(value)

  const angleScale = scaleLinear().domain([0, 1]).range([startAngle, endAngle]).clamp(true)

  const angle = angleScale(percent)

  const filledArc = arc()
    .innerRadius(0.85)
    .outerRadius(1)
    .startAngle(startAngle)
    .endAngle(angle)
    .cornerRadius(1)()

  const colorScale = scaleLinear().domain([0, 1]).range(['#EF2751', '#6CE988'])

  const gradientSteps = colorScale.ticks(10).map((value) => colorScale(value))

  // const markerLocation = getCoordsOnArc(
  //   angle,
  //   1 - ((1 - 0.65) / 2),
  // )

  return (
    <div className='text-center'>
      <svg className='mx-auto overflow-visible' width='15em' viewBox={[-1, -1, 2, 1].join(' ')}>
        <defs>
          <linearGradient id='Gauge__gradient' gradientUnits='userSpaceOnUse' x1='-1' x2='1' y2='0'>
            {gradientSteps.map((color, index) => (
              <stop
                key={color}
                stopColor={color}
                offset={`${index / (gradientSteps.length - 1)}`}
              />
            ))}
          </linearGradient>
        </defs>
        <path d={backgroundArc} fill={backgroundFillColor} />
        <path d={filledArc} fill='url(#Gauge__gradient)' />
        {/* <line
          y1="-1"
          y2="-0.65"
          stroke="white"
          strokeWidth="0.027"
        /> */}
        {/* <circle
          cx={markerLocation[0]}
          cy={markerLocation[1]}
          r='0.5'
          stroke='#2c3e50'
          strokeWidth='0.01'
          fill={colorScale(percent)}
        /> */}
        {/* <path
          d="M0.136364 0.0290102C0.158279 -0.0096701 0.219156 -0.00967009 0.241071 0.0290102C0.297078 0.120023 0.375 0.263367 0.375 0.324801C0.375 0.422639 0.292208 0.5 0.1875 0.5C0.0852272 0.5 -1.8346e-08 0.422639 -9.79274e-09 0.324801C0.00243506 0.263367 0.0803571 0.120023 0.136364 0.0290102ZM0.1875 0.381684C0.221591 0.381684 0.248377 0.356655 0.248377 0.324801C0.248377 0.292947 0.221591 0.267918 0.1875 0.267918C0.153409 0.267918 0.126623 0.292947 0.126623 0.324801C0.126623 0.356655 0.155844 0.381684 0.1875 0.381684Z"
          transform={`rotate(${angle * (180 / Math.PI)
            }) translate(-0.2, -0.33)`}
          fill="#6a6a85"
        /> */}
      </svg>

      {/* <div>
        {format(",")(value)}
      </div> */}

      <div
        className='relative'
        style={{
          top: '-5.5rem'
        }}
      >
        {label}
      </div>

      {/* {units && (
        <div style={{
          color: "#8b8ba7",
          lineHeight: "1.3em",
          fontWeight: "300",
        }}>
          { units}
        </div>
      )} */}
    </div>
  )
}
Example #14
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 #15
Source File: ErikDial.js    From Atlantis with MIT License 4 votes vote down vote up
Dial = ({
  value,
  min = 0,
  max = 100,
  label,
  units = timeMillisecond,
  responseTime,
  setPercent,
}) => {
  const [cached, setCached] = useState(false);

  // set state to invoke getCoords

  //  || state to boolen, triggered or not triggered, if
  //if runtime > 30s, not cached yet, else, it has been cached and can set to true
  // if state false, then ! getcoords on arc
  // once setPercent invoked and changed to 99.5
  // it will invoke State since conditional will be true
  // and true invokes getCoordsOnArc

  // console.log(newVal, 'newVal is here 1'); // undef
  // const newVal = setPercent(value)  //99.5

  // let triggered;

  // console.log(triggered,' here is triggered 1');
  // if (newVal === 99.5) {

  //   if (triggered) {
  //     console.log('entered coords');

  //   }

  //   console.log('here is market');
  //   triggered = true;
  // }
  // console.log(triggered,' here is triggered');

  // console.log(newVal, 'newVal is here 2'); // 0, 99.5
  const backgroundArc = arc()
    .innerRadius(0.65)
    .outerRadius(1)
    .startAngle(-Math.PI / 2)
    .endAngle(Math.PI / 2)
    .cornerRadius(1)();

  const percentScale = scaleLinear().domain([min, max]).range([0, 1]);
  const percent = percentScale(value);

  const angleScale = scaleLinear()
    .domain([0, 1])
    .range([-Math.PI / 2, Math.PI / 2])
    .clamp(true);

  const angle = angleScale(percent);

  const filledArc = arc()
    .innerRadius(0.65)
    .outerRadius(1)
    .startAngle(-Math.PI / 2)
    .endAngle(angle)
    .cornerRadius(1)();

  const colorScale = scaleLinear()
    .domain([0, 1, 2, 3])
    .range(['#9B554E', '#67EFE5', '#A6FFF8', '#A6FFF8']);

  const gradientSteps = colorScale.ticks(10).map((value) => colorScale(value));

  const markerLocation = getCoordsOnArc(angle, 1 - (1 - 0.65) / 2);

  return (
    <div
      style={{
        textAlign: 'center',
      }}
    >
      <svg
        style={{ overflow: 'visible' }}
        width="9em"
        viewBox={[-1, -1, 2, 1].join(' ')}
      >
        <defs>
          <linearGradient
            id="Gauge__gradient"
            gradientUnits="userSpaceOnUse"
            x1="-1"
            x2="1"
            y2="0"
          >
            {gradientSteps.map((color, index) => (
              <stop
                key={color}
                stopColor={color}
                offset={`${index / (gradientSteps.length - 1)}`}
              />
            ))}
          </linearGradient>
        </defs>
        <path d={backgroundArc} fill="#dbdbe7" />
        <path d={filledArc} fill="url(#Gauge__gradient)" />
        <circle
          cx={markerLocation[0]}
          cy={markerLocation[1]}
          r="0.2"
          stroke="#2c3e50"
          strokeWidth="0.01"
          fill={colorScale(percent)}
        />
        <path
          d="M0.136364 0.0290102C0.158279 -0.0096701 0.219156 -0.00967009 0.241071 0.0290102C0.297078 0.120023 0.375 0.263367 0.375 0.324801C0.375 0.422639 0.292208 0.5 0.1875 0.5C0.0852272 0.5 -1.8346e-08 0.422639 -9.79274e-09 0.324801C0.00243506 0.263367 0.0803571 0.120023 0.136364 0.0290102ZM0.1875 0.381684C0.221591 0.381684 0.248377 0.356655 0.248377 0.324801C0.248377 0.292947 0.221591 0.267918 0.1875 0.267918C0.153409 0.267918 0.126623 0.292947 0.126623 0.324801C0.126623 0.356655 0.155844 0.381684 0.1875 0.381684Z"
          transform={`rotate(${
            angle * (180 / Math.PI)
          }) translate(-0.2, -0.33)`}
          fill="#6a6a85"
        />
      </svg>

      <div
        style={{
          marginTop: '0.4em',
          fontSize: '2em',
          lineHeight: '1em',
          fontWeight: '900',
          fontFeatureSettings: "'zero', 'tnum' 1",
        }}
      >
        {format(',')(value)}
      </div>

      {!!label && (
        <div
          style={{
            color: '#8b8ba7',
            marginTop: '0.6em',
            fontSize: '1.3em',
            lineHeight: '1.3em',
            fontWeight: '700',
          }}
        >
          {label}
        </div>
      )}

      {!!units && (
        <div
          style={{
            color: '#8b8ba7',
            lineHeight: '1.3em',
            fontWeight: '300',
          }}
        >
          {units}
        </div>
      )}
    </div>
  );
}
Example #16
Source File: Dial.js    From Atlantis with MIT License 4 votes vote down vote up
Dial = ({
  value = 99.5,
  min = 0,
  max = 100,
  label,
  units = timeMillisecond,
  responseTime,
  setPercent,
  isCached,
}) => {
  // console.log(value, 'here is value in dial');
  const [cached, setCached] = useState(isCached);

  const onChange = (value) => {
    if (isCached === true) {
      setCached(!isCached);
      return setPercent(value);
    }
  };
  // console.log(value, 'here is value in dial2');

  const backgroundArc = arc()
    .innerRadius(0.65)
    .outerRadius(1)
    .startAngle(-Math.PI / 2)
    .endAngle(Math.PI / 2)
    .cornerRadius(1)();

  const percentScale = scaleLinear().domain([min, max]).range([0, 1]);
  const percent = percentScale(value);

  const angleScale = scaleLinear()
    .domain([0, 1])
    .range([-Math.PI / 2, Math.PI / 2])
    .clamp(true);

  const angle = angleScale(percent);

  const filledArc = arc()
    .innerRadius(0.65)
    .outerRadius(1)
    .startAngle(-Math.PI / 2)
    .endAngle(angle)
    .cornerRadius(1)();

  const colorScale = scaleLinear()
    .domain([0, 1, 2, 3])
    .range(['#9B554E', '#67EFE5', '#A6FFF8', '#A6FFF8']);

  const gradientSteps = colorScale.ticks(10).map((value) => colorScale(value));

  const markerLocation = getCoordsOnArc(angle, 1 - (1 - 0.65) / 2);

  return (
    <div
      id="dial-down"
      style={{
        textAlign: 'center',
      }}
    >
      <svg
        style={{ overflow: 'visible' }}
        width="9em"
        viewBox={[-1, -1, 2, 1].join(' ')}
      >
        <defs>
          <linearGradient
            id="Gauge__gradient"
            gradientUnits="userSpaceOnUse"
            x1="-1"
            x2="1"
            y2="0"
          >
            {gradientSteps.map((color, index) => (
              <stop
                key={color}
                stopColor={color}
                offset={`${index / (gradientSteps.length - 1)}`}
              />
            ))}
          </linearGradient>
        </defs>
        <path d={backgroundArc} fill="#dbdbe7" />
        <path d={filledArc} fill="url(#Gauge__gradient)" />
        <circle
          cx={markerLocation[0]}
          cy={markerLocation[1]}
          r="0.2"
          stroke="#2c3e50"
          strokeWidth="0.01"
          fill={colorScale(percent)}
        />
        <path
          d="M0.136364 0.0290102C0.158279 -0.0096701 0.219156 -0.00967009 0.241071 0.0290102C0.297078 0.120023 0.375 0.263367 0.375 0.324801C0.375 0.422639 0.292208 0.5 0.1875 0.5C0.0852272 0.5 -1.8346e-08 0.422639 -9.79274e-09 0.324801C0.00243506 0.263367 0.0803571 0.120023 0.136364 0.0290102ZM0.1875 0.381684C0.221591 0.381684 0.248377 0.356655 0.248377 0.324801C0.248377 0.292947 0.221591 0.267918 0.1875 0.267918C0.153409 0.267918 0.126623 0.292947 0.126623 0.324801C0.126623 0.356655 0.155844 0.381684 0.1875 0.381684Z"
          transform={`rotate(${
            angle * (180 / Math.PI)
          }) translate(-0.2, -0.33)`}
          fill="#6a6a85"
        />
      </svg>

      <div
        style={{
          marginTop: '0.4em',
          fontSize: '1em',
          lineHeight: '1em',
          fontWeight: '900',
          fontFeatureSettings: "'zero', 'tnum' 1",
        }}
      >
        <h4> </h4>
        {format(',')(value) + '% faster'}
      </div>

      {!!label && (
        <div
          style={{
            color: '#8b8ba7',
            marginTop: '0.6em',
            fontSize: '1.3em',
            lineHeight: '1.3em',
            fontWeight: '700',
          }}
        >
          {label}
        </div>
      )}

      {!!units && (
        <div
          style={{
            color: '#8b8ba7',
            lineHeight: '1.3em',
            fontWeight: '300',
          }}
        >
          {units}
        </div>
      )}
    </div>
  );
}
Example #17
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 #18
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 #19
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 #20
Source File: TimeseriesBrush.js    From covid19india-react with MIT License 4 votes vote down vote up
function TimeseriesBrush({
  timeseries,
  dates,
  currentBrushSelection,
  endDate,
  lookback,
  setBrushSelectionEnd,
  setLookback,
  animationIndex,
}) {
  const chartRef = useRef();
  const [wrapperRef, {width, height}] = useMeasure();

  const endDateMin =
    lookback !== null
      ? min([
          formatISO(addDays(parseIndiaDate(dates[0]), lookback), {
            representation: 'date',
          }),
          endDate,
        ])
      : endDate;

  const xScale = useMemo(() => {
    const T = dates.length;

    // Chart extremes
    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]);

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

    // Chart extremes
    const chartBottom = height - margin.bottom;

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

    // Switched to daily confirmed instead of cumulative ARD
    const timeseriesStacked = stack()
      .keys(BRUSH_STATISTICS)
      .value((date, statistic) =>
        Math.max(0, getStatistic(timeseries[date], 'delta', statistic))
      )(dates);

    const yScale = scaleLinear()
      .clamp(true)
      .domain([
        0,
        max(
          timeseriesStacked[timeseriesStacked.length - 1],
          ([, y1]) => yBufferTop * y1
        ),
      ])
      .range([chartBottom, margin.top]);

    const svg = select(chartRef.current);

    const t = svg.transition().duration(D3_TRANSITION_DURATION);

    svg
      .select('.x-axis')
      .attr('pointer-events', 'none')
      .style('transform', `translate3d(0, ${chartBottom}px, 0)`)
      .transition(t)
      .call(xAxis);

    const areaPath = area()
      .curve(curveMonotoneX)
      .x((d) => xScale(parseIndiaDate(d.data)))
      .y0((d) => yScale(d[0]))
      .y1((d) => yScale(d[1]));

    svg
      .select('.trend-areas')
      .selectAll('.trend-area')
      .data(timeseriesStacked)
      .join(
        (enter) =>
          enter
            .append('path')
            .attr('class', 'trend-area')
            .attr('fill', ({key}) => STATISTIC_CONFIGS[key].color)
            .attr('fill-opacity', 0.4)
            .attr('stroke', ({key}) => STATISTIC_CONFIGS[key].color)
            .attr('d', areaPath)
            .attr('pointer-events', 'none'),
        (update) =>
          update
            .transition(t)
            .attrTween('d', function (date) {
              const previous = select(this).attr('d');
              const current = areaPath(date);
              return interpolatePath(previous, current);
            })
            .selection()
      );
  }, [dates, width, height, xScale, timeseries]);

  const defaultSelection = currentBrushSelection.map((date) =>
    xScale(parseIndiaDate(date))
  );

  const brush = useMemo(() => {
    if (!width || !height) return;
    // Chart extremes
    const chartRight = width - margin.right;
    const chartBottom = height - margin.bottom;

    const brush = brushX()
      .extent([
        [margin.left, margin.top],
        [chartRight, chartBottom],
      ])
      .handleSize(20);
    return brush;
  }, [width, height]);

  const brushed = useCallback(
    ({sourceEvent, selection}) => {
      if (!sourceEvent) return;
      const [brushStartDate, brushEndDate] = selection.map(xScale.invert);

      ReactDOM.unstable_batchedUpdates(() => {
        setBrushSelectionEnd(formatISO(brushEndDate, {representation: 'date'}));
        setLookback(differenceInDays(brushEndDate, brushStartDate));
      });
    },
    [xScale, setBrushSelectionEnd, setLookback]
  );

  const beforebrushstarted = useCallback(
    (event) => {
      const svg = select(chartRef.current);
      const selection = brushSelection(svg.select('.brush').node());

      if (!selection) return;

      const dx = selection[1] - selection[0];
      const [[cx]] = pointers(event);
      const [x0, x1] = [cx - dx / 2, cx + dx / 2];
      const [X0, X1] = xScale.range();
      svg
        .select('.brush')
        .call(
          brush.move,
          x1 > X1 ? [X1 - dx, X1] : x0 < X0 ? [X0, X0 + dx] : [x0, x1]
        );
    },
    [brush, xScale]
  );

  const brushended = useCallback(
    ({sourceEvent, selection}) => {
      if (!sourceEvent || !selection) return;
      const domain = selection
        .map(xScale.invert)
        .map((date) => formatISO(date, {representation: 'date'}));

      const svg = select(chartRef.current);
      svg
        .select('.brush')
        .call(
          brush.move,
          domain.map((date) => xScale(parseIndiaDate(date)))
        )
        .call((g) => g.select('.overlay').attr('cursor', 'pointer'));
    },
    [brush, xScale]
  );

  useEffect(() => {
    if (!brush) return;
    brush.on('start brush', brushed).on('end', brushended);
    const svg = select(chartRef.current);
    svg
      .select('.brush')
      .call(brush)
      .call((g) =>
        g
          .select('.overlay')
          .attr('cursor', 'pointer')
          .datum({type: 'selection'})
          .on('mousedown touchstart', beforebrushstarted)
      );
  }, [brush, brushed, brushended, beforebrushstarted]);

  useEffect(() => {
    if (!brush) return;
    const svg = select(chartRef.current);
    svg.select('.brush').call(brush.move, defaultSelection);
  }, [brush, defaultSelection]);

  const handleWheel = (event) => {
    if (event.deltaX) {
      setBrushSelectionEnd(
        max([
          endDateMin,
          dates[
            Math.max(
              0,
              Math.min(
                dates.length - 1,
                dates.indexOf(currentBrushSelection[1]) +
                  Math.sign(event.deltaX) * brushWheelDelta
              )
            )
          ],
        ])
      );
    }
  };

  return (
    <div className="Timeseries">
      <div
        className={classnames('svg-parent is-brush fadeInUp')}
        ref={wrapperRef}
        onWheel={handleWheel}
        style={{animationDelay: `${animationIndex * 250}ms`}}
      >
        <svg ref={chartRef} preserveAspectRatio="xMidYMid meet">
          <defs>
            <clipPath id="clipPath">
              <rect
                x={0}
                y={`${margin.top}`}
                width={width}
                height={`${Math.max(0, height - margin.bottom)}`}
              />
            </clipPath>
            <mask id="mask">
              <rect
                x={0}
                y={`${margin.top}`}
                width={width}
                height={`${Math.max(0, height - margin.bottom)}`}
                fill="hsl(0, 0%, 40%)"
              />
              <use href="#selection" fill="white" />
            </mask>
          </defs>

          <g className="brush" clipPath="url(#clipPath)">
            <g mask="url(#mask)">
              <rect className="overlay" />
              <g className="trend-areas" />
              <rect className="selection" id="selection" />
            </g>
          </g>
          <g className="x-axis" />
        </svg>
      </div>
    </div>
  );
}
Example #21
Source File: area-chart.js    From website with Apache License 2.0 4 votes vote down vote up
AreaChart = ({
  annotations,
  data,
  fill,
  height,
  labelOrder,
  marginBottom,
  marginLeft,
  marginRight,
  marginTop,
  xTicks,
  width,
  yFormat,
  yMax,
  yTicks,
  showTicks,
  focusable,
  dateExtent,
  renderTooltipContents,
}) => {
  const grouped = nest()
    .key(d => d.label)
    .entries(data)

  const sorted = !labelOrder
    ? grouped
    : labelOrder
        .map(label => {
          const match = grouped.find(d => d.key === label)
          return match
        })
        .filter(d => d)

  const domain = dateExtent || extent(data, d => d.date)
  const valueMax = max(data, d => d.value)

  const totalXMargin = marginLeft + marginRight
  const totalYMargin = marginTop + marginBottom
  const fillFn = typeof fill === 'string' ? fill : d => fill(d.key)
  const xScale = scaleTime()
    .domain(domain)
    .range([0, width - totalXMargin])
  const yScale = scaleLinear()
    .domain([0, yMax || valueMax])
    .range([height - totalYMargin, 0])

  const a = area()
    .x(d => xScale(d.date))
    .y0(d => yScale(d.value))
    .y1(height - totalYMargin)

  const [tooltip, setTooltip] = useState(null)
  function svgPoint(svg, x, y) {
    const pt = svg.createSVGPoint()
    pt.x = x
    pt.y = y
    return pt.matrixTransform(svg.getScreenCTM().inverse())
  }
  const handleMouseMove = event => {
    const isTouchEvent = !event.clientX
    if (!renderTooltipContents) return
    const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
    const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
    const result = svgPoint(event.currentTarget, eventX, eventY)
    const date = xScale.invert(result.x - marginLeft + 8)
    date.setHours(0, 0, 0)
    setTooltip({
      top: isTouchEvent ? eventY - 130 : eventY + 10,
      left: isTouchEvent ? eventX - 80 : eventX + 5,
      date,
    })
  }
  const handleMouseLeave = () => setTooltip(null)
  const dateMap = useMemo(
    () =>
      merge(
        ...data.map(d => ({
          [d.date]: {
            date: d.date,
            [d.label]: d.value,
          },
        })),
      ),
    [data],
  )

  const handleTouchEndCapture = event => {
    setTooltip(null)
    event.preventDefault()
  }

  return (
    <>
      <svg
        className={chartStyles.chart}
        viewBox={`0 0 ${width} ${height}`}
        focusable={focusable}
        aria-hidden
        onTouchStart={handleMouseMove}
        onTouchEndCapture={handleTouchEndCapture}
        onMouseMoveCapture={handleMouseMove}
        onMouseLeave={handleMouseLeave}
      >
        {showTicks ? (
          <g transform={`translate(${marginLeft} ${marginTop})`}>
            <g transform={`translate(0 ${height - totalYMargin})`}>
              {xScale.ticks(xTicks).map(tick => (
                <text
                  className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
                  key={tick}
                  x={xScale(tick)}
                  y={20}
                >
                  {formatDate(tick)}
                </text>
              ))}
            </g>
            <g>
              {yScale.ticks(yTicks).map((tick, i) => (
                <g key={tick}>
                  {/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
                  <svg
                    y={yScale(tick) + 4}
                    x="-10"
                    className={chartStyles.yTickLabel}
                  >
                    <text className={chartStyles.label}>
                      {yFormat
                        ? yFormat(tick, i, yScale.ticks(yTicks).length)
                        : formatNumber(tick)}
                    </text>
                  </svg>
                  <line
                    className={chartStyles.gridLine}
                    x1={0}
                    x2={width - totalXMargin}
                    y1={yScale(tick)}
                    y2={yScale(tick)}
                  />
                </g>
              ))}
            </g>
          </g>
        ) : (
          <line
            className={chartStyles.gridLine}
            x1={0}
            x2={width}
            y1={height - 1}
            y2={height - 1}
          />
        )}
        <g transform={`translate(${marginLeft} ${marginTop})`}>
          {sorted.map(d => (
            <path key={d.key} d={a(d.values)} opacity={0.8} fill={fillFn(d)} />
          ))}
        </g>
        {annotations && (
          <g transform={`translate(${marginLeft} ${marginTop})`}>
            {annotations.map(d => (
              <line
                key={d.date}
                stroke="black"
                strokeWidth="2px"
                x1={xScale(d.date) - 1}
                x2={xScale(d.date) - 1}
                y1="0"
                y2={height - marginTop - marginBottom}
              />
            ))}
          </g>
        )}
      </svg>
      {tooltip && renderTooltipContents && dateMap[tooltip.date] && (
        <Tooltip {...tooltip}>
          {renderTooltipContents(dateMap[tooltip.date])}
        </Tooltip>
      )}
    </>
  )
}
Example #22
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 #23
Source File: MapVisualizer.js    From covid19india-react with MIT License 4 votes vote down vote up
function MapVisualizer({
  mapCode,
  isDistrictView,
  mapViz,
  data,
  regionHighlighted,
  setRegionHighlighted,
  statistic,
  getMapStatistic,
  transformStatistic,
  noDistrictData,
}) {
  const {t} = useTranslation();
  const svgRef = useRef(null);

  const mapMeta = MAP_META[mapCode];
  const history = useHistory();

  const {data: geoData} = useSWR(
    mapMeta.geoDataFile,
    async (file) => {
      return await json(file);
    },
    {suspense: false, revalidateOnFocus: false}
  );

  const statisticTotal = useMemo(() => {
    return getMapStatistic(data[mapCode]);
  }, [data, mapCode, getMapStatistic]);

  const statisticConfig = STATISTIC_CONFIGS[statistic];

  const strokeColor = useCallback(
    (alpha) => (statisticConfig?.color || '#343a40') + alpha,
    [statisticConfig]
  );

  const features = useMemo(() => {
    if (!geoData) return null;

    const featuresWrap = !isDistrictView
      ? feature(geoData, geoData.objects.states).features
      : mapMeta.mapType === MAP_TYPES.COUNTRY && mapViz !== MAP_VIZS.CHOROPLETH
      ? [
          ...feature(geoData, geoData.objects.states).features,
          ...feature(geoData, geoData.objects.districts).features,
        ]
      : feature(geoData, geoData.objects.districts).features;
    // Add id to each feature
    return featuresWrap.map((feature) => {
      const district = feature.properties.district;
      const state = feature.properties.st_nm;
      const obj = Object.assign({}, feature);
      obj.id = `${mapCode}-${state}${district ? '-' + district : ''}`;
      return obj;
    });
  }, [geoData, mapCode, isDistrictView, mapViz, mapMeta]);

  const districtsSet = useMemo(() => {
    if (!geoData || !isDistrictView) return {};
    return feature(geoData, geoData.objects.districts).features.reduce(
      (stateCodes, feature) => {
        const stateCode = STATE_CODES[feature.properties.st_nm];
        if (!stateCodes[stateCode]) {
          stateCodes[stateCode] = new Set();
        }
        stateCodes[stateCode].add(feature.properties.district);
        return stateCodes;
      },
      {}
    );
  }, [geoData, isDistrictView]);

  const statisticMax = useMemo(() => {
    const stateCodes = Object.keys(data).filter(
      (stateCode) =>
        stateCode !== 'TT' && Object.keys(MAP_META).includes(stateCode)
    );

    if (!isDistrictView) {
      return max(stateCodes, (stateCode) =>
        transformStatistic(getMapStatistic(data[stateCode]))
      );
    } else {
      const districtData = stateCodes.reduce((res, stateCode) => {
        const districts = Object.keys(data[stateCode]?.districts || []).filter(
          (districtName) =>
            (districtsSet?.[stateCode] || new Set()).has(districtName) ||
            (mapViz !== MAP_VIZS.CHOROPLETH &&
              districtName === UNKNOWN_DISTRICT_KEY)
        );
        res.push(
          ...districts.map((districtName) =>
            transformStatistic(
              getMapStatistic(data[stateCode].districts[districtName])
            )
          )
        );
        return res;
      }, []);
      return max(districtData);
    }
  }, [
    data,
    isDistrictView,
    getMapStatistic,
    mapViz,
    districtsSet,
    transformStatistic,
  ]);

  const mapScale = useMemo(() => {
    if (mapViz === MAP_VIZS.BUBBLE) {
      // No negative values
      return scaleSqrt([0, Math.max(1, statisticMax || 0)], [0, 40])
        .clamp(true)
        .nice(3);
    } else if (mapViz === MAP_VIZS.SPIKE) {
      return scaleLinear([0, Math.max(1, statisticMax || 0)], [0, 80])
        .clamp(true)
        .nice(3);
    } else if (STATISTIC_CONFIGS[statistic]?.mapConfig?.colorScale) {
      return STATISTIC_CONFIGS[statistic].mapConfig.colorScale;
    } else {
      // No negative values
      return scaleSequential(
        [0, Math.max(1, statisticMax || 0)],
        colorInterpolator(statistic)
      ).clamp(true);
    }
  }, [mapViz, statistic, statisticMax]);

  const fillColor = useCallback(
    (d) => {
      if (mapViz === MAP_VIZS.CHOROPLETH) {
        const stateCode = STATE_CODES[d.properties.st_nm];
        const district = d.properties.district;
        const stateData = data[stateCode];
        const districtData = stateData?.districts?.[district];
        const n = transformStatistic(
          getMapStatistic(district ? districtData : stateData)
        );
        const color = n ? mapScale(n) : '#ffffff00';
        return color;
      }
    },
    [mapViz, data, mapScale, getMapStatistic, transformStatistic]
  );

  const populateTexts = useCallback(
    (regionSelection) => {
      regionSelection.select('title').text((d) => {
        if (mapViz !== MAP_VIZS.CHOROPLETH && !statisticConfig?.nonLinear) {
          const state = d.properties.st_nm;
          const stateCode = STATE_CODES[state];
          const district = d.properties.district;

          const stateData = data[stateCode];
          const districtData = stateData?.districts?.[district];
          let n;
          if (district) n = getMapStatistic(districtData);
          else n = getMapStatistic(stateData);
          return `${formatNumber(
            100 * (n / (statisticTotal || 0.001)),
            '%'
          )} from ${toTitleCase(district ? district : state)}`;
        }
      });
    },
    [mapViz, data, getMapStatistic, statisticTotal, statisticConfig]
  );

  const onceTouchedRegion = useRef(null);

  // Reset on tapping outside map
  useEffect(() => {
    const svg = select(svgRef.current);

    svg.attr('pointer-events', 'auto').on('click', () => {
      onceTouchedRegion.current = null;
      setRegionHighlighted({
        stateCode: mapCode,
        districtName: null,
      });
    });
  }, [mapCode, setRegionHighlighted]);

  const path = useMemo(() => {
    if (!geoData) return null;
    return geoPath(geoIdentity());
  }, [geoData]);

  // Choropleth
  useEffect(() => {
    if (!geoData) return;
    const svg = select(svgRef.current);
    const T = transition().duration(D3_TRANSITION_DURATION);

    svg
      .select('.regions')
      .selectAll('path')
      .data(mapViz === MAP_VIZS.CHOROPLETH ? features : [], (d) => d.id)
      .join(
        (enter) =>
          enter
            .append('path')
            .attr('d', path)
            .attr('stroke-width', 1.8)
            .attr('stroke-opacity', 0)
            .style('cursor', 'pointer')
            .on('mouseenter', (event, d) => {
              if (onceTouchedRegion.current) return;
              setRegionHighlighted({
                stateCode: STATE_CODES[d.properties.st_nm],
                districtName: d.properties.district,
              });
            })
            .on('pointerdown', (event, d) => {
              if (onceTouchedRegion.current === d)
                onceTouchedRegion.current = null;
              else onceTouchedRegion.current = d;
              setRegionHighlighted({
                stateCode: STATE_CODES[d.properties.st_nm],
                districtName: d.properties.district,
              });
            })
            .attr('fill', '#fff0')
            .attr('stroke', '#fff0'),
        (update) => update,
        (exit) =>
          exit
            .transition(T)
            .attr('stroke', '#fff0')
            .attr('fill', '#fff0')
            .remove()
      )
      .attr('pointer-events', 'all')
      .on('click', (event, d) => {
        event.stopPropagation();
        const stateCode = STATE_CODES[d.properties.st_nm];
        if (
          onceTouchedRegion.current ||
          mapMeta.mapType === MAP_TYPES.STATE ||
          !data[stateCode]?.districts
        )
          return;
        // Disable pointer events till the new map is rendered
        svg.attr('pointer-events', 'none');
        svg.select('.regions').selectAll('path').attr('pointer-events', 'none');
        // Switch map
        history.push(
          `/state/${stateCode}${window.innerWidth < 769 ? '#MapExplorer' : ''}`
        );
      })
      .call((sel) => {
        sel
          .transition(T)
          .attr('fill', fillColor)
          .attr('stroke', strokeColor.bind(this, ''));
      });
  }, [
    mapViz,
    data,
    features,
    fillColor,
    geoData,
    history,
    mapMeta.mapType,
    path,
    setRegionHighlighted,
    strokeColor,
  ]);

  const sortedFeatures = useMemo(() => {
    if (mapViz === MAP_VIZS.CHOROPLETH) {
      return [];
    } else {
      return (features || [])
        .map((feature) => {
          const stateCode = STATE_CODES[feature.properties.st_nm];
          const districtName = feature.properties.district;
          const stateData = data[stateCode];

          if (!isDistrictView) {
            feature.value = getMapStatistic(stateData);
          } else {
            const districtData = stateData?.districts?.[districtName];

            if (districtName) feature.value = getMapStatistic(districtData);
            else
              feature.value = getMapStatistic(
                stateData?.districts?.[UNKNOWN_DISTRICT_KEY]
              );
          }

          return feature;
        })
        .filter((feature) => feature.value > 0)
        .sort((featureA, featureB) => featureB.value - featureB.value);
    }
  }, [mapViz, isDistrictView, getMapStatistic, features, data]);

  // Bubble
  useEffect(() => {
    const svg = select(svgRef.current);
    const T = transition().duration(D3_TRANSITION_DURATION);

    const regionSelection = svg
      .select('.circles')
      .selectAll('circle')
      .data(
        mapViz === MAP_VIZS.BUBBLE ? sortedFeatures : [],
        (feature) => feature.id
      )
      .join(
        (enter) =>
          enter
            .append('circle')
            .attr(
              'transform',
              (feature) => `translate(${path.centroid(feature)})`
            )
            .attr('fill-opacity', 0.25)
            .style('cursor', 'pointer')
            .attr('pointer-events', 'all')
            .call((enter) => {
              enter.append('title');
            }),
        (update) => update,
        (exit) => exit.call((exit) => exit.transition(T).attr('r', 0).remove())
      )
      .on('mouseenter', (event, feature) => {
        if (onceTouchedRegion.current) return;
        setRegionHighlighted({
          stateCode: STATE_CODES[feature.properties.st_nm],
          districtName: !isDistrictView
            ? null
            : feature.properties.district || UNKNOWN_DISTRICT_KEY,
        });
      })
      .on('pointerdown', (event, feature) => {
        if (onceTouchedRegion.current === feature)
          onceTouchedRegion.current = null;
        else onceTouchedRegion.current = feature;
        setRegionHighlighted({
          stateCode: STATE_CODES[feature.properties.st_nm],
          districtName: !isDistrictView
            ? null
            : feature.properties.district || UNKNOWN_DISTRICT_KEY,
        });
      })
      .on('click', (event, feature) => {
        event.stopPropagation();
        if (onceTouchedRegion.current || mapMeta.mapType === MAP_TYPES.STATE)
          return;
        history.push(
          `/state/${STATE_CODES[feature.properties.st_nm]}${
            window.innerWidth < 769 ? '#MapExplorer' : ''
          }`
        );
      })
      .call((sel) => {
        sel
          .transition(T)
          .attr('fill', statisticConfig.color + '70')
          .attr('stroke', statisticConfig.color + '70')
          .attr('r', (feature) => mapScale(feature.value));
      });

    window.requestIdleCallback(() => {
      populateTexts(regionSelection);
    });
  }, [
    mapMeta.mapType,
    mapViz,
    isDistrictView,
    sortedFeatures,
    history,
    mapScale,
    path,
    setRegionHighlighted,
    populateTexts,
    statisticConfig,
    getMapStatistic,
  ]);

  // Spike (Note: bad unmodular code)
  useEffect(() => {
    const svg = select(svgRef.current);
    const T = transition().duration(D3_TRANSITION_DURATION);

    const regionSelection = svg
      .select('.spikes')
      .selectAll('path')
      .data(
        mapViz === MAP_VIZS.SPIKE ? sortedFeatures : [],
        (feature) => feature.id,
        (feature) => feature.id
      )
      .join(
        (enter) =>
          enter
            .append('path')
            .attr(
              'transform',
              (feature) => `translate(${path.centroid(feature)})`
            )
            .attr('opacity', 0)
            .attr('fill-opacity', 0.25)
            .style('cursor', 'pointer')
            .attr('pointer-events', 'all')
            .attr('d', spike(0))
            .call((enter) => {
              enter.append('title');
            }),
        (update) => update,
        (exit) =>
          exit.call((exit) =>
            exit.transition(T).attr('opacity', 0).attr('d', spike(0)).remove()
          )
      )
      .on('mouseenter', (event, feature) => {
        if (onceTouchedRegion.current) return;
        setRegionHighlighted({
          stateCode: STATE_CODES[feature.properties.st_nm],
          districtName: !isDistrictView
            ? null
            : feature.properties.district || UNKNOWN_DISTRICT_KEY,
        });
      })
      .on('pointerdown', (event, feature) => {
        if (onceTouchedRegion.current === feature)
          onceTouchedRegion.current = null;
        else onceTouchedRegion.current = feature;
        setRegionHighlighted({
          stateCode: STATE_CODES[feature.properties.st_nm],
          districtName: !isDistrictView
            ? null
            : feature.properties.district || UNKNOWN_DISTRICT_KEY,
        });
      })
      .on('click', (event, feature) => {
        event.stopPropagation();
        if (onceTouchedRegion.current || mapMeta.mapType === MAP_TYPES.STATE)
          return;
        history.push(
          `/state/${STATE_CODES[feature.properties.st_nm]}${
            window.innerWidth < 769 ? '#MapExplorer' : ''
          }`
        );
      })
      .call((sel) => {
        sel
          .transition(T)
          .attr('opacity', 1)
          .attr('fill', statisticConfig.color + '70')
          .attr('stroke', statisticConfig.color + '70')
          .attr('d', (feature) => spike(mapScale(feature.value)));
      });

    window.requestIdleCallback(() => {
      populateTexts(regionSelection);
    });
  }, [
    mapMeta.mapType,
    mapViz,
    isDistrictView,
    sortedFeatures,
    history,
    mapScale,
    path,
    setRegionHighlighted,
    populateTexts,
    statisticConfig,
    getMapStatistic,
  ]);

  // Boundaries
  useEffect(() => {
    if (!geoData) return;
    const svg = select(svgRef.current);
    const T = transition().duration(D3_TRANSITION_DURATION);

    let meshStates = [];
    let meshDistricts = [];

    if (mapMeta.mapType === MAP_TYPES.COUNTRY) {
      meshStates = [mesh(geoData, geoData.objects.states)];
      meshStates[0].id = `${mapCode}-states`;
    }

    if (
      mapMeta.mapType === MAP_TYPES.STATE ||
      (isDistrictView && mapViz === MAP_VIZS.CHOROPLETH)
    ) {
      // Add id to mesh
      meshDistricts = [mesh(geoData, geoData.objects.districts)];
      meshDistricts[0].id = `${mapCode}-districts`;
    }

    svg
      .select('.state-borders')
      .attr('fill', 'none')
      .attr('stroke-width', 1.5)
      .selectAll('path')
      .data(meshStates, (d) => d.id)
      .join(
        (enter) => enter.append('path').attr('d', path).attr('stroke', '#fff0'),
        (update) => update,
        (exit) => exit.transition(T).attr('stroke', '#fff0').remove()
      )
      .transition(T)
      .attr('stroke', strokeColor.bind(this, '40'));

    svg
      .select('.district-borders')
      .attr('fill', 'none')
      .attr('stroke-width', 1.5)
      .selectAll('path')
      .data(meshDistricts, (d) => d.id)
      .join(
        (enter) =>
          enter
            .append('path')
            .attr('d', path)
            .attr('d', path)
            .attr('stroke', '#fff0'),
        (update) => update,
        (exit) => exit.transition(T).attr('stroke', '#fff0').remove()
      )
      .transition(T)
      .attr('stroke', strokeColor.bind(this, '40'));
  }, [
    geoData,
    mapMeta,
    mapCode,
    mapViz,
    isDistrictView,
    statistic,
    path,
    strokeColor,
  ]);

  // Highlight
  useEffect(() => {
    const stateCode = regionHighlighted.stateCode;
    const stateName = STATE_NAMES[stateCode];
    const district = regionHighlighted.districtName;

    const svg = select(svgRef.current);

    if (mapViz === MAP_VIZS.BUBBLE) {
      svg
        .select('.circles')
        .selectAll('circle')
        .attr('fill-opacity', (d) => {
          const highlighted =
            stateName === d.properties.st_nm &&
            ((!district && stateCode !== mapCode) ||
              district === d.properties?.district ||
              !isDistrictView ||
              (district === UNKNOWN_DISTRICT_KEY && !d.properties.district));
          return highlighted ? 1 : 0.25;
        });
    } else if (mapViz === MAP_VIZS.SPIKE) {
      svg
        .select('.spikes')
        .selectAll('path')
        .attr('fill-opacity', (d) => {
          const highlighted =
            stateName === d.properties.st_nm &&
            ((!district && stateCode !== mapCode) ||
              district === d.properties?.district ||
              !isDistrictView ||
              (district === UNKNOWN_DISTRICT_KEY && !d.properties.district));
          return highlighted ? 1 : 0.25;
        });
    } else {
      svg
        .select('.regions')
        .selectAll('path')
        .each(function (d) {
          const highlighted =
            stateName === d.properties.st_nm &&
            ((!district && stateCode !== mapCode) ||
              district === d.properties?.district ||
              !isDistrictView);
          if (highlighted) this.parentNode.appendChild(this);
          select(this).attr('stroke-opacity', highlighted ? 1 : 0);
        });
    }
  }, [
    geoData,
    data,
    mapCode,
    isDistrictView,
    mapViz,
    regionHighlighted.stateCode,
    regionHighlighted.districtName,
    statistic,
  ]);

  return (
    <>
      <div className="svg-parent">
        <svg
          id="chart"
          className={classnames({
            zone: !!statisticConfig?.mapConfig?.colorScale,
          })}
          viewBox={`0 0 ${MAP_DIMENSIONS[0]} ${MAP_DIMENSIONS[1]}`}
          preserveAspectRatio="xMidYMid meet"
          ref={svgRef}
        >
          <g className="regions" />
          <g className="state-borders" />
          <g className="district-borders" />
          <g className="circles" />
          <g className="spikes" />
        </svg>
        {noDistrictData && statisticConfig?.hasPrimary && (
          <div className={classnames('disclaimer', `is-${statistic}`)}>
            <AlertIcon />
            <span>
              {t('District-wise data not available in state bulletin')}
            </span>
          </div>
        )}
      </div>

      {mapScale && <MapLegend {...{data, statistic, mapViz, mapScale}} />}

      <svg style={{position: 'absolute', height: 0}}>
        <defs>
          <filter id="balance-color" colorInterpolationFilters="sRGB">
            <feColorMatrix
              type="matrix"
              values="0.91372549  0           0            0  0.08627451
                      0           0.91372549  0            0  0.08627451
                      0           0           0.854901961  0  0.145098039
                      0           0           0            1  0"
            />
          </filter>
        </defs>
      </svg>
    </>
  );
}
Example #24
Source File: MapLegend.js    From covid19india-react with MIT License 4 votes vote down vote up
function legend({
  svg,
  color,
  title,
  tickSize = 6,
  width = 320,
  height = 44 + tickSize,
  marginTop = 18,
  marginRight = 0,
  marginBottom = 16 + tickSize,
  marginLeft = 0,
  ticks = width / 64,
  tickFormat,
  tickValues,
  ordinalWeights,
} = {}) {
  const t = svg.transition().duration(D3_TRANSITION_DURATION);

  let tickAdjust = (g) => {
    const ticks = g.selectAll('.tick line');
    ticks.attr('y1', marginTop + marginBottom - height);
    // select(ticks.nodes()[ticks.size() - 1]).remove();
  };
  let x;

  // Continuous
  if (color.interpolate) {
    const n = Math.min(color.domain().length, color.range().length);

    x = color
      .copy()
      .rangeRound(quantize(interpolate(marginLeft, width - marginRight), n));

    svg
      .select('.ramp')
      .attr('x', marginLeft)
      .attr('y', marginTop)
      .attr('width', width - marginLeft - marginRight)
      .attr('height', height - marginTop - marginBottom)
      .attr(
        'xlink:href',
        ramp(color.copy().domain(quantize(interpolate(0, 1), n))).toDataURL()
      );
  }

  // Sequential
  else if (color.interpolator) {
    svg
      .select('.bars')
      .selectAll('rect')
      .transition(t)
      .attr('opacity', 0)
      .remove();

    x = Object.assign(
      color
        .copy()
        .interpolator(interpolateRound(marginLeft, width - marginRight)),
      {
        range() {
          return [marginLeft, width - marginRight];
        },
      }
    );

    svg
      .select('.ramp')
      .attr('x', marginLeft)
      .attr('y', marginTop)
      .attr('width', width - marginLeft - marginRight)
      .attr('height', height - marginTop - marginBottom)
      .attr('xlink:href', ramp(color.interpolator()).toDataURL())
      .attr('display', 'visible')
      .transition(t)
      .attr('opacity', 1);

    // scaleSequentialQuantile doesn’t implement ticks or tickFormat.
    if (!x.ticks) {
      if (tickValues === undefined) {
        const n = Math.round(ticks + 1);
        tickValues = range(n).map((i) => quantile(color.domain(), i / (n - 1)));
      }
      if (typeof tickFormat !== 'function') {
        tickFormat = format(tickFormat === undefined ? ',f' : tickFormat);
      }
    }
  }

  // Threshold
  else if (color.invertExtent) {
    const thresholds = color.thresholds
      ? color.thresholds() // scaleQuantize
      : color.quantiles
      ? color.quantiles() // scaleQuantile
      : color.domain(); // scaleThreshold

    const thresholdFormat =
      tickFormat === undefined
        ? (d) => d
        : typeof tickFormat === 'string'
        ? format(tickFormat)
        : tickFormat;

    x = scaleLinear()
      .domain([-1, color.range().length - 1])
      .rangeRound([marginLeft, width - marginRight]);

    svg
      .append('g')
      .selectAll('rect')
      .data(color.range())
      .join('rect')
      .attr('x', (d, i) => x(i - 1))
      .attr('y', marginTop)
      .attr('width', (d, i) => x(i) - x(i - 1))
      .attr('height', height - marginTop - marginBottom)
      .attr('fill', (d) => d);

    tickValues = range(-1, thresholds.length);
    tickFormat = (i) => {
      if (i === -1) return thresholdFormat(1);
      else if (i === thresholds.length - 1) return;
      else if (i === thresholds.length - 2)
        return thresholdFormat(thresholds[i] + '+', i);
      return thresholdFormat(thresholds[i], i);
    };
  }

  // Ordinal
  else {
    svg
      .select('.ramp')
      .transition(t)
      .attr('opacity', 0)
      .attr('xlink:href', null);
    if (!ordinalWeights) {
      x = scaleBand()
        .domain(color.domain().filter((d) => d))
        .rangeRound([marginLeft, width - marginRight]);
      svg
        .select('.bars')
        .selectAll('rect')
        .data(color.domain().filter((d) => d))
        .join('rect')
        .attr('x', x)
        .attr('y', marginTop)
        .attr('width', Math.max(0, x.bandwidth() - 1))
        .attr('height', height - marginTop - marginBottom)
        .attr('fill', color);
    } else {
      const widthScale = scaleLinear()
        .domain([0, ordinalWeights.reduce((a, b) => a + b)])
        .rangeRound([0, width - marginLeft - marginRight]);

      const xPos = ordinalWeights.map((w, i) =>
        ordinalWeights
          .slice(0, i)
          .reduce((acc, w) => acc + widthScale(w), marginLeft)
      );

      x = scaleOrdinal().domain(color.domain()).range(xPos);

      svg
        .select('.bars')
        .selectAll('rect')
        .data(color.domain())
        .join((enter) =>
          enter
            .append('rect')
            .attr('x', x)
            .attr('width', (d, i) => widthScale(ordinalWeights[i]))
        )
        .attr('y', marginTop)
        .attr('height', height - marginTop - marginBottom)
        .attr('fill', color)
        .transition(t)
        .attr('x', x)
        .attr('width', (d, i) => widthScale(ordinalWeights[i]))
        .attr('opacity', 1);
    }

    tickAdjust = () => {};
  }

  svg
    .select('.axis')
    .attr('transform', `translate(0,${height - marginBottom})`)
    .transition(t)
    .attr('class', 'axis')
    .call(
      axisBottom(x)
        .ticks(ticks, typeof tickFormat === 'string' ? tickFormat : undefined)
        .tickFormat(typeof tickFormat === 'function' ? tickFormat : undefined)
        .tickSize(tickSize)
        .tickValues(tickValues)
    )
    .on('start', () => {
      svg.call(tickAdjust).call((svg) => svg.select('.domain').remove());
    })
    .call((g) =>
      g
        .select('.axistext')
        .attr('class', 'axistext')
        .attr('x', marginLeft)
        .attr('y', marginTop + marginBottom - height - 6)
        .attr('fill', 'currentColor')
        .attr('text-anchor', 'start')
        .attr('font-weight', 'bold')
        .text(title)
    );

  return svg.node();
}
Example #25
Source File: DeltaBarGraph.js    From covid19india-react with MIT License 4 votes vote down vote up
function DeltaBarGraph({timeseries, statistic, lookback}) {
  const svgRef = useRef();
  const [wrapperRef, {width, height}] = useMeasure();

  const pastDates = Object.keys(timeseries || {}).filter(
    (date) => date <= getIndiaDateYesterdayISO()
  );
  const dates = pastDates.slice(-lookback);

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

  useEffect(() => {
    if (!width) return;
    const svg = select(svgRef.current);

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

    // const formatTime = timeFormat('%e %b');
    const xScale = scaleBand()
      .domain(dates)
      .range([margin.left, chartRight])
      .paddingInner(width / 1000);

    const [statisticMin, statisticMax] = extent(dates, (date) =>
      getDeltaStatistic(date, statistic)
    );

    const yScale = scaleLinear()
      .domain([Math.min(0, statisticMin || 0), Math.max(1, statisticMax || 0)])
      .range([chartBottom, margin.top]);

    const xAxis = axisBottom(xScale)
      .tickSize(0)
      .tickFormat((date) => formatDate(date, 'dd MMM'));

    const t = svg.transition().duration(D3_TRANSITION_DURATION);

    const statisticConfig = STATISTIC_CONFIGS[statistic];

    svg
      .select('.x-axis')
      .transition(t)
      .style('transform', `translate3d(0, ${yScale(0)}px, 0)`)
      .call(xAxis)
      .on('start', () => svg.select('.domain').remove())
      .selectAll('text')
      .attr('y', 0)
      .attr('dy', (date, i) =>
        getDeltaStatistic(date, statistic) < 0 ? '-1em' : '1.5em'
      )
      .style('text-anchor', 'middle')
      .attr('fill', statisticConfig.color);

    svg
      .selectAll('.bar')
      .data(dates)
      .join((enter) =>
        enter
          .append('path')
          .attr('class', 'bar')
          .attr('d', (date) =>
            roundedBar(xScale(date), yScale(0), xScale.bandwidth(), 0, r)
          )
      )
      .transition(t)
      .attr('d', (date) =>
        roundedBar(
          xScale(date),
          yScale(0),
          xScale.bandwidth(),
          yScale(0) - yScale(getDeltaStatistic(date, statistic)),
          r
        )
      )
      .attr('fill', (date, i) => {
        return i < dates.length - 1
          ? statisticConfig.color + '90'
          : statisticConfig.color;
      });

    const textSelection = svg
      .selectAll('.label')
      .data(dates)
      .join('text')
      .attr('class', 'label')
      .attr('x', (date) => xScale(date) + xScale.bandwidth() / 2)
      .text((date) =>
        formatNumber(
          getDeltaStatistic(date, statistic),
          statisticConfig?.showDelta || statisticConfig?.nonLinear
            ? statisticConfig.format
            : 'short'
        )
      );

    textSelection
      .transition(t)
      .attr('fill', statisticConfig.color)
      .attr('y', (date) => {
        const val = getDeltaStatistic(date, statistic);
        return yScale(val) + (val < 0 ? 15 : -6);
      });

    textSelection
      .append('tspan')
      .attr(
        'dy',
        (date) => `${getDeltaStatistic(date, statistic) < 0 ? 1.2 : -1.2}em`
      )
      .attr('x', (date) => xScale(date) + xScale.bandwidth() / 2)
      .text((date, i) => {
        if (i === 0) return '';
        const prevVal = getDeltaStatistic(dates[i - 1], statistic);
        if (!prevVal) return '';
        const delta = getDeltaStatistic(date, statistic) - prevVal;
        return `${delta > 0 ? '+' : ''}${formatNumber(
          (100 * delta) / Math.abs(prevVal),
          '%'
        )}`;
      })
      .transition(t)
      .attr('fill', statisticConfig.color + '90');
  }, [dates, height, statistic, width, getDeltaStatistic]);

  return (
    <div className="DeltaBarGraph" ref={wrapperRef}>
      <svg
        ref={svgRef}
        width={width}
        height={250}
        viewBox={`0 0 ${width} ${height}`}
        preserveAspectRatio="xMidYMid meet"
      >
        <g className="x-axis" />
        <g className="y-axis" />
      </svg>
    </div>
  );
}
Example #26
Source File: symbol.js    From t-viSNE with MIT License 4 votes vote down vote up
export default function symbol() {
  let scale = scaleLinear(),
    shape = "path",
    shapeWidth = 15,
    shapeHeight = 15,
    shapeRadius = 10,
    shapePadding = 5,
    cells = [5],
    cellFilter,
    labels = [],
    classPrefix = "",
    title = "",
    locale = helper.d3_defaultLocale,
    specifier = helper.d3_defaultFormatSpecifier,
    labelAlign = "middle",
    labelOffset = 10,
    labelDelimiter = helper.d3_defaultDelimiter,
    labelWrap,
    orient = "vertical",
    ascending = false,
    titleWidth,
    legendDispatcher = dispatch("cellover", "cellout", "cellclick")

  function legend(svg) {
    const type = helper.d3_calcType(
        scale,
        ascending,
        cells,
        labels,
        locale.format(specifier),
        labelDelimiter
      ),
      legendG = svg.selectAll("g").data([scale])

    if (cellFilter) {
      helper.d3_filterCells(type, cellFilter)
    }

    legendG
      .enter()
      .append("g")
      .attr("class", classPrefix + "legendCells")

    let cell = svg
      .select("." + classPrefix + "legendCells")
      .selectAll("." + classPrefix + "cell")
      .data(type.data)
    const cellEnter = cell
      .enter()
      .append("g")
      .attr("class", classPrefix + "cell")
    cellEnter.append(shape).attr("class", classPrefix + "swatch")

    let shapes = svg.selectAll("g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch")

    //add event handlers
    helper.d3_addEvents(cellEnter, legendDispatcher)

    //remove old shapes
    cell
      .exit()
      .transition()
      .style("opacity", 0)
      .remove()
    shapes
      .exit()
      .transition()
      .style("opacity", 0)
      .remove()
    shapes = shapes.merge(shapes)

    helper.d3_drawShapes(
      shape,
      shapes,
      shapeHeight,
      shapeWidth,
      shapeRadius,
      type.feature
    )
    const text = helper.d3_addText(
      svg,
      cellEnter,
      type.labels,
      classPrefix,
      labelWrap
    )

    // we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
    cell = cellEnter.merge(cell)

    // sets placement
    const textSize = text.nodes().map(d => d.getBBox()),
      shapeSize = shapes.nodes().map(d => d.getBBox())

    const maxH = max(shapeSize, d => d.height),
      maxW = max(shapeSize, d => d.width)

    let cellTrans,
      textTrans,
      textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1

    //positions cells and text
    if (orient === "vertical") {
      const cellSize = textSize.map((d, i) => Math.max(maxH, d.height))

      cellTrans = (d, i) => {
        const height = sum(cellSize.slice(0, i))
        return `translate(0, ${height + i * shapePadding} )`
      }
      textTrans = (d, i) => `translate( ${maxW + labelOffset},
              ${shapeSize[i].y + shapeSize[i].height / 2 + 5})`
    } else if (orient === "horizontal") {
      cellTrans = (d, i) => `translate( ${i * (maxW + shapePadding)},0)`
      textTrans = (d, i) => `translate( ${shapeSize[i].width * textAlign +
        shapeSize[i].x},
              ${maxH + labelOffset})`
    }

    helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign)
    helper.d3_title(svg, title, classPrefix, titleWidth)
    cell.transition().style("opacity", 1)
  }

  legend.scale = function(_) {
    if (!arguments.length) return scale
    scale = _
    return legend
  }

  legend.cells = function(_) {
    if (!arguments.length) return cells
    if (_.length > 1 || _ >= 2) {
      cells = _
    }
    return legend
  }

  legend.cellFilter = function(_) {
    if (!arguments.length) return cellFilter
    cellFilter = _
    return legend
  }

  legend.shapePadding = function(_) {
    if (!arguments.length) return shapePadding
    shapePadding = +_
    return legend
  }

  legend.labels = function(_) {
    if (!arguments.length) return labels
    labels = _
    return legend
  }

  legend.labelAlign = function(_) {
    if (!arguments.length) return labelAlign
    if (_ == "start" || _ == "end" || _ == "middle") {
      labelAlign = _
    }
    return legend
  }

  legend.locale = function(_) {
    if (!arguments.length) return locale
    locale = formatLocale(_)
    return legend
  }

  legend.labelFormat = function(_) {
    if (!arguments.length) return legend.locale().format(specifier)
    specifier = formatSpecifier(_)
    return legend
  }

  legend.labelOffset = function(_) {
    if (!arguments.length) return labelOffset
    labelOffset = +_
    return legend
  }

  legend.labelDelimiter = function(_) {
    if (!arguments.length) return labelDelimiter
    labelDelimiter = _
    return legend
  }

  legend.labelWrap = function(_) {
    if (!arguments.length) return labelWrap
    labelWrap = _
    return legend
  }

  legend.orient = function(_) {
    if (!arguments.length) return orient
    _ = _.toLowerCase()
    if (_ == "horizontal" || _ == "vertical") {
      orient = _
    }
    return legend
  }

  legend.ascending = function(_) {
    if (!arguments.length) return ascending
    ascending = !!_
    return legend
  }

  legend.classPrefix = function(_) {
    if (!arguments.length) return classPrefix
    classPrefix = _
    return legend
  }

  legend.title = function(_) {
    if (!arguments.length) return title
    title = _
    return legend
  }

  legend.titleWidth = function(_) {
    if (!arguments.length) return titleWidth
    titleWidth = _
    return legend
  }

  legend.on = function() {
    const value = legendDispatcher.on.apply(legendDispatcher, arguments)
    return value === legendDispatcher ? legend : value
  }

  return legend
}
Example #27
Source File: size.js    From t-viSNE with MIT License 4 votes vote down vote up
export default function size() {
  let scale = scaleLinear(),
    shape = "rect",
    shapeWidth = 15,
    shapePadding = 2,
    cells = [5],
    cellFilter,
    labels = [],
    classPrefix = "",
    title = "",
    locale = helper.d3_defaultLocale,
    specifier = helper.d3_defaultFormatSpecifier,
    labelOffset = 10,
    labelAlign = "middle",
    labelDelimiter = helper.d3_defaultDelimiter,
    labelWrap,
    orient = "vertical",
    ascending = false,
    path,
    titleWidth,
    legendDispatcher = dispatch("cellover", "cellout", "cellclick")

  function legend(svg) {
    const type = helper.d3_calcType(
        scale,
        ascending,
        cells,
        labels,
        locale.format(specifier),
        labelDelimiter
      ),
      legendG = svg.selectAll("g").data([scale])

    if (cellFilter) {
      helper.d3_filterCells(type, cellFilter)
    }

    legendG
      .enter()
      .append("g")
      .attr("class", classPrefix + "legendCells")

    let cell = svg
      .select("." + classPrefix + "legendCells")
      .selectAll("." + classPrefix + "cell")
      .data(type.data)
    const cellEnter = cell
      .enter()
      .append("g")
      .attr("class", classPrefix + "cell")
    cellEnter.append(shape).attr("class", classPrefix + "swatch")

    let shapes = svg.selectAll(
      "g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch"
    )

    //add event handlers
    helper.d3_addEvents(cellEnter, legendDispatcher)

    cell
      .exit()
      .transition()
      .style("opacity", 0)
      .remove()

    shapes
      .exit()
      .transition()
      .style("opacity", 0)
      .remove()
    shapes = shapes.merge(shapes)

    //creates shape
    if (shape === "line") {
      helper.d3_drawShapes(shape, shapes, 0, shapeWidth)
      shapes.attr("stroke-width", type.feature)
    } else {
      helper.d3_drawShapes(
        shape,
        shapes,
        type.feature,
        type.feature,
        type.feature,
        path
      )
    }

    const text = helper.d3_addText(
      svg,
      cellEnter,
      type.labels,
      classPrefix,
      labelWrap
    )

    // we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
    cell = cellEnter.merge(cell)

    //sets placement

    const textSize = text.nodes().map(d => d.getBBox()),
      shapeSize = shapes.nodes().map((d, i) => {
        const bbox = d.getBBox()
        const stroke = scale(type.data[i])

        if (shape === "line" && orient === "horizontal") {
          bbox.height = bbox.height + stroke
        } else if (shape === "line" && orient === "vertical") {
          bbox.width = bbox.width
        }
        return bbox
      })

    const maxH = max(shapeSize, d => d.height + d.y),
      maxW = max(shapeSize, d => d.width + d.x)

    let cellTrans,
      textTrans,
      textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1

    //positions cells and text
    if (orient === "vertical") {
      const cellSize = textSize.map((d, i) =>
        Math.max(d.height, shapeSize[i].height)
      )
      const y =
        shape == "circle" || shape == "line" ? shapeSize[0].height / 2 : 0
      cellTrans = (d, i) => {
        const height = sum(cellSize.slice(0, i))

        return `translate(0, ${y + height + i * shapePadding})`
      }

      textTrans = (d, i) => `translate( ${maxW + labelOffset},
          ${shapeSize[i].y + shapeSize[i].height / 2 + 5})`
    } else if (orient === "horizontal") {
      cellTrans = (d, i) => {
        const width = sum(shapeSize.slice(0, i), d => d.width)
        const y = shape == "circle" || shape == "line" ? maxH / 2 : 0
        return `translate(${width + i * shapePadding}, ${y})`
      }

      const offset = shape == "line" ? maxH / 2 : maxH
      textTrans = (d, i) => {
        return `translate( ${shapeSize[i].width * textAlign + shapeSize[i].x},
              ${offset + labelOffset})`
      }
    }

    helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign)
    helper.d3_title(svg, title, classPrefix, titleWidth)

    cell.transition().style("opacity", 1)
  }

  legend.scale = function(_) {
    if (!arguments.length) return scale
    scale = _
    return legend
  }

  legend.cells = function(_) {
    if (!arguments.length) return cells
    if (_.length > 1 || _ >= 2) {
      cells = _
    }
    return legend
  }

  legend.cellFilter = function(_) {
    if (!arguments.length) return cellFilter
    cellFilter = _
    return legend
  }

  legend.shape = function(_, d) {
    if (!arguments.length) return shape
    if (_ == "rect" || _ == "circle" || _ == "line") {
      shape = _
      path = d
    }
    return legend
  }

  legend.shapeWidth = function(_) {
    if (!arguments.length) return shapeWidth
    shapeWidth = +_
    return legend
  }

  legend.shapePadding = function(_) {
    if (!arguments.length) return shapePadding
    shapePadding = +_
    return legend
  }

  legend.labels = function(_) {
    if (!arguments.length) return labels
    labels = _
    return legend
  }

  legend.labelAlign = function(_) {
    if (!arguments.length) return labelAlign
    if (_ == "start" || _ == "end" || _ == "middle") {
      labelAlign = _
    }
    return legend
  }

  legend.locale = function(_) {
    if (!arguments.length) return locale
    locale = formatLocale(_)
    return legend
  }

  legend.labelFormat = function(_) {
    if (!arguments.length) return legend.locale().format(specifier)
    specifier = formatSpecifier(_)
    return legend
  }

  legend.labelOffset = function(_) {
    if (!arguments.length) return labelOffset
    labelOffset = +_
    return legend
  }

  legend.labelDelimiter = function(_) {
    if (!arguments.length) return labelDelimiter
    labelDelimiter = _
    return legend
  }

  legend.labelWrap = function(_) {
    if (!arguments.length) return labelWrap
    labelWrap = _
    return legend
  }

  legend.orient = function(_) {
    if (!arguments.length) return orient
    _ = _.toLowerCase()
    if (_ == "horizontal" || _ == "vertical") {
      orient = _
    }
    return legend
  }

  legend.ascending = function(_) {
    if (!arguments.length) return ascending
    ascending = !!_
    return legend
  }

  legend.classPrefix = function(_) {
    if (!arguments.length) return classPrefix
    classPrefix = _
    return legend
  }

  legend.title = function(_) {
    if (!arguments.length) return title
    title = _
    return legend
  }

  legend.titleWidth = function(_) {
    if (!arguments.length) return titleWidth
    titleWidth = _
    return legend
  }

  legend.on = function() {
    const value = legendDispatcher.on.apply(legendDispatcher, arguments)
    return value === legendDispatcher ? legend : value
  }

  return legend
}
Example #28
Source File: color.js    From t-viSNE with MIT License 4 votes vote down vote up
export default function color() {
  let scale = scaleLinear(),
    shape = "rect",
    shapeWidth = 15,
    shapeHeight = 15,
    shapeRadius = 10,
    shapePadding = 2,
    cells = [5],
    cellFilter,
    labels = [],
    classPrefix = "",
    useClass = false,
    title = "",
    locale = helper.d3_defaultLocale,
    specifier = helper.d3_defaultFormatSpecifier,
    labelOffset = 10,
    labelAlign = "middle",
    labelDelimiter = helper.d3_defaultDelimiter,
    labelWrap,
    orient = "vertical",
    ascending = false,
    path,
    titleWidth,
    legendDispatcher = dispatch("cellover", "cellout", "cellclick")

  function legend(svg) {
    const type = helper.d3_calcType(
        scale,
        ascending,
        cells,
        labels,
        locale.format(specifier),
        labelDelimiter
      ),
      legendG = svg.selectAll("g").data([scale])

    legendG
      .enter()
      .append("g")
      .attr("class", classPrefix + "legendCells")

    if (cellFilter) {
      helper.d3_filterCells(type, cellFilter)
    }

    let cell = svg
      .select("." + classPrefix + "legendCells")
      .selectAll("." + classPrefix + "cell")
      .data(type.data)

    const cellEnter = cell
      .enter()
      .append("g")
      .attr("class", classPrefix + "cell")
    cellEnter.append(shape).attr("class", classPrefix + "swatch")

    let shapes = svg
      .selectAll(
        "g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch"
      )
      .data(type.data)

    //add event handlers
    helper.d3_addEvents(cellEnter, legendDispatcher)

    cell
      .exit()
      .transition()
      .style("opacity", 0)
      .remove()
    shapes
      .exit()
      .transition()
      .style("opacity", 0)
      .remove()

    shapes = shapes.merge(shapes)

    helper.d3_drawShapes(
      shape,
      shapes,
      shapeHeight,
      shapeWidth,
      shapeRadius,
      path
    )
    const text = helper.d3_addText(
      svg,
      cellEnter,
      type.labels,
      classPrefix,
      labelWrap
    )

    // we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
    cell = cellEnter.merge(cell)

    // sets placement
    const textSize = text.nodes().map(d => d.getBBox()),
      shapeSize = shapes.nodes().map(d => d.getBBox())
    //sets scale
    //everything is fill except for line which is stroke,
    if (!useClass) {
      if (shape == "line") {
        shapes.style("stroke", type.feature)
      } else {
        shapes.style("fill", type.feature)
      }
    } else {
      shapes.attr("class", d => `${classPrefix}swatch ${type.feature(d)}`)
    }

    let cellTrans,
      textTrans,
      textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1

    //positions cells and text
    if (orient === "vertical") {
      const cellSize = textSize.map((d, i) =>
        Math.max(d.height, shapeSize[i].height)
      )

      cellTrans = (d, i) => {
        const height = sum(cellSize.slice(0, i))
        return `translate(0, ${height + i * shapePadding})`
      }

      textTrans = (d, i) =>
        `translate( ${shapeSize[i].width +
          shapeSize[i].x +
          labelOffset}, ${shapeSize[i].y + shapeSize[i].height / 2 + 5})`
    } else if (orient === "horizontal") {
      cellTrans = (d, i) =>
        `translate(${i * (shapeSize[i].width + shapePadding)},0)`
      textTrans = (d, i) => `translate(${shapeSize[i].width * textAlign +
        shapeSize[i].x},
          ${shapeSize[i].height + shapeSize[i].y + labelOffset + 8})`
    }

    helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign)
    helper.d3_title(svg, title, classPrefix, titleWidth)

    cell.transition().style("opacity", 1)
  }

  legend.scale = function(_) {
    if (!arguments.length) return scale
    scale = _
    return legend
  }

  legend.cells = function(_) {
    if (!arguments.length) return cells
    if (_.length > 1 || _ >= 2) {
      cells = _
    }
    return legend
  }

  legend.cellFilter = function(_) {
    if (!arguments.length) return cellFilter
    cellFilter = _
    return legend
  }

  legend.shape = function(_, d) {
    if (!arguments.length) return shape
    if (
      _ == "rect" ||
      _ == "circle" ||
      _ == "line" ||
      (_ == "path" && typeof d === "string")
    ) {
      shape = _
      path = d
    }
    return legend
  }

  legend.shapeWidth = function(_) {
    if (!arguments.length) return shapeWidth
    shapeWidth = +_
    return legend
  }

  legend.shapeHeight = function(_) {
    if (!arguments.length) return shapeHeight
    shapeHeight = +_
    return legend
  }

  legend.shapeRadius = function(_) {
    if (!arguments.length) return shapeRadius
    shapeRadius = +_
    return legend
  }

  legend.shapePadding = function(_) {
    if (!arguments.length) return shapePadding
    shapePadding = +_
    return legend
  }

  legend.labels = function(_) {
    if (!arguments.length) return labels
    labels = _
    return legend
  }

  legend.labelAlign = function(_) {
    if (!arguments.length) return labelAlign
    if (_ == "start" || _ == "end" || _ == "middle") {
      labelAlign = _
    }
    return legend
  }

  legend.locale = function(_) {
    if (!arguments.length) return locale
    locale = formatLocale(_)
    return legend
  }

  legend.labelFormat = function(_) {
    if (!arguments.length) return legend.locale().format(specifier)
    specifier = formatSpecifier(_)
    return legend
  }

  legend.labelOffset = function(_) {
    if (!arguments.length) return labelOffset
    labelOffset = +_
    return legend
  }

  legend.labelDelimiter = function(_) {
    if (!arguments.length) return labelDelimiter
    labelDelimiter = _
    return legend
  }

  legend.labelWrap = function(_) {
    if (!arguments.length) return labelWrap
    labelWrap = _
    return legend
  }

  legend.useClass = function(_) {
    if (!arguments.length) return useClass
    if (_ === true || _ === false) {
      useClass = _
    }
    return legend
  }

  legend.orient = function(_) {
    if (!arguments.length) return orient
    _ = _.toLowerCase()
    if (_ == "horizontal" || _ == "vertical") {
      orient = _
    }
    return legend
  }

  legend.ascending = function(_) {
    if (!arguments.length) return ascending
    ascending = !!_
    return legend
  }

  legend.classPrefix = function(_) {
    if (!arguments.length) return classPrefix
    classPrefix = _
    return legend
  }

  legend.title = function(_) {
    if (!arguments.length) return title
    title = _
    return legend
  }

  legend.titleWidth = function(_) {
    if (!arguments.length) return titleWidth
    titleWidth = _
    return legend
  }

  legend.textWrap = function(_) {
    if (!arguments.length) return textWrap
    textWrap = _
    return legend
  }

  legend.on = function() {
    const value = legendDispatcher.on.apply(legendDispatcher, arguments)
    return value === legendDispatcher ? legend : value
  }

  return legend
}
Example #29
Source File: indexRollupNext.js    From t-viSNE with MIT License 4 votes vote down vote up
function symbol() {
  var scale = scaleLinear(),
      shape = "path",
      shapeWidth = 15,
      shapeHeight = 15,
      shapeRadius = 10,
      shapePadding = 5,
      cells = [5],
      cellFilter = void 0,
      labels = [],
      classPrefix = "",
      title = "",
      locale = helper.d3_defaultLocale,
      specifier = helper.d3_defaultFormatSpecifier,
      labelAlign = "middle",
      labelOffset = 10,
      labelDelimiter = helper.d3_defaultDelimiter,
      labelWrap = void 0,
      orient = "vertical",
      ascending = false,
      titleWidth = void 0,
      legendDispatcher = dispatch("cellover", "cellout", "cellclick");

  function legend(svg) {
    var type = helper.d3_calcType(scale, ascending, cells, labels, locale.format(specifier), labelDelimiter),
        legendG = svg.selectAll("g").data([scale]);

    if (cellFilter) {
      helper.d3_filterCells(type, cellFilter);
    }

    legendG.enter().append("g").attr("class", classPrefix + "legendCells");

    var cell = svg.select("." + classPrefix + "legendCells").selectAll("." + classPrefix + "cell").data(type.data);
    var cellEnter = cell.enter().append("g").attr("class", classPrefix + "cell");
    cellEnter.append(shape).attr("class", classPrefix + "swatch");

    var shapes = svg.selectAll("g." + classPrefix + "cell " + shape + "." + classPrefix + "swatch");

    //add event handlers
    helper.d3_addEvents(cellEnter, legendDispatcher);

    //remove old shapes
    cell.exit().transition().style("opacity", 0).remove();
    shapes.exit().transition().style("opacity", 0).remove();
    shapes = shapes.merge(shapes);

    helper.d3_drawShapes(shape, shapes, shapeHeight, shapeWidth, shapeRadius, type.feature);
    var text = helper.d3_addText(svg, cellEnter, type.labels, classPrefix, labelWrap);

    // we need to merge the selection, otherwise changes in the legend (e.g. change of orientation) are applied only to the new cells and not the existing ones.
    cell = cellEnter.merge(cell);

    // sets placement
    var textSize = text.nodes().map(function (d) {
      return d.getBBox();
    }),
        shapeSize = shapes.nodes().map(function (d) {
      return d.getBBox();
    });

    var maxH = max(shapeSize, function (d) {
      return d.height;
    }),
        maxW = max(shapeSize, function (d) {
      return d.width;
    });

    var cellTrans = void 0,
        textTrans = void 0,
        textAlign = labelAlign == "start" ? 0 : labelAlign == "middle" ? 0.5 : 1;

    //positions cells and text
    if (orient === "vertical") {
      (function () {
        var cellSize = textSize.map(function (d, i) {
          return Math.max(maxH, d.height);
        });

        cellTrans = function cellTrans(d, i) {
          var height = sum(cellSize.slice(0, i));
          return "translate(0, " + (height + i * shapePadding) + " )";
        };
        textTrans = function textTrans(d, i) {
          return "translate( " + (maxW + labelOffset) + ",\n              " + (shapeSize[i].y + shapeSize[i].height / 2 + 5) + ")";
        };
      })();
    } else if (orient === "horizontal") {
      cellTrans = function cellTrans(d, i) {
        return "translate( " + i * (maxW + shapePadding) + ",0)";
      };
      textTrans = function textTrans(d, i) {
        return "translate( " + (shapeSize[i].width * textAlign + shapeSize[i].x) + ",\n              " + (maxH + labelOffset) + ")";
      };
    }

    helper.d3_placement(orient, cell, cellTrans, text, textTrans, labelAlign);
    helper.d3_title(svg, title, classPrefix, titleWidth);
    cell.transition().style("opacity", 1);
  }

  legend.scale = function (_) {
    if (!arguments.length) return scale;
    scale = _;
    return legend;
  };

  legend.cells = function (_) {
    if (!arguments.length) return cells;
    if (_.length > 1 || _ >= 2) {
      cells = _;
    }
    return legend;
  };

  legend.cellFilter = function (_) {
    if (!arguments.length) return cellFilter;
    cellFilter = _;
    return legend;
  };

  legend.shapePadding = function (_) {
    if (!arguments.length) return shapePadding;
    shapePadding = +_;
    return legend;
  };

  legend.labels = function (_) {
    if (!arguments.length) return labels;
    labels = _;
    return legend;
  };

  legend.labelAlign = function (_) {
    if (!arguments.length) return labelAlign;
    if (_ == "start" || _ == "end" || _ == "middle") {
      labelAlign = _;
    }
    return legend;
  };

  legend.locale = function (_) {
    if (!arguments.length) return locale;
    locale = formatLocale(_);
    return legend;
  };

  legend.labelFormat = function (_) {
    if (!arguments.length) return legend.locale().format(specifier);
    specifier = formatSpecifier(_);
    return legend;
  };

  legend.labelOffset = function (_) {
    if (!arguments.length) return labelOffset;
    labelOffset = +_;
    return legend;
  };

  legend.labelDelimiter = function (_) {
    if (!arguments.length) return labelDelimiter;
    labelDelimiter = _;
    return legend;
  };

  legend.labelWrap = function (_) {
    if (!arguments.length) return labelWrap;
    labelWrap = _;
    return legend;
  };

  legend.orient = function (_) {
    if (!arguments.length) return orient;
    _ = _.toLowerCase();
    if (_ == "horizontal" || _ == "vertical") {
      orient = _;
    }
    return legend;
  };

  legend.ascending = function (_) {
    if (!arguments.length) return ascending;
    ascending = !!_;
    return legend;
  };

  legend.classPrefix = function (_) {
    if (!arguments.length) return classPrefix;
    classPrefix = _;
    return legend;
  };

  legend.title = function (_) {
    if (!arguments.length) return title;
    title = _;
    return legend;
  };

  legend.titleWidth = function (_) {
    if (!arguments.length) return titleWidth;
    titleWidth = _;
    return legend;
  };

  legend.on = function () {
    var value = legendDispatcher.on.apply(legendDispatcher, arguments);
    return value === legendDispatcher ? legend : value;
  };

  return legend;
}