Back to index

Diverging Survey

Stack
Generates documents
Diverging Survey

Seen as a design

Take it with you

Copy and paste, or save a file and hand it over. For a machine with no network, one “Everything” file is enough.

Everything

diverging-survey-everything.md

Everything in this panel, including the contents behind each URL, in one Markdown file

Prompt

1.3 KB
Build an HTML document with "Diverging Survey". This layout runs, so there is no composition to rebuild — what it needs is content.

## What this layout does
Diverging Survey with explicit source values and reading conditions.

## What you have to supply
- Provide the source values, labels and decision conditions described by the schema.

## The shape of the input
- `title` — string — 結論を伝える見出し
- `subtitle` — string? — 出典・時点・母集団や仮定
- `items` — array (label, negative, neutral, positive, n) — 賛否3区分の割合(合計100)と回答者数

schema.ts holds the machine-checkable validation. Ask rather than guess when an input is missing.

## When not to use it
- Do not compare values with incompatible units, populations, or assumptions.

## How to build it
Put the input in a JSON file and run:

```bash
node scripts/render.mjs diverging-survey <input.json> --theme ink -o out.html
```

The output is one self-contained file and fetches nothing at render time.

## The source
- https://design-vault.pages.dev/designs/diverging-survey/source/template.tsx
- https://design-vault.pages.dev/designs/diverging-survey/source/schema.ts
- https://design-vault.pages.dev/designs/diverging-survey/source/sample-data.json
- https://design-vault.pages.dev/designs/diverging-survey/sample.html

Files

Template, input schema, sample data and metadata. This document was generated from those four. The generated document

template.tsx

7.5 KB

/designs/diverging-survey/source/template.tsx

Show contents
import type { ReactNode } from "react";
import type { Props } from "./schema";
const colors = ["var(--color-tertiary)", "var(--color-primary)", "var(--color-secondary)", "var(--color-accent-violet)"];
function Ledger({heads,rows,caption}:{heads:string[];rows:ReactNode[][];caption?:string}) { return <table className="ledger">{caption && <caption>{caption}</caption>}<thead><tr>{heads.map((h,i)=><th key={i} scope="col">{h}</th>)}</tr></thead><tbody>{rows.map((r,i)=><tr key={i}>{r.map((v,j)=><td key={j} data-label={heads[j]}>{v}</td>)}</tr>)}</tbody></table>; }
function Notes({children}:{children:ReactNode}) { return <details><summary>データ・判断条件を確認</summary>{children}</details>; }

