plotto 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/theme.js ADDED
@@ -0,0 +1,93 @@
1
+ // Theme: a style (colors, font) and a format (size, text size, line width) from theme.json, plus overrides.
2
+ // Every style is merged over "pastel" (the default style), so a style lists only what it changes.
3
+ import * as d3 from "d3";
4
+ import THEMES from "../theme.json" with { type: "json" };
5
+
6
+ export const STYLES = Object.keys(THEMES.styles);
7
+ export const FORMATS = Object.keys(THEMES.formats);
8
+ export const PALETTES = Object.keys(THEMES.palettes);
9
+ export const SCHEMES = Object.keys(THEMES.schemes);
10
+ const ROLES = ["ink", "muted", "grid", "tick", "strip", "accent", "background"];
11
+
12
+ export function resolveTheme({ style = "pastel", format = "screen", legend = "right", ...overrides } = {}) {
13
+ if (!THEMES.styles[style]) throw new Error(`theme(): unknown style "${style}". Valid: ${STYLES.join(", ")}`);
14
+ if (!THEMES.formats[format]) throw new Error(`theme(): unknown format "${format}". Valid: ${FORMATS.join(", ")}`);
15
+ const base = THEMES.styles.pastel;
16
+ const s = THEMES.styles[style];
17
+ const f = THEMES.formats[format];
18
+ const fixed = overrides.width != null;
19
+ for (const k of Object.keys(overrides)) if (overrides[k] == null) delete overrides[k];
20
+ const theme = { ...base, ...s, colors: { ...base.colors, ...s.colors }, ...f, legend, ...overrides, style, format };
21
+ theme.reflow = f.reflow && !fixed;
22
+ // weight: the style's line and point scale (pastel colors need slightly thicker lines).
23
+ theme.line = f.line * (s.weight ?? base.weight ?? 1);
24
+ theme.point = f.point * Math.sqrt(s.weight ?? base.weight ?? 1);
25
+ // A set width fixes the size; the height follows the format aspect ratio unless it is set too.
26
+ // A set height alone keeps the reflow of the width and fixes the height.
27
+ if (overrides.width != null && overrides.height == null) theme.height = Math.round((overrides.width * f.height) / f.width);
28
+ theme.fixedHeight = overrides.height != null;
29
+ theme.palette = palette(theme, theme.palette);
30
+ return theme;
31
+ }
32
+
33
+ // The colors of a named palette (or of a list of colors), resolved against the theme tokens.
34
+ export function palette(theme, name) {
35
+ const list = Array.isArray(name) ? name : THEMES.palettes[name];
36
+ if (!list) throw new Error(`color(): unknown palette "${name}". Valid: ${PALETTES.join(", ")}`);
37
+ return list.map((c) => colorOf(theme, c) ?? c);
38
+ }
39
+
40
+ // An interpolator t ∈ [0, 1] → color: a plotto scheme (theme.json), a d3 scheme name, or a list of colors.
41
+ export function interpolator(theme, scheme) {
42
+ const resolve = (list) => d3.piecewise(d3.interpolateLab, list.map((c) => colorOf(theme, c) ?? c));
43
+ if (Array.isArray(scheme)) return resolve(scheme);
44
+ if (THEMES.schemes[scheme]) return resolve(THEMES.schemes[scheme]);
45
+ const fn = typeof scheme === "string" && d3[`interpolate${scheme[0].toUpperCase()}${scheme.slice(1)}`];
46
+ if (!fn) throw new Error(`color(): unknown scheme "${scheme}". plotto schemes: ${SCHEMES.join(", ")}; or a d3 scheme such as viridis, magma, blues`);
47
+ return fn;
48
+ }
49
+
50
+ // A color is a theme token ("blue", "muted", ...) or any CSS color. Returns undefined for anything else.
51
+ export function colorOf(theme, value) {
52
+ if (typeof value !== "string") return undefined;
53
+ if (theme.colors[value]) return theme.colors[value];
54
+ if (ROLES.includes(value)) return theme[value];
55
+ return d3.color(value) ? value : undefined;
56
+ }
57
+
58
+ export function colorTokens(theme) {
59
+ return [...Object.keys(theme.colors), ...ROLES];
60
+ }
61
+
62
+ // Header colors of a hover box: the series color as background, with readable text.
63
+ // Light colors get dark text; other colors get white text, on a background darkened until the contrast is at least 4.
64
+ export function headerColors(color) {
65
+ const lum = (c) => {
66
+ const ch = [c.r, c.g, c.b].map((v) => {
67
+ v /= 255;
68
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
69
+ });
70
+ return 0.2126 * ch[0] + 0.7152 * ch[1] + 0.0722 * ch[2];
71
+ };
72
+ const rgb = d3.rgb(color);
73
+ if (lum(rgb) > 0.42) return { bg: rgb.formatHex(), fg: "#1F2328" };
74
+ let c = d3.hcl(rgb);
75
+ for (let i = 0; i < 20 && 1.05 / (lum(d3.rgb(c)) + 0.05) < 4; i++) c = d3.hcl(c.h, c.c, c.l - 4);
76
+ return { bg: d3.rgb(c).formatHex(), fg: "#FFFFFF" };
77
+ }
78
+
79
+ // Black or white text, whichever reads better on the given background color.
80
+ export function textOn(background) {
81
+ return headerColors(background).fg;
82
+ }
83
+
84
+ // Font sizes in px, all derived from the theme base size.
85
+ export function fontSizes(theme) {
86
+ const s = theme.size;
87
+ return { base: s, tick: s - 1, title: Math.round(s * 1.35), subtitle: s, strip: s - 1, legend: s - 1, axisTitle: s, hover: Math.max(11, s - 1) };
88
+ }
89
+
90
+ // The first family of a CSS font stack, without quotes.
91
+ export function firstFamily(font) {
92
+ return font.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
93
+ }
package/src/toolbar.js ADDED
@@ -0,0 +1,126 @@
1
+ import { textOn } from "./theme.js";
2
+
3
+ // Toolbar and page styles: a floating pill of icon buttons at the top right of the figure.
4
+ // The button size follows the figure width. The styles go into the document once, as one <style> element.
5
+
6
+ const ICONS = {
7
+ zoom: '<path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"/><rect x="9" y="9" width="6" height="6"/>',
8
+ pan: '<path d="M12 3v18M3 12h18M12 3l-2.5 2.5M12 3l2.5 2.5M12 21l-2.5-2.5M12 21l2.5-2.5M3 12l2.5-2.5M3 12l2.5 2.5M21 12l-2.5-2.5M21 12l-2.5 2.5"/>',
9
+ zoomIn: '<path d="M12 5v14M5 12h14"/>',
10
+ zoomOut: '<path d="M5 12h14"/>',
11
+ home: '<path d="M4 11l8-7 8 7M6 9.5V20h12V9.5"/>',
12
+ copy: '<rect x="8.5" y="8.5" width="11" height="11"/><path d="M15.5 8.5V4.5h-11v11h4"/>',
13
+ download: '<path d="M12 4v11M7.5 10.5L12 15l4.5-4.5M5 19.5h14"/>',
14
+ check: '<path d="M5 12.5l4.5 4.5L19 7.5"/>',
15
+ };
16
+
17
+ const CSS = `
18
+ .pt-root{position:relative;--pt-size:24px}
19
+ .pt-root svg{display:block;user-select:none;-webkit-user-select:none}
20
+ .pt-root .pt-key{cursor:pointer}
21
+ .pt-root:focus{outline:none}
22
+ .pt-bar{position:absolute;top:6px;right:6px;display:flex;align-items:stretch;z-index:3;background:var(--pt-bg);
23
+ border:1px solid var(--pt-line);opacity:0;transition:opacity .12s ease}
24
+ .pt-root:hover .pt-bar,.pt-root:focus-within .pt-bar,.pt-bar.pt-open{opacity:1}
25
+ @media (hover:none){.pt-bar{opacity:1}}
26
+ .pt-btn{all:unset;box-sizing:border-box;display:grid;place-items:center;width:var(--pt-size);height:var(--pt-size);
27
+ color:var(--pt-muted);cursor:pointer}
28
+ .pt-btn svg{width:58%;height:58%;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:square;stroke-linejoin:miter}
29
+ .pt-btn:hover{color:var(--pt-ink);background:var(--pt-soft)}
30
+ .pt-btn:focus-visible{outline:2px solid var(--pt-accent);outline-offset:-2px}
31
+ .pt-btn.pt-on{color:var(--pt-on-accent);background:var(--pt-accent)}
32
+ .pt-sep{width:1px;background:var(--pt-line)}
33
+ .pt-menu{position:absolute;top:100%;right:-1px;display:none;flex-direction:column;min-width:72px;margin-top:-1px;
34
+ background:var(--pt-bg);border:1px solid var(--pt-line)}
35
+ .pt-menu.pt-open{display:flex}
36
+ .pt-menu button{all:unset;padding:5px 12px;font:600 11px/1.4 system-ui,sans-serif;letter-spacing:.04em;color:var(--pt-ink);cursor:pointer}
37
+ .pt-menu button:hover{color:var(--pt-on-accent);background:var(--pt-accent)}
38
+ .pt-tip{position:absolute;display:none;pointer-events:none;z-index:4;min-width:110px;max-width:280px;background:var(--pt-bg);
39
+ color:var(--pt-ink);border:1px solid var(--pt-c);line-height:1.3;font-variant-numeric:tabular-nums}
40
+ .pt-tip.pt-pinned{border-width:2px;box-shadow:3px 3px 0 color-mix(in srgb,var(--pt-c) 35%,transparent)}
41
+ .pt-tip .pt-head{padding:3px 7px;background:var(--pt-c);color:var(--pt-on-c);font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
42
+ .pt-tip .pt-rows{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:7px;row-gap:0;padding:4px 7px 5px}
43
+ .pt-tip .pt-sw{width:8px;height:8px;align-self:center}
44
+ .pt-tip .pt-k{color:var(--pt-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
45
+ .pt-tip .pt-v{text-align:right;white-space:nowrap}
46
+ .pt-tip .pt-b{color:var(--pt-ink);font-weight:700}
47
+ .pt-tip .pt-t{border-top:1px solid var(--pt-line);padding-top:2px;margin-top:1px}
48
+ .pt-tip .pt-more{grid-column:1/-1;color:var(--pt-muted);font-size:.9em}
49
+ .pt-error{padding:8px 10px;border-left:3px solid #d93025;background:#fdecea;color:#5f2120;font:12px/1.4 ui-monospace,monospace;white-space:pre-wrap}
50
+ `;
51
+
52
+ export function installStyles(doc) {
53
+ if (doc.getElementById("plotto-style")) return;
54
+ const style = doc.createElement("style");
55
+ style.id = "plotto-style";
56
+ style.textContent = CSS;
57
+ doc.head.append(style);
58
+ }
59
+
60
+ // The theme colors as CSS variables of the root element.
61
+ export function themeVars(root, theme) {
62
+ const vars = { bg: theme.background, ink: theme.ink, muted: theme.muted, line: theme.grid, soft: theme.strip, accent: theme.accent, "on-accent": textOn(theme.accent) };
63
+ for (const [k, v] of Object.entries(vars)) root.style.setProperty(`--pt-${k}`, v);
64
+ }
65
+
66
+ // groups: [[{ name, title, action, menu? }]]. Returns { el, button(name), setSize(width), flash(name) }.
67
+ export function toolbar(doc, groups) {
68
+ const el = doc.createElement("div");
69
+ el.className = "pt-bar";
70
+ const buttons = {};
71
+ let menu = null;
72
+ groups.forEach((group, g) => {
73
+ if (g) el.append(Object.assign(doc.createElement("div"), { className: "pt-sep" }));
74
+ for (const spec of group) {
75
+ const b = doc.createElement("button");
76
+ b.className = "pt-btn";
77
+ b.type = "button";
78
+ b.title = spec.title;
79
+ b.setAttribute("aria-label", spec.title);
80
+ b.innerHTML = icon(spec.name);
81
+ b.addEventListener("pointerdown", (e) => e.stopPropagation());
82
+ b.addEventListener("click", (e) => (e.stopPropagation(), spec.menu ? toggleMenu() : spec.action(b)));
83
+ el.append(b);
84
+ buttons[spec.name] = b;
85
+ if (spec.menu) menu = addMenu(doc, el, spec.menu);
86
+ }
87
+ });
88
+ function toggleMenu() {
89
+ const open = !menu.classList.contains("pt-open");
90
+ menu.classList.toggle("pt-open", open);
91
+ el.classList.toggle("pt-open", open);
92
+ }
93
+ if (menu) doc.addEventListener("click", () => menu.classList.contains("pt-open") && toggleMenu());
94
+ return {
95
+ el,
96
+ button: (name) => buttons[name],
97
+ // Button size: 1/34 of the figure width, from 22 to 30 px.
98
+ setSize: (width) => el.parentElement?.style.setProperty("--pt-size", `${Math.round(Math.min(30, Math.max(22, width / 34)))}px`),
99
+ flash(name, title) {
100
+ const b = buttons[name];
101
+ const [html, old] = [b.innerHTML, b.title];
102
+ b.innerHTML = icon("check");
103
+ b.title = title;
104
+ b.classList.add("pt-on");
105
+ setTimeout(() => ((b.innerHTML = html), (b.title = old), b.classList.remove("pt-on")), 1100);
106
+ },
107
+ };
108
+ }
109
+
110
+ function addMenu(doc, bar, items) {
111
+ const menu = doc.createElement("div");
112
+ menu.className = "pt-menu";
113
+ for (const [label, action] of items) {
114
+ const item = doc.createElement("button");
115
+ item.type = "button";
116
+ item.textContent = label;
117
+ item.addEventListener("click", (e) => (e.stopPropagation(), menu.classList.remove("pt-open"), bar.classList.remove("pt-open"), action()));
118
+ menu.append(item);
119
+ }
120
+ bar.append(menu);
121
+ return menu;
122
+ }
123
+
124
+ function icon(name) {
125
+ return `<svg viewBox="0 0 24 24" aria-hidden="true">${ICONS[name]}</svg>`;
126
+ }
package/src/views.js ADDED
@@ -0,0 +1,84 @@
1
+ // Views: the data that one layer draws in one panel, after its statistic (summary, stack, bins, density, smooth).
2
+ // A view has columns named like the channels (x, y, ymin, ...), and the hover anchor and fields of each row.
3
+ import { channelValues } from "./scales.js";
4
+ import { barIntervals, bin, binEdges, density, position, smooth, summarize } from "./stats.js";
5
+
6
+ // Bin edges of each hist layer, shared by all panels and colors.
7
+ export function layerEdges(ns) {
8
+ return ns.layers.map((layer) => {
9
+ if (layer.type !== "hist" || !layer.channels.x.column) return null;
10
+ return binEdges(ns.tables[layer.table].columns[layer.channels.x.column].values, layer.options.bins ?? 30);
11
+ });
12
+ }
13
+
14
+ // The view of one layer for the given source rows. hidden: the color values that the legend switched off.
15
+ export function makeView(ctx, layer, rows, hidden) {
16
+ const { ns, kind, colorLevels, edges } = ctx;
17
+ if (hidden.size && layer.channels.color?.column) {
18
+ const values = ns.tables[layer.table].columns[layer.channels.color.column].values;
19
+ rows = rows.filter((r) => !hidden.has(values[r]));
20
+ }
21
+ const raw = {};
22
+ for (const ch of Object.keys(layer.channels)) raw[ch] = channelValues(ns, layer, ch, rows);
23
+ const n = rows.length;
24
+ const o = layer.options;
25
+ const stacked = (cols, m, mode) => position(cols, m, mode, colorLevels);
26
+ const sizeOf = (cols) => cols.x?.length ?? 0;
27
+ let cols = raw;
28
+ let source = rows;
29
+
30
+ if (o.summary && (layer.type === "line" || layer.type === "point")) cols = summarize(raw, n, o.summary);
31
+ else if (layer.type === "bar") {
32
+ cols = stacked(raw, n, o.position ?? "stack");
33
+ if (kind.x !== "category") cols = barIntervals(cols, n);
34
+ } else if (layer.type === "area") cols = stacked(raw, n, o.position ?? "stack");
35
+ else if (layer.type === "hist") {
36
+ const b = bin(raw, n, edges[ns.layers.indexOf(layer)], o.density);
37
+ cols = stacked(b, sizeOf(b), o.position ?? "stack");
38
+ } else if (layer.type === "density") cols = density(raw, n);
39
+ else if (layer.type === "smooth") cols = smooth(raw, n, o);
40
+ if (cols !== raw) source = null;
41
+ const view = { cols, n: cols === raw ? n : sizeOf(cols), rows: source };
42
+ view.hover = hoverOf(ns, layer, view);
43
+ return view;
44
+ }
45
+
46
+ // The hover anchor (hx, hy) and the label fields of a view, or null for layers without hover.
47
+ function hoverOf(ns, layer, { cols, rows }) {
48
+ // A removed axis title (null) still names the hover row: by the column, or by the channel.
49
+ const X = ns.labels.x || layer.channels.x?.column || "x";
50
+ const Y = ns.labels.y || layer.channels.y?.column || "y";
51
+ const field = (label, axis, values, values2) => ({ label, axis, values, values2 });
52
+ const xField = field(X, "x", cols.x);
53
+ const tooltip = layer.channels.tooltip?.column && rows ? [field(layer.channels.tooltip.column, null, cols.tooltip)] : [];
54
+ switch (layer.type) {
55
+ case "line":
56
+ case "path":
57
+ case "step":
58
+ case "point":
59
+ return { hx: cols.x, hy: cols.y, fields: [xField, field(Y, "y", cols.y), ...tooltip] };
60
+ case "bar":
61
+ case "area":
62
+ return { hx: cols.x, hy: cols.ymax, fields: [xField, field(Y, "y", cols.value), ...tooltip] };
63
+ case "hist":
64
+ return { hx: cols.x, hy: cols.ymax, fields: [field(X, "x", cols.xmin, cols.xmax), field(layer.options.density ? "density" : "count", null, cols.value)] };
65
+ case "density":
66
+ return { hx: cols.x, hy: cols.y, fields: [xField, field("density", null, cols.y)] };
67
+ case "smooth": {
68
+ const band = cols.ymin.some((v) => v !== null) ? [field("95% CI", "y", cols.ymin, cols.ymax)] : [];
69
+ return { hx: cols.x, hy: cols.y, fields: [xField, field(Y, "y", cols.y), ...band] };
70
+ }
71
+ case "ribbon":
72
+ return { hx: cols.x, hy: cols.ymax, fields: [xField, field(Y, "y", cols.ymin, cols.ymax)] };
73
+ case "errorbar":
74
+ return { hx: cols.x, hy: cols.ymax, fields: [xField, field(Y, "y", cols.ymin, cols.ymax)] };
75
+ case "tile": {
76
+ const value = layer.channels.color?.column ? [field(ns.labels.color ?? "value", null, cols.color)] : [];
77
+ return { hx: cols.x, hy: cols.y, fields: [xField, field(Y, "y", cols.y), ...value, ...tooltip], tile: true };
78
+ }
79
+ case "box":
80
+ return { hx: cols.x, hy: cols.y, fields: [xField, field(Y, "y", cols.y)] };
81
+ default:
82
+ return null;
83
+ }
84
+ }
package/theme.json ADDED
@@ -0,0 +1,150 @@
1
+ {
2
+ "styles": {
3
+ "pastel": {
4
+ "font": "Helvetica, Arial, 'Liberation Sans', sans-serif",
5
+ "metrics": "arial",
6
+ "background": "#FFFEFB",
7
+ "ink": "#3A3631",
8
+ "muted": "#7A736A",
9
+ "grid": "#ECE8E1",
10
+ "gridMinor": "#F6F3EE",
11
+ "tick": "#D6D0C6",
12
+ "strip": "#F4F0E9",
13
+ "accent": "#3E8A99",
14
+ "palette": "pastel",
15
+ "scheme": "ai",
16
+ "colors": {
17
+ "blue": "#7AA6DA",
18
+ "orange": "#EF977F",
19
+ "green": "#7DB89A",
20
+ "purple": "#A79BD6",
21
+ "pink": "#E48FAE",
22
+ "yellow": "#DDB459",
23
+ "teal": "#63B7C1",
24
+ "red": "#CF7468",
25
+ "brown": "#BB9C84",
26
+ "gray": "#97A1AD"
27
+ },
28
+ "weight": 1.25
29
+ },
30
+ "wota": {
31
+ "font": "'M PLUS 1p', sans-serif",
32
+ "metrics": "mplus",
33
+ "ink": "#333333",
34
+ "muted": "#666666",
35
+ "grid": "#D9D9D9",
36
+ "gridMinor": "#EFEFEF",
37
+ "tick": "#BBBBBB",
38
+ "strip": "#EEF1FA",
39
+ "accent": "#5C6BC0",
40
+ "palette": "wota",
41
+ "scheme": "sakura",
42
+ "colors": {
43
+ "pink": "#E4007F",
44
+ "blue": "#5C6BC0",
45
+ "teal": "#00A3A3",
46
+ "orange": "#F5A623",
47
+ "purple": "#8E44AD",
48
+ "gray": "#7F7F7F",
49
+ "green": "#3BA55C",
50
+ "red": "#D7263D",
51
+ "yellow": "#F2C14E",
52
+ "brown": "#8D6E63"
53
+ },
54
+ "weight": 1,
55
+ "background": "#FFFFFF"
56
+ },
57
+ "paper": {
58
+ "ink": "#000000",
59
+ "muted": "#333333",
60
+ "grid": "#E6E6E6",
61
+ "gridMinor": "#F5F5F5",
62
+ "tick": "#8C8C8C",
63
+ "strip": "#EBEBEB",
64
+ "accent": "#0072B2",
65
+ "palette": ["blue", "orange", "green", "pink", "yellow", "teal", "red", "brown", "purple", "gray"],
66
+ "scheme": "viridis",
67
+ "colors": {
68
+ "blue": "#0072B2",
69
+ "orange": "#E69F00",
70
+ "green": "#009E73",
71
+ "pink": "#CC79A7",
72
+ "yellow": "#F0E442",
73
+ "teal": "#56B4E9",
74
+ "red": "#D55E00",
75
+ "brown": "#8C6D31",
76
+ "purple": "#6A3D9A",
77
+ "gray": "#7F7F7F"
78
+ },
79
+ "weight": 1,
80
+ "background": "#FFFFFF"
81
+ },
82
+ "dark": {
83
+ "background": "#16181D",
84
+ "ink": "#E8EAED",
85
+ "muted": "#A6ABB3",
86
+ "grid": "#2E323A",
87
+ "gridMinor": "#22252B",
88
+ "tick": "#4F5560",
89
+ "strip": "#252933",
90
+ "accent": "#8AB4F8",
91
+ "palette": ["blue", "orange", "green", "purple", "pink", "yellow", "teal", "red", "brown", "gray"],
92
+ "scheme": "yuhi",
93
+ "colors": {
94
+ "blue": "#8AB4F8",
95
+ "orange": "#FBA94C",
96
+ "green": "#81C995",
97
+ "purple": "#C58AF9",
98
+ "pink": "#FF8BCB",
99
+ "yellow": "#FDD663",
100
+ "teal": "#78D9D3",
101
+ "red": "#F28B82",
102
+ "brown": "#C8A27A",
103
+ "gray": "#9AA0A6"
104
+ },
105
+ "weight": 1.15
106
+ }
107
+ },
108
+ "palettes": {
109
+ "pastel": ["#7AA6DA", "#EF977F", "#7DB89A", "#A79BD6", "#E48FAE", "#DDB459", "#63B7C1", "#CF7468", "#BB9C84", "#97A1AD"],
110
+ "wota": ["#E4007F", "#5C6BC0", "#00A3A3", "#F5A623", "#8E44AD", "#7F7F7F", "#3BA55C", "#D7263D", "#F2C14E", "#8D6E63"],
111
+ "sakura": ["#E16B8C", "#5DAC81", "#8B81C3", "#FFB11B", "#7DB9DE", "#F596AA", "#B9887D", "#91AD70"],
112
+ "aizome": ["#0F2540", "#51A8DD", "#66BAB7", "#656765", "#0D5661", "#A5DEE4", "#7B90D2"],
113
+ "momiji": ["#CB4042", "#FFB11B", "#6C6A2D", "#6D2E5B", "#ED784A", "#734338", "#C99833"],
114
+ "tableau": ["#4E79A7", "#F28E2B", "#E15759", "#76B7B2", "#59A14F", "#EDC948", "#B07AA1", "#FF9DA7", "#9C755F", "#BAB0AC"]
115
+ },
116
+ "schemes": {
117
+ "ai": ["#F4F8F8", "#A5DEE4", "#33A6B8", "#0D5661", "#0F2540"],
118
+ "sakura": ["#FEF4F4", "#FEDFE1", "#F596AA", "#D05A6E", "#8E354A", "#4A1D30"],
119
+ "matcha": ["#F8F6E3", "#D7E3B5", "#91AD70", "#5DAC81", "#1B813E", "#0B3B2A"],
120
+ "yuhi": ["#FFF3D6", "#FFB11B", "#ED784A", "#CB4042", "#6D2E5B", "#26113A"],
121
+ "sumi": ["#FCFAF2", "#BDC0BA", "#828282", "#434343", "#1C1C1C"],
122
+ "aiaka": ["#0F2540", "#33A6B8", "#A5DEE4", "#F7F6F0", "#F4A7A0", "#CB4042", "#5C1A1B"]
123
+ },
124
+ "formats": {
125
+ "screen": {
126
+ "width": 900,
127
+ "height": 450,
128
+ "size": 12,
129
+ "line": 1.5,
130
+ "point": 2.5,
131
+ "reflow": true
132
+ },
133
+ "report": {
134
+ "width": 640,
135
+ "height": 400,
136
+ "size": 10,
137
+ "line": 1.2,
138
+ "point": 2,
139
+ "reflow": false
140
+ },
141
+ "slide": {
142
+ "width": 1280,
143
+ "height": 720,
144
+ "size": 20,
145
+ "line": 2.5,
146
+ "point": 4,
147
+ "reflow": false
148
+ }
149
+ }
150
+ }