前端JS-SDK能力说明
概述
观远BI JS-SDK 是观远BI面向客户、合作伙伴提供的开放能力之一,便于其他系统通过JS代码的方式内嵌观远BI相关页面或者与观远BI系统进行通信。
此文档面向网页开发者介绍观远JS-SDK如何使用以及相关的注意事项。
JS-SDK相关功能与早期提供的Web SDK能力有一定重叠,建议未来使用全新的JS-SDK能力。
示例代码库:https://github.com/GuandataOSS/sdk-demo
此功能自6.3版本开始生效。
JS-SDK使用步骤
1. 在需要调用JS接口的页面引入如下JS文件:[JS-SDK](https://guandata-libs.oss-cn-hangzhou.aliyuncs.com/js-sdk.js)-
按需参照技术说明文档使用JS-SDK中的能力,当前开放的能力如下:
- frame模式内嵌仪表板
- iframe模式内嵌卡片
- 联动内嵌仪表板筛选器(6.5.0之前不支持移动端联动)
- 联动内嵌卡片筛选器(6.5.0之前不支持移动端联动)
- 图表渲染
相关开放能力持续拓展中。 如有相关需求,欢迎通过观远数据技术支持、客户成功团队反馈。
内嵌参数说明
通用参数
| 属性 | 描述 | 是否必填 | 默认值 |
|---|---|---|---|
| host | 观远BI 访问地址 如:https://demo.guandata.com | 是 | - |
| domain | 域ID,默认值一般为 guanbi | 是 | - |
| ssoToken | 免密登录Token | 是 | 参照SSO集成说明 |
| theme | 主题 LIGHT: 朱鹭白 DARK: 月光蓝 | LIGHT (浅色主题) | |
| ps | 内嵌模式指 如:iframe | 参照此处说明 | |
| width | 宽度 | 100% | |
| height | 高度 | 300px | |
| filters | 筛选条件 | JSON.stringify 之后的筛选数组(目前仅支持PC端卡片) | |
| 自定义访问属性 | 使用 data- 开头 如:data-name -> name 如:data-gy-app-id -> gyAppId | - |
自定义访问参数会转化为URL中的QueryString传递给观远BI系统。安全性起见,SSOToken的生产方式应该在后端完成,具体实现方式参考此处说明。
场景化参数
卡片内嵌场景
| 属性 | 描述 | 是否必填 | 默认值 |
| cdId | 卡片ID | 是 | - |
仪表板内嵌场景
| 属性 | 描述 | 是否必填 | 默认值 |
| dashboardId | 仪表板ID | 是 | - |
筛选器参数说明
通用参数
| 属性 | 描述 | 是否必填 | 默认值 |
| name | 筛选器名称 | 是 | - |
| filterValue | [ '筛选值' ] | 是 | - |
| filterType | 筛选类型 | 是 |
筛选类型和筛选值
filterType表示筛选类型,其中的值有如下可选。filterValue表示筛选值,必须是一个字符串数组,它根据filterType的不同表示筛选条件也不同。
-
IN:表示选择筛选,filterValue可以是多个值,表示选择多个筛选值。
-
EQ: 表示相等,filterValue只能是一个值。
-
NE: 表示不相等,filterValue只能是一个值。
-
CONTAINS: 表示包含,filterValue只能是一个值。
-
NOT_CONTAINS: 表示不包含,filterValue只能是一个值。
-
STARTSWITH: 表示以...开始,filterValue只能是一个值。
-
ENDSWITH: 表示以...结束,filterValue只能是一个值。
-
NOT_STARTSWITH: 表示不以...开始,filterValue只能是一个值。
-
NOT_ENDSWITH: 表示不以...结束,filterValue只能是一个值。
-
BT: 表示区间,filterValue只能是两个值,表示区间范围。
-
GT: 表示大于,filterValue只能是一个值。
-
GE: 表示大于等于,filterValue只能是一个值。
-
LT: 表示小于,filterValue只能是一个值。
-
LE: 表示小于等于,filterValue只能是一个值。
-
IS_NULL: 表示为空,filterValue中没有值。
-
NOT_NULL: 表示不为空,filterValue中没有值。
传入的筛选参数中的 filterType 和 filterValue 需要与所要匹配的内部筛选器相匹配。 比如内部筛选器为选择筛选器,则传入的 filterType 必须是 IN。
场景化参数
卡片筛选器联动场景
| 属性 | 描述 | 是否必填 | 默认值 |
| fdId | 字段ID | 是 | - |
仪表板筛选器联动场景
| 属性 | 描述 | 是否必填 | 默认值 |
| cdId | 筛选器卡片id | 是 | - |
图表渲染(viz-sdk)说明
基础用法
<script src="path/to/js-sdk.x.y.z.js"></script>
// 创建 SDK 实例
const sdk = new GDVizSDK(document.getElementById('chart'), {
BIAddress: {
baseUrl: 'http://localhost:3001/',
publicPath: '' // 可选,路径前缀
},
});
// 设置事件回调
sdk.setEventCallback((event) => {
console.log('图表事件:', event.detail);
});
// 渲染图表 - 使用 message 格式
sdk.setMessage(messageData);
// 渲染图表 - 使用 report 中的可视化配置,即 GuanViz.json
sdk.setGuanViz(guanVizJson, id);
// 或者使用 option 格式
sdk.setOption(optionData);
配置选项
const config = {
BIAddress: {
baseUrl: 'http://your-bi-address.com', // BI 地址(必填)
publicPath: '', // 路径前缀(可选)在 BI 的 console 中查看 __GD_PUBLIC_PATH__ 来获取
staticHTML: false, // 不再依赖 BI 站点,而是依赖纯静态的 BI 前端资源(不推荐)
},
theme: 'LIGHT', // 主题:'LIGHT'(默认) 或 'DARK'
renderType: 'card' // 渲染类型:'card'(默认)或 'largeScreenCard'(大屏模式)
//注意: 大屏模式需配合 theme: DARK 一起使用
};
API 方法
实例方法
setMessage(message, chartType?)- 设置图表数据(Chat-BI 消息格式)setGuanViz(vizConfig, id)- 设置图表数据(洞察分析 GuanViz 格式)setOption(option)- 设置图表配置(通用选项格式)setEventCallback(callback)- 设置事件回调setTheme(theme)- 设置主题('LIGHT' 或 'DARK')destroy()- 销毁 SDK 实例
静态方法
GDVizSDK.isMessageHasViz(message)- 检查消息是否包含可视化数据GDVizSDK.getAvailableChartTypes(optionOrMessage)- 获取可用的图表类型GDVizSDK.convertNumberFormat(value, format)- 数字格式化工具GDVizSDK.version- 获取SDK版本号
事件处理
SDK 支持监听图表事件,目前支持的事件类型:
TableHeightChange- 表格高度变化事件Error- 图表渲染出错的事件
// 设置事件回调
sdk.setEventCallback((event) => {
const { name, ...data } = event.detail;
switch (name) {
case 'TableHeightChange':
// 表格高度发生变化时触发
console.log('表格高度变化:', data.tableHeight);
// 可以根据新高度调整容器大小
break;
default:
console.log('未知事件:', name, data);
}
});
使用示例
内嵌一个仪表板
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<gd-dashboard
host="https://demo.guandata.com"
ssoToken="**********"
domain="demo"
dashboardId="k5ec1f12793f649c3960ec2a"
ps="iframe2"
></gd-dashboard>
</body>
<script src="js-sdk.js"></script>
</html>
内嵌一个卡片
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<gd-card
host="https://demo.guandata.com"
ssoToken="**********"
domain="demo"
cdId="lf0c8ab616a394559b180529"
ps="embed"
></gd-card>
</body>
<script src="js-sdk.js"></script>
</html>
内嵌仪表板并联动筛选器
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<gd-dashboard
id="custom-dashboard"
host="https://demo.guandata.com"
ssoToken="**********"
domain="demo"
dashboardId="k5ec1f12793f649c3960ec2a"
ps="iframe2"
></gd-dashboard>
</body>
<script>
const $dashboard = document.getElementById('custom-dashboard');
function setDashboardFilter () {
$dashboard.setAttribute('filters', JSON.stringify([{
cdId: 'q9db09a2e5b8b44a4a46b271',
name: '测试',
filterType: 'GT',
filterValue: ['2020-03-01']
}]));
}
</script>
<script src="js-sdk.js"></script>
</html>
内嵌卡片并联动筛选器
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<gd-card
id="custom-card"
host="https://demo.guandata.com"
ssoToken="**********"
domain="demo"
cdId="lf0c8ab616a394559b180529"
ps="embed"
></gd-card>
</body>
<script>
const $card = document.getElementById('custom-card');
function setCardFilter () {
$card.setAttribute('filters', JSON.stringify([{
fdId: 'l72472c483a2742de81e9767',
name: '日期',
filterType: 'EQ',
filterValue: ['2020-01-01']
}]));
}
</script>
<script src="js-sdk.js"></script>
</html>
图表渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Viz SDK Example</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.example-section {
margin-bottom: 40px;
padding: 20px;
border: 1px solid #e8e8e8;
border-radius: 6px;
}
.example-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 15px;
}
.chart-container {
width: 100%;
height: 500px;
border-radius: 4px;
background: #fafafa;
margin-bottom: 15px;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
button {
padding: 8px 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #40a9ff;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.log-container {
background: #f6f8fa;
border: 1px solid #e1e4e8;
border-radius: 4px;
padding: 10px;
max-height: 200px;
overflow-y: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
color: #333;
}
.log-entry {
margin-bottom: 5px;
padding: 2px 0;
}
.log-entry.error {
color: #d73a49;
}
.log-entry.success {
color: #28a745;
}
.editor-container {
display: flex;
gap: 20px;
margin-bottom: 15px;
}
.editor-panel {
flex: 1;
}
.editor-label {
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.editor-textarea {
width: 100%;
height: 300px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.4;
resize: vertical;
background: #fafafa;
}
.editor-textarea:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* JSON 编辑器样式 */
.json-editor-container {
position: relative;
width: 100%;
height: 400px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fafafa;
}
.json-editor-container.focused {
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.json-editor-container.valid {
border-color: #52c41a;
background: #f6ffed;
}
.json-editor-container.invalid {
border-color: #ff4d4f;
background: #fff2f0;
}
.json-editor {
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 10px;
margin: 0;
border: none;
outline: none;
resize: none;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.4;
background: transparent;
color: transparent;
caret-color: #333;
z-index: 2;
}
.json-highlight {
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 10px;
margin: 0;
border: none;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.4;
background: transparent;
white-space: pre-wrap;
word-wrap: break-word;
overflow: auto;
z-index: 1;
pointer-events: none;
}
/* 自定义 highlight.js 样式 */
.json-highlight .hljs {
background: transparent;
padding: 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.4;
}
.editor-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
.format-btn {
background: #52c41a;
}
.format-btn:hover {
background: #73d13d;
}
.reset-btn {
background: #faad14;
}
.reset-btn:hover {
background: #ffc53d;
}
#templateSelect,
#exampleSelector,
#chartTypeSelector1,
#chartTypeSelector2 {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
}
#templateSelect:focus,
#exampleSelector:focus,
#chartTypeSelector1:focus,
#chartTypeSelector2:focus {
outline: none;
border-color: #1890ff;
}
/* 配置输入框样式 */
#baseUrlInput,
#publicPathInput,
#chart1Width,
#chart1Height,
#chart2Width,
#chart2Height {
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
#baseUrlInput:focus,
#publicPathInput:focus,
#chart1Width:focus,
#chart1Height:focus,
#chart2Width:focus,
#chart2Height:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
#updateConfig,
#updateChart1Size,
#updateChart2Size {
background: #52c41a;
transition: background-color 0.2s ease;
}
#updateConfig:hover,
#updateChart1Size:hover,
#updateChart2Size:hover {
background: #73d13d;
}
#updateChart1Size,
#updateChart2Size {
background: #1890ff;
}
#updateChart1Size:hover,
#updateChart2Size:hover {
background: #40a9ff;
}
#toggleTheme1,
#toggleTheme2 {
background: #722ed1;
}
#toggleTheme1:hover,
#toggleTheme2:hover {
background: #9254de;
}
#changeChartType1,
#changeChartType2 {
background: #fa8c16;
}
#changeChartType1:hover,
#changeChartType2:hover {
background: #ffa940;
}
/* Tooltip 样式 */
.tooltip-container {
position: relative;
display: inline-block;
cursor: help;
color: #666;
font-size: 14px;
margin-left: 5px;
}
.tooltip-content {
visibility: hidden;
width: 300px;
background-color: #333;
color: #fff;
text-align: left;
border-radius: 4px;
padding: 8px;
position: absolute;
z-index: 1000;
bottom: 125%;
left: 50%;
margin-left: -150px;
opacity: 0;
transition: opacity 0.3s;
font-size: 12px;
line-height: 1.4;
}
.tooltip-content::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #333 transparent transparent transparent;
}
.tooltip-container:hover .tooltip-content {
visibility: visible;
opacity: 1;
}
</style>
</head>
<body>
<div class="container">
<h1>Viz SDK 示例</h1>
<div class="example-section">
<div class="example-title">SDK 配置</div>
<div class="controls">
<div style="display: flex; gap: 20px; align-items: center; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 8px;">
<label for="baseUrlInput" style="font-weight: 500; color: #333;">Base URL:</label>
<input type="text" id="baseUrlInput" value="http://localhost:3001/"
style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; width: 200px; font-size: 14px;">
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<label for="publicPathInput" style="font-weight: 500; color: #333;">Public Path:</label>
<input type="text" id="publicPathInput" value=""
style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; width: 200px; font-size: 14px;">
<span class="tooltip-container">
❓
<div class="tooltip-content">
可以在 BI 的 console 中查看 __GD_PUBLIC_PATH__ 来获取
</div>
</span>
</div>
<button id="updateConfig" style="background: #52c41a;">更新配置</button>
</div>
</div>
</div>
<div class="example-section">
<div class="example-title">图表示例选择器</div>
<div class="controls">
<select id="exampleSelector">
<option value="">选择示例...</option>
</select>
<button id="loadSelectedExample">加载图表</button>
<button id="destroySDK">清除图表</button>
<button id="toggleTheme1" style="background: #722ed1;">切换主题</button>
</div>
<div class="controls" style="margin-top: 10px;">
<div style="display: flex; gap: 20px; align-items: center;">
<div style="display: flex; align-items: center; gap: 8px;">
<label for="chartTypeSelector1" style="font-weight: 500; color: #333;">图表类型:</label>
<select id="chartTypeSelector1"
style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; width: 200px; font-size: 14px;">
<option value="">请先加载图表...</option>
</select>
</div>
<button id="changeChartType1" style="background: #fa8c16;">切换类型</button>
</div>
</div>
<div class="controls" style="margin-top: 10px;">
<div style="display: flex; gap: 20px; align-items: center;">
<div style="display: flex; align-items: center; gap: 8px;">
<label for="chart1Width" style="font-weight: 500; color: #333;">宽度:</label>
<input type="number" id="chart1Width" value="100" min="200" max="2000"
style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; width: 80px; font-size: 14px;">
<span style="color: #666;">px</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<label for="chart1Height" style="font-weight: 500; color: #333;">高度:</label>
<input type="number" id="chart1Height" value="500" min="200" max="1000"
style="padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; width: 80px; font-size: 14px;">
<span style="color: #666;">px</span>
</div>
<button id="updateChart1Size" style="background: #1890ff;">更新尺寸</button>
</div>
</div>
<div id="chart1" class="chart-container"></div>
<div class="log-container">
<div id="log1"></div>
</div>
</div>
</div>
<script src="https://guandata-libs.oss-cn-hangzhou.aliyuncs.com/js-sdk.1.1.3.js"></script>
<!-- 引入示例数据和配置 -->
<script type="module">
const EXAMPLES = {
messages: {
'basic_column': {
name: '基础柱状图',
description: '使用 message 格式的基础柱状图示例',
data: {
"message_id": "e90bac12-7c0d-4efe-b93b-5e4d1ac885d6",
"card": {
"card_version": 2,
"card_meta": {
"chartType": "BASIC_COLUMN",
"chartName": "柱状图",
"chartMeta": {
"chartMain": {
"zoneData": {
"row": [
{
"fdType": "STRING",
"metaType": "DIM",
"name": "会员等级"
}
],
"column": [
{
"metaType": "MPH"
}
],
"tooltip": [],
"metric": [
{
"fdType": "DOUBLE",
"metaType": "METRIC",
"name": "平均交易金额",
"fieldFormat": {
"numberFormat": {
"formatType": "NUMBER",
"decimalPlaces": 2,
"useThousandsSeparator": true,
"suffix": "元",
"divideDataBy": 1
}
}
}
],
"metric_additional": []
},
"props": {
"chartLegend": {
"showLegend": true
},
"dataLabels": {
"metric": {
"showNumber": true
},
"metricAdditional": {
"showNumber": true
}
},
"grandTotal": {
"onRow": {
"position": "TOP"
}
}
}
}
}
}
},
"query_result": {
"columns": [
{
"name": "会员等级",
"fdType": "STRING",
"seqNo": 0
},
{
"name": "平均交易金额",
"fdType": "DOUBLE",
"seqNo": 1
}
],
"preview": [
[
"L1",
"120.57382293762551"
],
[
"L2",
"123.99325147347734"
],
[
"L3",
"122.96379170879656"
],
[
"L4",
"117.10799580272817"
],
[
"L5",
"121.52193768257028"
]
],
"truncated": false,
"sql": ""
},
}
},
},
options: {}
};
// 获取所有示例列表
function getAllExamples() {
const examples = [];
// 添加 messages 下的示例
Object.keys(EXAMPLES.messages).forEach(key => {
const example = EXAMPLES.messages[key];
examples.push({
id: key,
name: example.name,
description: example.description,
type: 'message',
data: example.data
});
});
// 添加 options 下的示例
Object.keys(EXAMPLES.options).forEach(key => {
const example = EXAMPLES.options[key];
examples.push({
id: key,
name: example.name,
description: example.description,
type: 'option',
data: example.data
});
});
return examples;
}
// 根据ID获取示例
function getExampleById(id) {
const allExamples = getAllExamples();
return allExamples.find(example => example.id === id);
}
// 主题枚举
const VizTheme = {
LIGHT: 'LIGHT',
DARK: 'DARK'
};
// 日志工具
function addLog(containerId, message, type = 'info') {
const logContainer = document.getElementById(containerId);
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${type}`;
logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function extractPathPrefix(location, pathSuffix) {
if (!location || !location.pathname) {
return '';
}
const pathname = location.pathname;
if (!pathSuffix) {
const lastSlashIndex = pathname.lastIndexOf('/');
return lastSlashIndex > 0 ? pathname.substring(0, lastSlashIndex) : '';
}
const normalizedSuffix = pathSuffix.startsWith('/') ? pathSuffix : '/' + pathSuffix;
// 检查是否以完整后缀结尾
if (pathname.endsWith(normalizedSuffix)) {
return pathname.substring(0, pathname.length - normalizedSuffix.length);
}
// 检查是否包含指定的目录段
const suffixIndex = pathname.indexOf(normalizedSuffix);
if (suffixIndex !== -1) {
return pathname.substring(0, suffixIndex);
}
// fallback: 返回去掉文件名的目录路径
const lastSlashIndex = pathname.lastIndexOf('/');
return lastSlashIndex > 0 ? pathname.substring(0, lastSlashIndex) : '';
}
// 默认 SDK 配置
let config = {
BIAddress: {
baseUrl: 'https://demo.guandata.com',
publicPath: '',
},
theme: VizTheme.LIGHT
};
addLog('log1', JSON.stringify(location, null, 2), 'info')
addLog('log1', `使用配置: ${JSON.stringify(config)}`, 'info');
// 主题状态跟踪
let currentTheme = VizTheme.LIGHT;
// 配置更新功能
function updateSDKConfig() {
const baseUrl = document.getElementById('baseUrlInput').value.trim();
const publicPath = document.getElementById('publicPathInput').value.trim();
if (!baseUrl) {
addLog('log1', 'Base URL 不能为空', 'error');
return;
}
// 更新配置
config = {
BIAddress: {
baseUrl: baseUrl,
...(publicPath && { publicPath: publicPath })
}
};
addLog('log1', `配置已更新: ${JSON.stringify(config)}`, 'success');
// 如果已有 SDK 实例,需要重新创建
if (sdk1) {
sdk1.destroy();
sdk1 = null;
addLog('log1', '已销毁旧的 SDK 实例,请重新加载图表', 'info');
}
}
// 绑定配置更新按钮事件
document.getElementById('updateConfig').addEventListener('click', updateSDKConfig);
// 图表尺寸更新功能
function updateChartSize(chartId, widthInputId, heightInputId, sdk, logId) {
const width = document.getElementById(widthInputId).value;
const height = document.getElementById(heightInputId).value;
const chartContainer = document.getElementById(chartId);
if (width && height) {
// 更新容器样式
chartContainer.style.width = width + 'px';
chartContainer.style.height = height + 'px';
// 如果 SDK 实例存在,触发 resize 事件
if (sdk) {
// 等待 DOM 更新后再触发 resize
setTimeout(() => {
// 触发窗口 resize 事件,让图表自适应新尺寸
window.dispatchEvent(new Event('resize'));
addLog(logId, `图表尺寸已更新: ${width}px × ${height}px`, 'success');
}, 100);
} else {
addLog(logId, `容器尺寸已更新: ${width}px × ${height}px (请加载图表后生效)`, 'info');
}
} else {
addLog(logId, '请输入有效的宽度和高度值', 'error');
}
}
// 绑定图表1尺寸更新按钮事件
document.getElementById('updateChart1Size').addEventListener('click', () => {
updateChartSize('chart1', 'chart1Width', 'chart1Height', sdk1, 'log1');
});
// 示例1:图表示例选择器
let sdk1 = null;
let currentData1 = null; // 存储当前图表数据
let currentDataType1 = null; // 存储当前数据类型
const exampleSelector = document.getElementById('exampleSelector');
const chartTypeSelector1 = document.getElementById('chartTypeSelector1');
// 初始化示例选择器
function initializeExampleSelector() {
const examples = getAllExamples();
// 清空现有选项
exampleSelector.innerHTML = '<option value="">选择示例...</option>';
// 添加示例选项
examples.forEach(example => {
const option = document.createElement('option');
option.value = example.id;
option.textContent = `${example.name} (${example.type})`;
option.title = example.description;
exampleSelector.appendChild(option);
});
addLog('log1', `已加载 ${examples.length} 个示例`, 'info');
}
// 监听示例选择变化
exampleSelector.addEventListener('change', (e) => {
const selectedId = e.target.value;
if (selectedId) {
const example = getExampleById(selectedId);
if (example) {
addLog('log1', `已选择: ${example.name} - ${example.description}`, 'info');
}
}
});
// 加载选中的示例
document.getElementById('loadSelectedExample').addEventListener('click', () => {
try {
const selectedId = exampleSelector.value;
if (!selectedId) {
addLog('log1', '请先选择一个示例', 'error');
return;
}
const example = getExampleById(selectedId);
if (!example) {
addLog('log1', `未找到示例: ${selectedId}`, 'error');
return;
}
const chartContainer = document.getElementById('chart1');
if (!sdk1) {
sdk1 = new GDVizSDK(chartContainer, config);
window.sdk1 = sdk1;
addLog('log1', `SDK1 已创建,使用主题: ${currentTheme}`, 'info');
}
// 设置事件回调
sdk1.setEventCallback((event) => {
addLog('log1', `图表事件: ${JSON.stringify(event.detail)}`, 'info');
});
// 根据示例类型加载数据
if (example.type === 'message') {
sdk1.setMessage(example.data);
currentData1 = example.data;
currentDataType1 = 'message';
} else if (example.type === 'option') {
sdk1.setOption(example.data);
currentData1 = example.data;
currentDataType1 = 'option';
}
// 更新图表类型选择器
const currentType = currentDataType1 === 'message' ?
example.data.card.card_meta.chartType :
example.data.chartType;
const availableTypes = updateChartTypeSelector(chartTypeSelector1, currentData1, currentType);
addLog('log1', `${example.name} 加载成功`, 'success');
addLog('log1', `可用图表类型: ${availableTypes.map(t => chartTypeDisplayNames[t] || t).join(', ')}`, 'info');
} catch (error) {
addLog('log1', `加载失败: ${error.message}`, 'error');
console.error('加载示例失败:', error);
}
});
document.getElementById('destroySDK').addEventListener('click', () => {
if (sdk1) {
sdk1.destroy();
sdk1 = null;
currentData1 = null;
currentDataType1 = null;
// 重置图表类型选择器
chartTypeSelector1.innerHTML = '<option value="">请先加载图表...</option>';
addLog('log1', 'SDK 已销毁', 'info');
}
});
// 切换主题功能
function toggleTheme() {
currentTheme = currentTheme === VizTheme.LIGHT ? VizTheme.DARK : VizTheme.LIGHT;
// 更新配置
config.theme = currentTheme;
// 更新现有的SDK实例
if (sdk1) {
sdk1.setTheme(currentTheme);
addLog('log1', `已切换到 ${currentTheme} 主题`, 'success');
}
// 更新按钮文本
const themeText = currentTheme === VizTheme.LIGHT ? '浅色' : '深色';
document.getElementById('toggleTheme1').textContent = `切换主题 (当前: ${themeText})`;
addLog('log1', `全局主题已切换为: ${currentTheme}`, 'info');
}
// 绑定主题切换按钮事件
document.getElementById('toggleTheme1').addEventListener('click', toggleTheme);
// 绑定图表类型切换按钮事件
document.getElementById('changeChartType1').addEventListener('click', () => {
const selectedType = chartTypeSelector1.value;
if (!selectedType) {
addLog('log1', '请选择要切换的图表类型', 'error');
return;
}
const newData = changeChartType(sdk1, currentData1, currentDataType1, selectedType, 'log1');
if (newData) {
currentData1 = newData;
// 更新选择器以反映新的当前类型
updateChartTypeSelector(chartTypeSelector1, currentData1, selectedType);
}
});
// 图表类型中文名称映射
const chartTypeDisplayNames = {
'BASIC_COLUMN': '单柱图',
'GROUPED_COLUMN': '簇状图',
'STACKED_COLUMN': '堆积图',
'STACKED_SPLIT_COLUMN': '分组堆积图',
'PERCENT_STACKED_COLUMN': '百分比堆积图',
'GROUPED_COLUMN_WITH_LINE': '簇状 + 折线图',
'STACKED_COLUMN_WITH_LINE': '堆积 + 折线图',
'GROUPED_COLUMN_WITH_SYMBOL': '簇状 + 符号图',
'STACKED_COLUMN_WITH_SYMBOL': '堆积 + 符号图',
'BASIC_BAR': '单条图',
'BASIC_LINE': '单线图',
'MULTI_LINE': '多线图',
'PIE': '饼图',
'BASIC_BUBBLE': '气泡图',
'PROGRESS_BAR': '进度条',
'KPI_CARD': '对比指标卡',
'SINGLE_VALUE': '指标卡',
'PIVOT_TABLE': '表格'
};
// 更新图表类型选择器
function updateChartTypeSelector(selector, currentData, currentType) {
// 清空现有选项
selector.innerHTML = '';
if (!currentData) {
selector.innerHTML = '<option value="">请先加载图表...</option>';
return;
}
try {
// 获取可用的图表类型
const availableTypes = GDVizSDK.getAvailableChartTypes(currentData);
if (availableTypes.length === 0) {
selector.innerHTML = '<option value="">暂无可用图表类型</option>';
return;
}
// 添加选项
availableTypes.forEach(type => {
const option = document.createElement('option');
option.value = type;
option.textContent = chartTypeDisplayNames[type] || type;
if (type === currentType) {
option.selected = true;
}
selector.appendChild(option);
});
return availableTypes;
} catch (error) {
console.error('获取可用图表类型失败:', error);
selector.innerHTML = '<option value="">获取图表类型失败</option>';
return [];
}
}
// 切换图表类型
function changeChartType(sdk, currentData, currentDataType, newChartType, logId) {
if (!sdk || !currentData || !newChartType) {
addLog(logId, '请先加载图表并选择新的图表类型', 'error');
return null;
}
try {
let resultData = currentData;
// 使用新接口进行图表类型切换
if (currentDataType === 'message') {
// 使用新的 setMessage 接口,传入 chartType 参数
sdk.setMessage(currentData, newChartType);
} else if (currentDataType === 'option') {
// 对于 option 数据,仍需要修改数据结构
let newData = JSON.parse(JSON.stringify(currentData));
newData.chartType = newChartType;
sdk.setOption(newData);
resultData = newData;
}
addLog(logId, `图表类型已切换为: ${chartTypeDisplayNames[newChartType] || newChartType}`, 'success');
return resultData;
} catch (error) {
addLog(logId, `切换图表类型失败: ${error.message}`, 'error');
console.error('切换图表类型失败:', error);
return null;
}
}
// 初始化配置输入框
function initializeConfigInputs() {
const baseUrlInput = document.getElementById('baseUrlInput');
const publicPathInput = document.getElementById('publicPathInput');
// 使用当前配置初始化输入框
baseUrlInput.value = config.BIAddress.baseUrl;
publicPathInput.value = config.BIAddress.publicPath || '';
addLog('log1', '配置输入框已初始化', 'info');
}
// 初始化尺寸输入框
function initializeSizeInputs() {
// 初始化图表1的尺寸输入框
document.getElementById('chart1Width').value = 960;
document.getElementById('chart1Height').value = 500;
addLog('log1', '尺寸输入框已初始化', 'info');
}
// 初始化图表类型选择器
function initializeChartTypeSelectors() {
chartTypeSelector1.innerHTML = '<option value="">请先加载图表...</option>';
addLog('log1', '图表类型选择器已初始化', 'info');
}
// 页面加载完成后的初始化
window.addEventListener('load', () => {
addLog('log1', 'Viz SDK Example 已加载', 'success');
// 初始化配置输入框
initializeConfigInputs();
// 初始化尺寸输入框
initializeSizeInputs();
// 初始化图表类型选择器
initializeChartTypeSelectors();
// 初始化主题按钮文本
const themeText = currentTheme === VizTheme.LIGHT ? '浅色' : '深色';
document.getElementById('toggleTheme1').textContent = `切换主题 (当前: ${themeText})`;
initializeExampleSelector();
});
// 全局错误处理
window.addEventListener('error', (event) => {
addLog('log1', `全局错误: ${event.error?.message || event.message}`, 'error');
addLog('log2', `全局错误: ${event.error?.message || event.message}`, 'error');
});
</script>
</body>
</html>