Source

75 chart lines · 2 files · 2.2 kBBenchmark harness excluded · shared/mount.ts

Likert survey responses500 records · CSV · 8.2 kB
@charts-poc/demo-data/survey

Selection: Complete published snapshot

Question
string
ID
number
Response
string

Eitan Leesobservablehq/plot@356f579b1d94 · revision 356f579b1d94 · test/data/survey.csv · Observable Plot repository MIT; upstream source credited · SHA-256 de6879d71248Pinned snapshot

cases/78-gauge/tanstack.ts45 lines · entry
cases/78-gauge/tanstack.ts
import { survey } from '@charts-poc/demo-data/survey'
import { defineChart } from '@tanstack/charts'
import { polar, radialArc } from '@tanstack/charts/polar'
import { pie } from 'd3-shape'
import { agreementPercent, gaugeSegments } from './transform'
import { tanstackMount } from '../../shared/mount'
import type { GaugeDatum } from './transform'
import type { ConformanceInput } from '../../types'

const startAngle = (-Math.PI * 3) / 4
const endAngle = (Math.PI * 3) / 4
const pieLayout = pie<GaugeDatum>()
  .sort(null)
  .value(({ value }) => value)
  .startAngle(startAngle)
  .endAngle(endAngle)
const ids: readonly GaugeDatum['id'][] = ['value', 'remainder']
const colors = ['#ef4444', '#e2e8f0']

const definition = (input: ConformanceInput) => {
  const question = `Q${(input.revision % 2) + 1}`
  const arcs = pieLayout([...gaugeSegments(agreementPercent(survey, question))])

  return defineChart({
    marks: [
      polar({
        inset: 0,
        radiusRatio: 0.8,
        marks: [
          radialArc(arcs, {
            startAngle: 'startAngle',
            endAngle: 'endAngle',
            padAngle: 'padAngle',
            innerRadius: ({ radius }: { radius: number }) => radius * 0.72,
            color: ({ data }) => data.id,
          }),
        ],
      }),
    ],
    color: { domain: ids, range: colors },
    margin: 0,
  })
}

export const mount = tanstackMount(definition, 'Survey agreement share gauge')
cases/78-gauge/transform.ts30 lines · support
cases/78-gauge/transform.ts
export interface GaugeDatum {
  id: 'value' | 'remainder'
  label: string
  value: number
}

export function gaugeSegments(value: number): readonly GaugeDatum[] {
  return [
    { id: 'value', label: 'Agree', value },
    {
      id: 'remainder',
      label: 'Other responses',
      value: 100 - value,
    },
  ]
}

export function agreementPercent(
  rows: readonly SurveyRow[],
  question: string,
): number {
  const responses = rows.filter((row) => row.Question === question)
  const agreements = responses.filter(
    (row) => row.Response === 'Agree' || row.Response === 'Strongly Agree',
  )
  return responses.length === 0
    ? 0
    : Math.round((agreements.length / responses.length) * 100)
}
import type { SurveyRow } from '@charts-poc/demo-data/survey'