human-product · Stage 3
静的グラフィックスを同等の情報構造として設計する
roadmapとquantitative chartをsemantic HTMLとCSSで表し、色や形だけに依存せず、同じ関係と値をテキスト構造から検証できるようにする。
到達目標
データ、幾何、rasterization、合成というgraphics pipelineと表示上の制約を説明できる
- 視覚表現と同等のテキスト構造を持つ静的データ図
- graphics pipeline、SVG 2、semantic HTML、知覚encodingを説明する5分発表
roadmapのnodeとedge、chartのlabelとvalueをsemantic HTMLと同等のテキスト構造へ保持できる
- 視覚表現と同等のテキスト構造を持つ静的データ図
- 視覚encodingと情報同等性の欠落を診断する回答
quantitative valueを共通尺度上の位置で表し、colorだけに依存しない表示へ移行できる
- 視覚encodingと情報同等性の欠落を診断する回答
- display modeだけを変えて情報同等性を再検証した記録
能力の進行
recognize
データ、mark、channel、semantic structure、text equivalentを区別できる
証拠: 視覚表現と同等のテキスト構造を持つ静的データ図
explain
OpenGL 4.6とSVG 2の描画機構、ClevelandとMunznerの知覚設計を役割別に説明できる
証拠: graphics pipeline、SVG 2、semantic HTML、知覚encodingを説明する5分発表
apply
roadmapとquantitative chartをCSS付きsemantic HTMLと同等テキストへ変換できる
証拠: 視覚表現と同等のテキスト構造を持つ静的データ図
diagnose
欠落したedge、値、単位、順序、color-only encodingをデータから検出できる
証拠: 視覚encodingと情報同等性の欠落を診断する回答
lead
視覚品質、知覚精度、アクセシビリティ、保守性の境界を関係者と合意できる
証拠: display modeだけを変えて情報同等性を再検証した記録
なぜ重要か
図は関係や量を一目で伝える一方、情報をpixel、位置、colorだけへ閉じ込めると、読み上げ、monochrome印刷、CSS無効化、低視力、検索、差分reviewでは意味を失う。静的な教材では、視覚表現と同じdataからsemantic HTMLと同等のテキスト構造を導出し、どの経路でも同じ判断へ到達できるようにする。
OpenGL 4.6はgraphics pipelineの機構、SVG 2は文書内のvector表現を定める。Clevelandのgraphical perceptionとMunznerの分析設計は、読者のtaskとdata typeに合うencodingを選ぶ根拠になる。W3C Complex Imagesは、短い要約だけでは足りない複雑な関係や値をtextへ残す実践を示す。
メンタルモデル
dataを唯一の情報源とし、視覚表現、semantic HTML、同等textを別々のviewとして導出する。roadmapではnodeとdirected edge、quantitative chartではlabel、value、unit、比較結果を情報契約にする。CSSは配置と装飾を担当し、関係や値そのものを生成しない。
Clevelandが示した知覚taskを踏まえ、量の比較では共通尺度上のpositionを優先する。colorは群や強調の補助にできるが、colorだけで状態や順位を符号化しない。Munznerのwhat、why、howを使い、data、読者task、encodingを混同しない。
注記
図を読む際の補足情報です。
- Data:
- Transform:
- Visual:
- Equivalent:
- Verify:
一つのdata modelから視覚表現と同等textを作り、どの不変条件を照合するか。
- Data
- roadmapのnodeとedge、chartのlabelとvalueをID付きで定義する。
- Data → Transform: typed values
- Transform
- 並び、scale、minimum、maximumを決定的に計算する。
- Transform → Visual: 同じtransformから決定的layout
- Transform → Equivalent: 同じtransformから同等text
- Visual
- semantic HTMLとCSSでroadmapとquantitative chartを配置する。
- Visual → Verify: 視覚側のID・値を照合
- Equivalent
- node関係list、caption付きtable、summaryを同じdataから導出する。
- Equivalent → Verify: text側のID・値を照合
- Verify
- ID、edge、row、値、集約、display modeの不変条件を照合する。
Data、Transform、Visual、Equivalent、Verifyの順序を説明し、別目的の0–100% worked-example chartを二重計上しない。
動く例で考える
roadmapとquantitative chartの同等性を検証する
- 前提
- 4つのroadmap node、3つのdirected edge、3点のsynthetic chart dataを固定する。production計画や実測値ではない。
- 入力
- node ID、label、edgeのfromとto、chart pointのID、label、valueを使う。display modeはcolorをbaselineとする。
- 操作
- roadmapのsemantic HTML用node IDとtext equivalentのnode、edgeを同じdataから導出する。chartは共通尺度上のpositionをencodingとし、同じrowをtableとtextへ複製する。
- 観測
- roadmapのnodeとedge、chartの3 row、maximum、minimumが一致する。colorからmonochromeへdisplay modeだけを変えてもdataとtext equivalenceは不変である。
- 結論
- 視覚表現は情報そのものではなくdataのviewである。同等textを同じ変換から生成し、欠落mutationを失敗させることでdriftを検出できる。
0%
100%
color表示
monochrome表示
| 活動 | ID | 時間 |
|---|---|---|
| 学習 | learn | 12 hours |
| 実践 | practice | 20 hours |
| 復習 | review | 8 hours |
python3.13 - <<'PY'
from html import escape
from html.parser import HTMLParser
from hashlib import sha256
import json
HARNESS = "graphics_semantic_equivalence_lab_v1"
ROADMAP_NODES = [
{"id": "foundation", "label": "基礎"},
{"id": "build", "label": "構築"},
{"id": "operate", "label": "運用"},
{"id": "lead", "label": "牽引"},
]
ROADMAP_EDGES = [
{"from": "foundation", "to": "build"},
{"from": "build", "to": "operate"},
{"from": "operate", "to": "lead"},
]
CHART_DATA = [
{"id": "learn", "label": "学習", "value": 12},
{"id": "practice", "label": "実践", "value": 20},
{"id": "review", "label": "復習", "value": 8},
]
ROADMAP_CSS = (
".roadmap { border: 1px solid currentColor; padding: 1rem; } "
".roadmap-nodes { display: grid; gap: 0.5rem; } "
".roadmap-edges { list-style: none; padding: 0; }"
)
CHART_CSS = (
".quantitative-chart { border: 1px solid currentColor; padding: 1rem; } "
".chart-bars { list-style: none; padding: 0; } "
".chart-bar { inline-size: 100%; accent-color: #245d63; } "
".quantitative-chart table { border-collapse: collapse; width: 100%; } "
".quantitative-chart th, .quantitative-chart td { "
"border: 1px solid currentColor; padding: 0.25rem; }"
)
DISPLAY_MODE_PRESENTATIONS = {
"color": {
"class_name": "display-color",
"css": (
".display-color { color: CanvasText; background: Canvas; } "
".display-color .roadmap, "
".display-color .quantitative-chart { "
"border-style: solid; } "
".display-color .chart-bar { accent-color: #245d63; }"
),
},
"monochrome": {
"class_name": "display-monochrome",
"css": (
".display-monochrome { "
"color: CanvasText; background: Canvas; } "
".display-monochrome .roadmap, "
".display-monochrome .quantitative-chart { "
"border-style: double; } "
".display-monochrome .chart-bar { "
"accent-color: CanvasText; border-style: double; "
"background-image: repeating-linear-gradient("
"135deg, CanvasText 0 2px, Canvas 2px 4px); }"
),
},
}
UNSAFE_CSS_TOKENS = (
"@import",
"url(",
"javascript:",
"expression(",
"</style",
"<!--",
"-->",
)
ARTIFACT_TAG_ATTRIBUTES = {
"section": {"class"},
"figure": {"class"},
"figcaption": set(),
"ol": {"class"},
"li": {"id", "data-from", "data-to"},
"span": set(),
"table": set(),
"caption": set(),
"thead": set(),
"tbody": set(),
"tr": set(),
"th": {"scope"},
"td": set(),
"meter": {
"class",
"data-id",
"data-raw-value",
"min",
"max",
"value",
},
}
class ArtifactParser(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.tags = []
self.ids = []
self.edge_pairs = []
self.table_header_scopes = []
self.figure_caption_count = 0
self.table_caption_count = 0
self.table_rows = []
self.meter_values = []
self.section_classes = []
self.unsafe = []
self.in_tbody = False
self.current_row = None
self.current_cell = None
def handle_starttag(self, tag, attrs):
normalized_tag = tag.casefold()
values = {
name.casefold(): value or ""
for name, value in attrs
}
self.tags.append(normalized_tag)
allowed_attributes = ARTIFACT_TAG_ATTRIBUTES.get(normalized_tag)
if allowed_attributes is None:
self.unsafe.append(f"tag:{normalized_tag}")
else:
supplied_attributes = {
name.casefold()
for name, _ in attrs
}
if (
len(supplied_attributes) != len(attrs)
or not supplied_attributes <= allowed_attributes
):
self.unsafe.append(f"attributes:{normalized_tag}")
if values.get("id"):
self.ids.append(values["id"])
if normalized_tag == "section":
self.section_classes.append(values.get("class", ""))
if normalized_tag == "li" and {
"data-from",
"data-to",
}.issubset(values):
self.edge_pairs.append(
[values["data-from"], values["data-to"]]
)
if normalized_tag == "th":
self.table_header_scopes.append(values.get("scope", ""))
if normalized_tag == "figcaption":
self.figure_caption_count += 1
if normalized_tag == "caption":
self.table_caption_count += 1
if normalized_tag == "meter":
self.meter_values.append(
{
"id": values.get("data-id", ""),
"min": int(values.get("min", "0")),
"max": int(values.get("max", "0")),
"value": int(values.get("value", "0")),
"raw_value": int(values.get("data-raw-value", "0")),
}
)
if normalized_tag == "tbody":
self.in_tbody = True
if normalized_tag == "tr" and self.in_tbody:
self.current_row = []
if (
normalized_tag in {"th", "td"}
and self.current_row is not None
):
self.current_cell = []
if normalized_tag in {
"script",
"style",
"iframe",
"object",
"embed",
}:
self.unsafe.append(normalized_tag)
for name, value in attrs:
normalized_name = name.casefold()
candidate = value or ""
if (
normalized_name == "style"
or normalized_name.startswith("on")
or "://" in candidate
):
self.unsafe.append(normalized_name)
def handle_data(self, data):
if self.current_cell is not None:
self.current_cell.append(data)
def handle_endtag(self, tag):
normalized_tag = tag.casefold()
if (
normalized_tag in {"th", "td"}
and self.current_cell is not None
and self.current_row is not None
):
self.current_row.append(
" ".join("".join(self.current_cell).split())
)
self.current_cell = None
if (
normalized_tag == "tr"
and self.current_row is not None
):
if len(self.current_row) == 3:
self.table_rows.append(
{
"id": self.current_row[1],
"label": self.current_row[0],
"value": int(self.current_row[2]),
}
)
self.current_row = None
if normalized_tag == "tbody":
self.in_tbody = False
def render_roadmap_html(nodes, edges):
node_items = "".join(
(
f'<li id="{escape(node["id"])}">'
f'{escape(node["label"])}</li>'
)
for node in nodes
)
edge_items = "".join(
(
f'<li data-from="{escape(edge["from"])}" '
f'data-to="{escape(edge["to"])}">'
f'{escape(edge["from"])} to {escape(edge["to"])}'
"</li>"
)
for edge in edges
)
return (
'<figure class="roadmap">'
"<figcaption>学習roadmapと依存関係</figcaption>"
f'<ol class="roadmap-nodes">{node_items}</ol>'
f'<ol class="roadmap-edges">{edge_items}</ol>'
"</figure>"
)
def render_meter_rows(chart_data, scale_max):
# All marks share one maximum so bar length preserves quantitative ratios;
# visible labels keep the raw value available without relying on color.
raw_max = max(point["value"] for point in chart_data)
return "".join(
(
"<li>"
f'<span>{escape(point["label"])}: '
f'{point["value"]} hours '
f'({round(point["value"] / raw_max * scale_max)}%)'
"</span>"
f'<meter class="chart-bar" data-id="{escape(point["id"])}" '
f'data-raw-value="{point["value"]}" min="0" '
f'max="{scale_max}" '
f'value="{round(point["value"] / raw_max * scale_max)}">'
f'{round(point["value"] / raw_max * scale_max)}%'
"</meter>"
"</li>"
)
for point in chart_data
)
def render_chart_html(chart_data):
scale_max = 100
meter_rows = render_meter_rows(chart_data, scale_max)
rows = "".join(
(
"<tr>"
f'<th scope="row">{escape(point["label"])}</th>'
f'<td>{escape(point["id"])}</td>'
f'<td>{point["value"]}</td>'
"</tr>"
)
for point in chart_data
)
return (
'<figure class="quantitative-chart">'
"<figcaption>学習活動の時間比較</figcaption>"
f'<ol class="chart-bars">{meter_rows}</ol>'
"<table>"
"<caption>活動別時間の quantitative chart</caption>"
"<thead><tr>"
'<th scope="col">活動</th>'
'<th scope="col">ID</th>'
'<th scope="col">時間</th>'
"</tr></thead>"
f"<tbody>{rows}</tbody>"
"</table>"
"</figure>"
)
def validate_roadmap_artifact(html, node_ids, edge_pairs):
parser = ArtifactParser()
parser.feed(html)
parser.close()
valid = (
{"figure", "figcaption", "ol", "li"}.issubset(parser.tags)
and parser.ids == node_ids
and parser.edge_pairs == edge_pairs
and parser.figure_caption_count == 1
and not parser.unsafe
)
return {
"valid": valid,
"node_ids": parser.ids,
"edge_pairs": parser.edge_pairs,
"unsafe": parser.unsafe,
}
def validate_chart_artifact(html, expected_rows):
parser = ArtifactParser()
parser.feed(html)
parser.close()
required_tags = {
"figure",
"figcaption",
"table",
"caption",
"thead",
"tbody",
"tr",
"th",
"td",
}
raw_max = max(row["value"] for row in expected_rows)
expected_meters = [
{
"id": row["id"],
"min": 0,
"max": 100,
"value": round(row["value"] / raw_max * 100),
"raw_value": row["value"],
}
for row in expected_rows
]
semantic_valid = (
required_tags.issubset(parser.tags)
and parser.figure_caption_count == 1
and parser.table_caption_count == 1
and bool(parser.table_header_scopes)
and all(
scope in {"col", "row"}
for scope in parser.table_header_scopes
)
and parser.table_rows == expected_rows
and not parser.unsafe
)
scale_valid = (
"meter" in parser.tags
and parser.meter_values == expected_meters
)
return {
"valid": semantic_valid and scale_valid,
"semantic_valid": semantic_valid,
"scale_valid": scale_valid,
"table_rows": parser.table_rows,
"meter_values": parser.meter_values,
"header_scopes": parser.table_header_scopes,
"unsafe": parser.unsafe,
}
def artifact_data_fingerprint(nodes, edges, chart_data):
# Canonical JSON proves that the transfer changes presentation only,
# rather than silently changing the roadmap or quantitative evidence.
canonical = json.dumps(
{
"nodes": nodes,
"edges": edges,
"chart_data": chart_data,
},
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
return sha256(canonical.encode("utf-8")).hexdigest()
def validate_display_artifact(
html,
css,
expected_class,
expected_css,
):
parser = ArtifactParser()
parser.feed(html)
parser.close()
unsafe_css = [
token
for token in UNSAFE_CSS_TOKENS
if token in css.casefold()
]
html_valid = (
not parser.unsafe
and parser.section_classes == [expected_class]
and parser.figure_caption_count == 2
and parser.table_caption_count == 1
)
# CSS is executable presentation input. Exact comparison against the
# selected fixed rule set prevents selector or declaration injection
# from being mistaken for a validated artifact.
css_valid = (
type(css) is str
and css == expected_css
and not unsafe_css
)
return {
"valid": html_valid and css_valid,
"html_valid": html_valid,
"css_valid": css_valid,
"section_classes": parser.section_classes,
"unsafe_html": parser.unsafe,
"unsafe_css": unsafe_css,
}
def render_display_artifact(
display_mode,
roadmap_artifact,
chart_artifact,
data_fingerprint,
):
assert (
type(display_mode) is str
and display_mode in DISPLAY_MODE_PRESENTATIONS
), "graphics-display-mode-invariant"
assert (
roadmap_artifact["css"] == ROADMAP_CSS
and chart_artifact["css"] == CHART_CSS
), "graphics-css-invariant"
presentation = DISPLAY_MODE_PRESENTATIONS[display_mode]
class_name = presentation["class_name"]
html = (
f'<section class="{class_name}">'
f'{roadmap_artifact["html"]}{chart_artifact["html"]}'
"</section>"
)
css = ROADMAP_CSS + CHART_CSS + presentation["css"]
validation = validate_display_artifact(
html,
css,
class_name,
ROADMAP_CSS + CHART_CSS + presentation["css"],
)
assert validation["html_valid"], "graphics-artifact-invariant"
assert validation["css_valid"], "graphics-css-invariant"
return {
"display_mode": display_mode,
"html": html,
"css": css,
"data_fingerprint": data_fingerprint,
"validation": validation,
"valid": validation["valid"],
}
def derive_roadmap(nodes, edges):
node_ids = [node["id"] for node in nodes]
edge_pairs = [
[edge["from"], edge["to"]]
for edge in edges
]
roadmap_html = render_roadmap_html(nodes, edges)
artifact_validation = validate_roadmap_artifact(
roadmap_html,
node_ids,
edge_pairs,
)
assert artifact_validation["valid"], "graphics-artifact-invariant"
return {
"data": {
"nodes": [dict(node) for node in nodes],
"edges": [dict(edge) for edge in edges],
},
"semantic_html": {
"element": "ol",
"node_ids": list(node_ids),
"css_role": "layout-and-decoration-only",
},
"text_equivalent": {
"node_ids": list(node_ids),
"edge_pairs": edge_pairs,
"format": "ordered-node-list-and-directed-relations",
},
"artifact": {
"html": roadmap_html,
"css": ROADMAP_CSS,
},
"artifact_validation": artifact_validation,
}
def derive_chart(chart_data):
expected_rows = [
{
"id": point["id"],
"label": point["label"],
"value": point["value"],
}
for point in chart_data
]
text_rows = [dict(point) for point in chart_data]
assert text_rows == expected_rows, "graphics-causal-invariant"
chart_html = render_chart_html(chart_data)
artifact_validation = validate_chart_artifact(
chart_html,
expected_rows,
)
assert (
artifact_validation["semantic_valid"]
), "graphics-artifact-invariant"
assert artifact_validation["scale_valid"], "graphics-scale-invariant"
maximum = max(chart_data, key=lambda point: point["value"])
minimum = min(chart_data, key=lambda point: point["value"])
raw_max = maximum["value"]
return {
"data": [dict(point) for point in chart_data],
"semantic_html": {
"element": "table",
"caption": "学習活動の時間比較",
"table_rows": expected_rows,
},
"text_equivalent": {
"rows": text_rows,
"summary": (
f"maximum={maximum['id']}; minimum={minimum['id']}"
),
},
"summary": {
"maximum_id": maximum["id"],
"minimum_id": minimum["id"],
},
"encoding": {
"quantitative": "position-on-common-scale",
"color_only": False,
"unit": "hours",
},
"visual_encoding": {
"mark": "meter-bar",
"scale": "normalized-common-zero-to-100",
"scale_min": 0,
"scale_max": 100,
"raw_max": raw_max,
"visible_value_labels": True,
"non_color_cue": "length-pattern-border-and-text",
},
"artifact": {
"html": chart_html,
"css": CHART_CSS,
},
"artifact_validation": artifact_validation,
}
def mastery_evidence():
return {
"lab_steps": [
{"step": 1, "evidence": "ID付きnode、edge、chart fixture"},
{"step": 2, "evidence": "semantic HTML nodeとCSS責務"},
{"step": 3, "evidence": "共通尺度とcaption付きtable rows"},
{"step": 4, "evidence": "node、edge、row、集約の等価検証"},
{"step": 5, "evidence": "display-modeだけを変えたtransfer"},
],
"assessments": [
{"assessment": 1, "evidence": "directed edgeの同等text"},
{"assessment": 2, "evidence": "position encodingとcolor-only回避"},
],
"rubric_dimensions": [
"technical-correctness",
"judgment",
"evidence",
"communication",
],
"transfer": {
"task": (
"color表示からmonochrome表示へ変え、roadmapとchartの"
"同等情報を再検証する"
),
"changed_assumption": "display-mode",
"evidence": "同じdataとtext equivalentで表示modeだけを変更",
},
}
def main():
roadmap = derive_roadmap(ROADMAP_NODES, ROADMAP_EDGES)
chart = derive_chart(CHART_DATA)
data_fingerprint = artifact_data_fingerprint(
ROADMAP_NODES,
ROADMAP_EDGES,
CHART_DATA,
)
baseline_artifact = render_display_artifact(
"color",
roadmap["artifact"],
chart["artifact"],
data_fingerprint,
)
transferred_artifact = render_display_artifact(
"monochrome",
roadmap["artifact"],
chart["artifact"],
data_fingerprint,
)
assert (
baseline_artifact["valid"]
and transferred_artifact["valid"]
), "graphics-artifact-invariant"
assert (
baseline_artifact["data_fingerprint"]
== transferred_artifact["data_fingerprint"]
), "graphics-artifact-invariant"
expected_node_ids = [node["id"] for node in ROADMAP_NODES]
expected_edges = [
[edge["from"], edge["to"]]
for edge in ROADMAP_EDGES
]
assert (
roadmap["text_equivalent"]["node_ids"] == expected_node_ids
), "graphics-causal-invariant"
assert (
roadmap["text_equivalent"]["edge_pairs"] == expected_edges
), "graphics-causal-invariant"
report = {
"harness": HARNESS,
"roadmap": roadmap,
"quantitative_chart": chart,
"display_mode_transfer": {
"changed_assumption": "display-mode",
"changed_fields": ["display_mode"],
"baseline_mode": "color",
"transferred_mode": "monochrome",
"data_unchanged": (
baseline_artifact["data_fingerprint"]
== transferred_artifact["data_fingerprint"]
),
"text_equivalence_preserved": (
roadmap["semantic_html"]["node_ids"]
== roadmap["text_equivalent"]["node_ids"]
and chart["semantic_html"]["table_rows"]
== chart["text_equivalent"]["rows"]
),
"baseline_artifact": baseline_artifact,
"transferred_artifact": transferred_artifact,
},
"fixture_metadata": {
"kind": "synthetic",
"provenance": "lesson-defined roadmap and chart records",
"limitations": (
"small deterministic fixture; not measured learner data"
),
},
"runtime_bound": {
"records": (
len(ROADMAP_NODES)
+ len(ROADMAP_EDGES)
+ len(CHART_DATA)
),
"subprocesses": 0,
},
"mastery_evidence": mastery_evidence(),
"external_network_used": False,
}
return report
print(json.dumps(main(), ensure_ascii=False, indent=2))
PY
トレードオフと失敗モード
| task | 主encoding | 同等text | 避ける設計 |
|---|---|---|---|
| 依存関係をたどる | nodeとdirected edge | 順序付きnode listとfrom-to関係 | 線の位置だけで関係を表す |
| 量を正確に比較する | 共通尺度上のposition | caption付きtableとmaximum、minimum | 面積やcolorだけで値を表す |
| 状態を素早く識別する | label、形、pattern、補助color | 状態名と説明 | 赤と緑だけで区別する |
- 誤診: SVG 2で描いたので自動的にsemanticでaccessibileな図になる。反証: SVGの要素だけではreader task、directed edge、値、要約の同等性を保証しない。dataからsemantic HTMLと同等のテキスト構造を導出して照合する。
- 誤診: color legendを追加すればcolorだけのencodingでも十分である。反証: legendもcolor識別を要求する。label、位置、形、pattern、tableを併用し、monochromeでも同じ関係と値を取得できる必要がある。
- 失敗モード: 視覚図とtableを別々に更新すると、node削除や値変更でdriftし、異なる結論を伝える。
- 失敗モード: 3D効果や面積で一次元の量を表すと、装飾が比較taskへ不要な知覚誤差を加える。
知識チェック
- OpenGL 4.6とSVG 2は、この教材の情報設計でどの異なる層を扱うか。
- roadmapの同等textにnode labelだけでなくedgeが必要な理由は何か。
- Clevelandの知覚taskを量の比較へどう適用するか。
- colorからmonochromeへ変える時に同等性をどう反証するか。
出典と次の学習
rendering機構はThe OpenGL Graphics System Version 4.6 Core ProfileのMay 5, 2022版、文書内vector表現はScalable Vector Graphics 2へ版を固定する。encodingの知覚根拠はClevelandとMcGillのGraphical Perception、分析設計はMunznerのVisualization Analysis and Design、複雑図のtext実践はW3C Complex Imagesを参照する。
次は、視覚化したproduct仮説を反証可能なsuccess metric、guardrail、stop conditionへ変換し、simulated dataで継続判断を再現する。
実践ラボ
roadmapとquantitative chartの視覚・意味同等性を作る
提出成果物: 視覚表現と同等のテキスト構造を持つ静的データ図
- 固定synthetic dataからroadmapのnodeとdirected edge、chartのlabel、value、単位を定義する
- roadmapをsemantic HTMLの順序付きnodeと関係listへ変換し、CSSを表示の責務に限定する
- quantitative chartを共通尺度上のpositionで表し、同じ値をcaption付きtableとtext summaryへ変換する
- node、edge、row、maximum、minimumが視覚dataと同等のテキスト構造で一致することを検証する
- 同じdataとtext equivalentのままcolorからmonochromeへdisplay modeだけを変え、情報同等性を再検証する
説明して理解を確かめる
5分で、OpenGL 4.6のgraphics pipeline、SVG 2の文書表現、Clevelandのgraphical perception、Munznerの分析設計、W3C Complex Imagesのtext equivalentを役割別に説明する。
アセスメント
問い: 色分けしたroadmapが見栄え良く表示されるが、読み上げでは依存関係が分からない。何を直すか。
期待する証拠: data由来のnode ID、directed edge、semantic HTML、同等のテキスト構造、CSS責務の分離
問い: barの面積と彩度だけで3つの値を比較するchartをどう再設計するか。
期待する証拠: 共通尺度上の位置、label、value、単位、table、summary、color-only回避
別問題へ転用する
color表示からmonochrome表示へ変え、roadmapとchartの同等情報を再検証する
復習スケジュール
- 1日後
roadmapのvisual orderと依存関係dataを分ける理由は何か
- 7日後
quantitative valueの比較に共通尺度上のpositionを使う利点は何か
- 30日後
monochrome transferで不変にすべき情報を三つ挙げる
- 90日後
roadmapのvisual orderと依存関係dataを分ける理由は何か
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| technical-correctness | 視覚位置と色だけに情報を持たせ、元dataとの対応がない | dataと図は対応するがedge、値、単位のいずれかがtextから欠ける | node、edge、row、値、集約をdataから一貫して導出する | 表示変換、順序、精度、fallbackの境界でも同等性を維持する |
| judgment | 装飾性だけでencodingを選び読者の比較タスクを考慮しない | 複数encodingを使うが知覚精度と複雑性を比較しない | 読者のtask、data type、知覚精度、保守性からencodingを選ぶ | 密度、国際化、印刷、支援技術の緊張を再評価条件へ含める |
| evidence | 画像だけを提出し元dataと検証結果を残さない | 元dataはあるが視覚表現とtext equivalentを別々に手書きする | 同じfixtureから視覚用dataと同等textを再現可能に導出する | text欠落mutationを失敗させ、最大値と関係も機械的に照合する |
| communication | 凡例、単位、captionがなく図の意図を説明できない | 説明はあるが意思決定に必要な比較と関係が散在する | caption、summary、table、関係listで同じ結論へ到達できる | 視覚利用者と非視覚利用者が同じ根拠をレビューできる |
出典
以下の外部資料は利用者が選択したときだけ開きます。
- The OpenGL Graphics System: A Specification, Version 4.6 (Core Profile), May 5, 2022 (standard)
- Scalable Vector Graphics (SVG) 2 (standard)
- Graphical Perception: Theory, Experimentation, and Application to the Development of Graphical Methods (peer-reviewed)
- Visualization Analysis and Design (primary)
- Complex Images (primary)