const CSS = String.raw`
.asset{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2rem;line-height:1.6;font-size:var(--text-body-sm)}
.asset *{box-sizing:border-box}.asset header{margin-bottom:2rem}.asset .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);letter-spacing:.08em;color:var(--color-secondary);margin-bottom:.75rem}.asset h1{font-size:var(--text-h1);font-weight:700;line-height:1.35;letter-spacing:-.025em;max-width:32em}.asset h2{font-size:var(--text-h3);font-weight:600;margin-bottom:.75rem}.asset h3{font-size:var(--text-body-md);font-weight:600}.asset .subtitle,.asset .muted{color:var(--color-secondary)}.asset .subtitle{margin-top:.75rem}.asset .number{font-variant-numeric:tabular-nums;font-family:var(--font-mono)}.asset .hero-number{font-size:var(--text-display);line-height:1.2;font-weight:700;letter-spacing:-.04em}.asset .callout{border-left:.25rem solid var(--color-tertiary);padding:1rem 1.5rem;background:var(--color-surface-subtle);margin-bottom:1.5rem}.asset .grid2{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}.asset .grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.5rem}.asset .panel{padding:1.5rem;background:var(--color-surface-subtle);border-radius:var(--radius-md);min-width:0}.asset .rule{border-top:1px solid var(--color-outline);padding-top:1rem;margin-top:1rem}.asset .pill{display:inline-block;padding:.25rem .75rem;border:1px solid var(--color-outline);border-radius:var(--radius-full);font-size:var(--text-label)}.asset .focus{background:var(--color-tertiary-container);color:var(--color-on-tertiary-container)}.asset .dark{background:var(--color-primary);color:var(--color-on-primary)}.asset .dark .muted{color:var(--color-on-primary-container)}.asset .row{display:grid;grid-template-columns:12rem minmax(0,1fr) 6rem;gap:1rem;align-items:center;padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .row>*{min-width:0}.asset .track{height:1rem;position:relative;background:var(--color-neutral);border-radius:var(--radius-sm)}.asset .bar{height:100%;background:var(--color-tertiary);border-radius:var(--radius-sm)}.asset .track.tall{height:2rem}.asset .flex{display:flex;gap:1rem;align-items:center;justify-content:space-between;flex-wrap:wrap}.asset .stack>*+*{margin-top:1rem}.asset ul{list-style:disc;padding-left:1.25rem}.asset li+li{margin-top:.5rem}.asset .plot{display:block;width:100%;height:auto;overflow:visible}.asset .plot text{font-family:var(--font-sans);font-size:14px;fill:var(--color-secondary)}.asset .plot .ink{fill:var(--color-primary)}.asset .plot .axis{stroke:var(--color-outline);stroke-width:1}.asset .plot .accent{stroke:var(--color-tertiary);fill:none;stroke-width:3}.asset .legend{display:flex;gap:1.5rem;flex-wrap:wrap;font-size:var(--text-label);margin-bottom:1rem}.asset .legend b{display:inline-block;width:.75rem;height:.75rem;margin-right:.5rem}.asset .ledger{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}.asset .ledger caption{text-align:left;font-weight:600;margin:1rem 0}.asset .ledger th{font-weight:600;color:var(--color-secondary);text-align:left}.asset .ledger th,.asset .ledger td{padding:.75rem 1rem;border-bottom:1px solid var(--color-outline);vertical-align:top;overflow-wrap:anywhere}.asset .ledger th:first-child,.asset .ledger td:first-child{padding-left:0}.asset .ledger th:last-child,.asset .ledger td:last-child{padding-right:0}.asset .ledger tbody tr:hover{background:var(--color-surface-subtle)}.asset details{border-top:1px solid var(--color-outline);margin-top:1.5rem;padding-top:1rem}.asset summary{cursor:pointer;color:var(--color-secondary);padding:.5rem 0;min-height:2.75rem}.asset summary:focus-visible{outline:2px solid var(--color-tertiary);outline-offset:.25rem}.asset .small{font-size:var(--text-label)}.asset .mobile-only{display:none}.asset .horizontal-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.asset .board-column{border-top:.25rem solid var(--color-primary);padding-top:1rem;min-width:0}.asset .board-item{padding:1rem 0;border-bottom:1px solid var(--color-outline)}.asset .board-item p{margin-top:.5rem}.asset .statement{font-size:var(--text-h2);line-height:1.5;font-weight:600}.asset .meta-line{margin-top:.75rem;font-size:var(--text-label);color:var(--color-secondary)}.asset pre{white-space:pre-wrap;overflow-wrap:anywhere}.asset footer{margin-top:2rem;font-size:var(--text-label);color:var(--color-secondary)}
@media(max-width:640px){.asset{padding:1.5rem 1rem}.asset h1{font-size:var(--text-h2)}.asset header{margin-bottom:1.5rem}.asset .grid2,.asset .grid3,.asset .horizontal-list{grid-template-columns:1fr}.asset .row{grid-template-columns:minmax(0,1fr) 5rem;gap:.5rem}.asset .row>:first-child{grid-column:1/-1}.asset .desktop-only{display:none!important}.asset .mobile-only{display:block}.asset .panel{padding:1rem}.asset .ledger,.asset .ledger tbody,.asset .ledger tr,.asset .ledger td{display:block}.asset .ledger thead{display:none}.asset .ledger tr{padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .ledger td{display:grid;grid-template-columns:7rem minmax(0,1fr);gap:.75rem;border:0;padding:.25rem 0!important}.asset .ledger td:before{content:attr(data-label);color:var(--color-secondary);font-size:var(--text-label)}.asset .hero-number{font-size:var(--text-h1)}}
@media print{.asset{max-width:none;padding:1rem}.asset details>*{display:block}.asset summary{display:none}}
`;

