demo/src/methods/ranked-pairs/viz-ranked-pairs.tsx

Summary

Maintainability
B
6 hrs
Test Coverage
import React, { useRef } from 'react'
import { Tag } from 'antd'
import _ from 'lodash'

import { RankedPairs, utils } from 'votes'

import { useStore } from '../../store'
import {
  selectSkewMatrix,
  useCandidatesById,
  useCandidatesString,
} from '../../store/selectors'
import { trafficColor } from '../../traffic-color'
import { ScoresSummary } from '../viz/scores-summary'

type Edge = { from: number; to: number; value: number }

const green = trafficColor(1, 55, 55)
const red = trafficColor(0, 55, 55)
const gray = trafficColor(0, 0, 55)
const color = (ok: boolean | null) => {
  if (ok === null) return 'gray'
  return ok ? green : red
}

const colorClass = (ok: boolean | null) => {
  if (ok === null) return 'default'
  return ok ? 'green' : 'red'
}

export const VizRankedPairs: React.FC = () => {
  const matrix = useStore(selectSkewMatrix)
  const candidatesById = useCandidatesById()
  const candidatesStrings = useCandidatesString()
  // const width = useStore(selectWidth)

  const rp = new RankedPairs(matrix)

  // const [divWidth, setDivWidth] = useState<null | number>(null)
  const ref = useRef<HTMLDivElement>(null)
  // useLayoutEffect(() => {
  //   if (ref.current) setDivWidth(ref.current.offsetWidth)
  // }, [width])

  const allEdges: Edge[] = matrix.array.flatMap(
    (row, from) =>
      row
        .map((value, to) =>
          value > 0 && to !== from ? { from, to, value } : null,
        )
        .filter(Boolean) as Edge[],
  )
  const edgesGroups = _.groupBy(allEdges, 'value')
  const groups = Object.keys(edgesGroups)
    .sort((a, b) => Number(b) - Number(a))
    .map((value) => edgesGroups[value])

  const acyclicGraph = groups.reduce(
    (graph: Edge[], edgesToAdd) =>
      utils.generateAcyclicGraph(graph, edgesToAdd),
    [] as Edge[],
  )

  const groupp = groups.map((g) =>
    g.map((e) => ({
      ...e,
      ok: acyclicGraph.some((ace) => ace.from === e.from && ace.to === e.to),
    })),
  )

  // const ag = acyclicGraph.map((c) => ({
  //   ...c,
  //   from: candidatesStrings[c.from],
  //   to: candidatesStrings[c.to],
  // }))
  return (
    <div className="container" ref={ref}>
      <ScoresSummary scores={rp.scores()} candidatesById={candidatesById} />
      {groupp.map((g) =>
        g.map((e) => (
          <Tag
            key={`${e.from}-${e.to}`}
            color={color(e.ok)}
            style={{ marginBottom: 5 }}
            className={`li ${colorClass(e.ok)}`}
          >
            {e.value}: {candidatesStrings[e.from]}→{candidatesStrings[e.to]}
          </Tag>
        )),
      )}
      {/*{divWidth && candidatesStrings.length > 2 && <ForceGraph />}*/}

      <style jsx>{`
        .li {
          display: inline-block;
          padding: 2px;
        }
        .li.default {
          background: ${gray};
        }
        .li.red {
          background: ${red};
        }
        .li.green {
          background: ${green};
        }
      `}</style>
    </div>
  )
}