@topmindspace/tms-skills 0.1.16 → 0.1.18
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/CHANGELOG.md +47 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/package.json +1 -1
- package/top-ppt-html/README.en.md +2 -2
- package/top-ppt-html/README.md +1 -1
- package/top-ppt-html/SKILL.md +2 -2
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +122 -48
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +122 -48
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +122 -48
- package/top-ppt-html/assets/examples/2026-09-26-topmind-tms-skills-showcase.html +126 -52
- package/top-ppt-html/assets/examples/2026-09-26-topmind-tms-skills-showcase.model.json +3 -3
- package/top-ppt-html/assets/pptx-export.js +118 -45
- package/top-ppt-html/assets/style-gallery.html +19 -3
- package/top-ppt-html/assets/templates/architecture.html +122 -48
- package/top-ppt-html/assets/templates/engine.css +3 -2
- package/top-ppt-html/assets/templates/presentation.html +122 -48
- package/top-ppt-html/assets/templates/research.html +122 -48
- package/top-ppt-html/package-lock.json +2 -2
- package/top-ppt-html/package.json +1 -1
- package/top-ppt-html/references/chart-decision-tree.md +26 -25
- package/top-ppt-html/references/components-atoms.md +5 -4
- package/top-ppt-html/references/content-rules.md +3 -1
- package/top-ppt-html/references/failure-modes.md +29 -3
- package/top-ppt-html/references/layouts-combo.md +1 -1
- package/top-ppt-html/references/modes.md +1 -1
- package/top-ppt-html/references/page-type-matrix.md +10 -6
- package/top-ppt-html/references/pptx-export.md +3 -3
- package/top-ppt-html/scripts/build_pptx.js +101 -42
- package/top-ppt-html/scripts/cross_verify.py +84 -1
- package/top-ppt-html/scripts/extract_model.py +5 -0
- package/top-ppt-html/scripts/layout-constants.json +42 -2
- package/top-ppt-html/scripts/lib_layout_regions.js +6 -3
- package/top-ppt-html/scripts/model-schema.json +1 -0
- package/top-ppt-html/scripts/render_from_model.py +133 -17
- package/top-ppt-html/scripts/sync_runtime.py +19 -3
- package/top-ppt-html/scripts/test_feedback_gates.py +73 -0
- package/top-ppt-html/scripts/validate_pptx.py +148 -0
- package/top-ppt-html/scripts/validate_report.py +24 -0
|
@@ -156,6 +156,15 @@ function regionOf(pageType, slotId, opts) {
|
|
|
156
156
|
return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
|
|
157
157
|
dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
|
|
158
158
|
}
|
|
159
|
+
/* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
|
|
160
|
+
if (type === 'kpi' && slot === 'metrics') {
|
|
161
|
+
var dxK = P.dividerX != null ? P.dividerX : 0.56;
|
|
162
|
+
return {
|
|
163
|
+
x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
|
|
164
|
+
w: CW * (1 - dxK) - 0.5,
|
|
165
|
+
h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
159
168
|
if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
|
|
160
169
|
var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
|
|
161
170
|
if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
|
|
@@ -188,15 +197,22 @@ function regionOf(pageType, slotId, opts) {
|
|
|
188
197
|
}
|
|
189
198
|
if (type === 'image' && slot === 'primary') {
|
|
190
199
|
var iy2 = Math.max(P.y || 2.3, opts.top || 0);
|
|
191
|
-
|
|
200
|
+
/* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
|
|
201
|
+
var iBottom = (opts.bottom != null) ? opts.bottom
|
|
202
|
+
: (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
|
|
203
|
+
return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
|
|
192
204
|
}
|
|
193
205
|
if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
|
|
194
206
|
var rs = PT.research || {};
|
|
195
|
-
|
|
207
|
+
var ht = opts.top != null ? opts.top : topDefault;
|
|
208
|
+
var hb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
209
|
+
return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
|
|
196
210
|
}
|
|
197
211
|
if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
|
|
198
212
|
var rs2 = PT.research || {};
|
|
199
|
-
|
|
213
|
+
var ht2 = opts.top != null ? opts.top : topDefault;
|
|
214
|
+
var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
215
|
+
return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
|
|
200
216
|
}
|
|
201
217
|
if (type === 'matrix' && slot === 'primary') {
|
|
202
218
|
var rm = PT.research || {};
|
|
@@ -217,7 +233,7 @@ function regionOf(pageType, slotId, opts) {
|
|
|
217
233
|
/* __TOPPPT_SCHEMA_START__ */
|
|
218
234
|
/* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
|
|
219
235
|
/* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
|
|
220
|
-
var MODEL_SCHEMA = {"version":"0.1","modes":["presentation","research","architecture"],"model":{"required":["title","sections:array","closing.title","closing.points:array"],"optional":["mode","style","theme","subtitle","meta","agenda:array"],"agendaMin":{"presentation":3,"research":3,"architecture":0},"sectionsMin":1,"sectionsRecommended":3,"agendaComfortMax":16},"commonSectionFields":["eyebrow","title","lead","soWhat","footnote","flags","image","exhibitNo","layoutPreset"],"flagsHint":"flags = 待核实/待二次修改条目字符串数组(如 '2027 增速 xx%(口径未定)');渲染为 accent 强调色标注行,提示用户核对。","imageHint":"image = 素材图片/配图占位对象。三选一必填:src(用户图,data: 内联或相对路径,零外链铁律禁 http(s))|items(多图版式:grid/compare/wall,元素 {src?, alt?, caption?, placeholder?})|placeholder:true(无图时出配图占位,原生形状渲染、pictures 不增)。其余可选:layout(full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec.layouts)、fit(cover 默认 / contain)、caption、alt、hint、ratio。","imageLayoutHint":"layout 与 HTML 版式对应:full=版心全宽图|half=左图右注(points 为右栏注解)|bleed=通栏出血|grid=多图网格(items 2/3/4/6 张)|compare=双图 A/B 对比(items 2 张)|wall=Logo 墙(items 3–12 张小图)。占位符标签文本由双引擎按 imageSpec 拼同一串,保证 A/B 通道逐页文本一致。","chartTypes":["bar","hbar","stack","stackline","line","dualline","area","donut","multidonut","pie","radar","scatter","bubble","waterfall","gauge","pareto","funnel","gantt","vsbar","progress","sparkline","slope","dumbbell","lollipop","dotplot","bulletchart","waffle","radialbar","rose","candlestick"],"chartTypesHint":"chart.type 可取值 = 本表(30 类:16 原生 + 14 形状还原)。其余 6 类复杂信息图(sankey/treemap/boxplot/network/marimekko/streamgraph)是**专属页型**而非 chart.type——它们要按列/段/节点等结构化载荷表达,故走 sections[].type。不变量:charts.registry.types == chartTypes ∪ 上述 6 类页型(sync_runtime.py 校验)。","chartDataTable":{"enum":["notes","inline","appendix","off"],"default":"notes","rule":"非原生图表(registry.pptx=shape)的 dataTable 不得为 off;缺失时按 registry 默认值补齐。"},"pageTypes":{"points":{"label":"要点列表页","modes":["presentation","research","architecture"],"required":["points:array"],"optional":["metrics:array"]},"metrics":{"label":"指标带页(4–6 个核心数字)","modes":["presentation","research"],"required":["metrics:array"]},"kpi":{"label":"大数指标页(hero 大数字 + 支撑指标行)","modes":["presentation","research"],"required":["hero:array"],"optional":["metrics:array"]},"table":{"label":"对比表页","modes":["presentation","research","architecture"],"required":["table.head:array","table.rows:array"],"optional":["table.colW:array"]},"timeline":{"label":"时间线 / 路线页","modes":["presentation","research"],"required":["phases:array"]},"steps":{"label":"步骤条页(N 步横排 + 箭头,可分组)","modes":["presentation","research","architecture"],"required":["steps:array"],"optional":["groups:array","note"]},"bar":{"label":"图表页(chart.type 见 chartTypes;native 通道走原生数据图表,shape 通道走高保真形状还原 + 数据表)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.start:array","chart.target:array","chart.dataTable","note"]},"donut":{"label":"环形图页(原生 pie 楔形 + 图例)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.unit","chart.centerLabel","chart.colors:array","chart.dataTable","note"]},"heatmap":{"label":"热力矩阵页(行 × 列 + 强度色阶)","modes":["presentation","research"],"required":["rowHeads:array","colHeads:array","cells:array"],"optional":["unit","scaleLabel:array","note"]},"bullet":{"label":"达成对比页(实际 vs 目标条)","modes":["presentation","research"],"required":["items:array"],"optional":["unit","max","note"]},"pyramid":{"label":"金字塔页(层级递进,顶层最窄)","modes":["presentation","research","architecture"],"required":["levels:array"],"optional":["note"]},"image":{"label":"素材图片页(full/half/bleed/grid/compare/wall,效果与位置锁定;支持配图占位)","modes":["presentation","research","architecture"],"required":["anyof:image.src:str|image.items:array|image.placeholder"],"optional":["image.caption","image.alt","image.layout","image.fit","image.ratio","image.hint","points:array","note"]},"cards":{"label":"卡片网格页(cards 元素须为 {title, points:[[k,v]…]},不接受元组)","modes":["presentation","research"],"required":["cards:array"],"optional":["columns"]},"split":{"label":"双区组合页(left / right 各可为 要点|图表|表格|图片:type = points(默认)| table | image | 图表类型名;图表/表格字段直接挂在对应侧)","modes":["presentation","research"],"required":["anyof:left.points:array|left.type:str"],"optional":["left.cap","left.image","left.labels:array","left.values:array","left.series:array","left.points:array","left.max","left.unit","left.colors:array","left.dataTable","left.head:array","left.rows:array","left.colW:array","right.type","right.cap","right.image","right.labels:array","right.values:array","right.series:array","right.points:array","right.max","right.unit","right.colors:array","right.dataTable","right.head:array","right.rows:array","right.colW:array"]},"comparison":{"label":"对比页(左右双栏 + 可选结论条)","modes":["presentation","research"],"required":["left.title","left.points:array","right.title","right.points:array"],"optional":["verdict"]},"quote":{"label":"引用 / 金句页(深色全幅)","modes":["presentation","research"],"required":["quote","author"],"optional":["context"]},"diagram":{"label":"分层架构页(architecture 模式走全幅几何)","modes":["presentation","research","architecture"],"required":["layers:array"],"optional":["legend:array"]},"exhibit":{"label":"Exhibit 编号图表页(research R2)","modes":["research"],"required":["chart.labels:array","chart.values:array"],"optional":["exhibitNo","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"twocol":{"label":"双栏论证页(research R1)","modes":["research"],"required":["paragraphs:array"]},"threecol":{"label":"三栏证据页(research R6)","modes":["research"],"required":["paragraphs:array"]},"halftable":{"label":"半表半图页(research R7)","modes":["research"],"required":["table.head:array","table.rows:array","chart.labels:array","chart.values:array"],"optional":["table.colW:array","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"matrix":{"label":"矩阵图页(research R8)","modes":["research"],"required":["rowHeads:array","colHeads:array","cells:array"]},"lane":{"label":"泳道页(architecture A2)","modes":["architecture"],"required":["lanes:array"]},"sankey":{"label":"桑基图页(节点-流带,流向与流量)","modes":["research","architecture"],"required":["flows:array"],"optional":["unit","chart.dataTable","note"]},"treemap":{"label":"树图页(面积编码的层级构成)","modes":["research","architecture"],"required":["items:array"],"optional":["unit","chart.dataTable","note"]},"boxplot":{"label":"箱线图页(分布对比:min/q1/median/q3/max)","modes":["research","architecture"],"required":["groups:array"],"optional":["unit","chart.dataTable","note"]},"network":{"label":"关系网络页(节点-边拓扑)","modes":["research","architecture"],"required":["nodes:array","edges:array"],"optional":["chart.dataTable","note"]},"marimekko":{"label":"马赛克图页(列宽 × 列高双重编码)","modes":["research","architecture"],"required":["cols:array","cells:array"],"optional":["unit","chart.dataTable","note"]},"streamgraph":{"label":"流带图页(时间上的构成演变)","modes":["research","architecture"],"required":["series:array"],"optional":["labels:array","chart.dataTable","note"]}}};
|
|
236
|
+
var MODEL_SCHEMA = {"version":"0.1","modes":["presentation","research","architecture"],"model":{"required":["title","sections:array","closing.title","closing.points:array"],"optional":["mode","style","theme","subtitle","meta","agenda:array"],"agendaMin":{"presentation":3,"research":3,"architecture":0},"sectionsMin":1,"sectionsRecommended":3,"agendaComfortMax":16},"commonSectionFields":["eyebrow","title","lead","soWhat","footnote","flags","image","exhibitNo","layoutPreset"],"mutualExclusionHint":"verdict 与 soWhat 互斥:二者共用 annotation 结论条槽位,同页双填必叠印。comparison 页填 verdict 时不要再填 soWhat(渲染器已自动二选一,但模型侧应只给一个)。","flagsHint":"flags = 待核实/待二次修改条目字符串数组(如 '2027 增速 xx%(口径未定)');渲染为 accent 强调色标注行,提示用户核对。","imageHint":"image = 素材图片/配图占位对象。三选一必填:src(用户图,data: 内联或相对路径,零外链铁律禁 http(s))|items(多图版式:grid/compare/wall,元素 {src?, alt?, caption?, placeholder?})|placeholder:true(无图时出配图占位,原生形状渲染、pictures 不增)。其余可选:layout(full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec.layouts)、fit(cover 默认 / contain)、caption、alt、hint、ratio。","imageLayoutHint":"layout 与 HTML 版式对应:full=版心全宽图|half=左图右注(points 为右栏注解)|bleed=通栏出血|grid=多图网格(items 2/3/4/6 张)|compare=双图 A/B 对比(items 2 张)|wall=Logo 墙(items 3–12 张小图)。占位符标签文本由双引擎按 imageSpec 拼同一串,保证 A/B 通道逐页文本一致。","chartTypes":["bar","hbar","stack","stackline","line","dualline","area","donut","multidonut","pie","radar","scatter","bubble","waterfall","gauge","pareto","funnel","gantt","vsbar","progress","sparkline","slope","dumbbell","lollipop","dotplot","bulletchart","waffle","radialbar","rose","candlestick"],"chartTypesHint":"chart.type 可取值 = 本表(30 类:16 原生 + 14 形状还原)。其余 6 类复杂信息图(sankey/treemap/boxplot/network/marimekko/streamgraph)是**专属页型**而非 chart.type——它们要按列/段/节点等结构化载荷表达,故走 sections[].type。不变量:charts.registry.types == chartTypes ∪ 上述 6 类页型(sync_runtime.py 校验)。","chartDataTable":{"enum":["notes","inline","appendix","off"],"default":"notes","rule":"非原生图表(registry.pptx=shape)的 dataTable 不得为 off;缺失时按 registry 默认值补齐。"},"pageTypes":{"points":{"label":"要点列表页","modes":["presentation","research","architecture"],"required":["points:array"],"optional":["metrics:array"]},"metrics":{"label":"指标带页(4–6 个核心数字)","modes":["presentation","research"],"required":["metrics:array"]},"kpi":{"label":"大数指标页(hero 大数字 + 支撑指标行)","modes":["presentation","research"],"required":["hero:array"],"optional":["metrics:array"]},"table":{"label":"对比表页","modes":["presentation","research","architecture"],"required":["table.head:array","table.rows:array"],"optional":["table.colW:array"]},"timeline":{"label":"时间线 / 路线页","modes":["presentation","research"],"required":["phases:array"]},"steps":{"label":"步骤条页(N 步横排 + 箭头,可分组)","modes":["presentation","research","architecture"],"required":["steps:array"],"optional":["groups:array","note"]},"bar":{"label":"图表页(chart.type 见 chartTypes;native 通道走原生数据图表,shape 通道走高保真形状还原 + 数据表)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.start:array","chart.target:array","chart.dataTable","note"]},"donut":{"label":"环形图页(原生 pie 楔形 + 图例)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.unit","chart.centerLabel","chart.colors:array","chart.dataTable","note"]},"heatmap":{"label":"热力矩阵页(行 × 列 + 强度色阶)","modes":["presentation","research"],"required":["rowHeads:array","colHeads:array","cells:array"],"optional":["unit","scaleLabel:array","note"]},"bullet":{"label":"达成对比页(实际 vs 目标条)","modes":["presentation","research"],"required":["items:array"],"optional":["unit","max","note"]},"pyramid":{"label":"金字塔页(层级递进,顶层最窄)","modes":["presentation","research","architecture"],"required":["levels:array"],"optional":["note"]},"image":{"label":"素材图片页(full/half/bleed/grid/compare/wall,效果与位置锁定;支持配图占位)","modes":["presentation","research","architecture"],"required":["anyof:image.src:str|image.items:array|image.placeholder"],"optional":["image.caption","image.alt","image.layout","image.fit","image.ratio","image.hint","points:array","note"]},"cards":{"label":"卡片网格页(cards 元素须为 {title, points:[[k,v]…]},不接受元组)","modes":["presentation","research"],"required":["cards:array"],"optional":["columns"]},"split":{"label":"双区组合页(left / right 各可为 要点|图表|表格|图片:type = points(默认)| table | image | 图表类型名;图表/表格字段直接挂在对应侧)","modes":["presentation","research"],"required":["anyof:left.points:array|left.type:str"],"optional":["left.cap","left.image","left.labels:array","left.values:array","left.series:array","left.points:array","left.max","left.unit","left.colors:array","left.dataTable","left.head:array","left.rows:array","left.colW:array","right.type","right.cap","right.image","right.labels:array","right.values:array","right.series:array","right.points:array","right.max","right.unit","right.colors:array","right.dataTable","right.head:array","right.rows:array","right.colW:array"]},"comparison":{"label":"对比页(左右双栏 + 可选结论条)","modes":["presentation","research"],"required":["left.title","left.points:array","right.title","right.points:array"],"optional":["verdict"]},"quote":{"label":"引用 / 金句页(深色全幅)","modes":["presentation","research"],"required":["quote","author"],"optional":["context"]},"diagram":{"label":"分层架构页(architecture 模式走全幅几何)","modes":["presentation","research","architecture"],"required":["layers:array"],"optional":["legend:array"]},"exhibit":{"label":"Exhibit 编号图表页(research R2)","modes":["research"],"required":["chart.labels:array","chart.values:array"],"optional":["exhibitNo","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"twocol":{"label":"双栏论证页(research R1)","modes":["research"],"required":["paragraphs:array"]},"threecol":{"label":"三栏证据页(research R6)","modes":["research"],"required":["paragraphs:array"]},"halftable":{"label":"半表半图页(research R7)","modes":["research"],"required":["table.head:array","table.rows:array","chart.labels:array","chart.values:array"],"optional":["table.colW:array","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"matrix":{"label":"矩阵图页(research R8)","modes":["research"],"required":["rowHeads:array","colHeads:array","cells:array"]},"lane":{"label":"泳道页(architecture A2)","modes":["architecture"],"required":["lanes:array"]},"sankey":{"label":"桑基图页(节点-流带,流向与流量)","modes":["research","architecture"],"required":["flows:array"],"optional":["unit","chart.dataTable","note"]},"treemap":{"label":"树图页(面积编码的层级构成)","modes":["research","architecture"],"required":["items:array"],"optional":["unit","chart.dataTable","note"]},"boxplot":{"label":"箱线图页(分布对比:min/q1/median/q3/max)","modes":["research","architecture"],"required":["groups:array"],"optional":["unit","chart.dataTable","note"]},"network":{"label":"关系网络页(节点-边拓扑)","modes":["research","architecture"],"required":["nodes:array","edges:array"],"optional":["chart.dataTable","note"]},"marimekko":{"label":"马赛克图页(列宽 × 列高双重编码)","modes":["research","architecture"],"required":["cols:array","cells:array"],"optional":["unit","chart.dataTable","note"]},"streamgraph":{"label":"流带图页(时间上的构成演变)","modes":["research","architecture"],"required":["series:array"],"optional":["labels:array","chart.dataTable","note"]}}};
|
|
221
237
|
/* __TOPPPT_SCHEMA_END__ */
|
|
222
238
|
|
|
223
239
|
var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
|
|
@@ -508,6 +524,15 @@ function footnoteLine(sh, text, S) {
|
|
|
508
524
|
sh.push(txSp(R.x, R.y, R.w, R.h,
|
|
509
525
|
[[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
|
|
510
526
|
}
|
|
527
|
+
/* R2:sec.note 与 sec.footnote 共用注释带末行(footnoteY=6.72)。
|
|
528
|
+
note.y=6.55 会压进 so-what 带(6.05–6.67)——同页二者必须合并为一行。 */
|
|
529
|
+
function noteOrFootnote(sh, sec, S) {
|
|
530
|
+
var parts = [];
|
|
531
|
+
if (sec && sec.note) parts.push(String(sec.note));
|
|
532
|
+
if (sec && sec.footnote) parts.push(String(sec.footnote));
|
|
533
|
+
if (!parts.length) return;
|
|
534
|
+
footnoteLine(sh, parts.join(' · '), S);
|
|
535
|
+
}
|
|
511
536
|
/* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
|
|
512
537
|
function flagBar(sh, items, S, yTop) {
|
|
513
538
|
var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
|
|
@@ -696,7 +721,9 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
|
696
721
|
: [{ name: '数值', values: ec.values || [] }];
|
|
697
722
|
var nb = Math.max(1, labels.length);
|
|
698
723
|
var ns = Math.max(1, series.length);
|
|
699
|
-
|
|
724
|
+
/* R2:类目标签带必须含在 h 内(原 baseY+0.08 画在图区外,会压进 so-what/来源行) */
|
|
725
|
+
var labelH = 0.36;
|
|
726
|
+
var baseY = y + Math.max(0.6, h - labelH), plotW = w, step = plotW / nb;
|
|
700
727
|
var cluster = Math.min(1.1, step * (ns > 1 ? 0.78 : 0.55));
|
|
701
728
|
var bw = cluster / ns;
|
|
702
729
|
var all = [];
|
|
@@ -705,26 +732,27 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
|
705
732
|
});
|
|
706
733
|
var vmax = ec.max || Math.max.apply(null, all.concat([1]));
|
|
707
734
|
var top = all.length ? Math.max.apply(null, all) : 0;
|
|
735
|
+
var plotH = Math.max(0.35, baseY - y);
|
|
708
736
|
sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
|
|
709
737
|
series.forEach(function (se, si) {
|
|
710
738
|
var vals = se.values || [];
|
|
711
739
|
var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
|
|
712
740
|
vals.forEach(function (v, i) {
|
|
713
741
|
var num = Number(v) || 0;
|
|
714
|
-
var hh = Math.max(0.05, num / vmax * (
|
|
742
|
+
var hh = Math.max(0.05, num / vmax * (plotH - 0.35));
|
|
715
743
|
var bx = x + i * step + (step - cluster) / 2 + si * bw;
|
|
716
744
|
var hot = ns === 1 && num === top;
|
|
717
745
|
sh.push(shape('roundRect', bx, baseY - hh, Math.max(0.04, bw * 0.92), hh,
|
|
718
746
|
hot ? (dcols ? dcols[i % 5] : S.accent) : col, { adj: 6000 }));
|
|
719
747
|
if (ns === 1) {
|
|
720
|
-
sh.push(txSp(bx - 0.3, baseY - hh - 0.
|
|
748
|
+
sh.push(txSp(bx - 0.3, baseY - hh - 0.36, bw + 0.6, 0.30,
|
|
721
749
|
[[{ t: valText(num, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
|
|
722
750
|
}
|
|
723
751
|
});
|
|
724
752
|
});
|
|
725
753
|
labels.forEach(function (lb, i) {
|
|
726
754
|
var lx = x + i * step;
|
|
727
|
-
sh.push(txSp(lx, baseY + 0.
|
|
755
|
+
sh.push(txSp(lx, baseY + 0.06, step, labelH - 0.08,
|
|
728
756
|
[[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
729
757
|
});
|
|
730
758
|
/* Multi-series legend along top of plot (keeps category labels; avoids blank chart). */
|
|
@@ -747,7 +775,10 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
|
747
775
|
var all = [];
|
|
748
776
|
series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
|
|
749
777
|
var vmax = ec.max || Math.max.apply(null, all.concat([1]));
|
|
750
|
-
|
|
778
|
+
/* R2:类目标签带含在 h 内,禁画到图区外压 so-what */
|
|
779
|
+
var labelH = 0.36;
|
|
780
|
+
var baseY = y + Math.max(0.6, h - labelH);
|
|
781
|
+
var plotH = Math.max(0.35, baseY - y - 0.2);
|
|
751
782
|
sh.push(shape('rect', x, baseY, w, 0.02, S.line));
|
|
752
783
|
var stepX = n > 1 ? w / (n - 1) : 0;
|
|
753
784
|
series.forEach(function (se, si) {
|
|
@@ -767,7 +798,7 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
|
767
798
|
});
|
|
768
799
|
labels.forEach(function (lb, i) {
|
|
769
800
|
var px = x + (n > 1 ? i * stepX : w / 2);
|
|
770
|
-
sh.push(txSp(px - 0.5, baseY + 0.
|
|
801
|
+
sh.push(txSp(px - 0.5, baseY + 0.06, 1.0, labelH - 0.08,
|
|
771
802
|
[[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
772
803
|
});
|
|
773
804
|
}
|
|
@@ -1219,25 +1250,44 @@ function slidesOf(model, S) {
|
|
|
1219
1250
|
c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
|
|
1220
1251
|
slides.push(c);
|
|
1221
1252
|
|
|
1222
|
-
/* Agenda(>8
|
|
1253
|
+
/* Agenda(>8 条自动双列;>12 条自动分页;长标题截断+全称入 notes;列优先与 HTML 一致) */
|
|
1223
1254
|
var items = model.agenda || [];
|
|
1224
1255
|
if (items.length) {
|
|
1225
|
-
var
|
|
1226
|
-
|
|
1227
|
-
var
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
var
|
|
1234
|
-
var
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1256
|
+
var ROWS_PER_PAGE = 12;
|
|
1257
|
+
var agPages = [];
|
|
1258
|
+
for (var ai = 0; ai < items.length; ai += ROWS_PER_PAGE) agPages.push(items.slice(ai, ai + ROWS_PER_PAGE));
|
|
1259
|
+
agPages.forEach(function (pageItems, pi) {
|
|
1260
|
+
var ag = [];
|
|
1261
|
+
var sub = agPages.length > 1 ? '(' + (pi + 1) + '/' + agPages.length + ')' : '';
|
|
1262
|
+
head(ag, 'AGENDA', '报告大纲' + sub);
|
|
1263
|
+
var colN = pageItems.length > 8 ? 2 : 1;
|
|
1264
|
+
var perCol = Math.ceil(pageItems.length / colN) || 1;
|
|
1265
|
+
var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
|
|
1266
|
+
var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
|
|
1267
|
+
var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
|
|
1268
|
+
var agNotes = [];
|
|
1269
|
+
pageItems.forEach(function (it, i) {
|
|
1270
|
+
var col = Math.floor(i / perCol), row = i % perCol;
|
|
1271
|
+
var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
|
|
1272
|
+
var titleBoxW = CW / colN - 1.15;
|
|
1273
|
+
var rawTitle = String(it[1] || '');
|
|
1274
|
+
var titleEstW = estTextH(rawTitle, titleBoxW, tSz, 1.2);
|
|
1275
|
+
var maxTitleH = rowH * 0.55;
|
|
1276
|
+
var title = rawTitle;
|
|
1277
|
+
if (titleEstW > maxTitleH) {
|
|
1278
|
+
var maxChars = Math.max(10, Math.floor(rawTitle.length * (maxTitleH / titleEstW)));
|
|
1279
|
+
if (rawTitle.length > maxChars) {
|
|
1280
|
+
title = rawTitle.slice(0, maxChars - 1) + '…';
|
|
1281
|
+
agNotes.push('条目 ' + it[0] + ' 全称:' + rawTitle);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
ag.push(txSp(x, y, 0.9, Math.min(0.9, rowH), [[{ t: it[0], sz: numSz, b: 1, col: S.accent, font: S.font }]]));
|
|
1285
|
+
ag.push(txSp(x + 1.0, y + 0.03, titleBoxW, Math.min(0.95, rowH),
|
|
1286
|
+
[[{ t: title, sz: tSz, b: 1, col: S.ink, font: S.font }],
|
|
1287
|
+
[{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
|
|
1288
|
+
});
|
|
1289
|
+
slides.push(ag);
|
|
1239
1290
|
});
|
|
1240
|
-
slides.push(ag);
|
|
1241
1291
|
}
|
|
1242
1292
|
|
|
1243
1293
|
/* 章节页 */
|
|
@@ -1323,12 +1373,13 @@ function slidesOf(model, S) {
|
|
|
1323
1373
|
{ t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
|
|
1324
1374
|
});
|
|
1325
1375
|
});
|
|
1376
|
+
/* R2:verdict 与 soWhat 共用 annotation 槽位——只画一个(verdict 优先) */
|
|
1326
1377
|
if (sec.verdict) {
|
|
1327
1378
|
var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
|
|
1328
|
-
sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
|
|
1379
|
+
sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent, { name: 'tr:soWhat' }));
|
|
1329
1380
|
sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
|
|
1330
1381
|
[[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
|
|
1331
|
-
{ t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
|
|
1382
|
+
{ t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr', name: 'tr:soWhat' }));
|
|
1332
1383
|
}
|
|
1333
1384
|
} else if (type === 'quote') {
|
|
1334
1385
|
var Q = PT.quote;
|
|
@@ -1361,7 +1412,6 @@ function slidesOf(model, S) {
|
|
|
1361
1412
|
} else if (type === 'donut') {
|
|
1362
1413
|
var dcn = sec.chart || {};
|
|
1363
1414
|
if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme), bodyBottom);
|
|
1364
|
-
if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
|
|
1365
1415
|
} else if (type === 'heatmap') {
|
|
1366
1416
|
var H = PT.heatmap;
|
|
1367
1417
|
var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
|
|
@@ -1508,7 +1558,7 @@ function slidesOf(model, S) {
|
|
|
1508
1558
|
} else if (type === 'bar') {
|
|
1509
1559
|
var ch = sec.chart || {};
|
|
1510
1560
|
var dcols2 = (ch.colors || dataColors(model.style, model.theme));
|
|
1511
|
-
var botY = chartBottom(!!sec.soWhat, !!sec.footnote
|
|
1561
|
+
var botY = chartBottom(!!sec.soWhat, !!(sec.footnote || sec.note)) - 0.05;
|
|
1512
1562
|
var isHBar = ch.type === 'hbar';
|
|
1513
1563
|
var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
|
|
1514
1564
|
{ x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
|
|
@@ -1535,13 +1585,12 @@ function slidesOf(model, S) {
|
|
|
1535
1585
|
} else {
|
|
1536
1586
|
chartBlockShapes(sh, ch, S, bx, bRR.y, bw, Math.max(1.5, bRR.h), dcols2);
|
|
1537
1587
|
}
|
|
1538
|
-
if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
|
|
1539
1588
|
} else if (type === 'twocol' || type === 'threecol') {
|
|
1540
1589
|
var ps2 = sec.paragraphs || [];
|
|
1541
1590
|
var nCol = (type === 'threecol') ? 3 : 2;
|
|
1542
1591
|
var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
|
|
1543
|
-
|
|
1544
|
-
|
|
1592
|
+
/* R3:多栏均分用版心全宽(regOf('twocol') 返回单栏宽,当总宽会压扁三栏) */
|
|
1593
|
+
var tcR = { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
|
|
1545
1594
|
var gC = cols(tcR.w, nCol, gapC);
|
|
1546
1595
|
var perC = Math.ceil(ps2.length / nCol);
|
|
1547
1596
|
var availC = tcR.h;
|
|
@@ -1737,7 +1786,7 @@ function slidesOf(model, S) {
|
|
|
1737
1786
|
var dStart = dgR.y;
|
|
1738
1787
|
var dEnd = dgR.y + dgR.h;
|
|
1739
1788
|
var nLay = Math.max(1, lys.length);
|
|
1740
|
-
var lh2 = Math.max(0.
|
|
1789
|
+
var lh2 = Math.max(0.5, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
|
|
1741
1790
|
lys.forEach(function (lay, li) {
|
|
1742
1791
|
var y9 = dStart + li * (lh2 + D.layerGap);
|
|
1743
1792
|
var focus = lay[2] === 'focus';
|
|
@@ -1753,9 +1802,26 @@ function slidesOf(model, S) {
|
|
|
1753
1802
|
var ntt = isRec(nd) ? nd.t : nd;
|
|
1754
1803
|
var nds = isRec(nd) ? (nd.d || '') : '';
|
|
1755
1804
|
sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1805
|
+
/* R1: 注解不得「浮回」标题上。标题顶对齐,注解钳在标题下边;空间不足降级单框混排。 */
|
|
1806
|
+
var padXn = 0.12, padYn = 0.10;
|
|
1807
|
+
var innerWn = nw2 - padXn * 2;
|
|
1808
|
+
var titleHn = 0.32, noteHn = 0.34, gapTNn = 0.04;
|
|
1809
|
+
var needHn = titleHn + gapTNn + (nds ? noteHn : 0) + padYn * 2;
|
|
1810
|
+
if (!nds || lh2 >= needHn) {
|
|
1811
|
+
var tYn = y9 + padYn;
|
|
1812
|
+
sh.push(txSp(x10 + padXn, nds ? tYn : y9 + lh2 / 2 - 0.18, innerWn, titleHn,
|
|
1813
|
+
[[{ t: ntt, sz: nds ? 11 : 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]],
|
|
1814
|
+
{ anchor: 'ctr' }));
|
|
1815
|
+
if (nds) {
|
|
1816
|
+
var nYn = Math.max(tYn + titleHn + gapTNn, y9 + lh2 - padYn - noteHn);
|
|
1817
|
+
sh.push(txSp(x10 + padXn, nYn, innerWn, Math.min(noteHn, y9 + lh2 - padYn - nYn),
|
|
1818
|
+
[[{ t: nds, sz: 10, col: S.faint, font: S.font }]], { anchor: 't' }));
|
|
1819
|
+
}
|
|
1820
|
+
} else {
|
|
1821
|
+
sh.push(txSp(x10 + padXn, y9 + padYn * 0.5, innerWn, lh2 - padYn,
|
|
1822
|
+
[[{ t: ntt, sz: 11, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }],
|
|
1823
|
+
[{ t: nds, sz: 9, col: S.faint, font: S.font }]], { anchor: 't', lineSpacing: 12 }));
|
|
1824
|
+
}
|
|
1759
1825
|
});
|
|
1760
1826
|
if (li < lys.length - 1) {
|
|
1761
1827
|
var cy2 = y9 + lh2 + D.layerGap / 2;
|
|
@@ -1765,10 +1831,14 @@ function slidesOf(model, S) {
|
|
|
1765
1831
|
}
|
|
1766
1832
|
});
|
|
1767
1833
|
if (sec.legend) {
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1834
|
+
/* R2:图例进图区底部(与 HTML .arch__legend 同源),禁用 PH-0.62 固定偏移 */
|
|
1835
|
+
var lgH2 = 0.28;
|
|
1836
|
+
var lgY2 = Math.max(dStart, bodyBottom - lgH2 - 0.02);
|
|
1837
|
+
var nLg2 = Math.max(1, sec.legend.length);
|
|
1838
|
+
var step2 = Math.min(2.4, CW / nLg2);
|
|
1839
|
+
sec.legend.forEach(function (lg2, li2) {
|
|
1840
|
+
sh.push(txSp(MX + CW - nLg2 * step2 + li2 * step2, lgY2, step2 - 0.05, lgH2,
|
|
1841
|
+
[[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
|
|
1772
1842
|
});
|
|
1773
1843
|
}
|
|
1774
1844
|
} else if (type === 'exhibit') {
|
|
@@ -1825,8 +1895,10 @@ function slidesOf(model, S) {
|
|
|
1825
1895
|
}
|
|
1826
1896
|
}
|
|
1827
1897
|
/* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
|
|
1828
|
-
|
|
1829
|
-
if (sec.
|
|
1898
|
+
/* R2:verdict 已占 annotation 槽位时跳过 soWhat,禁止同槽双条叠印 */
|
|
1899
|
+
if (sec.soWhat && !sec.verdict) soWhatBar(sh, sec.soWhat, S);
|
|
1900
|
+
/* R2:note 与 footnote 共用注释带末行(禁 note.y=6.55 压 so-what) */
|
|
1901
|
+
noteOrFootnote(sh, sec, S);
|
|
1830
1902
|
if (flagH) flagBar(sh, flagItems, S, flagY);
|
|
1831
1903
|
slides.push(sh);
|
|
1832
1904
|
});
|
|
@@ -1996,9 +2068,10 @@ function validateModel(model) {
|
|
|
1996
2068
|
}
|
|
1997
2069
|
var agMax = (M.model && M.model.agendaComfortMax) || 16;
|
|
1998
2070
|
if ((model.agenda || []).length > agMax) {
|
|
1999
|
-
warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + '
|
|
2071
|
+
warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',将自动分页');
|
|
2000
2072
|
}
|
|
2001
|
-
var
|
|
2073
|
+
var agPageCount = hasAgenda ? Math.ceil(model.agenda.length / 12) : 0;
|
|
2074
|
+
var pages = 1 + agPageCount + secs.length + 1; /* 封面+大纲(可多页)+章节+收尾 */
|
|
2002
2075
|
return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
|
|
2003
2076
|
}
|
|
2004
2077
|
|
|
@@ -284,6 +284,15 @@ function regionOf(pageType, slotId, opts) {
|
|
|
284
284
|
return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
|
|
285
285
|
dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
|
|
286
286
|
}
|
|
287
|
+
/* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
|
|
288
|
+
if (type === 'kpi' && slot === 'metrics') {
|
|
289
|
+
var dxK = P.dividerX != null ? P.dividerX : 0.56;
|
|
290
|
+
return {
|
|
291
|
+
x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
|
|
292
|
+
w: CW * (1 - dxK) - 0.5,
|
|
293
|
+
h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
287
296
|
if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
|
|
288
297
|
var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
|
|
289
298
|
if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
|
|
@@ -316,15 +325,22 @@ function regionOf(pageType, slotId, opts) {
|
|
|
316
325
|
}
|
|
317
326
|
if (type === 'image' && slot === 'primary') {
|
|
318
327
|
var iy2 = Math.max(P.y || 2.3, opts.top || 0);
|
|
319
|
-
|
|
328
|
+
/* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
|
|
329
|
+
var iBottom = (opts.bottom != null) ? opts.bottom
|
|
330
|
+
: (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
|
|
331
|
+
return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
|
|
320
332
|
}
|
|
321
333
|
if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
|
|
322
334
|
var rs = PT.research || {};
|
|
323
|
-
|
|
335
|
+
var ht = opts.top != null ? opts.top : topDefault;
|
|
336
|
+
var hb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
337
|
+
return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
|
|
324
338
|
}
|
|
325
339
|
if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
|
|
326
340
|
var rs2 = PT.research || {};
|
|
327
|
-
|
|
341
|
+
var ht2 = opts.top != null ? opts.top : topDefault;
|
|
342
|
+
var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
343
|
+
return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
|
|
328
344
|
}
|
|
329
345
|
if (type === 'matrix' && slot === 'primary') {
|
|
330
346
|
var rm = PT.research || {};
|