export default function Template(d: Props) {
const rows=d.items.map(r=>[r.label,r.negative+"%",r.neutral+"%",r.positive+"%",r.n]);
return <section className="asset asset-diverging-survey"><style>{CSS}</style><header><p className="eyebrow">DIVERGING SURVEY</p><h1>{d.title}</h1>{d.subtitle && <p className="subtitle">{d.subtitle}</p>}</header>
<div className="legend"><span>左:反対</span><span>中央:中立(左右に半分ずつ)</span><span>右:賛成</span></div>{d.items.map((r,i)=><div className="row" key={i}><strong>{r.label}</strong><div style={{position:"relative",height:"2rem"}}><div style={{position:"absolute",left:(50-(r.negative+r.neutral/2)/2)+"%",width:r.negative/2+"%",height:"100%",background:colors[2]}}/><div style={{position:"absolute",left:(50-r.neutral/4)+"%",width:r.neutral/2+"%",height:"100%",background:"var(--color-outline)"}}/><div style={{position:"absolute",left:(50+r.neutral/4)+"%",width:r.positive/2+"%",height:"100%",background:colors[0]}}/><span style={{position:"absolute",left:"50%",height:"100%",borderLeft:"1px solid var(--color-primary)"}}/></div><span className="number">賛成 {r.positive}%</span></div>)}<Ledger heads={["設問","反対","中立","賛成","回答数"]} rows={rows}/><Notes><p>割合は各設問の回答者を母数とします。回答数が異なる設問は比率だけで結論を出しません。</p></Notes>
<footer>尺度は左右それぞれ100%。中立を賛否に加算しない。</footer></section>;
}

schema.ts

754 B

/designs/diverging-survey/source/schema.ts

Show contents
import { z } from "zod";
const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);
const value = z.number().finite();
const positive = value.positive();
const pct = value.min(0).max(100);

export const schema = z.object({
  title: text.describe("結論を伝える見出し"),
  subtitle: text.optional().describe("出典・時点・母集団や仮定"),
  items:z.array(z.object({label:short,negative:pct,neutral:pct,positive:pct,n:z.number().int().positive()}).strict()).min(2).max(7).describe("賛否3区分の割合(合計100)と回答者数"),
}).strict().refine(d=>d.items.every(r=>Math.abs(r.negative+r.neutral+r.positive-100)<0.001),"割合の合計は100%");
export type Props = z.infer<typeof schema>;

meta.yaml

1.2 KB

/designs/diverging-survey/source/meta.yaml

Show contents
id: diverging-survey
name:
  ja: 賛否の発散バー
  en: Diverging Survey
purpose:
  ja: 反対・中立・賛成の割合を区別して、設問ごとの分布を比較する。
  en: Diverging Survey with explicit source values and reading conditions.
status: published
still: ./still.png
category: analysis
catalogOrder: 250
jobs:
  - measure-change
useCases:
  - kpi
layoutKinds:
  - stack
tags:
  - diverging-survey
  - analysis
  - offline
  - responsive
whenToUse:
  - ja: 反対・中立・賛成の割合を区別して、設問ごとの分布を比較する。
    en: Use to communicate diverging survey.
avoidWhen:
  - ja: 同じ条件・単位で比較できないデータには使わない
    en: Do not compare values with incompatible units, populations, or assumptions.
requiredInputs:
  - ja: 尺度は左右それぞれ100%。中立を賛否に加算しない。
    en: Provide the source values, labels and decision conditions described by the schema.
relatedLayouts: []
dataSchema:
  title: string — 結論を伝える見出し
  subtitle: string? — 出典・時点・母集団や仮定
  items: array (label, negative, neutral, positive, n) — 賛否3区分の割合(合計100)と回答者数
sources: []

sample-data.json

660 B

/designs/diverging-survey/source/sample-data.json

Show contents
{
  "title": "操作性への評価は高いが、検索には改善余地",
  "subtitle": "架空サンプル|レイアウトの使用例",
  "items": [
    {
      "label": "操作性",
      "negative": 10,
      "neutral": 15,
      "positive": 75,
      "n": 200
    },
    {
      "label": "検索精度",
      "negative": 32,
      "neutral": 26,
      "positive": 42,
      "n": 200
    },
    {
      "label": "処理速度",
      "negative": 18,
      "neutral": 22,
      "positive": 60,
      "n": 200
    },
    {
      "label": "案内の明確さ",
      "negative": 25,
      "neutral": 20,
      "positive": 55,
      "n": 200
    }
  ]
}