TanStack
Catalog

Marimekko survey composition

part-to-whole

120 lines · 2 files · 3.0 kB

cases/64-marimekko-mosaic/example.tsx99 lines · entry
cases/64-marimekko-mosaic/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip as exampleTooltip } from '@tanstack/charts/tooltip'

import {
  colorLegend,
  defineChart,
  groupBy,
  mosaicY,
  rect,
  select,
  text,
} from '@tanstack/charts'
import { format } from 'd3-format'
import { scaleLinear } from 'd3-scale'
import { survey } from '@tanstack/charts-data/survey'
import { isMosaicResponse, mosaicResponses } from './selection'

const percent = format('.0%')
const colors = ['#991b1b', '#ef4444', '#cbd5e1', '#60a5fa', '#1d4ed8']

export const createExampleChart = (input?: ChartOptions) => {
  const observations = survey.filter(isMosaicResponse)
  const counts = groupBy(observations, {
    by: {
      Question: 'Question',
      Response: 'Response',
    },
    outputs: { count: { reduce: 'count' } },
  })
  const cells = mosaicY(counts, {
    x: 'Question',
    y: 'Response',
    value: 'count',
    yOrder: mosaicResponses,
  })
  const labels = select(cells, {
    by: 'xValue',
    select: 'first',
  })

  return defineChart(
    {
      marks: [
        rect(cells, {
          id: 'response-cells',
          x: 'x',
          x1: 'x1',
          x2: 'x2',
          y: 'y',
          y1: 'y1',
          y2: 'y2',
          color: 'yValue',
          key: (datum) => `${datum.xValue}:${datum.yValue}`,
          inset: 1,
        }),
        ...(input?.preview === true
          ? []
          : [
              text(labels, {
                id: 'question-labels',
                x: 'x',
                y: () => 1.055,
                text: 'Question',
                key: 'xValue',
                fill: '#334155',
                fontSize: 11,
              }),
            ]),
      ],
      x: {
        scale: scaleLinear().domain([0, 1]),
        axis: { ticks: { format: percent }, label: 'Share of responses' },
      },
      y: {
        scale: scaleLinear().domain([0, 1.12]),
        axis: { ticks: { format: percent }, label: 'Within-question share' },
      },
      color: {
        domain: mosaicResponses,
        range: colors,
        legend: colorLegend({ label: 'Response' }),
      },
    },
    { keyboard: true, tooltip: exampleTooltip },
  )
}
export interface ChartOptions {
  preview?: boolean
}

export const exampleAriaLabel = 'Marimekko survey composition'

export const chart = createExampleChart({
  preview: false,
})

export default function Example() {
  return <Chart ariaLabel={exampleAriaLabel} definition={chart} height={480} />
}
cases/64-marimekko-mosaic/selection.ts21 lines · dependency
cases/64-marimekko-mosaic/selection.ts
import type { SurveyRow } from '@tanstack/charts-data/survey'

export const mosaicResponses = [
  'Strongly Disagree',
  'Disagree',
  'Neutral',
  'Agree',
  'Strongly Agree',
] as const

export type MosaicResponse = (typeof mosaicResponses)[number]

export type MosaicSurveyRow = SurveyRow & {
  readonly Response: MosaicResponse
}

const responseSet = new Set<string>(mosaicResponses)

export function isMosaicResponse(row: SurveyRow): row is MosaicSurveyRow {
  return responseSet.has(row.Response)
}