需要在高级中加入“dv”:1,即可开启datav模式
+参考视屏 + + 1. DataV基础应用 + + 2. DataV翻盘器
+DataV配置方法文档: + + 图表
+diff --git a/docs/smartchart/404.html b/docs/smartchart/404.html new file mode 100644 index 0000000..4542223 --- /dev/null +++ b/docs/smartchart/404.html @@ -0,0 +1,54 @@ + + + +
+ + + + + + + + + +需要在高级中加入“dv”:1,即可开启datav模式
+参考视屏 + + 1. DataV基础应用 + + 2. DataV翻盘器
+DataV配置方法文档: + + 图表
+如下图, base.html 改为 basevue.html
+将自动开启加载vue和elementui
+
+
+注意vue的变量引用在 模板编辑界面中, 写法变更为 {[ ]}
+ ElementUI组件说明 + + 视屏参考
+新增一个数据集(点击金色的新增按钮, 这样会新增一个可拖拽的数据集) +修改相应的数据集及图形 +数据集端
+select H1, H2, qty, rate from smartdemo2 limit 100
+
图形端
+let dataset = __dataset__;
+let tableData = ds_createMap_all(dataset);
+
+vapp.d0={
+ tableData: tableData
+}
+
模板Body区端
+<div class="smtdrag" id="id_1639824145817">
+ <template>
+ <el-table
+ stripe
+ border
+ height="100%"
+ :data="d0.tableData"
+ style="width: 100%">
+ <el-table-column label="hero">
+ <el-table-column
+ prop="H1"
+ label="H1"
+ fixed
+ :default-sort = "{prop: 'H2', order: 'descending'}"
+ width="180">
+ </el-table-column>
+ <el-table-column
+ prop="H2"
+ label="H2"
+ sortable
+ width="180">
+ </el-table-column>
+ </el-table-column>
+
+ <el-table-column
+ sortable
+ prop="qty"
+ label="qty">
+ </el-table-column>
+ <el-table-column
+ prop="rate"
+ label="rate">
+ </el-table-column>
+ </el-table>
+ </template>
+ </div>
+
//显示变量message
+<p>{[ message ]}</p>
+//循环产生li,变量sites
+<ol>
+<li v-for="site in sites">
+ {[ site.name ]}
+</li>
+</ol>
+//绑定输入值变量use
+<input type="checkbox" v-model="use">
+//显示控制
+<p v-if="seen">现在你看到我了</p>
+<p v-show="seen">现在你看到我了</p>
+//绑定属性
+<a :href="url"></a>
+<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
+<div :class="[errorClass ,isActive ? activeClass : '']"></div>
+//绑定点击方法
+<a @click="doSomething"></a>
+
需要在高级中加入“dv”:2,即可开启vue模式
+
+
+你可以在图形编辑器中给vue的变量赋值,我们内置了17个变量,从d0, d1… d16
+赋值方式 vapp.d0 = xxxx
+
+
你可以将d0赋值为字典, 如: +vapp.d0 = { ‘index1’: 100, ‘index2’: 300}
+++注意,我们修改了vue的默认引用方式, 你需要采用如下方法引用: +{[d0.index1]}
+
你也可以在 模板开发中 使用VUE +开启方法, 首先你需要在高级设定中 , 设定 “template”:“diy”, +然后你可以看到 模板 的菜单, 进入编辑器
+如果你需要更多自定义的方法, 例如加入方法, 你可以在模板的script中加入自定义代码
+<script>
+ var vapp = new Vue({el: '#vue_app', delimiters: ['{[', ']}'],
+ data: {
+ tableData:''
+ },
+ methods: {
+ formatter(row, column) {
+ return row.address;
+ }
+ }
+ });
+</script>
+
点击smartchart图标,切换到菜单固定模式, 你可看到主题的选择
+
+
+
注意: 复制出来的仪表盘, 数据集是与原仪表盘公用的!!
+为项目能持续维护,并保持稳定的模式,按照社区投票的意见, 开始区分免费版本和专业版本 +目前免费版本无需激活, 你可以使用到常用的功能,我们也会保持持续的更新 +为保持项目的健康发展,如需进行商用,您需提供使用方并知会作者进行授权
+++免费版使用者必须保留SmartChart相关版权标识及LOGO,禁止对其进行修改和删除 +如果违反,将保留对侵权者追究责任的权利
+
您在初次安装Smartchart后会自动免费激活20天的专业版本, 之后专业版本功能会限制使用 +请务必仔细阅读免费版本与专业版本的区别, 避免带来的不便 +后续如果您还需要继续体验专业版本, + 点击查看激活试用方式, +如果您是企业用途,建意使用专业版,获取更快的开发效率,可视化效果, 可靠性的保障及极速的查询体验
+++ +针对个人独立开发者,你可以采用廉价的专业版仪表盘激活方案, 可满足小项目的可视化要求 +如果需要永久激活专业版,可关注公众号与客服联系,或扫码联系微信客服了解, 非诚勿扰
+
微信客服不提供技术咨询, 如有使用方法的疑惑,建意加QQ群:476715246 进行沟通
+功能 | +免费版 | +专业版 | +中台版 | +
---|---|---|---|
栅格布局 | +V | +V | +V | +
DATAV | +V | +V | +V | +
拖拽布局 | ++ | V | +V | +
自由开发 | ++ | V | +V | +
切换图表主题 | ++ | V | +V | +
主题自由设计 | ++ | V | +V | +
引入JS | ++ | V | +V | +
引入CSS | ++ | V | +V | +
上传静态资源 | ++ | V | +V | +
使用VUE | +V | +V | +V | +
数据集开发 | +V | +V | +V | +
所有数据源 | +V | +V | +V | +
图形开发 | +V | +V | +V | +
图形商店 | +V | +V | +V | +
普通模板应用 | +V | +V | +V | +
专业模板应用 | ++ | V | +V | +
复制仪表盘 | +V | +V | +V | +
钻取 | +V | +V | +V | +
联动 | +V | +V | +V | +
筛选 | +V | +V | +V | +
单点登录 | +V | +V | +V | +
嵌入认证 | ++ | V | +V | +
LDAP认证 | ++ | V | +V | +
Juypter | +V | +V | +V | +
快捷存档 | ++ | V | +V | +
数据加速 | ++ | V | +V | +
数据API服务 | ++ | V | +V | +
后台API刷新 | ++ | V | +V | +
仪表盘同步 | ++ | V | +V | +
仪表盘版本管理 | ++ | V | +V | +
数据填报 | ++ | V | +V | +
报表Portal | ++ | V | +V | +
多级项目菜单 | ++ | + | V | +
商业授权 | ++ | V | +V | +
优先咨询 | ++ | V | +V | +
+ 专业边框背景 | ++ | V | +V | +
3D场景 | ++ | V | +V | +
中国式报表 | ++ | V | +V | +
生产部署文档 | ++ | V | +V | +
个性化修改 | ++ | V | +V | +
授权书 | ++ | V | +V | +
低代码ETL | ++ | + | V | +
调度平台 | ++ | + | V | +
智慧BI | ++ | + | V | +
数据资产 | ++ | + | V | +
数据血缘 | ++ | + | V | +
租户管理 | ++ | + | V | +
在开发模式下,点击“开发管理”->数据源->新增
+
配置连接池参数,注意数据库填写是备注中有写的名称
+
你可以通过新建一个数据集来测试连接池的连通性
+点击“保存” 后,回到数据集列表 点击如下图标"E",进入数据集开发界面
+
在开发界面调试
+
你可以在“参数”中设定安全控制,可避免用户误操作导致前后端卡死
+
+
+limit: 可限定最大返回数据数量(但实际数据库查询无limit,需通过mode控制)
+mode: 控制用户查询行为,默认为模式1
模式 | +说明 | +
---|---|
0 | +严格模式,每次查询向数据库都会增加limit,MPP类型数据库可能会排序失效 | +
1 | +开发模式,仅调试查询数据库都会增加limit,调试时MPP类型数据库可能会排序失效,但不影响实际 | +
2 | +宽松模式,查询都不带limit, 仅控制返回limit,需开发者避免大查询 | +
数据库 | +驱动填写 | +需安装 | +使用说明 | +
---|---|---|---|
Mysql | +mysql | +默认支持 | ++ |
Mysql连接池 | +mysqlpool | ++ | + |
Sqlite | +sqlite | +默认支持 | +连接地址填写绝对路径 | +
API | +任意 | +默认支持 | +参考数据集说明文档 | +
EXCEL | +任意 | +默认支持 | +参考数据集说明文档 | +
SQL Server | +mssql | +需安装 pip install pymssql | ++ |
SQL Server连接池 | +mssqlpool | ++ | + |
ORACLE | +oracle | +pip install cx_Oracle | ++ |
ORACLE连接池 | +oraclepool | ++ | + |
PostgreSql | +gp | +pip install psycopg2 | ++ |
GP | +gp | +pip install psycopg2 | ++ |
Impala | +impala | +pip install impyla | ++ |
Hive | +hive | +pip install impyla | ++ |
DB2 | +db2 | +pip install ibm_db | ++ |
达梦 | +dm | +pip install dmPython | ++ |
Python | +python | +pip install pandas, openpyxl | +参考数据集->特殊数据源 | +
Redis | +redis | +pip install redis | +参考数据集->特殊数据源 | +
Mongodb | +mongodb | +pip install pymongo | +参考数据集->特殊数据源 | +
Clickhouse | +clickhouse | +pip install clickhouse_driver | ++ |
Elasticsearch | +es | +pip install elasticsearch==7.13.0 | +参考数据集->特殊数据源 | +
Sqlalchemy | +sqlalchemy | +pip install sqlalchemy | +参考数据集->特殊数据源 | +
JDBC | +jdbc | +pip install JayDeBeApi | +参考数据集->特殊数据源 | +
自定义 | +自定义 | +用户自由定义 | +参考数据集->特殊数据源 | +
你可以快速应用开发好的模板,极大的提高你的开发和学习效率
+你可以快速应用本地备份的模板, 我们内置了一个 通用的数据查询和下载模板 , 你可以尝试
+新建一个全新的dashboard, 不要放任何报表, 点击保存且编辑后, 下方可以看到下载链接, 然后点击下载即可直接应用
+
输入"01_SMARTCHART", 点击本地恢复即可
+
方法同上"应用本地模板", 注意应用商店模板为收费增值服务
+ +++如果仪表盘中有数据集且不再需要, 可以在下载密钥前面加上FORCE即可自动清空已有数据集后自动下载 +注意模板太廉价,购买后并没有咨询服务,请务必自行了解如何使用
+
++资源文件放置路径 +有些资源会离线打包提供给你, 只需上传即可, + 上传方法参考
+
++如果不清楚可以观看视屏说明 +使用方法可参考视屏: + 一键应用模板
+
开发前你可以先观看操作方面的 +:exclamation: + 操作视屏教程 :exclamation: + 5.0变更操作视屏教程 +:exclamation: + 开发系列合集,关注作者持续更新
+由于版本的变更, 一些图标可能会有一些变化, 但位置无太大的变化
+环境准备: 官方 + 最新Python下载链接,可以到 + 淘宝镜象下载,也可以下载 + WINDOWS64位安装版, + MAC电脑安装版
+
+ Window平台安装视屏介绍,注意: Windows安装Python时需选中"Add to Path"
+
Linux安装可参考文档下方的部署说明, + Linux安装说明
+在Shell或CMD命令行执行
+ pip3 install smartchart
+
+ 如果安装过程慢,建意使用
+ pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple smartchart -U
+
+ 升级方法:
+ pip3 install smartchart -U (升级)
+
本地命令行启动:
+ smartchart
+ 如果你是服务器部署,远程访问,服务端启动方式:
+ smartchart runserver 0.0.0.0:8000 --insecure --noreload
+ 如果出现套接字,端口被占用, 可修改端口号启动, 如
+ smartchart runserver 0.0.0.0:8001 --insecure --noreload
+
一般本地启动后访问: http://127.0.0.1:8000 +管理员帐号密码: admin/admin, 请及时更改密码
+如果忘记密码, 可以使用此命令重置
+smartchart changepassword 用户名
+
++注意: 不切换是正常的用户报表浏览模式!! + +
++仅管理员或开发人员能看到DEV菜单,用户只会有报表菜单页面
目前的权限管理,大概如下:
+你点击DEV后, 才会出进入后台的图标, 在后台中你可以控制用户的开发权限
+
+
+
+新建用户默认是没有开发权限的, 在首页也看不到任何开发相关的菜单
如果你需要给用户开发权限, 需要设定如下:
+
+
+
+
你可以在 仪表盘设定 中进行权限管理
+
+
在编辑入口只会显示 有按人员分配编辑权限的报表清单, 在查看入口中会显示已启用且上线且(已分配查看权限或编辑权限或公开)的报表 +所以你可以么这么搭配: +对于通用报表可以所有人访问的, 但你又不想让他在清单中显示, 你可以将他设为公开但不上线 +对于开发中的报表, 你可以设为未上线
+由于版本的变更, 一些图标可能会有一些变化, 但位置无太大的变化
+select H1 as heroname, sum(qty) as 出场数 from smartdemo2
+group by H1
+order by sum(qty) desc
+
+
点开“图形编辑”
+
+
点击“云图标”,第一次使用可能要你进行登记,按提示操作即可, 在商店中找到合适的图形点击,会自动复制到剪贴板
+
+
贴粘到图形编辑器后,点击保存, 关闭图形编辑框
+
+
启动显示 以一种访问权限不允许的方式做了一个访问套接字的尝试 +出现这种情况在Windows中很常见,就是端口被占用,酷狗音乐会占用8000端口 +使用netstat -ano|findstr 8000 找到进程号 +使用taskkill /pid 进程号 /F
+输入命令找不到smartchart +检查你是否有安装多个python环境出现环境变量冲突,请卸载一个或取消一个环境变量
+如法安装pip +请确认在安装python时,有没有加入环境变量, 可自行加入, 或卸载重装
+关于mac版本安装后的各种问题 +目前来看最大的可能是,/Library/Developer/CommandLineTools这个目录下有python3,应该是在某一个版本的Xcode command line tools安装时生成的 +可以先把python3全部卸载,再重新按说明安装,命令行中输入python3 和 pip3, 找不到command时才说明完全卸载成功
+sudo rm -rf /Library/Developer/CommandLineTools
+sudo rm -f /usr/bin/python3
+
如果密码忘记了怎么办 +命令行输入smartchart changepassword 你的用户名
+试用专业版激活码需要每3天激活一次,激活方式:
+
+由于开发很忙, 文档可能会写得有不尽之处, 多多包涵
SQLite3版本错误 +在部分操作系统下(比如CentOS 7)使用SQLite3数据库运行会出现如下的错误提示:
+django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).
+
这表明操作系统自带的sqlite3版本过低,需要将系统的sqlite3进行升级。
+以下是一种方法,来自于 StackOverlow:
+1、下载新版本的SQLite3
+wget https://www.sqlite.org/2019/sqlite-autoconf-3290000.tar.gz
+
2、解压文件
+tar zxvf sqlite-autoconf-3290000.tar.gz
+
3、进行解压后的目录
+cd sqlite-autoconf-3290000
+
4、配置安装目录
+./configure --prefix=$HOME/opt/sqlite
+
5、编译安装
+make && make install
+
6、指定环境变量
+export PATH=$HOME/opt/sqlite/bin:$PATH
+export LD_LIBRARY_PATH=$HOME/opt/sqlite/lib
+export LD_RUN_PATH=$HOME/opt/sqlite/lib
+
完成之后可以运行sqlite3 –version 命令来查看当前的SQLite3版本。
+* Centos 7
+* Python 3.9
+/data/smartchart/ 项目主目录
+/data/smartchart/tools 项目相关软件
+下述内容中,凡是涉及到/data/smartchart路径的,都可以将其修改为你自己系统上的路径。
+
cd /data/smartchart/tools
+yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel
+下载https://npm.taobao.org/mirrors/python/3.9.0/
+上传服务器,放入安装目录解压 或者
+Wget https://npm.taobao.org/mirrors/python/3.9.0/Python-3.9.0.tgz
+tar -zxvf Python-3.9.0.tgz
+
+进行源码目录
+配置安装路径
+./Python-3.9.0/configure --prefix=/data/smartchart/tools/python3
+编译安装
+make && make install
+建立软链接
+ln -s /data/smartchart/tools/python3/bin/python3.9 /usr/bin/python3
+ln -s /data/smartchart/tools/python3/bin/pip3.9 /usr/bin/pip3
+测试是否安装成功
+python3 --version
+
python3 -m venv myvenv
+cd myvenv
+source bin/activate
+
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple smartchart -U
+
smartchart runserver 0.0.0.0:8000 --insecure --noreload
+
购买专业版本,提供企业生产部署及无网离线部署方案
+有一些场景, 比如已有一些固定的筛选器,或是需要测试用,或者Demo,或者其它图形需要用到一些共用的已确定好的数据 +这样我们可以不需要通过查询数据库的方式, 而直接写入数据集, 支持数组和字典的格式 +你只需要在数据集中起始写入 dataset= , 这样就是默认是固定数据
+如何快速的输入固定数据集, 你可以通过直接从EXCEL复制到数据集编辑器(以下图片非目前编辑器, 供参考),保存以后会自动生成:
+
+
+
+
固定数据集也支持之前提到传参数, 魔术方法, 缓存等所有数据集的功能
+
+
smartchart默认是不自带文件上传功能
+但是smartchart是可以自已创造上传页面, 在模板商店中你可以找到相关模板进行购买
+然后通过模板下载的方式下载后进行操作
+这样每一个页面是可以单独使用权限控制的,就和控制报表权限一样,你还可能按需随意定制页面
+
+
默认的上传主目录是在项目的log的文件夹下面, +你可以在setting.py(自定义django) 或 config.ini中设定UPLOAD_PATH来修改你的上传目录 +比如你上传页面的报表ID是23, 那么文件将会被上传到UPLOAD_PATH/23/你的文件名
+你需要使用 + python连接器, 来操作你的上传的数据, 内置了变量ds_path为你的上传目录, 所以可以更方便的读取上传的文件,如上文件 +df = pd.read_excel(ds_path+’/23/文件名')
+SmartChart标准数据集你可以想象为一个EXCEL的二维表, 有行和列 +你直接在数据集开发界面填写SQL即可
+比如你的原始数据库中表的格式如下, 表名tb_name:
+城市 户型 数量
+长沙 A 12
+长沙 A 23
+上海 B 19
+
+查询的sql: select 城市,户型,sum(数量) AS 数量 from tb_name group by 城市,户型
+正常的查询的结果为:
+[['城市','户型','数量'],
+ ['长沙','A',35],
+ ['上海','B ',19]]
+由于生成的数据格式第二行是 [字符,字符,数值], 后台会自动进行转列动作,
+生成图表更容易使用格式:
+[['Categroy','A','B'],
+ ['长沙', 35, 0],
+ ['上海', 0, 19]]
+
再比如我们有一个表的数据格式, 指标是展开的:
+城市 A B
+长沙 10 12
+上海 11 19
+长沙 9 10
+
+我们可以写的sql是:
+select 城市, sum(A) as A, sum(B) as B from tb_name group by 城市
+这样得到的结果是:
+[['城市','A','B'],
+ ['长沙', 19, 22],
+ ['上海', 11, 19]]
+和我们的标准格式也是一样的
+
有时你一个数据集可能只用一个SQL查询还不够,比如你需要一个清单数据,同时你需要一个汇总数据做为说明在图形中显示,这样你就需要使用多条SQL语句,在数据集中的写法你只需要用分号隔开,如:
+select ... from xxx;
+select ..... from xxxxxxx
+
+传递到图形中的格式为:
+{"df0":[[...]]. "df1":[[......]]}
+df0, df1分别对应的是第一段和第二段查询
+
数据库 | +驱动填写 | +需安装 | +使用说明 | +
---|---|---|---|
Mysql | +mysql | +默认支持 | ++ |
Mysql连接池 | +mysqlpool | ++ | + |
Sqlite | +sqlite | +默认支持 | +连接地址填写绝对路径 | +
API | +任意 | +默认支持 | +参考数据集说明文档 | +
EXCEL | +任意 | +默认支持 | +参考数据集说明文档 | +
SQL Server | +mssql | +需安装 pip install pymssql | ++ |
SQL Server连接池 | +mssqlpool | ++ | + |
ORACLE | +oracle | +pip install cx_Oracle | ++ |
ORACLE连接池 | +oraclepool | ++ | + |
PostgreSql | +gp | +pip install psycopg2 | ++ |
GP | +gp | +pip install psycopg2 | ++ |
Impala | +impala | +pip install impyla | ++ |
Hive | +hive | +pip install impyla | ++ |
DB2 | +db2 | +pip install ibm_db | ++ |
达梦 | +dm | +pip install dmPython | ++ |
Python | +python | +pip install pandas, openpyxl | +参考数据集->特殊数据源 | +
Redis | +redis | +pip install redis | +参考数据集->特殊数据源 | +
Mongodb | +mongodb | +pip install pymongo | +参考数据集->特殊数据源 | +
Clickhouse | +clickhouse | +pip install clickhouse_driver | ++ |
Elasticsearch | +es | +pip install elasticsearch==7.13.0 | +参考数据集->特殊数据源 | +
Sqlalchemy | +sqlalchemy | +pip install sqlalchemy | +参考数据集->特殊数据源 | +
JDBC | +jdbc | +pip install JayDeBeApi | +参考数据集->特殊数据源 | +
自定义 | +自定义 | +用户自由定义 | +参考数据集->特殊数据源 | +
在数据开发界面点击按钮就可以切换成共享数据集/普通数据集
+
+
切换完成, 你会发现数据集消失,然后图标跑到菜单上面去了, 你可以在此修改你的查询
+
+
在”高级“ 中配置
+
+
这样1,2号图形都映射到了共享数据集的数据
+
+
你也可以在数据集中写多个SQL查询
+
+
在”高级“ 设定中进行数据映射
+
+
观察1,2 号图形的变化
+
+
开发前建意先观看视屏, 了解基础说明, 视屏有点老和现在界面不一样, +目前很多功能已经做成可视化配置, 理解过程即可, 具体以文档为准
+
+
类似于前后端开发, 后端会提供一些接口给前端, 但前端不一定需要在一打开页面就进行查询接口, 而是当有需要时再查询, +比如数据下载, 只有当用户有下载需求时再加载, +再比如有些与后台的数据联动, 我们只需要第一次加载时只显示第一层级, 点击时再加载其它层级
+你可以点击数据集的开发界面的"连接" 图标, 将"前端刷新(秒)" 修改为-1 +这时当打开仪表盘时, 此数据集不会被加载
+一般懒加载数据集主要是用来做数据查询的, 所以并不太需要显示图形, 所以我们主要是用于在图形中进行赋值操作
+比如先在模板中定义一个全局变量:
+
+
+然后修改图形编辑中的代码:
+
+
+即刷新数据集时会进行变量赋值
+如果您使用VUE, 那么会更方便, 你可以直接使用vapp.变量名 = dataset进行赋值
你可以随时手动触发数据集的刷新, 比如懒加载的数据集序号为0 +你可以在你需要触发刷新的地方调用ds_refresh(0)即可刷新0号数据集并执行0号数据集中的JS代码
+++TIPS +可以将懒加载的数据集同时转化为共享数据集(参考上文), 懒加载数据集将移到菜单栏显示
+
smartchart提供非常精细的数据刷新功能,及内存加速功能
+你可以在数据集开发界面,点击连接的图标,进行设定
+
+
你可以设定前端页面数据集向后端请求刷新的时间间隔,单位秒
+++如果你发现定时刷新,数据并没有变化,可能原因是您数据的缓存时间设定大于定时刷新的时间
+
smartchart专业版提供内存加速技术,对数据库仅需请求一次,之后都是毫秒级响应
+请参考文档 + 后台主动触发刷新
+对接外部API取数, 注意返回一定要是JSON格式 +你只需要在数据集编辑框中如下输入
+-- GET 方法:
+dataset= {
+"url":"https://www.smartchart.cn/smartdata/api/?i=loaddataset1&j=1"
+}
+
+-- POST 方法:
+dataset= {
+"url":"https://www.smartchart.cn/smartdata/api",
+"method":"POST",
+"data":{"i":"loaddataset1", "j":"1"}
+ ...
+}
+
例如你可以传入参数做出联动效果
+dataset= {
+"url":"https://www.smartchart.cn/smartdata/api",
+"method":"POST",
+"data":{"i":"loaddataset1", "j":"/*$参数名*/"}
+...
+}
+
你也可以增加header等认证方式
+dataset= {
+"url":"https://www.smartchart.cn/smartdata/api",
+"method":"GET",
+"headers":{"Cookie":"xxxxxxx"}
+...
+}
+
+
+
模糊查询
+body = {
+ 'query': { # 查询命令
+ 'match': { # 查询方法:模糊查询(会被分词)。比如此代码,会查到只包含:“我爱你”, “中国”的内容
+ 'name': '刘'
+ }
+ },
+ 'size': 20 # 不指定默认是10,最大值不超过10000(可以修改,但是同时会增加数据库压力)
+}
+
+term,精准单值查询
+# 注:此方法只能查询一个字段,且只能指定一个值。类似于mysql中的where ziduan='a'
+body ={
+ 'query':{
+ 'term':{
+ 'ziduan1.keyword': '刘婵' # 查询内容等于“我爱你中国的”的数据。查询中文,在字段后面需要加上.keyword
+ # 'ziduan2': 'liuchan'
+ }
+ }
+}
+erms,精准多值查询
+#此方法只能查询一个字段,但可以同时指定多个值。类似于mysql中的where ziduan in (a, b,c...)
+body ={
+ "query":{
+ "terms":{
+ "ziduan1.keyword": ["刘婵", "赵云"] # 查询ziduan1="刘婵"或=赵云...的数据
+ }
+ }
+}
+multi_match,多字段查询
+# 查询多个字段中都包含指定内容的数据
+body = {
+ "query":{
+ "multi_match":{
+ "query":"我爱你中国", # 指定查询内容,注意:会被分词
+ "fields":["ziduan1", "ziduan2"] # 指定字段
+ }
+ }
+}
+
+prefix,前缀查询
+body = {
+ 'query': {
+ 'prefix': {
+ 'ziduan.keyword': '我爱你' # 查询前缀是指定字符串的数据
+ }
+ }
+}
+
+# 注:英文不需要加keyword
+
+wildcard,通配符查询
+body = {
+ 'query': {
+ 'wildcard': {
+ 'ziduan1.keyword': '?刘婵*' # ?代表一个字符,*代表0个或多个字符
+ }
+ }
+}
+# 注:此方法只能查询单一格式的(都是英文字符串,或者都是汉语字符串)。两者混合不能查询出来。
+
+regexp,正则匹配
+body = {
+ 'query': {
+ 'regexp': {
+ 'ziduan1': 'W[0-9].+' # 使用正则表达式查询
+ }
+ }
+}
+bool,多条件查询
+# must:[] 各条件之间是and的关系
+body = {
+ "query":{
+ "bool":{
+ 'must': [{"term":{'ziduan1.keyword': '我爱你中国'}},
+ {'terms': {'ziduan2': ['I love', 'China']}}]
+ }
+ }
+ }
+
+# should: [] 各条件之间是or的关系
+body = {
+ "query":{
+ "bool":{
+ 'should': [{"term":{'ziduan1.keyword': '我爱你中国'}},
+ {'terms': {'ziduan2': ['I love', 'China']}}]
+ }
+ }
+ }
+
+# must_not:[]各条件都不满足
+body = {
+ "query":{
+ "bool":{
+ 'must_not': [{"term":{'ziduan1.keyword': '我爱你中国'}},
+ {'terms': {'ziduan2': ['I love', 'China']}}]
+ }
+ }
+ }
+
+
+
+# bool嵌套bool
+# ziduan1、ziduan2条件必须满足的前提下,ziduan3、ziduan4满足一个即可
+body = {
+ "query":{
+ "bool":{
+ "must":[{"term":{"ziduan1":"China"}}, # 多个条件并列 ,注意:must后面是[{}, {}],[]里面的每个条件外面有个{}
+ {"term":{"ziduan2.keyword": '我爱你中国'}},
+ {'bool': {
+ 'should': [
+ {'term': {'ziduan3': 'Love'}},
+ {'term': {'ziduan4': 'Like'}}
+ ]
+ }}
+ ]
+ }
+ }
+}
+
当SQL查询无法满足你的需求, 你需要对查询后的结果进行处理, 或者你需要使用Excel的数据源, 甚至你需要对不同系统的数据进行查询, Python连接器可以帮到你 +我们又称他为万能数据集, 你可以使用任何python语法, +需要把数据集的结果赋值给ds变量!!
+首先你需要新建python连接器, 由于安全控制只允许超级管理员建立
+
+
# 内置函数说明
+ds_get(id) #输入目标数据集的id名, 可以获取目标数据集
+ds_df(id) #输入目标数据集的id名, 转化成pandas的df对象
+ds_sql(conn_name, sql_str) #输入连接池中的名称, SQL语句, 获取数据集
+ds_list(df) #将pandas的df对象转化成数据集
+
# 读取Excel数据处理, 如需上传页面可参考"数据上传"说明
+import pandas as pd
+df = pd.read_excel('/Users/../smartdemo.xlsx', 'demo')
+df = df.groupby('c3').agg({'qty':'sum'}).reset_index()
+ds = ds_list(df)
+
+#从数据集获取数据
+ds=ds_get(12)
+ds=ds[:15]
+
+#从数据集获取数据转化成pandas对象处理
+df = ds_df(12)
+df = df.sort_values(by="出场数", ascending=False)
+ds = ds_list(df)
+
+#可以生成字典格式的数据集供多个图形使用
+import pandas as pd
+df = pd.read_excel('/Users/../smartdemo.xlsx', 'demo')
+df1 = df.groupby('c3').agg({'qty':'sum'}).reset_index()
+df2 = df.groupby(['province','c3']).agg({'qty':'sum'}).reset_index()
+ds = {'df1': ds_list(df1), 'df2': ds_list(df2)}
+
+#可以直接执行SQL
+sql_str = '''select H1 as heroname, sum(qty) as 出场数 from T
+/* where H2 = '$H2' */
+group by H1 order by sum(qty) desc'''
+ds = ds_sql('XXX', sql_str)
+ds = ds[:10]
+
你可能会有这样的一些需求, 展示数据是要通过外部的程序计算好,如一些实时的计算场景,用spark计算好的数据 或爬虫爬取的数据, 然后写入redis或nosql的数据库,最后由前端图形直接展示或数据下载,SmartChart支持这一块的应用
+你可以创建一个redis的连接池, 然后按照通用的方法建立数据集 +不同的是, 数据集的SQL区不再是写sql代码, 而只需要写redis中的keyname
+如redis中存储的数据是keyname 为 “指标A”, 数据 ‘{“长沙”:1,“上海”:2}’ +这样我们只需要在数据集中写上
+指标A
+
即可, +最后你会得到{“长沙”:1,“上海”:2}的返回结果
+如果你需要的是表格格式, 那么你只需要往redis中存入一个二维数组, 比如: +[[“省份”,“数量”],[“长沙”,1],[“上海”,2]]
+++注意数据存入redis为字符串格式,你可使用python的json.dumps来生成字符串格式存入
+
比如还有一个"指标B", 数据是'12345' +我们可以同时写上两个指标,用分号隔开:
+指标A;指标B
+
最后你会得到的结果是: +{ +“指标A”:{“长沙”:1,“上海”:2}, +“指标B”:12345 +}
+用于获取kafka指定分区的最后一条记录, 用于实时场景 +使用方法参考"自定义数据源" +以下为参考代码:
+def dataset(*args, **kwargs):
+ """
+ 返回查询数据集
+ :return: 二维数组或JSON字典
+ """
+ from kafka import KafkaConsumer, TopicPartition
+ import json
+
+ sqlList = args[0] # 数据集编辑界面的输入已按分号拆分成数组 [sql1, sql2...]
+ config = args[1] # 相关的配置字典{'host','port','user','password','db'}
+ # 插入你的数据查询及处理代码, 生成result即可
+ result = {}
+ consumer = KafkaConsumer(sasl_mechanism='PLAIN',
+ security_protocol='SASL_PLAINTEXT',
+ sasl_plain_username=config['user'],
+ sasl_plain_password=config['password'],
+ bootstrap_servers=config['host'],
+ auto_offset_reset='earliest',
+ api_version=(1, 0, 0),
+ consumer_timeout_ms=50,
+ value_deserializer=lambda v: json.loads(v.decode('utf-8')),
+ )
+ topic = sqlList[0]
+ partition = int(config['db'])
+ tp = TopicPartition(topic=topic, partition=partition)
+ consumer.assign([tp])
+ end_offsets = consumer.end_offsets([tp]).get(tp) # 获取当前消费者最大偏移量
+ consumer.seek(tp, offset=end_offsets-1)
+ for message in consumer:
+ result = message.value
+ break
+ return result
+
+def insert_dataset(*args, **kwargs):
+ """
+ 数据填报实现
+ """
+ from kafka import KafkaProducer
+ import json
+
+ contents = args[0] # 传入的数据集二维数组格式
+ table = args[1] # 配置中的表名
+ config = args[3] # 相关的配置字典{'host','port','user','password','db'}
+ # 插入你的写入数据逻辑代码
+ producer = KafkaProducer(sasl_mechanism='PLAIN',
+ security_protocol='SASL_PLAINTEXT',
+ sasl_plain_username=config['user'],
+ sasl_plain_password=config['password'],
+ bootstrap_servers=config['host'],
+ value_serializer=lambda v: json.dumps(v).encode('utf-8')
+ )
+ producer.send(table, value=contents, partition=int(config['db']))
+
连接池正常配置即可
+数据集开发中,填写查询需求:
+
+
{"db": "db1", "table": "tb1", "filter": {"name": "Zarten"},
+"projection": {"_id": 0}, "sort": [["_id", 1]], "limit": 10}
+
由于返回的字典格式, 如需转化成二维数组, 可使图形中的转化函数ds_mapToList
+let dataset=ds_mapToList(__dataset__);
+
除table,其它都为可选参数
+参数 | +说明 | +样列 | +
---|---|---|
db | +数库名,默认连接设定中db名 | ++ |
table | +表名[必填] | ++ |
filter | +筛选项,具体用法参考下文 | +{“name”: “Zarten”,“date”:“2020-10-01”} | +
projection | +显示列 | +{“name”: 1,“date”:1} | +
sort | +排序,-1为降序 | +[[“date”, -1]] | +
limit | +限定返回数量 | ++ |
且条件
+{"age":{"$gt":22}, "name":{"$regex":"user"}}
+
或条件
+{ "$or": [ {"age": {"$gt": 22}}, {"name": {"$regex": "user"}} ] }
+
比较查询
+$lt和<,$lte和<=,$gt和>,gte和>=,ne和!=是一一对应的
+{"field_name": {"$lt": value, "$gt": value}}
+关联查询$in和$nin
+{"field_name": {"$in": [1,5,8]}}
+$regex为模糊查询的字符串提供正则表达式功能
+{"$or": [{"field_name": {'$regex': value}},{"field_name2": {"$regex": value}}]}
+
常规的连接池的设定, 大家应该都很清楚了,
+Smartchart也支持sqlalchemy连接, 对于一些smartchart不支持的数据源可以使用此方法
+配置方法:
+
+
+只用填以上内容, 其它可留空
+连接地址的写法参考sqlalchemy说明:
可选参数。一个标准的链接URL是这样的:
+dialect+driver://username:password@host:port/database
+dialect,是数据库类型,大概包括:sqlite, mysql, postgresql, oracle, or mssql.
+driver,是使用的数据库API,驱动,连接包,随便叫什么吧。
+username,用户名
+password,密码
+host,网络地址,可以用ip,域名,计算机名,当然是你能访问到的。
+port,数据库端口。
+databas,数据库名。
+其实这些也就dialect和dirver需要解释。
+
+二:连接sqlite3
+1,驱动
+sqlite3是个文件数据库,不需要什么驱动,或者说python内置了驱动。
+2,标准连接参数
+# sqlite://<nohostname>/<path>
+没有hostname
+3,各种链接参数
+# 相对路径,就是这个python文件同目录下foo.db
+engine = create_engine('sqlite:///foo.db')
+#绝对路径
+#Unix/Mac下用四条////表示
+engine = create_engine('sqlite:////absolute/path/to/foo.db')
+#Windows下用三条///加盘符路径用两条\\
+engine = create_engine('sqlite:///C:\\path\\to\\foo.db')
+#Windows 也可以这么用三条///加盘符路径用一条\
+engine = create_engine(r'sqlite:///C:\path\to\foo.db')
+#数据库建在内存里。URI保持为空即可
+engine = create_engine('sqlite://')
+
+三:连接mysql(mariadb)
+sqlalchemy默认使用mysql-python作为链接驱动,既default模式
+选哪种驱动,就装哪个包。
+1,default默认链接方式
+engine = create_engine('mysql://scott:tiger@localhost/foo')
+2,# mysql-python,声明使用mysql-python驱动
+engine = create_engine('mysql+mysqldb://scott:tiger@localhost/foo')
+3,MySQL-connector-python 声明使用MySQL-connector-python驱动(推荐使用)
+engine = create_engine('mysql+mysqlconnector://scott:tiger@localhost/foo')
+4,OurSQL 声明使用OurSQL驱动
+engine = create_engine('mysql+oursql://scott:tiger@localhost/foo')
+
+四:连接Microsoft SQL Server
+sqlalchemy默认使用 pyodbc作为链接驱动。
+1,pyodbc
+engine = create_engine('mssql+pyodbc://scott:tiger@mydsn')
+2,pymssql
+engine = create_engine('mssql+pymssql://scott:tiger@hostname:port/dbname')
+
+
+五:连接PostgreSQL
+PostgreSQL默认使用 psycopg2作为链接驱动,既default模式
+1, default
+engine = create_engine('postgresql://scott:tiger@localhost/mydatabase')
+2,psycopg2
+engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/mydatabase')
+3, pg8000
+engine = create_engine('postgresql+pg8000://scott:tiger@localhost/mydatabase')
+
+ 六:连接Oracle
+Oracle可能只有 cx_oracle一个驱动包,既default模式和声明模式一样。
+1,default
+engine = create_engine('oracle://scott:tiger@127.0.0.1:1521/sidname')
+2,cx_oracle
+engine = create_engine('oracle+cx_oracle://scott:tiger@tnsname')
+
smartchart已实现大部分常用的数据源连接, 对于其它的, 您也可以使用python数据源进行处理 +但是使用python数据源有一定的缺陷, 需要在dataset上写python代码, 不能复用 +对于一些带连接信息的还需要重复写入 +所以你还可以使用自定义数据源
+def dataset(*args, **kwargs):
+ """
+ 返回查询数据集
+ :return: 二维数组或JSON字典
+ """
+ sqlList = args[0] # 数据集编辑界面的输入已按分号拆分成数组 [sql1, sql2...]
+ config = args[1] # 相关的配置字典{'host','port','user','password','db'}
+ # 插入你的数据查询及处理代码, 生成result即可
+ result = [[]]
+
+ return result
+
+def insert_dataset(*args, **kwargs):
+ """
+ 数据填表实现
+ """
+ contents = args[0] # 传入的数据集二维数组格式
+ table = args[1] # 配置中的表名
+ config = args[3] # 相关的配置字典{'host','port','user','password','db'}
+ # 插入你的写入数据逻辑代码
+
在任意的仪表盘开发界面中 “模板” –> 点击上传图标, 将这个python文件上传即可
+
+
新建数据源, 驱动填写之前上传过的文件名, 比如我们这个是diy_conn, 其它参数按照你自定义的需求填写, 会自动传入你的自定义函数
+
+
之后你可以正常使用这个自定义数据源了
+如果你对python不熟悉, 也可按需定制, 你只需上传即可使用
+Smartchart提供了很多通用的图形,你可以在商店中直接使用 +如果要个性化需要你进行自定义, 比如你可能需要在同一个图上展示柱形图和线性图
+开发前建意先观看视屏, 了解基础说明, 视屏有点老和现在界面不一样, +目前很多功能已经做成可视化配置, 理解过程即可, 具体以文档为准
+首先我们在ECHART官网可能找一个你喜欢的图形, 如下简单柱形图链接:
+
+
+
打开我们可以看对应的option:
+option = {
+ xAxis: {
+ type: 'category',
+ data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+ },
+ yAxis: {
+ type: 'value'
+ },
+ series: [{
+ data: [120, 200, 150, 80, 70, 110, 130],
+ type: 'bar'
+ }]
+};
+
复制到Smartchart图形编辑器, 点击"刀叉“ 图标(目前是魔法梆), 会自动进行初步转化
+
+
接下来我们就进行下改造, 请注意对比, 你只需照着复制即可
+let dataset = __dataset__ //传入dataset
+let legend_label = ds_rowname(dataset) //可选, 自动获取legend
+let xlabel = dataset[0].splice(1) //x轴的标签列
+dataset = ds_createMap(dataset) //转化成KV格式
+
+//初始化series
+var series=[];
+series.push({
+ data: dataset[legend_label[0]], //对应的第一个图列
+ type: 'bar'
+ });
+series.push({
+ data: dataset[legend_label[1]], //对应的第二个图列
+ type: 'line'
+ });
+
+option__name__ = {
+ xAxis: {
+ type: 'category',
+ data: xlabel //X轴的标签
+ },
+ yAxis: {
+ type: 'value'
+ },
+ series:series,
+};
+charts.push(myChart__name__);
+
这样一个柱形+线性图就出来了
+
+
+
当然一个图形还有很多其它的元素, 比如标题, legend, 等等 更多option的配置项, 可以点击”!“号图标查看,你可以直接参考echarts的设定, 完全一样!!
+以下我们做了些简单的修改
+option__name__ = {
+ title: {
+ text: '自定义图示例',
+ left: 'center'
+ }, //定义标题的显示
+ tooltip: {
+ trigger: 'item',
+ formatter: '{a} <br/>{b} : {c}' //鼠标移动提示的格式
+ },
+ legend: {
+ left: 'left',
+ data: legend_label
+ }, //定义图例的显示
+ xAxis: {
+ type: 'category',
+ data: xlabel
+ }, //定义X轴的显示
+ yAxis: {
+ type: 'value'
+ },
+ //图例定义
+ series:series,
+};
+
//关于自动化series, 可以参考以下代码
+var series =[];
+for (var i=1;i<dataset[0].length;i++){
+ series.push({type: 'bar'})
+}
+
是不是非常简单 Smartchart让你使用echarts没有门槛
+++TIPS:
++
+- 如果你在图形编辑器中可以显示图形, 但是保存后在dashboard中无法, 首先检查下所有的mychart, option是否都有转化成带__name__, 如果都有,可能原因是你的代码中有mychart.setoption, 这样你可以在代码下方加上myChart__name__.setOption(option__name__);即可
+- 如果你在开发界面的仪表盘能看到图形显示,但预览仪表盘时,不显示图形,一般都是因为你图形代码中js结束需加分号的地方没有添加导致的
+
需要开启模板开发模式, 并开启basevue模板
+
+
<!--表格-->
+ <div class="smtdrag" id="id_1654907858638">
+ <el-table
+ :data="tableData.slice((currentPage-1)*pageSize, currentPage*pageSize)"
+ height="100%"
+ size="mini"
+ header-cell-class-name="tablehead"
+ border
+ style="width: 100%">
+ <el-table-column v-for="item in tableHead" :label="item.label" :property="item.prop" sortable>
+ </el-table-column>
+ </el-table>
+ <!--表格结束-->
+ <!--分页控件-->
+ <el-pagination align='center'
+ @size-change="handlerSizeChange"
+ @current-change="handlerCurrentChange"
+ :current-page="currentPage"
+ :page-size="pageSize"
+ layout="total,sizes,prev,pager,next,jumper"
+ :total="tableData.length"
+ ></el-pagination>
+ <!--分页控件结束-->
+ </div>
+
var vapp = new Vue({el: '#vue_app', delimiters: ['{[', ']}'],
+ data: {
+ tableData:[], //表数据
+ tableHead:[], //表头
+ currentPage:1,
+ total:20,
+ pageSize:10
+
+ },
+ methods: {
+ //处理分页数量
+ handlerSizeChange(val){
+ this.currentPage = 1;
+ this.pageSize=val;
+ },
+ //处理页选择
+ handlerCurrentChange(val){
+ this.currentPage = val;
+ }
+ }
+
+ });
+
select * from smartdemo2
+limit /* $limit -- */ 100
+
let df0 = __dataset__;
+//处理表头
+let columnsDict = {'c1':'渠道','qty':'数量'};
+let tableHead = [];
+let tableHeadLabel;
+for (let i=0;i<df0[0].length;i++){
+ if(columnsDict.hasOwnProperty(df0[0][i])){
+ df0[0][i] = columnsDict[df0[0][i]]
+ }
+ tableHeadLabel=df0[0][i];
+ tableHead.push({label: tableHeadLabel, prop:df0[0][i]});
+}
+
+//VUE赋值
+vapp.tableHead = tableHead;
+vapp.tableData=ds_createMap_all(df0);
+
+
++smartchart内置了这个查询模板, 你可以通过 + 本地模板恢复快速应用
+
++如果你还不熟悉html, 建意先花几分钟看下文档, 推荐 + HTML基础 +实际应用中有不熟悉的组件, 你都可以通过baidu搜索到, 如时间选择器 + +
+
比如我们要实现一个有多选项和按钮的网页元素
+
+
从各大搜索平台上我们可以找到html的代码是:
+<label><input type="checkbox">孙尚香</label>
+.....
+<button id='id_select0'>提交</button>
+
那么我们可以直接在图形编辑器写上
+let dataset=__dataset__;
+let table = '';
+table = `<label><input type="checkbox">孙一香</label>
+ <label><input type="checkbox">孙二香</label>
+ <label><input type="checkbox">孙三香</label>`
+table = table + "<button id='id_select0'>提交</button>"
+
+dom__name__.innerHTML=table;
+
但是由于我们是要通过传入的数据动态变化的,所以只需要做简单修改
+let dataset=__dataset__;
+let table = '';
+for (let i=1;i<dataset.length;i++){
+ table = `${table}<label><input type="checkbox"/>${dataset[i][0]}</label> `
+}
+table = table + "<button id='id_select__name__'>提交</button>"
+
+dom__name__.innerHTML=table;
+
所有html你都可以进行转化成smartchart组件, +你可以通过学习”万能表格系列视屏“ 来了解通用组件开发 + + 第一波 + + 第二波 + + 第三波
+在做自定义html组件的时候你可能需要用得上:
+一、向上遍历
+$("span").parent().css({
+ "color":"red",
+ "border":"1px solid red"
+})
+
$("span").parents().css({
+ "color":"red",
+ "border":"1px solid red"
+})
+
$("span").parentsUntil("div").css({ //向上查找直到遇见div元素为止
+ "color":"red",
+ "border":"1px solid red"
+})
+
二、向下遍历
+1. children() 查找子元素[按照从属关系]
+
+$("ul").children("li:first-child")
+
+2. find() 按照指定的条件向下查找
+
+$("ul").find("span")
+
三、水平遍历
+1. siblings() 获取元素的所有兄弟元素
+
+$(".start").siblings().css({color:"red",border:"2px solid red"})
+
+2. next() 获取元素的下一个兄弟元素
+
+$(".start").next().css({color:"red",border:"2px solid red"})
+
+3. nextAll() 获取其后的所有兄弟元素
+
+$(".start").nextAll().css({color:"red",border:"2px solid red"})
+
+4. nextUntil() 查找后面所有的兄弟元素,直到遇见某个元素为止
+
+$(".start").nextUntil("li:last-child").css({color:"red",border:"2px solid red"})
+
+5. prev() 查找上一个兄弟元素
+
+$("li.start").prev().css({color:"red",border:"2px solid red"})
+
+6. prevAll() 查找上面所有的兄弟元素
+
+$("li.start").prevAll().css({color:"red",border:"2px solid red"})
+
+prevUntil() 查找上面所有的兄弟元素,直到遇见某个元素为止
+$(".start").prevUntil("li:first").css({"color":"red","border":"2px solid red"})
+
四、过滤
+1. first() 获取第一个元素
+
+$("li").first().css("color","red");
+
+2. last() 获取最后一个元素
+
+$("li").last().css("color","red");
+
+3. not() 获取不是…的元素
+
+$("li").not(":eq(2)").css("font-size","26px");
+
+4. eq(n) 获取索引为n的元素
+
+$("li").eq(3).css("background","green");
+
+5. has() 检测某个子元素是否存在
+
+$("li").eq(1).has("span").length)
+
+6. filter() 筛选出与符合条件的DOM元素
+
+$("div")..filter(".middle")
+
+7. is() 用来判断是否符合条件
+
+$("p").parent().is("div") //判断p的父元素是不是div,是就返回true,不是就返回false
+
五、each遍历
+1. each() 方法为每个匹配元素规定要运行的函数。
+
+$(selector).each(function(index,element){
+ .....
+})
+//index 表示当前遍历元素的索引
+ element 当前的元素(也可使用 "this" 选择器)
+
+2. $.each(obj,function( index,value){})
+
在图形开发中,我们可能需要使用js对传递过来的数据进行处理
+假设dataset的格式是, SQL = Select 维度1,维度2,数据 from xxxx, 生成的数据集如下
+dataset = [['category','C1','C2'],
+ ['R1', 12, 18],
+ ['R2', 10, 17] ]
+
result = ds_createMap(dataset)
+结果 = {"category":['C1','C2'],
+ "R1" : [12, 10],
+ "R2" : [18, 17]}
+
result = ds_createMap_all(dataset)
+结果 = [{"category":"R1", "C1": 12, "C2": 18},
+ {"category":"R2", "C1": 10, "C2": 17}]
+
result = ds_rowname(dataset)
+结果 = ['R1','R2']
+
result = ds_transform(dataset)
+结果 = [['category','R1','R2'],
+ ['C1', 12, 10],
+ ['C2', 18, 17]]
+
假设需要关联的数据集格式:
+dataset2 = [['category','C3'],
+ ['R1', 38],
+ ['R6', 13]]
+处理后的结果:
+result = ds_leftjoin(dataset, dataset2)
+结果 = [['category','C1','C2','C3'],
+ ['R1', 12, 18, 38],
+ ['R2', 10, 17, 0] ]
+
比如需要将dataset3的户型变成指标
+dataset3 = [['城市','户型','数量'],
+ ['长沙','A',35],
+ ['上海','B',19]]
+处理后的结果:
+result = ds_pivot(dataset3)
+结果 = [["城市","A","B"],
+ ["长沙",35,0],
+ ["上海",0,19]]
+
比如移除第1列(序号0)
+result=ds_remove_column(dataset,remove_list=[0])
+ 结果 = [['R1','R2'],
+ [12, 10],
+ [18, 17] ]
+
函数名 | +函数说明 | +样列 | +
---|---|---|
ds_transform(dataset) | +行列替换 | ++ |
ds_split(data,sep=’,’,head_add=[]) | +将第一列拆分成多个字段,默认逗号分隔, 如果不传表头,取SQL中的字段名拆分 | ++ |
ds_createMap(data) | +data表示传入的二位数组,生成结果表示为key->[], 常用于echarts指定数据 | ++ |
ds_createMap_all(data) | +data表示传入的二维数组,生成结果表示为[{A:A1,B:B1,C:C1},{A:A2,B:B2,C:C2}…] | ++ |
ds_mapToList(data) | +将createMap_all的格式还原成二维数组, 常用于将nosql(mongodb,es..)数据源数据处理 | ++ |
ds_fontSize(rem) | +基于分辨率自动转字体大小, 参数rem | ++ |
ds_rowname(dataset,start_row=1,column=0) | +获取指定列的数据, 默认取第一列从第二行(序号1)开始的数据,常用于获取维度 | ++ |
ds_remove_column(dataset,remove_list=[0]) | +默认移除第一列, 也要移除指定的多个列 | ++ |
ds_toThousands(num) | +转逗号分隔的千分位 | ++ |
ds_distinct(a, b=[]) | +对单个或多个二维数组去重 | ++ |
ds_leftjoin(a,b,withhead=true,type=1) | +两个数组join [[1,2,3,4],[2,3,4,5]] ,[[2,3,4]], 如果带头,合并头 | ++ |
ds_crossjoin(a,b,withhead=true) | ++ | + |
ds_fulljoin(a,b,withhead=true) | ++ | + |
ds_union(a,b,withhead=true) | +合并两个数据集, 可选是否带头, 取第一个数据集的头, 去除第二个头 | ++ |
ds_pivot(arr) | +传入一下二维数组(维度, 维度, 值), 进行透视 | ++ |
ds_sort(arr, index=0, asc=true) | +指定二维数组列序号排序,默认升序,index参数也可以是函数,如(a,b)=>{return a.qty - b.qty} | ++ |
getUndefined(param,defaultValue) | +获取value值,如果为空,null,undefined给默认值 | ++ |
ds_round(num,qty=2) | +小数点处理, 默认保留两位小数 | ++ |
ds_param(name) | +传入参数名,获取图形点击时传递来的参数值 | ++ |
ds_setParam(‘参数名’, 参数值) | +设定全局参数, 此方法将自动判断当参数值为空时, 删除参数回到初始未传参状态 | ++ |
ds_refresh(id, param=filter_param) | +刷新图形, id为图形序号,默认采取全局参数刷新,也可指定param,参数为字典{“参数名”:“值”,…} | ++ |
常规数据集中提到 A类数据源的情况, 格式都是:
+维度A 维度B 数据
+但还有情况比如你有一个数据格式是:
+维度A 维度B 维度C 数据
+你需要在表格中将 A,B维度做维度, 但C做透视为指标名进行展示
+由于我们的数据透视只支持"字符, 字符, 数值"的SQL写法,
+所以如果要多维, 我们需要做下转变, 可以写成:
+select concat_ws(',',维度A,维度B) AS 维度,维度C,SUM(数据) AS 度量
+ from tablename group by 维度, 维度C
+得到的数据样式
+dataset=[['维度','C1','C2'....]
+ ,['A1,B1',1,1...]
+ ,['A2,B2',2,2...]]
+最终在图形数据集处理中, 我们可以使用如下函数进行转化:
+dataset = ds_split(dataset,',',['维度A','维度B'])
+',' : 参数为分隔符
+['维度A','维度B'] : 指第一个字段需要拆分的表头名称
+
+最终得到的数据就是多维度透视
+[['维度A','维度B','C1','C2'....]
+,['A1','B1',1,1...]
+,['A2','B2',2,2...]]
+
//数组追加
+dataset.push(item)
+//数组前方插入
+dataset.unshit(item)
+//切片
+dataset = dataset.slice(1) 从序号1个开始到最最后一个
+dataset = dataset.slice(5, 10) 从第序号5开始截取到第10个
+dataset = dataset.slice(-3) 截取最后三个元素
+//循环遍历
+for最快,但可读性比较差(smartchart推荐)
+forEach比较快,能够控制内容
+for...in比较慢,不方便
+for(let i=0; i<dataset.length; i++){
+
+}
+
+
+配置项完全和Echarts原生一样
++注意: +对于一些特殊图形如地图js在图形编辑中需要进行动态加载, 如果你使用了非常规图形, +你可在图形编辑器使用ds_loadjs(‘smt_china’)加载中国地图, 同理我们有 ‘smt_wordcloud’, ‘smt_world’, ‘smt_ecStat’, ‘smt_liquidfill’ + +
++有些图形可能同时需要在模板中加载, 更多特殊图形加载说明参考 + 特殊图形模板加载
Smartchart内置了LineUp图形
+LineUp is an interactive technique designed to create, visualize and explore rankings of items based on a set of heterogeneous attributes.
+
+
LineUp图形参考
+ds_loadcss('smt_LineUp');
+ds_loadjs('smt_LineUp');
+let dataset = __dataset__;
+dataset = ds_createMap_all(dataset);
+try{Ljs__name__.destroy()}catch{}
+Ljs__name__ = LineUpJS.asTaggle(dom__name__, dataset);
+
+// 点击选中行响应动作
+Ljs__name__.on(LineUpJS.LineUp.EVENT_SELECTION_CHANGED, (selection) => {
+ console.log(Ljs__name__.data._data[selection]);
+ //通过以上log可以查看到数据格式, 以下就是标准的联动写法
+ filter_param['LineupParam'] = Ljs__name__.data._data[selection].xx
+ ds_refresh(2);
+});
+
+//更多响应动作
+Ljs__name__.on(LineUpJS.LineUp.EVENT_HIGHLIGHT_CHANGED, (highlight) => {
+});
+
+// document.querySelector('button#select').addEventListener('click', () => {
+// Ljs__name__.setSelection([1, 2, 3]);
+// });
+
+// document.querySelector('button#highlight').addEventListener('click', () => {
+// Ljs__name__.setHighlight(50);
+// });
+
+// 获取筛选后的数据并下载(来源与"路阳" 赞助开发)
+outputStr=Ljs__name__.data.exportTable(Ljs__name__.data.getRankings()[0], {});
+outputStr = outputStr.replace(/\t/g, ',');
+ds_download('abc.csv', outputStr);
+
在"模板"中加载图标资源
+
+
+使用方法, 可参考
+ font-awesome菜鸟教程
+V5图标名称参考, 也可以
+ 图标样列查询
+
+
+
+
+
例如你的html如下
+<div id="smtid" style="height:100%">
+ <ul>
+ <li>smartchart</li>
+ <li>bigdata</li>
+ <li>echarts</li>
+ <li>make it great</li>
+ </ul>
+</div>
+
你只需要使用以下函数, 即可实现在无缝滚动 +由于smtid是ID, 则使用 ds_liMarquee(’#smtid’) 即可开启自动滚动 +如果 class=“smtclass”, 那么也可以使用类选择器 ds_liMarquee(’.smtclass')
+我们也可以使用更多的配置方法
+ marconfig={
+ playtime: 3000, //滚动3秒
+ pausetime: 3000, //停3秒
+ config:{
+ direction: 'up',//向上滚动
+ runshort: false,//内容不足时不滚动
+ scrollamount: 20//速度
+ }
+ }
+
可以使用 ds_liMarquee(’#smtid’, marconfig) 传入配置
+更多config说明:
+名称 | +类型 | +默认值 | +说明 | +
---|---|---|---|
direction | +字符串 | +left | +滚动方向,可选 left / right / up / down | +
loop | +整数 | +-1 | +循环次数,-1 为无限循环 | +
scrolldelay | +整数 | +0 | +每次重复之前的延迟 | +
scrollamount | +整数 | +50 | +滚动速度,越大越快 | +
circular | +布尔值 | +true | +无缝滚动,如果为 false,则和 marquee 效果一样 | +
drag | +布尔值 | +true | +鼠标可拖动 | +
runshort | +布尔值 | +true | +内容不足是否滚动 | +
hoverstop | +布尔值 | +true | +鼠标悬停暂停 | +
xml | +布尔值 | +false | +加载xml 文件 | +
inverthover | +布尔值 | +false | +反向,即默认不滚动,鼠标悬停滚动 | +
smartchart内置了滚动表格, 可以一键生成 +如果你需要修改表格的样式, 如字体,颜色等, 你可以在模板中重定义样式 +具体样式的写法, 参考 + 样式快速入门
+如下例修改表头高度为5rem, 内容单元格高度3rem 及背景字体等
+/*表头样式*/
+.smtlisthead{
+ background: #fff2cc;
+ color: red;
+ height: 5rem;
+}
+
+.smtlisthead span{
+ height: 5rem;
+}
+
+/*表格本体样式*/
+.smtlistnav{
+ height: calc(100% - 5rem);
+ color: red;
+ overflow: auto;
+}
+
+.smtlistnav li span{
+ height: 3rem;
+}
+
+
+/*修改奇数行背景*/
+.smtlistnav ul li:nth-child(odd){ background: rgba(100,100,100,.1);}
+偶数行将odd改为even
+
+/*指定某单独格宽度对齐*/
+<span>
+ <span style="width:32rem;height:100%;flex-shrink:0;justify-content:left"><span>
+</span>
+
+/*单元格点击响应*/
+let lastClickDom;
+let lastDomColor;
+$('#smtlist__name__, li').click(function(params){
+ try{lastClickDom.css('background', lastDomColor)}catch{}
+ lastDomColor = $(this).css('background');
+ $(this).css('background', 'yellow');
+ lastClickDom = $(this);
+ let myparam = $(this).children('span').eq(0).text(); //获取点击的参数
+ //以下加入你的action
+
+});
+
smartchart默认只会引echarts的基础图形 +如果你需要使用到更多图形, 你可以自行引用,可以写在模板的javascript标签中
+中国地图
+<script src="/static/smartchart/opt/smt_china.js"></script>
+世界地图
+<script src="/static/smartchart/opt/smt_world.js"></script>
+统计图
+<script src="/static/smartchart/opt/smt_ecStat.js"></script>
+水球图
+<script src="/static/smartchart/opt/smt_liquidfill.js"></script>
+词云
+<script src="/static/smartchart/opt/smt_wordcloud.js"></script>
+百度地图
+<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5/dist/extension/bmap.min.js"></script>
+
+
大家可能比较熟悉使用F12来查看网页日志, 但有的同学会觉得这不够方便, 所以 +在5.1.11后, smartchart加入了可以页面直接显示日志的功能
+你只需要点击如下菜单, 即可切换是否显示日志
+
+
+当在刷新页面或执行时, 日志将直接显示在右上角中, 而且还能显示出对应出错的图表序号
+
+
smartchart基于python的使用习惯, 重定义的专用的日志打印函数print
+你可以在图形编辑器中使用些函数即可打印日志
+比如看看鼠标放在echarts图上params, 在编辑界面和console中都能看到日志, 方便你进行调试
+
+
不仅仅这些, 你可能会想写太多打印日志, 上线了不好 +smartchart已为你想到这些, 如果你在仪表盘中没有开启日志显示, print函数是不会打印任何日志
+有了仪表盘日志显示, 我们还能做更多的事情, 比如实时显示你拖拽的坐标, 让你精确定位
+
+
由于我们移除了boostrap的布局方式, 此布局不再做推荐! +初次学习的同学, 可能会需要一个拖拉拽的布局方式(3.9.9.13以上才支持), 所以下面介绍的是smartchart的辅助布局方法
+你可以在"布局" 中找到入口
+
+
进入编辑器后, 你可以拖拉拽的方式进行布局, 注意我们建意你一个column(C)容器只放一个smartchart的图形便于标准化
+
+
点击"生成", 复制相关代码
+
+
回到仪表盘"布局", 粘贴到编辑器, 点击 “闪电” 图标, 进行转化, 可能会提示你数据集不够,这样你需要新增足够数量的数据集后再点转化
+
+
注: 代码转化布局你可以不用可视化辅助, 可以用其它任意每三方布局工具生成代码后粘过来生成, 需要注意的格式是: +在你需要数据集的地方使用以下代码即可:
+<div style="height:100%" id="container_{name}"></div>
+
+
+
全局编辑器
+
+
移动图形
+你有两种方法移动数据集
+可以在“报表” 中直接暴力修改序号
+
+
也可以在数据集编辑器中移动,
+可选择插入(带着容器一起动),替换(容器不动,就是整体布局不变化, 只移动数据和图形)
+
+
对于仪表盘中不再使用的数据集,你可能会考虑删除
+首先smartchart推荐你不做删除,因为你可以保留他, 当你下次有新增数据集的需求时再拿出来
+所以优先推荐使用隐藏的方法,你可以在数据集编辑界面找到他
+
+
+如果你实在需要删除,可以在“报表”界面先中不需要容器后,选中删除,后保存
+
推荐先观看视屏了解 + smartchart布局方式
+容器 | +说明 | +
---|---|
定位容器 | +用于图形定位, 有拖拽和栅格两种. 在界面上新增时会自带;在模板编辑中新增图形时需自行加入容器 | +
图形容器 | +用于图形选择, 使用id选择器, 如序号为2的容器, 选择器为#container_2 | +
图形 | +可视化的实际单位, 如选择图形中的table标签, 可使用#container_2 table | +
一般移动端报表推荐使用响应式布局, 一次布局可以同时满足电脑端/移动端的需求 +当你新增一个数据集时, smartchart会给你一段默认的代码
+<div class="el-col-xs-24 el-col-md-24" style="padding:0.2rem;height:50%;" >
+ <div style="height:100%;" id="container_{name}"></div>
+</div>
+
+el-col-md-24 : 电脑端宽度设定
+控制图形父容器的宽度, 整行分成24个栅格, 如果你想让图形占一半, 就可以改成el-col-md-12
+el-col-xs-24 :移动端宽度设定
+padding:0.5% 0.5%:
+控制图形的上下, 左右内边距, padding: 上 右 下 左
+比如你想要图形在容器中往下走一点, 你可写成 padding: 1% 0 0 0
+height:50%;
+盒子的高度, 相对于父容器的高度, 最外层即浏览器高度
+
大家如果开发大屏, 只是用响应式布局可能对于复杂的布局不是很方便, 你也可以采用拖拽绝对定位的方法, 这样你可以随意指定图形的所在位置. +方法可参考视屏 + 拖拽说明 +你可以配合使用 ”模板开发“ 来实现高度定制化的效果, 参考 + 模板开发指引
+你可以考虑先用响应式布局把整体框架画出来, 如果要加一下装饰的情况, 可以用绝对定位(拖拽布局)来实现
+拖拽很方便, 但是精确对齐还是有些手抖, 所以smartchart增加了自动对齐的功能
+你可以在"模板" –> “转化” 中找到这个功能
+首先我们随意拖拽了一些组件
+
+
+然后选中拖拽代码段, 点"拖拽对齐" 后 点"保存"
+
+
+就可以查看对齐后的效果了, 再进行下拖拽微调, 重复以上动作到满意
+
+
- $参数名, 当有传递参数时将替换相关的值
+- /* ... $参数 ... */, 当参数写在这个区间时, 如果外部没有传入参数, 会自动忽略这一段代码
+- -- 标识之后单行的代码会被忽略
+
理解以下样列后, 可录活使用组合出各种可能的需求
+select xx from tablename where
+calmonth =/*'$calmonth' -- */ to_char(sysdate,'YYYYMM')
+
select xx from tablename where 1=1 /* and city = '$city' */
+/* and calmonth ='$calmonth'*/
+
select /*$calmonth,*/ city, count(1) as qty from tablename
+group by /*$calmonth,*/ city
+
select /*月份, -- $Month */
+城市, sum(度量) from tablename
+where 1=1
+/* and 月份 > '$Month' */
+group by 城市
+/*,月份 -- $Month*/
+
以上应用可以通过参数是否有传递来实现开关代码段的效果, 有些场景可能还希望通过参数值来进行代码段的开关 +如下代码可以实现当传递参数type=1 或 type=2 时执行不同的代码段 +使用 “$参数__值” 的方式做为开关
+/* select count(1) as qty from tablename1 $type__1*/
+/* select count(1) as qty from tablename2 $type__2*/
+
再比如 +当参数D传值为"月份"时是统计2022年按月的统计, +传"日期"时统计的是2022年10月按天的统计
+select $D, count(1) as qty from tablename
+where 1 = 1
+/* and year='2022' -- $D__日期 $D__月份 */
+/* and month='10' $D__日期 */
+group by
+$D
+
++加了"–“是为了避免语法错误
+
对于开发人员来说, 带参数的SQL调试不方便, 所以支持你在sql中写入默认参数
+方法如下格式, 你可以在sql编辑器的最上方写上 /* {xxxxxx} */,
+会默认在开发调试模式下取这些参数, 在用户模式下会忽略
+/* {"月份":"202009","城市":"中山"} */
+select xxx from table xxx
+
++TIPS +当设定参数后, 联动过程dataset的缓存功能失效, 所以不要让带参数的查询设计得太慢 +参数如果存在一些非法字符可能会有问题, 比如参数中不可以有#号
+
首先在需要进行联动的数据集中SQL的写法如下, 比如对应2号图形:
+注意 /* ... */的写法, 当参数写在这个区间时, 如果外部没有传入参数,
+会自动忽略这一段代码, 这样对于联动来说非常重要,
+初始时全部显示, 点击其它图形时传入参数进行动态联动
+
+select xxx,xxx,xxx from tablename /* where xxxx = '$参数名' */ ....
+
建意观看视屏比较容易理解: + + 数据联动说明
+比如你需要点击0号图形, 指定其它图形联动
+你只需要打开0号图形的数据集编辑页面, 点击标题的位置
+
+
+然后输入相关的参数即可, 以下为sample
+
+
+参数值设定的方法, 你可以先留空, 然后保存, F12打浏览器调试方法
+点击0号图形你需要点击的动作, 你可以右调试窗口的console看到输入的log
+比如我们需要传递的参数值是"廉颇", 那么取数据的方法就是data.name, 你把这个填入即可
+
+
这样就实现按所选数据或所选系列钻取/联动了, 重新点击当前所选, 恢复原来的 +如何在图形开发中获取参数值
+图形编辑器中,
+你可以使用函数 ds_param('参数名') 来获取传入的参数值
+
如果有更多个性化需求,可以在需要点击的图形的编辑器中加入以下代码,你仅仅需要修改的是序号和参数名
+//比如传入多个参数进行联动
+myChart__name__.on('click', function(params){
+ let myparam = params.seriesName; //获取点击的值
+ filter_param['参数名'] = myparam; //填写你的数据集的SQL设定中对应的参数名
+ filter_param['参数名2'] = myparam2; //你可以赋值给多个参数
+ ds_refresh(3); //3 为你要刷新图形序号
+});
+
你也可以使用更方便的参数赋值方法(5.6以上)
+//使用方法
+ds_setParam('参数名', 参数值)
+此方法将自动判断当参数值为空时, 删除参数回到初始未传参状态
+
你还可以实现钻取到另外一个报表
+myChart__name__.on('click', function (params) {
+ let myparam = `¶m={"参数名": "${params.seriesName}"}`;
+ //拼成url并传参,具体参考数据集说明中的数据联动url传参的方法
+ let myurl='http://localhost:8000/echart/?type=目标报表名'+ myparam;
+ window.open(myurl,'_blank','toolbar=no,scrollbar=no,top=100,left=100,width=800,height=500');
+});
+
++TIPS +如果你的参数中存在非法字符如&=, 你可以使用encodeURIComponent函数进行转义后赋值
+
SMARTCHART实现筛选最简单的方法只需要配一个控件即可:
+建立一个筛选清单数据集, 自动获取筛选的列表,那么可以这么写
Select xxxx as city from tablename.....
+ 则会生成一个如下的数据集:
+ [['city'],[选项1],[选项2],..]
+
良好的习惯, 先保存在数据集 +然后在筛选器数据集中的js编辑器(图形编辑器),填下如下代码:
+//如要要美化, 自已加样式, 只要保证id="id_select__name__"
+let dataset=__dataset__;
+let table ='<span>标题</span><select id="id_select__name__">';
+table = table + '<option value="" selected>----</option>';
+ for(let i=1;i<dataset.length;i++){
+ table = table + '<option>' + dataset[i][0] + '</option>';
+ }
+table = table + '</select></div></div>'
+
+dom__name__.innerHTML=table;
+
这个时候你已经可以看到筛选器了
+
+
现在我们来设定联动效果
+假设需要被筛选的数据集的SQL这样写,数据集的序号是0
+//那么在需要被联动的数据集中,如使用pcity做为参数写查询, 比如:
+select xx, xx, xx from tablename /* where xx = '$pcity' */
+
现在回到我们筛选器数据集,点击标题的位置, 我们需要使0号图形被筛选器联动, 设置如下即可:
+
+
然后你就可以看到筛选效果了, 非常的简单方便, 需要多个图形被联动, 只需用逗号分隔即可 比如: 0, 2, 4
+Smartchart对于单项筛选有通用的组件, 可以直接配置即可,但对于个性化的筛选,需要你进行一些简单的定制化,以下就针对多项筛选联动的需求来介绍如何定制化你的筛选联动效果
+通过此例,你可以了解如何自定义任意的联动效果
+
+
以我们内置的仪表盘为例,
+第一步,新建一个数据集
+第二步,在新的数据集中编写查询
+select distinct H1 as heroname from smartdemo2 limit 10
+
第三步,编辑此数据集的图形,可复制以下代码
+//select distinct xx from tablename
+let dataset=__dataset__;
+let table = '';
+for (let i=1;i<dataset.length;i++){
+ table = `${table}<label><input name="select__name__" type="checkbox" value="${dataset[i][0]}" />${dataset[i][0]}</label> `;
+}
+table = table + "<button id='id_select__name__'>提交</button>";
+
+dom__name__.innerHTML=table;
+
+$('#id_select__name__').click(
+ function(){
+ let res = [];
+ $("input[name='select__name__']:checked").each(function(i){
+ res.push("'" + $(this).val() + "'");
+ });
+
+ filter_param['H1'] = res.toString(); //参数赋值
+ ds_refresh(1); //刷新1号图形
+
+ }
+)
+
第四步,在你要联动刷新数据集中增加代码
+select H1 as heroname, sum(qty) as 出场数 from smartdemo2
+where 1=1
+/* and H1 in ($H1) */ --此处来新增
+group by H1
+order by sum(qty) desc
+
这样就完成了,任何其他需求,都可以采用类似方法自定义
+++TIPS +如果你这个点击的图形又可能被其它图形来点击联动, 你需要加入如下unbind否则会触发多次刷新 +$(’#id_select__name__’).unbind(‘click’).click(…..
+
++如果你想取消联动,恢复到初始效果, 你需要删除参数, 如: +delete filter_param[‘H1’]
+
++你需要在jupyter中通过smartchart分享的数据集获取数据进行分析 +你有很多线下数据需要进行个性化分析, 然后制做仪表盘 +在Jupyter的数据分析过程中, 你需要快速生成图形 +大屏或报表有部分数据集是需要能过复杂的分析生成的
+
Smartchart支持像pyecharts, Matplotlib 等python绘图工具一样在Jupyter中使用, +但她更加方便, 更加炫酷 和 通用化, +不仅仅是一个绘图工具, 而且是一个平台
+我们有什么特色:
+++使用上手非常简单, 仅仅只有两个命令, get and set, 配置项采用原生的Echarts配置, 无重复学习成本, 使用顺滑 +支持Echarts所有功能, 可定制化程度高, 显示效果好, 可嵌入也可以弹出窗口显示, 也可以dashboard中显示 +数据可以固化存储, 采用smartchart Portal可以直接拼接炫酷大屏
+
+ Smartchart与Pandas + + Smartchart与Jupyter + + SmartChart大屏新思路
+
+
你需要在jupyter相同的python环境中安装smartchart客户端
+pip install smartchart
+或pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple smartchart
+
+如果你只是需要连接已部署好的smartchart服务端, 本地无需启动smartchart
+
初始化认证: +第一次使用时, 打开jupyter后, 需要设定默认用户和smartchart服务端url +同一个环境, 只需初始化一次, 后面无需再设定
+from smart_chart import Smart
+Smart().set_auth('用户名', '密码',url = 'http://xxxxx')
+
如果smartchart服务端在本地, 可以省略url
+Smart().set_auth('用户名', '密码')
+
使用方法:
+from smart_chart import Smart
+mysmart = Smart()
+dataset = [['A','B','C'],[12,34,23],[22,33,37]]
+#把数据写入临时数据集并显示图形
+mysmart.set(1,dataset)
+#随意命名临时数据集, 不一定需要smartchart中数据集已有的
+mysmart.set('DD', dataset)
+
+#从已有的数据集中获取数据(格式参考smartchart数据集)
+ds1 = mysmart.get(1)
+ds2 = mysmart.get('DD')
+
方法一: 你可以在图形菜单中选择内置图形或主题 +方法二: set默认是表格显示,你也可通过名称加前缀 bar, line, pie进行修改
+mysmart.set('barxxx', dataset) #显示柱形图, 另外还有linexxx, piexxx
+
方法三: 简单图形,可能无法满足你的个性化要求, 你可以采用实例化数据集的方式后做出炫酷的自定义图形
+mysmart.set('myds_1', dataset, push=1) #参数push=1, 将实例化数据集
+
实例化的数据集, 在图形编辑区点击, 可以进入定制化图形开发, 可使用原生的Echarts配置和实时调试,或直接使用社区图形(第一次使用,有一个登记的过程, 按提示进行)
+
+
方法四: 如何在非实例化的数据集中使用自定义图形进行临时显示 +假如你已经实例化并自定义了图形, 比如 ‘myds_1’, 你可以直接用它的名称来set
+#不加push, 将使用新的数据采用myds_1的图形临时显示, 而不会改变原myds_1的数据
+mysmart.set('myds_1', dataset)
+
你可以通过参数来设定图形的高宽, 是否嵌入等个性化要求
+# width, height指定图形嵌入显示的宽高
+# embed 默认不嵌入, embed=1 嵌入, embed=0 不嵌入
+# editor 是否显示图形菜单, 1显示, 0不显示
+# push 是否持久化数据集 push=1, 无则新建有则保存数据
+# url 报表访问的url,默认是localhost
+
+#可以全局初始化设定
+mysmart = Smart(width=xx, height=xx, embed=1, editor='')
+#也可以全局单独进行设定
+mysmart.url = 'http://ip:8000'
+mysmart.embed = 1
+
+#也可以针对单独的一个图形设定
+mysmart.set(1,dataset,embed=1,height=200,editor=0)
+
Smartchart的set支持直接set Pandas的dataframe对象
+from smart_chart import Smart
+import pandas as pd
+mysmart = Smart()
+df = pd.read_excel('manual_smartdemo.xlsx', 'sheet1')
+mysmart.set('excelsample', df.sample(10))
+
+
+df1 = df.groupby('province').agg({'qty':'sum'}).reset_index()
+mysmart.set('ec_df1', df1, push=1)
+
+df2 = df.groupby('c1').agg({'qty':'sum'}).reset_index()
+mysmart.set('ec_df2', df2, push=1)
+
+df4 = df.groupby('province').agg({'qty':'count'}).reset_index()
+mysmart.set('ec_df4', df1, push=1)
+
+
+df3 = df.groupby('c3').agg({'qty':'sum'}).reset_index()
+print(df3)
+df3.loc[1, 'qty'] = df3.loc[1, 'qty'] * 100
+print(df3)
+
+mysmart.set('ec_df3', df3, push=1)
+
+#mysmart.set('pie0', df1)
+#df2 = df.groupby(['province','c3']).agg({'qty':'sum'}).reset_index()
+#print(df2)
+#mysmart.set('ssss', df2)
+#print(mysmart.get(15))
+
+
'/echart/smart_login?id=xxx&stamp=xxx&token=xxx&url=/'
+'''
+参数说明:
+id: 用户名(在smartchart平台中管理)
+stamp: 时间戳(1970年1月1日到生成时间的毫秒数)
+token: 采用sha1加密, token=SHA1(链接秘钥+stamp+id)
+ 请在安装smartchart的这台机器上设定环境变量SMART_KEY = 链接秘钥
+url: 登录成功后跳转链接
+'''
+
import time
+import hashlib
+import os
+
+"""
+参数说明:
+id: 用户名(在smartchart平台中管理)
+stamp: 时间戳(1970年1月1日到生成时间的毫秒数)
+token: 采用sha1加密, token=SHA1(链接秘钥+stamp+id)
+url: 登录成功后跳转链接
+"""
+
+SMART_CHART_URL = 'http://127.0.0.1:8000'
+LOGIN_URL = SMART_CHART_URL + '/echart/smart_login?id={id}&stamp={stamp}&token={token}&url={url}'
+SMART_KEY = 链接秘钥
+
+
+def get_smarturl(username, url='/'):
+ stamp = int(time.time() * 1000)
+ id = username
+ res = SMART_KEY + str(stamp) + id
+ token = hashlib.sha1(res.encode('utf-8')).hexdigest()
+ LOGIN_DICT = {
+ "id": id,
+ "stamp": stamp,
+ "token": token,
+ "url": url
+ }
+
+ # 拼接好的url,直接访问
+ visit_url = LOGIN_URL.format(**LOGIN_DICT)
+ return visit_url
+
与单点登录类似, 单点登录用于直接登录到平台访问报表 +但对于只嵌入报表, 用此方法更合适(需升级到5.3.11以上)
+嵌入的url: '/echart/?type={reportName}&visitor={visitor}&token={token}&stamp={stamp}'
+参数说明:
+reportName: 报表名或报表ID
+visitor: 用户名(在smartchart平台中管理)
+stamp: 时间戳(1970年1月1日到生成时间的毫秒数)
+token: 采用sha1加密, token=SHA1(链接秘钥+stamp+visitor+reportName)
+
用户名和秘钥设定参考 + 数据服务API的config文件
+同时你需要将visitor加入到对应的报表权限查看访问
+以下为python版的url生成样列,你可以转化成你对应的开发语言
+import time
+import hashlib
+import os
+
+SMART_CHART_URL = 'http://127.0.0.1:8000'
+reportID = '报表ID'
+LOGIN_URL = SMART_CHART_URL + '/echart/?type={reportID}&visitor={visitor}&token={token}&stamp={stamp}'
+TOKEN = 链接秘钥
+
+
+def get_smarturl(username, reportName):
+ stamp = int(time.time() * 1000)
+ visitor = username
+ res = TOKEN + str(stamp) + visitor + reportID
+ token = hashlib.sha1(res.encode('utf-8')).hexdigest()
+ VISIT_DICT = {
+ "visitor": id,
+ "stamp": stamp,
+ "token": token,
+ "reportID": reportName
+ }
+
+ # 拼接好的url,直接访问
+ visit_url = LOGIN_URL.format(** VISIT_DICT)
+ return visit_url
+
如果你需要对用户进行一些数据权限控制, 可以避免用户越权访问
+可以通过传入参数"id", 如/echart/?type=xxx&visitor=xx&token=xx&stamp=xxxxx&id=xxx
+后台会把这个id转化为参数名"_id"给对应的查询来进行数据权限控制
+加密参数需把id加入, 例如id=john
+那么 token=SHA1(链接秘钥+stamp+visitor+reportName+id)
+
如果需要将参数也加入认证中, 为保持兼容性, 我们把param这个参数改为params(具体参考参数文档中param的写法)
+'/echart/?type={reportName}&visitor={visitor}&token={token}&stamp={stamp}¶ms=xxxx'
+加密参数把params加入, 例如params为 {"a":"1","b":2"}
+res = TOKEN + str(stamp) + visitor + reportName + '{"a":"1","b":2"}'
+
你可能需要把Smartchart生成的图形嵌入到其它系统 +首先,所有smartchart设计出来的仪表盘都有一个访问url +你可以直接访问:
+http://localhost:8000/echart?type=仪表盘名称
+
+如:http://localhost:8000/echart?type=demo
+
但是smartchart默认是有权限管理的,所以如果你需要嵌入你自已的系统又不考虑权限,你可以在“设定” –> 公开
+
+
然后在你的网页就可以直接iframe了
+<iframe src="http://localhost:8000/echart?type=demo" style="width:100%;height:100%"></iframe>
+
如果你想对smartchart前端二次开发或关闭debug模式后找不到资源 +在settings中加入
+STATIC_ROOT = os.path.join(BASE_DIR, "static")
+
执行以下命令将静态文件静态文件克隆到根目录
+python3 manage.py collectstatic
+
你可以在你的django项目中直接使用smartchart做为插件的方式 + + 你可以查看相关视屏
+INSTALLED_APPS = [
+ 'smart_chart.smartui',
+ ....
+ ....
+ 'smart_chart.echart'
+]
+
MIDDLEWARE 中注释掉XFrameOptionsMiddleware
+检查确保在Templates的设定处有DIRS的相关设定
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [BASE_DIR / 'templates'], #此处需要有
+ 'APP_DIRS': True, #也要有
+ .....
+ },
+]
+
LANGUAGE_CODE = 'zh-hans'
+ TIME_ZONE = 'Asia/Shanghai'
+ USE_I18N = True
+ USE_L10N = True
+ USE_TZ = False # 此处必须为False
+
from django.conf.urls import include
+ from django.views.generic import RedirectView
+
path('echart/', include('smart_chart.echart.urls')),
+ path('', RedirectView.as_view(url='echart/index/')), #首页,可自定义路由
+
python manage.py makemigrations
+ python manage.py migrate
+
python manage.py createsuperuser
+
python manage.py runserver
+
10. 点击首页的组件升级进行初始化 :cupid: !!!! 重要!!!
+
一般来说如果你使用django遇到的问题,都不是smartchart导致的, +作者也很难给你解答, 建议你可以进行有偿问答
+对于实时程度要求比较高的情况下, 如果你后端已有实现websocket的接口, smartchart也可以很方便的接入 +参考以下步骤即可
+
+
let ws_data = [['初始化','V'],['A','2']];
+ let ws = null;
+
+ if('webSocket' in window){
+ print('支持webSocket');
+ ws = new webSocket('ws://127.0.0.1:2222/abc');
+ //连接成功
+ ws.onopen = function(){
+ print('ws连接成功');
+ }
+ //接收消息
+ ws.onmessage = function(evt){
+ ws_data = evt.data;
+ ds_refresh(0);
+ }
+ }
+ else{
+ print('浏览器不支持ws')
+ }
+
++使用后台API刷新,建意将仪表盘中数据集的缓存时间设置长一些,比如2天(2880分钟)
+
(购买专业版本后支持)
+API_TOKEN = 'xxxxxxxx'
+
找到你要刷新的仪表盘编码, 你可以在打开的仪表盘url上面找到这个type id
+后台访问如下api url即可
+http://ip:端口/echart/refresh_ds/?type=你的报表ID&token=你设定的API_TOKEN
+
为保持产品的轻量化及坚持专业的产品做专业的事情, 归一化统一化的架构设计, 我们不会集成相关调度系统, +一般我们推荐使用您自有的调度工具或平台, 如airflow, 我们也有相关的配套产品
+如果您仅仅是简单应用, 也无需使用专用调度来增加运维复杂度, 可以使用linux自带的即可
+vim refresh_smartchart.sh
+
echo start refresh $(date "+%Y-%m-%d %H:%M:%S")
+curl http://ip:端口/echart/?type=你的报表ID1&token=你设定的API_TOKEN
+curl http://ip:端口/echart/?type=你的报表ID2&token=你设定的API_TOKEN
+echo end refresh $(date "+%Y-%m-%d %H:%M:%S")
+
++如果你的网址是https, 可如下方法使用curl
+
curl -k --insecure "https://www.baidu.com”
+
chmod 775 refresh_smartchart.sh
+
# 编辑crontab
+crontab -e
+# 比如需要每天晚上5点10分执行
+10 5 * * * /data/smartchart/refresh_smartchart.sh >>/data/smartchart/log.txt 2>&1
+
+# 定时参数说明
+* * * * *
+- - - - -
+| | | | |
+| | | | +----- 星期中星期几 (0 - 6) (星期天 为0)
+| | | +---------- 月份 (1 - 12)
+| | +--------------- 一个月中的第几天 (1 - 31)
+| +-------------------- 小时 (0 - 23)
++------------------------- 分钟 (0 - 59)
+
具体使用方法请观看视屏 + 版本控制使用说明视屏
+可以在模板->点击如下图标, 完成快速备份(注意会覆盖历史), 备份号统一为:SNAPSHOT
+
+
你也可以在设定->备份恢复 中进行按版本备份
+
+
当不输入KEY值, 点击 本地备份时,可以查询此仪表盘已有的备份
+
+
备份可以在任意的仪表盘中进行恢复,如果是当前仪表盘, 仅输入KEY即可,比如:V01, +如果是跨仪表盘恢复, KEY需要带上仪表盘的编号如13_V01
+可以支持多种恢复模式, 只需要在KEY前面加上前缀即可, 如FORCEV01, FORCE13_V01.. +FORCE: 删除自身所有数据集及高级设定,模板等,完全恢复备份 +DATASET:只更新数据集及图形,模板,适用于测试上线正式 +CHART: 只更新图形及模板,适用于前端变更上线
+具体方法购买专业版本后提供
+由于公司内部人员是对业务最熟悉, 一般也都是后台数据管理相关的人员, 一般不太会有专职的前端开发和UI +所以数据开发人员可以使用smartchart开发仪表盘数据集, 并使用拖拽功能完成一个粗糙一点的框架和图形设计 +一般情况下已经可以满足数据可视化的需求
+如果需要达到更专业的可视化效果, 可以外包前端/UI或在smartchart社区咨询, 由于需求变得非常简单 +而前端开发又是一个通用技能, 可以使用非常廉价的费用获得最大的效果
+++专业的事情专业来做, 才能达到效益最大化, 这是smartchart的设计理念 +不管用什么工具, 在同等资源的投入下, 数据分析人员开发的可视化效果很难达到专业前端UI的效果 +另外BI/数据开发人员的费用可是比前端高的, 也更稀缺
+
请观看系列视屏, 相信人人都会 + + 头条视屏: 大屏模板转化系列 + + B站视屏: 大屏模板转化系列
+需要要下载数据集的数据到本地
+可在"模板" 中新建一个下载按钮, 并指定一个ID,如id_down1, 拖拽到你需要的位置
+
+
在任意一个图形开发或js代码段中加入以下代码即可
+$('#id_down1').click(()=>{
+ ds_download('报表数据.csv', dataset);
+});
+
这样就可以实现点击按钮下载数据了
+仅需要录活使用ds_download这个函数, 你可以开发出非常个性化的下载功能
+ds_download(name, dataset)
+参数说明:
+name: 文件名称
+dataset: 可以是二维数组也可以是字符串
+
只用指定表名
+dataset={
+ "table":"表名"
+}
+你也可以指定字段, 比如
+dataset={
+ "table":"表名(字段1, 字段2)"
+}
+
在"容器"管理, 取消这个数据集激活, 并记录下来此数据集的ID, 比如132
+
+
在"模板"中编写录入组件代码, + 具体可参考视屏
+ <h1 class="smtdrag" id="id_1648895680659">数据填报</h1>
+
+ <div class="smtdrag" id="id_1648895855760">
+ <label>用户</label><input id="id_visitor">
+ </div>
+ <div class="smtdrag" id="id_1648895859160">
+ <label>动作</label><input id="id_action">
+ </div>
+ <div class="smtdrag" id="id_1648895956207">
+ <button id="idbtn01">提交</button>
+ </div>
+
+
$('#idbtn01').click(function(){
+ let visitor = $('#id_visitor').val();
+ let action = $('#id_action').val();
+ let dataset = [visitor, action];
+ print(ds_save(132, dataset)); //132数据集ID, dataset要写入的数据
+ })
+
只写入一行数据, 样列如下:
+dataset = ['a','b']
+同时写入多行数据:
+dataset = [[], ['a1','b1'],['a2', 'b2]]
+如果需要自动记录写入者用户名:
+dataset = ['$username', 'b']
+
mongodb写入方式
+ds_save(419, {"h1":123, "h2":"bb"});
+ds_save(419, [[],{"h1":123, "h2":"aa"},{"h1":1234, "h2":"dd"}]);
+
{
+ "test": {
+ "token": "smartchart"
+ },
+ "test2": {
+ "token": "smartchartxxx",
+ "host": ["10.10.10.10","10.10.10.23"],
+ "limit': 60,
+ "log":1,
+ "cors": 1
+ }
+}
+
++可选设定参考test2 +host:API白名单配置,limit:一分钟内可调用次数, log:日志记录方式. +cors:永许跨域访问
+
#接口请求格式:
+url: /echart/dataset_api/?visitor=xxx&token=xxx&type=xxx&stamp=xxxxx¶m={"xx":"xxx","xx":"xxxx"}
+# 参数说明
+visitor: 用户名
+type: 接口数据集ID
+stamp: 时间戳(1970年1月1日到生成时间的毫秒数)
+token: 采用sha1加密, token=SHA1(秘钥 + stamp + Visitor + Type)
+param: 传入的参数值(可选),格式json字符串,如多个参数: '{"参数A":"xxxx", "参数B":"xxxx"}'
+# 接口返回格式
+Json:
+{
+"data":[[]],
+"result":"success",
+"maxpg":1,
+"pg":1
+}
+返回值说明:
+data : 二维数组,第一行为表头, 样列数据
+[["heroname", "qty"],["镜",658],["猪八戒",591]]
+result : success 或 error
+maxpg/pg : GET请求固定为1不分页
+
#接口请求格式:
+url: /echart/dataset_api/
+# 请求参数说明
+data:
+{
+"visitor":"xxx",
+"token":"xxx",
+"stamp":xxxxx,
+"type":"xxx",
+"pagesize":"xxx",
+"pg":"xxx",
+"param":'{"xxx":"xxxx"}'
+}
+# 参数说明
+visitor: 用户名
+type: 接口数据集ID
+stamp: 时间戳(1970年1月1日到生成时间的毫秒数)
+token: 采用sha1加密, token=SHA1(秘钥 + stamp + Visitor + Type)
+Pagesize: 采用分页,每页的数据量大小
+pg: 返回第几页
+param: 传入的参数值,格式json字符串,如多个参数
+'{"参数A":"xxxx", "参数B":"xxxx"}'
+
+
+#接口返回格式
+Json:
+{
+"data":[[]],
+"result":"success",
+"maxpg":xxx, #最大页数
+"pg":xx, #当前页数
+"casheflag": xx, #如果是999说明命中缓存
+"total":xx, #总条数
+}
+
++注意: +只有post是分页的, 第一页是带标题的, 后面页不带标题 +由于post方式会使用缓存进行分页,如命中缓存传参不会生效,小数据量请使用get方式请求 +不要请求大数据量,大量数据请采用limit, offset传参方式进行分页
+
GET 请求
+#接口请求格式:
+url: /echart/dataset_api/?visitor=xxx&token=xxx&type=xxx 数据集名或id名
+#接口返回格式
+Json:
+{
+"data":[[]],
+"result":"success",
+"maxpg":1,
+"pg":1
+}
+
+POST请求
+#接口请求格式:
+url: /echart/dataset_api/
+data:
+{
+"visitor":"xxx",
+"token":"xxx",
+"type":"xxx", #数据集名或id名
+"pagesize":"xxx", #每页数据条数
+"pg":"xxx", #返回第几页
+"param":'{"xxx":"xxxx"}' #参数可选
+}
+#接口返回格式
+Json:
+{
+"data":[[]],
+"result":"success",
+"maxpg":xxx, #最大页数
+"pg":xx, #当前页数
+"casheflag": xx, #如果是999说明命中缓存
+"total":xx, #总条数
+}
+
+注意:
+只有post是分页的, 第一页是带标题的, 后面页不带标题
+由于post方式会使用缓存, 小数据量建议你使用get方式请求
+
在模板中使用basesimple
+
+
+此时smartchart不会引用任何echarts, vue组件, 完全由您自已控制引入 , 你可以直接采用以下代码替换模板中的代码
{% extends "echart/basesimple.html" %}{% block head %}
+
+<!--head区域的引用或代码-->
+
+{% endblock %}{% block body %}
+
+<!--在此区间粘入body相关代码-->
+
+{% endblock %}{% block javascript %}
+
+<!--粘入js相关引用或代码-->
+
+{% endblock %}{% block footer %}{% endblock %}
+
按照自由开发模式中, 新增一个图形, 然后修改数据集为通用且懒加载数据集
+
+
+修改对应图形编辑器, 使数据赋值给一个全局变量或vue
+
+
+
+
由于你在开发中仅需要用到filter_param及ds_refresh, 建意新建一个js文件, 文件内容:
+//下面定义全局变量, 发布时需放入smartchart模板的script标签中
+var mypublicdata1 = xxxx;
+.....
+
+//以下为辅助方法, 发布时, 无需放入smartchart模板中
+var filter_param = {};
+function ds_refresh(num){
+ if(num === 0){你对应的图形中赋值代码,调试代码}
+ if(num === 1){....}
+ ......
+}
+
然后将这个js文件在你的项目中引用调试使用
+打包完成后会有相应的css, js 和index.html文件, 将index.html中的代码复制贴粘到对应的模板区域中即可
+点击 模板开发 中的菜单即可上传你的资源文件, 如css, js, 图片等
+
+ 视屏介绍说明
+
+
可直接上传单个文件或zip包上传, 注意zip包中不可以有中文文件名 +上传后会提示引用路径为/static/custom/仪表盘ID/…
+可把资源打包为zip文件, 上传名以usr_开头, 如usr_tp.zip +上传完后不会有路径提示, 引用路径为/static/custom/usr_tp/….
+上传后不会有路径提示
+在"模板"的style中加入以下样式
+@-webkit-keyframes spin {
+ from {-webkit-transform: rotate(0deg);}
+ to {-webkit-transform: rotate(360deg);}
+}
+@keyframes spin {
+ from {transform: rotate(0deg);}
+ to {transform: rotate(360deg);}
+}
+
+.Rotate {
+ -webkit-animation: spin 3s linear 3s 5 alternate;
+ animation: spin 3s linear infinite;
+}
+
如需任意组件自动旋转, 只需将Rotate这个类给到这个组件即可, 比如图形
+<img class="Rotate" src="https://www.smartchart.cn/media/editor/smc162_20220407150432307320.png">
+
常见变形沿着Y轴, 其它变形方式自已搜索, 比如需要0号,1号图形变形的样式写法
+#container_0{transform:skewY(10deg);}
+#container_1{transform:skewY(-10deg);}
+
效果如下:
+
+
把你的可视化页面移动的显示器上吧, 进入"模板" 开发页面(上节介绍如何进入)
+此方式当你新增数据集图形时会自动识别, 无需手动在模板在添加
+采用此方式请不要采用点击模板上方菜单的图形新增
+如需编辑图形或数据集可按如下方式:
+
+
+
+
你可以模板中按照常规的H5页面编辑, 只是在需要插入图形的地方插入即可(建意通过模板上方的图形新增) +开始畅快的开发
+++warning +注意请尽量避免删除DIV, 如果你中途有删除过div, 序号会不一样, +你可以在"布局"中重排序保持一致, 保持数据集编号从0开始
+
++如果使用自由布局, 请删除自动化DIV这一段代码 + +
+
可以观看视屏, 视屏比较老和现在不太一样, 仅参考即可 + + 自由开发模式视屏
+要想大屏做得好, 样式要写得好 +可是我们不是前端的同学也能写样式么 +当然可以, 相信你观看完以下视屏即可
++ 快速上手样式开发
+CSS(Cascading Style Sheet,层叠样式表)定义如何显示HTML元素。 +当浏览器读到一个样式表,它就会按照这个样式表来对文档进行格式化(渲染)。
+CSS实例
+每个CSS样式由两个组成部分:选择器和声明。声明又包括属性和属性值。每个声明之后用分号结束。
+
+
+CSS注释
/*这是注释*/
+
注释是代码之母, smartchart编辑中你可以使用CTRL+/快捷注释
+CSS的几种引入方式 +行内样式 +行内式是在标记的style属性中设定CSS样式。不推荐大规模使用。
+<p style="color: red">Hello world.</p>
+
内部样式 +嵌入式是将CSS样式集中写在网页的
标签对的标签对中。格式如下: +<head>
+ <style>
+ p{
+ background-color: #2b99ff;
+ }
+ </style>
+</head>
+
外部样式 +外部样式就是将css写在一个单独的文件中
+<link href="mystyle.css" rel="stylesheet" type="text/css"/>
+
基本选择器
+元素选择器
+p {color: "red";}
+
+ID选择器
+#i1 {
+ background-color: red;
+}
+
+类选择器
+.c1 {
+ font-size: 14px;
+}
+p .c1 {
+ color: red;
+}
+
注意: +样式类名不要用数字开头(有的浏览器不认)。 +标签中的class属性如果有多个,要用空格分隔。
+通用选择器
+* {
+ color: white;
+}
+
组合选择器
+后代选择器
+/*li内部的a标签设置字体颜色*/
+li a {
+ color: green;
+}
+儿子选择器
+/*选择所有父级是 <div> 元素的 <p> 元素*/
+div>p {
+ font-family: "Arial Black", arial-black, cursive;
+}
+毗邻选择器
+/*选择所有紧接着<div>元素之后的<p>元素*/
+div+p {
+ margin: 5px;
+}
+弟弟选择器
+/*i1后面所有的兄弟p标签*/
+#i1~p {
+ border: 2px solid royalblue;
+}
+属性选择器
+/*用于选取带有指定属性的元素。*/
+p[title] {
+ color: red;
+}
+/*用于选取带有指定属性和值的元素。*/
+p[title="213"] {
+ color: green;
+}
+/*找到所有title属性以hello开头的元素*/
+[title^="hello"] {
+ color: red;
+}
+/*找到所有title属性以hello结尾的元素*/
+[title$="hello"] {
+ color: yellow;
+}
+/*找到所有title属性中包含(字符串包含)hello的元素*/
+[title*="hello"] {
+ color: red;
+}
+/*找到所有title属性(有多个值或值以空格分割)中有一个值为hello的元素:*/
+[title~="hello"] {
+ color: green;
+}
+
分组和嵌套
+分组
+当多个元素的样式相同的时候,我们没有必要重复地为每个元素都设置样式,我们可以通过在多个选择器之间使用逗号分隔的分组选择器来统一设置元素样式。
+例如:
+div, p {
+ color: red;
+}
+上面的代码为div标签和p标签统一设置字体为红色。
+
+嵌套
+多种选择器可以混合起来使用,比如:.c1类内部所有p标签设置字体颜色为红色。
+.c1 p {
+ color: red;
+}
+
伪类选择器
+/* 未访问的链接 */
+a:link {
+ color: #FF0000
+}
+/* 鼠标移动到链接上 */
+a:hover {
+ color: #FF00FF
+}
+/* 选定的链接 */
+a:active {
+ color: #0000FF
+}
+/* 已访问的链接 */
+a:visited {
+ color: #00FF00
+}
+/*input输入框获取焦点时样式*/
+input:focus {
+ outline: none;
+ background-color: #eee;
+}
+
伪元素选择器
+first-letter
+常用的给首字母设置特殊样式:
+
+p:first-letter {
+ font-size: 48px;
+ color: red;
+}
+before
+
+/*在每个<p>元素之前插入内容*/
+p:before {
+ content:"*";
+ color:red;
+}
+after
+
+/*在每个<p>元素之后插入内容*/
+p:after {
+ content:"[?]";
+ color:blue;
+}
+before和after多用于清除浮动。
+
CSS继承
+继承是CSS的一个主要特征,它是依赖于祖先-后代的关系的。继承是一种机制,它允许样式不仅可以应用于某个特定的元素,还可以应用于它的后代。例如一个body定义了的字体颜色值也会应用到段落的文本中。
+body {
+ color: red;
+}
+此时页面上所有标签都会继承body的字体颜色。然而CSS继承性的权重是非常低的,是比普通元素的权重还要低的0。
+我们只要给对应的标签设置字体颜色就可覆盖掉它继承的样式。
+p {
+ color: green;
+}
+
选择器的优先级
+我们上面学了很多的选择器,也就是说在一个HTML页面中有很多种方式找到一个元素并且为其设置样式,那浏览器根据什么来决定应该应用哪个样式呢?
+其实是按照不同选择器的权重来决定的,具体的选择器权重计算方式如下图:
+
+
+除此之外还可以通过添加 !important方式来强制让样式生效,但并不推荐使用。
+因为如果过多的使用!important会使样式文件混乱不易维护。
+万不得已可以使用!important
宽和高
+width属性可以为元素设置宽度。
+height属性可以为元素设置高度。
+块级标签才能设置宽度,内联标签的宽度由内容来决定。
+
字体属性
+文字字体
+font-family可以把多个字体名称作为一个“回退”系统来保存。如果浏览器不支持第一个字体,则会尝试下一个。浏览器会使用它可识别的第一个值。
+简单实例:
+body {
+ font-family: "Microsoft Yahei", "微软雅黑", "Arial", sans-serif
+}
+字体大小
+p {
+ font-size: 14px;
+}
+如果设置成inherit表示继承父元素的字体大小值。
+
+字重(粗细)
+font-weight用来设置字体的字重(粗细)。
+值描述normal默认值,标准粗细bold粗体bolder更粗lighter更细100~900设置具体粗细,400等同于normal,而700等同于boldinherit继承父元素字体的粗细值
+
+文本颜色
+color
+颜色是通过CSS最经常的指定:
+十六进制值 - 如: #FF0000
+一个RGB值 - 如: RGB(255,0,0)
+颜色的名称 - 如: red
+还有rgba(255,0,0,0.3),第四个值为alpha, 指定了色彩的透明度/不透明度,它的范围为0.0到1.0之间。
+
文字属性
+文字对齐
+text-align 属性规定元素中的文本的水平对齐方式。
+值描述left左边对齐 默认值right右对齐center居中对齐justify两端对齐
+
+文字装饰
+text-decoration 属性用来给文字添加特殊效果。
+值描述none默认。定义标准的文本。underline定义文本下的一条线。overline定义文本上的一条线。line-through定义穿过文本下的一条线。inherit继承父元素的text-decoration属性的值。
+
+常用的为去掉a标签默认的自划线:
+a {
+ text-decoration: none;
+}
+首行缩进
+将段落的第一行缩进 32像素:
+p {
+ text-indent: 32px;
+}
+
背景属性
+/*背景颜色*/
+background-color: red;
+/*背景图片*/
+background-image: url('1.jpg');
+/*
+ 背景重复
+ repeat(默认):背景图片平铺排满整个网页
+ repeat-x:背景图片只在水平方向上平铺
+ repeat-y:背景图片只在垂直方向上平铺
+ no-repeat:背景图片不平铺
+*/
+background-repeat: no-repeat;
+/*背景位置*/
+background-position: left top;
+/*background-position: 200px 200px;*/
+支持简写:
+background:#336699 url('1.png') no-repeat left top;
+使用背景图片的一个常见案例就是很多网站会把很多小图标放在一张图片上,然后根据位置去显示图片。减少频繁的图片请求。
+
边框
+边框属性
+border-width
+border-style
+border-color
+#i1 {
+ border-width: 2px;
+ border-style: solid;
+ border-color: red;
+}
+通常使用简写方式:
+#i1 {
+ border: 2px solid red;
+}
+边框样式
+值描述none无边框。dotted点状虚线边框。dashed矩形虚线边框。solid实线边框。
+
+除了可以统一设置边框外还可以单独为某一个边框设置样式,如下所示:
+#i1 {
+ border-top-style:dotted;
+ border-top-color: red;
+ border-right-style:solid;
+ border-bottom-style:dotted;
+ border-left-style:none;
+}
+border-radius
+用这个属性能实现圆角边框的效果。
+将border-radius设置为长或高的一半即可得到一个圆形。
+
display属性
+用于控制HTML元素的显示效果。
+值意义display:"none"HTML文档中元素存在,但是在浏览器中不显示。一般用于配合JavaScript代码使用。display:"block"默认占满整个页面宽度,如果设置了指定宽度,则会用margin填充剩下的部分。display:"inline"按行内元素显示,此时再设置元素的width、height、margin-top、margin-bottom和float属性都不会有什么影响。display:"inline-block"使元素同时具有行内元素和块级元素的特点。
+display:"none"与visibility:hidden的区别:
+visibility:hidden: 可以隐藏某个元素,但隐藏的元素仍需占用与未隐藏之前一样的空间。也就是说,该元素虽然被隐藏了,但仍然会影响布局。
+display:none: 可以隐藏某个元素,且隐藏的元素不会占用任何空间。也就是说,该元素不但被隐藏了,而且该元素原本占用的空间也会从页面布局中消失。
+
CSS盒子模型
+margin: 用于控制元素与元素之间的距离;margin的最基本用途就是控制元素周围空间的间隔,从视觉角度上达到相互隔开的目的。
+padding: 用于控制内容与边框之间的距离;
+Border(边框): 围绕在内边距和内容外的边框。
+Content(内容): 盒子的内容,显示文本和图像。
+
+
margin外边距
+.margin-test {
+ margin-top:5px;
+ margin-right:10px;
+ margin-bottom:15px;
+ margin-left:20px;
+}
+推荐使用简写:
+.margin-test {
+ margin: 5px 10px 15px 20px;
+}
+顺序:上右下左
+
+常见居中:
+.mycenter {
+ margin: 0 auto;
+}
+padding内填充
+.padding-test {
+ padding-top: 5px;
+ padding-right: 10px;
+ padding-bottom: 15px;
+ padding-left: 20px;
+}
+推荐使用简写:
+.padding-test {
+ padding: 5px 10px 15px 20px;
+}
+顺序:上右下左
+
+补充padding的常用简写方式:
+提供一个,用于四边;
+提供两个,第一个用于上-下,第二个用于左-右;
+如果提供三个,第一个用于上,第二个用于左-右,第三个用于下;
+提供四个参数值,将按上-右-下-左的顺序作用于四边;
+
float
+在 CSS 中,任何元素都可以浮动。
+浮动元素会生成一个块级框,而不论它本身是何种元素。
+关于浮动的两个特点:
+浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。
+由于浮动框不在文档的普通流中,所以文档的普通流中的块框表现得就像浮动框不存在一样。
+三种取值
+left:向左浮动
+right:向右浮动
+none:默认值,不浮动
+
overflow溢出属性
+值描述visible默认值。内容不会被修剪,会呈现在元素框之外。hidden内容会被修剪,并且其余内容是不可见的。scroll内容会被修剪,但是浏览器会显示滚动条以便查看其余的内容。auto如果内容被修剪,则浏览器会显示滚动条以便查看其余的内容。inherit规定应该从父元素继承 overflow 属性的值。
+overflow(水平和垂直均设置)
+overflow-x(设置水平方向)
+overflow-y(设置垂直方向)
+
定位(position)
+static
+static 默认值,无定位,不能当作绝对定位的参照物,并且设置标签对象的left、top等值是不起作用的的。
+relative(相对定位)
+相对定位是相对于该元素在文档流中的原始位置,即以自己原始位置为参照物。有趣的是,即使设定了元素的相对定位以及偏移值,元素还占有着原来的位置,即占据文档流空间。对象遵循正常文档流,但将依据top,right,bottom,left等属性在正常文档流中偏移位置。而其层叠通过z-index属性定义。
+注意:position:relative的一个主要用法:方便绝对定位元素找到参照物。
+absolute(绝对定位)
+定义:设置为绝对定位的元素框从文档流完全删除,并相对于最近的已定位祖先元素定位,如果元素没有已定位的祖先元素,那么它的位置相对于最初的包含块(即body元素)。元素原先在正常文档流中所占的空间会关闭,就好像该元素原来不存在一样。元素定位后生成一个块级框,而不论原来它在正常流中生成何种类型的框。
+重点:如果父级设置了position属性,例如position:relative;,那么子元素就会以父级的左上角为原始点进行定位。这样能很好的解决自适应网站的标签偏离问题,即父级为自适应的,那我子元素就设置position:absolute;父元素设置position:relative;,然后Top、Right、Bottom、Left用百分比宽度表示。
+另外,对象脱离正常文档流,使用top,right,bottom,left等属性进行绝对定位。而其层叠通过z-index属性定义。
+fixed(固定)
+fixed:对象脱离正常文档流,使用top,right,bottom,left等属性以窗口为参考点进行定位,当出现滚动条时,对象不会随着滚动。而其层叠通过z-index属性 定义。 注意点: 一个元素若设置了 position:absolute | fixed; 则该元素就不能设置float。这 是一个常识性的知识点,因为这是两个不同的流,一个是浮动流,另一个是“定位流”。但是 relative 却可以。因为它原本所占的空间仍然占据文档流。
+在理论上,被设置为fixed的元素会被定位于浏览器窗口的一个指定坐标,不论窗口是否滚动,它都会固定在这个位置。
+
z-index
+#i2 {
+ z-index: 999;
+}
+设置对象的层叠顺序。
+z-index 值表示谁压着谁,数值大的压盖住数值小的,
+只有定位了的元素,才能有z-index,也就是说,不管相对定位,绝对定位,固定定位,都可以使用z-index,而浮动元素不能使用z-index
+z-index值没有单位,就是一个正整数,默认的z-index值为0如果大家都没有z-index值,或者z-index值一样,那么谁写在HTML后面,谁在上面压着别人,定位了元素,永远压住没有定位的元素。
+从父现象:父亲怂了,儿子再牛逼也没用
+
opacity +用来定义透明效果。取值范围是0~1,0是完全透明,1是完全不透明。
+功能 | +WIN | +MAC | +说明 | +
---|---|---|---|
显示菜单 | +CTRL-, | +Command-, | ++ |
折叠其它 | +Alt-0 | +Command-Option-0 | ++ |
查找替换 | +Ctrl-F | +Command-F | ++ |
重复选中 | +Ctrl-D | +Command-D | +5.6以前的版本是删除所选 | +
注释选中 | +Ctrl-/ | +Command-/ | ++ |
取消修改 | +Ctrl-z | +Command-z | ++ |
重新执行 | +Ctrl-y | +Command-y | ++ |
选中大写 | +Ctrl-U | +Ctrl-U | ++ |
选中小写 | +SHIFT-Ctrl-U | +SHIFT-Ctrl-U | ++ |
打造全生态的数据应用数据管理的平台,解决中小企业上中台难,上中台贵,见效慢的问题. 平台与时俱进,不断完善与优化中 +可以通过视屏了解我们的架构设计 + 企业数字化与smartchart的一站式解决方案
+我们不提供数据存储与计算引擎, ETL工具, BI工具. 因为已有更优秀的产品,我们只为数据开发人员提供更敏捷的工具与平台,致力于数据服务中台建设,边缘可视化与数据管道产品, 站在巨人的肩上, 让数据更有个性,更灵活与可定制化,由于他的通用性,功能的扩展变得也是非常的Smart
+实现全生态的数据服务平台(数据收集, 数据加工, 数据分享,数据管理,数据应用)
+h&&(l=0),l=l||0,g=l+c,g Smartchart6.0 预计2022年底发布, 带来全新的功能与体验, 请关注 0&&(e="."===(e=t)?null:e),e};Qt({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,greediness:6,argTypes:["math","math","size","text","math","math"]},handler:function(t,e){var r=t.parser,a=e[4],n=e[5],i=Vt(e[0],"atom");i&&(i=Ut(e[0],"open"));var o=i?Lr(i.text):null,s=Vt(e[1],"atom");s&&(s=Ut(e[1],"close"));var h,l=s?Lr(s.text):null,m=Ft(e[2],"size"),c=null;h=!!m.isBlank||(c=m.value).number>0;var u="auto",p=Vt(e[3],"ordgroup");if(p){if(p.body.length>0){var d=Ft(p.body[0],"textord");u=Er[Number(d.text)]}}else p=Ft(e[3],"textord"),u=Er[Number(p.text)];return{type:"genfrac",mode:r.mode,numer:a,denom:n,continued:!1,hasBarLine:h,barSize:c,leftDelim:o,rightDelim:l,size:u}},htmlBuilder:Rr,mathmlBuilder:Or}),Qt({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler:function(t,e){var r=t.parser,a=(t.funcName,t.token);return{type:"infix",mode:r.mode,replaceWith:"\\\\abovefrac",size:Ft(e[0],"size").value,token:a}}}),Qt({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:function(t,e){var r=t.parser,a=(t.funcName,e[0]),n=function(t){if(!t)throw new Error("Expected non-null, but got "+String(t));return t}(Ft(e[1],"infix").size),i=e[2],o=n.number>0;return{type:"genfrac",mode:r.mode,numer:a,denom:i,continued:!1,hasBarLine:o,barSize:n,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:Rr,mathmlBuilder:Or});var Hr=function(t,e){var r,a,n=e.style,i=Vt(t,"supsub");i?(r=i.sup?ue(i.sup,e.havingStyle(n.sup()),e):ue(i.sub,e.havingStyle(n.sub()),e),a=Ft(i.base,"horizBrace")):a=Ft(t,"horizBrace");var o,s=ue(a.base,e.havingBaseStyle(w.DISPLAY)),h=Oe(a,e);if(a.isOver?(o=Dt.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:h}]},e)).children[0].children[0].children[1].classes.push("svg-align"):(o=Dt.makeVList({positionType:"bottom",positionData:s.depth+.1+h.height,children:[{type:"elem",elem:h},{type:"kern",size:.1},{type:"elem",elem:s}]},e)).children[0].children[0].children[0].classes.push("svg-align"),r){var l=Dt.makeSpan(["mord",a.isOver?"mover":"munder"],[o],e);o=a.isOver?Dt.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:l},{type:"kern",size:.2},{type:"elem",elem:r}]},e):Dt.makeVList({positionType:"bottom",positionData:l.depth+.2+r.height+r.depth,children:[{type:"elem",elem:r},{type:"kern",size:.2},{type:"elem",elem:l}]},e)}return Dt.makeSpan(["mord",a.isOver?"mover":"munder"],[o],e)};Qt({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler:function(t,e){var r=t.parser,a=t.funcName;return{type:"horizBrace",mode:r.mode,label:a,isOver:/^\\over/.test(a),base:e[0]}},htmlBuilder:Hr,mathmlBuilder:function(t,e){var r=Re(t.label);return new ve.MathNode(t.isOver?"mover":"munder",[Me(t.base,e),r])}}),Qt({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:function(t,e){var r=t.parser,a=e[1],n=Ft(e[0],"url").url;return r.settings.isTrusted({command:"\\href",url:n})?{type:"href",mode:r.mode,href:n,body:ee(a)}:r.formatUnsupportedCmd("\\href")},htmlBuilder:function(t,e){var r=se(t.body,e,!1);return Dt.makeAnchor(t.href,[],r,e)},mathmlBuilder:function(t,e){var r=Se(t.body,e);return r instanceof ge||(r=new ge("mrow",[r])),r.setAttribute("href",t.href),r}}),Qt({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:function(t,e){var r=t.parser,a=Ft(e[0],"url").url;if(!r.settings.isTrusted({command:"\\url",url:a}))return r.formatUnsupportedCmd("\\url");for(var n=[],i=0;i {if(!((a-=s)>=i))return;let o=t*n[i];const l=s*t;for(let u=i,h=i+l;u 0&&(this.triangles=new Int32Array(3).fill(-1),this.halfedges=new Int32Array(3).fill(-1),this.triangles[0]=i[0],s[i[0]]=1,i.length===2&&(s[i[1]]=0,this.triangles[1]=i[1],this.triangles[2]=i[1]))}voronoi(e){return new Dv(this,e)}*neighbors(e){const{inedges:r,hull:n,_hullIndex:i,halfedges:a,triangles:s,collinear:o}=this;if(o){const d=o.indexOf(e);d>0&&(yield o[d-1]),d
+ 设计哲学:
+ #
+
+
+
+
+ 与传统BI的区别
+ #
+
+
+
+"},t}(),O={"\xee":"\u0131\u0302","\xef":"\u0131\u0308","\xed":"\u0131\u0301","\xec":"\u0131\u0300"},E=function(){function t(t,e,r,a,n,i,o,s){this.text=void 0,this.height=void 0,this.depth=void 0,this.italic=void 0,this.skew=void 0,this.width=void 0,this.maxFontSize=void 0,this.classes=void 0,this.style=void 0,this.text=t,this.height=e||0,this.depth=r||0,this.italic=a||0,this.skew=n||0,this.width=i||0,this.classes=o||[],this.style=s||{},this.maxFontSize=0;var h=function(t){for(var e=0;e
=s)){var P=void 0;(a>0||t.hskipBeforeAndAfter)&&0!==(P=c.deflt(O.pregap,p))&&((C=Dt.makeSpan(["arraycolsep"],[])).style.width=P+"em",R.push(C));var D=[];for(r=0;r0?3*c:7*c,d=e.fontMetrics().denom1):(m>0?(u=e.fontMetrics().num2,p=c):(u=e.fontMetrics().num3,p=3*c),d=e.fontMetrics().denom2),l){var y=e.fontMetrics().axisHeight;u-o.depth-(y+.5*m)0&&(u+=M,p-=M)}var z=[{type:"elem",elem:n,shift:p,marginRight:b,marginLeft:y},{type:"elem",elem:a,shift:-u,marginRight:b}];x=Dt.makeVList({positionType:"individualShift",children:z},e)}else if(n){p=Math.max(p,m.sub1,n.height-.8*m.xHeight);var A=[{type:"elem",elem:n,marginLeft:y,marginRight:b}];x=Dt.makeVList({positionType:"shift",positionData:p,children:A},e)}else{if(!a)throw new Error("supsub must have either sup or sub.");u=Math.max(u,i,a.depth+.25*m.xHeight),x=Dt.makeVList({positionType:"shift",positionData:-u,children:[{type:"elem",elem:a,marginRight:b}]},e)}var T=me(l,"right")||"mord";return Dt.makeSpan([T],[l,Dt.makeSpan(["msupsub"],[x])],e)},mathmlBuilder:function(t,e){var r,a=!1,n=Vt(t.base,"horizBrace");n&&!!t.sup===n.isOver&&(a=!0,r=n.isOver),!t.base||"op"!==t.base.type&&"operatorname"!==t.base.type||(t.base.parentIsSupSub=!0);var i,o=[Me(t.base,e)];if(t.sub&&o.push(Me(t.sub,e)),t.sup&&o.push(Me(t.sup,e)),a)i=r?"mover":"munder";else if(t.sub)if(t.sup){var s=t.base;i=s&&"op"===s.type&&s.limits&&e.style===w.DISPLAY?"munderover":s&&"operatorname"===s.type&&s.alwaysHandleSupSub&&(e.style===w.DISPLAY||s.limits)?"munderover":"msubsup"}else{var h=t.base;i=h&&"op"===h.type&&h.limits&&(e.style===w.DISPLAY||h.alwaysHandleSupSub)?"munder":h&&"operatorname"===h.type&&h.alwaysHandleSupSub&&(h.limits||e.style===w.DISPLAY)?"munder":"msub"}else{var l=t.base;i=l&&"op"===l.type&&l.limits&&(e.style===w.DISPLAY||l.alwaysHandleSupSub)?"mover":l&&"operatorname"===l.type&&l.alwaysHandleSupSub&&(l.limits||e.style===w.DISPLAY)?"mover":"msup"}return new ve.MathNode(i,o)}}),te({type:"atom",htmlBuilder:function(t,e){return Dt.mathsym(t.text,t.mode,e,["m"+t.family])},mathmlBuilder:function(t,e){var r=new ve.MathNode("mo",[be(t.text,t.mode)]);if("bin"===t.family){var a=we(t,e);"bold-italic"===a&&r.setAttribute("mathvariant",a)}else"punct"===t.family?r.setAttribute("separator","true"):"open"!==t.family&&"close"!==t.family||r.setAttribute("stretchy","false");return r}});var Zr={mi:"italic",mn:"normal",mtext:"normal"};te({type:"mathord",htmlBuilder:function(t,e){return Dt.makeOrd(t,e,"mathord")},mathmlBuilder:function(t,e){var r=new ve.MathNode("mi",[be(t.text,t.mode,e)]),a=we(t,e)||"italic";return a!==Zr[r.type]&&r.setAttribute("mathvariant",a),r}}),te({type:"textord",htmlBuilder:function(t,e){return Dt.makeOrd(t,e,"textord")},mathmlBuilder:function(t,e){var r,a=be(t.text,t.mode,e),n=we(t,e)||"normal";return r="text"===t.mode?new ve.MathNode("mtext",[a]):/[0-9]/.test(t.text)?new ve.MathNode("mn",[a]):"\\prime"===t.text?new ve.MathNode("mo",[a]):new ve.MathNode("mi",[a]),n!==Zr[r.type]&&r.setAttribute("mathvariant",n),r}});var Kr={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},Jr={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};te({type:"spacing",htmlBuilder:function(t,e){if(Jr.hasOwnProperty(t.text)){var r=Jr[t.text].className||"";if("text"===t.mode){var a=Dt.makeOrd(t,e,"textord");return a.classes.push(r),a}return Dt.makeSpan(["mspace",r],[Dt.mathsym(t.text,t.mode,e)],e)}if(Kr.hasOwnProperty(t.text))return Dt.makeSpan(["mspace",Kr[t.text]],[],e);throw new o('Unknown type of space "'+t.text+'"')},mathmlBuilder:function(t,e){if(!Jr.hasOwnProperty(t.text)){if(Kr.hasOwnProperty(t.text))return new ve.MathNode("mspace");throw new o('Unknown type of space "'+t.text+'"')}return new ve.MathNode("mtext",[new ve.TextNode("\xa0")])}});var Qr=function(){var t=new ve.MathNode("mtd",[]);return t.setAttribute("width","50%"),t};te({type:"tag",mathmlBuilder:function(t,e){var r=new ve.MathNode("mtable",[new ve.MathNode("mtr",[Qr(),new ve.MathNode("mtd",[Se(t.body,e)]),Qr(),new ve.MathNode("mtd",[Se(t.tag,e)])])]);return r.setAttribute("width","100%"),r}});var ta={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},ea={"\\textbf":"textbf","\\textmd":"textmd"},ra={"\\textit":"textit","\\textup":"textup"},aa=function(t,e){var r=t.font;return r?ta[r]?e.withTextFontFamily(ta[r]):ea[r]?e.withTextFontWeight(ea[r]):e.withTextFontShape(ra[r]):e};Qt({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup"],props:{numArgs:1,argTypes:["text"],greediness:2,allowedInText:!0},handler:function(t,e){var r=t.parser,a=t.funcName,n=e[0];return{type:"text",mode:r.mode,body:ee(n),font:a}},htmlBuilder:function(t,e){var r=aa(t,e),a=se(t.body,r,!0);return Dt.makeSpan(["mord","text"],Dt.tryCombineChars(a),r)},mathmlBuilder:function(t,e){var r=aa(t,e);return Se(t.body,r)}}),Qt({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler:function(t,e){return{type:"underline",mode:t.parser.mode,body:e[0]}},htmlBuilder:function(t,e){var r=ue(t.body,e),a=Dt.makeLineSpan("underline-line",e),n=e.fontMetrics().defaultRuleThickness,i=Dt.makeVList({positionType:"top",positionData:r.height,children:[{type:"kern",size:n},{type:"elem",elem:a},{type:"kern",size:3*n},{type:"elem",elem:r}]},e);return Dt.makeSpan(["mord","underline"],[i],e)},mathmlBuilder:function(t,e){var r=new ve.MathNode("mo",[new ve.TextNode("\u203e")]);r.setAttribute("stretchy","true");var a=new ve.MathNode("munder",[Me(t.body,e),r]);return a.setAttribute("accentunder","true"),a}}),Qt({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler:function(t,e,r){throw new o("\\verb ended by end of line instead of matching delimiter")},htmlBuilder:function(t,e){for(var r=na(t),a=[],n=e.havingStyle(e.style.text()),i=0;iwn in jr?Nst(jr,wn,{enumerable:!0,configurable:!0,writable:!0,value:fn}):jr[wn]=fn;var vl=(jr,wn,fn)=>(Bst(jr,typeof wn!="symbol"?wn+"":wn,fn),fn);var jr=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function wn(t){var e=t.default;if(typeof e=="function"){var r=function(){return e.apply(this,arguments)};r.prototype=e.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(t).forEach(function(n){var i=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(r,n,i.get?i:{enumerable:!0,get:function(){return t[n]}})}),r}function fn(t){throw new Error('Could not dynamically require "'+t+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var b_={exports:{}};(function(t,e){(function(r,n){t.exports=n()})(jr,function(){var r;function n(){return r.apply(null,arguments)}function i(g){return g instanceof Array||Object.prototype.toString.call(g)==="[object Array]"}function a(g){return g!=null&&Object.prototype.toString.call(g)==="[object Object]"}function s(g,E){return Object.prototype.hasOwnProperty.call(g,E)}function o(g){if(Object.getOwnPropertyNames)return Object.getOwnPropertyNames(g).length===0;for(var E in g)if(s(g,E))return;return 1}function l(g){return g===void 0}function u(g){return typeof g=="number"||Object.prototype.toString.call(g)==="[object Number]"}function h(g){return g instanceof Date||Object.prototype.toString.call(g)==="[object Date]"}function d(g,E){for(var I=[],O=g.length,G=0;G{i<<=2,a<<=2,s<<=2,e(r,n,i+0,a+0,s),e(r,n,i+1,a+1,s),e(r,n,i+2,a+2,s),e(r,n,i+3,a+3,s)}}function F0(t){const e=Math.floor(t);if(e===t)return RR(t);const r=t-e,n=2*t+1;return(i,a,s,o,l)=>{if(!((o-=l)>=s))return;let u=e*a[s];const h=l*e,d=h+l;for(let f=s,p=s+h;f=f)if(b>=f&&e===xl){const k=oo(d,f,x);isFinite(k)&&(k>0?f=(Math.floor(f/k)+1)*k:k<0&&(f=(Math.ceil(f*-k)+1)/-k))}else p.pop()}for(var m=p.length;p[0]<=d;)p.shift(),--m;for(;p[m-1]>f;)p.pop(),--m;var _=new Array(m+1),y;for(a=0;a<=m;++a)y=_[a]=[],y.x0=a>0?p[a-1]:d,y.x1=a=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r=i)&&(r=i)}return r}function H0(t,e){let r,n=-1,i=-1;if(e===void 0)for(const a of t)++i,a!=null&&(r=a)&&(r=a,n=i);else for(let a of t)(a=e(a,++i,t))!=null&&(r=a)&&(r=a,n=i);return n}function Tl(t,e){let r;if(e===void 0)for(const n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r>i||r===void 0&&i>=i)&&(r=i)}return r}function G0(t,e){let r,n=-1,i=-1;if(e===void 0)for(const a of t)++i,a!=null&&(r>a||r===void 0&&a>=a)&&(r=a,n=i);else for(let a of t)(a=e(a,++i,t))!=null&&(r>a||r===void 0&&a>=a)&&(r=a,n=i);return n}function Tu(t,e,r=0,n=t.length-1,i){for(i=i===void 0?so:V0(i);n>r;){if(n-r>600){const l=n-r+1,u=e-r+1,h=Math.log(l),d=.5*Math.exp(2*h/3),f=.5*Math.sqrt(h*d*(l-d)/l)*(u-l/2<0?-1:1),p=Math.max(r,Math.floor(e-u*d/l+f)),m=Math.min(n,Math.floor(e+(l-u)*d/l+f));Tu(t,e,p,m,i)}const a=t[e];let s=r,o=n;for(El(t,r,e),i(t[n],a)>0&&El(t,r,n);s0)for(var r=new Array(i),n=0,i,a;n=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),K0.hasOwnProperty(e)?{space:K0[e],local:t}:t}function NI(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===X0&&e.documentElement.namespaceURI===X0?e.createElement(t):e.createElementNS(r,t)}}function BI(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Mu(t){var e=Al(t);return(e.local?BI:NI)(e)}function DI(){}function Lu(t){return t==null?DI:function(){return this.querySelector(t)}}function OI(t){typeof t!="function"&&(t=Lu(t));for(var e=this._groups,r=e.length,n=new Array(r),i=0;i=1))throw new Error("invalid cell size");return s=Math.floor(Math.log(x)/Math.LN2),b()},f.thresholds=function(x){return arguments.length?(h=typeof x=="function"?x:Array.isArray(x)?Ia(Mv.call(x)):Ia(x),f):h},f.bandwidth=function(x){if(!arguments.length)return Math.sqrt(a*(a+1));if(!((x=+x)>=0))throw new Error("invalid bandwidth");return a=(Math.sqrt(4*x*x+1)-1)/2,b()},f}const Zi=11102230246251565e-32,Pr=134217729,wF=(3+8*Zi)*Zi;function Wd(t,e,r,n,i){let a,s,o,l,u=e[0],h=n[0],d=0,f=0;h>u==h>-u?(a=u,u=e[++d]):(a=h,h=n[++f]);let p=0;if(du&&(u=v),B>h&&(h=B),this._ids[L]=L}const d=(o+u)/2,f=(l+h)/2;let p=1/0,m,_,y;for(let L=0;L0&&(_=L,p=v)}let k=e[2*_],T=e[2*_+1],C=1/0;for(let L=0;Lw&&(L[v++]=D,w=this._dists[D])}this.hull=L.subarray(0,v),this.triangles=new Uint32Array(0),this.halfedges=new Uint32Array(0);return}if(sh(b,x,k,T,M,S)<0){const L=_,v=k,B=T;_=y,k=M,T=S,y=L,M=v,S=B}const R=IF(b,x,k,T,M,S);this._cx=R.x,this._cy=R.y;for(let L=0;L0&&Math.abs(D-v)<=Nv&&Math.abs(N-B)<=Nv||(v=D,B=N,w===m||w===_||w===y))continue;let z=0;for(let $=0,lt=this._hashKey(D,N);$=(d=(o+u)/2))?o=d:u=d,(y=r>=(f=(l+h)/2))?l=f:h=f,i=a,!(a=a[b=y<<1|_]))return i[b]=s,t;if(p=+t._x.call(null,a.data),m=+t._y.call(null,a.data),e===p&&r===m)return s.next=a,i?i[b]=s:t._root=s,t;do i=i?i[b]=new Array(4):t._root=new Array(4),(_=e>=(d=(o+u)/2))?o=d:u=d,(y=r>=(f=(l+h)/2))?l=f:h=f;while((b=y<<1|_)===(x=(m>=f)<<1|p>=d));return i[x]=a,i[b]=s,t}function xP(t){var e,r,n=t.length,i,a,s=new Array(n),o=new Array(n),l=1/0,u=1/0,h=-1/0,d=-1/0;for(r=0;r=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function fh(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function Eo(t){return t=fh(Math.abs(t)),t?t[1]:NaN}function tq(t,e){return function(r,n){for(var i=r.length,a=[],s=0,o=t[0],l=0;i>0&&o>0&&(l+o+1>n&&(o=Math.max(1,n-l)),a.push(r.substring(i-=o,i+o)),!((l+=o+1)>n));)o=t[s=(s+1)%t.length];return a.reverse().join(e)}}function eq(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var rq=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Co(t){if(!(e=rq.exec(t)))throw new Error("invalid format: "+t);var e;return new dh({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}Co.prototype=dh.prototype;function dh(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}dh.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function nq(t){t:for(var e=t.length,r=1,n=-1,i;rnr&&(nr=t)):t>Ss?Cn(tr,t)>Cn(tr,nr)&&(nr=t):Cn(t,nr)>Cn(tr,nr)&&(tr=t)}else Ba.push(Qi=[tr=t,nr=t]);e 0;){if(u=oo(s,o,r),u===l)return n[i]=s,n[a]=o,e(n);if(u>0)s=Math.floor(s/u)*u,o=Math.ceil(o/u)*u;else if(u<0)s=Math.ceil(s*u)/u,o=Math.floor(o*u)/u;else break;l=u}return t},t}function sp(){var t=ap();return t.copy=function(){return fc(t,sp())},On.apply(t,arguments),Oa(t)}function Hx(t){var e;function r(n){return n==null||isNaN(n=+n)?e:n}return r.invert=r,r.domain=r.range=function(n){return arguments.length?(t=Array.from(n,nf),r):t.slice()},r.unknown=function(n){return arguments.length?(e=n,r):e},r.copy=function(){return Hx(t).unknown(e)},t=arguments.length?Array.from(t,nf):[0,1],Oa(r)}function Gx(t,e){t=t.slice();var r=0,n=t.length-1,i=t[r],a=t[n],s;return aMath.pow(t,e)}function $z(t){return t===Math.E?Math.log:t===10&&Math.log10||t===2&&Math.log2||(t=Math.log(t),e=>Math.log(e)/t)}function Xx(t){return(e,r)=>-t(-e,r)}function op(t){const e=t(jx,$x),r=e.domain;let n=10,i,a;function s(){return i=$z(n),a=jz(n),r()[0]<0?(i=Xx(i),a=Xx(a),t(Wz,Hz)):t(jx,$x),e}return e.base=function(o){return arguments.length?(n=+o,s()):n},e.domain=function(o){return arguments.length?(r(o),s()):r()},e.ticks=o=>{const l=r();let u=l[0],h=l[l.length-1];const d=h0){for(;f<=p;++f)for(m=1;m53)return null;"w"in U||(U.w=1),"Z"in U?(j=Ep(mc(U.y,0,1)),P=j.getUTCDay(),j=P>4||P===0?yc.ceil(j):yc(j),j=gc.offset(j,(U.V-1)*7),U.y=j.getUTCFullYear(),U.m=j.getUTCMonth(),U.d=j.getUTCDate()+(U.w+6)%7):(j=Tp(mc(U.y,0,1)),P=j.getDay(),j=P>4||P===0?pc.ceil(j):pc(j),j=dc.offset(j,(U.V-1)*7),U.y=j.getFullYear(),U.m=j.getMonth(),U.d=j.getDate()+(U.w+6)%7)}else("W"in U||"U"in U)&&("w"in U||(U.w="u"in U?U.u%7:"W"in U?1:0),P="Z"in U?Ep(mc(U.y,0,1)).getUTCDay():Tp(mc(U.y,0,1)).getDay(),U.m=0,U.d="W"in U?(U.w+6)%7+U.W*7-(P+5)%7:U.w+U.U*7-(P+6)%7);return"Z"in U?(U.H+=U.Z/100|0,U.M+=U.Z%100,Ep(U)):Tp(U)}}function R(V,Q,q,U){for(var F=0,j=Q.length,P=q.length,et,at;F=0;)r[e]=e;return r}function oH(t,e){return t[e]}function lH(t){const e=[];return e.key=t,e}function cH(){var t=xe([]),e=Vo,r=qo,n=oH;function i(a){var s=Array.from(t.apply(this,arguments),lH),o,l=s.length,u=-1,h;for(const d of a)for(o=0,++u;o
/gi,eG=t=>Rf.test(t),rG=t=>t.split(Rf),nG=t=>t.replace(/#br#/g,"
"),Uk=t=>t.replace(Rf,"#br#"),iG=t=>{let e="";return t&&(e=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,e=e.replaceAll(/\(/g,"\\("),e=e.replaceAll(/\)/g,"\\)")),e},Mr=t=>!(t===!1||["false","null","0"].includes(String(t).trim().toLowerCase())),ja=function(t){let e=t;return t.indexOf("~")!==-1?(e=e.replace(/~([^~].*)/,"<$1"),e=e.replace(/~([^~]*)$/,">$1"),ja(e)):e},pe={getRows:JH,sanitizeText:ai,sanitizeTextOrArray:tG,hasBreaks:eG,splitBreaks:rG,lineBreakRegex:Rf,removeScript:zk,getUrl:iG,evaluate:Mr},If={min:{r:0,g:0,b:0,s:0,l:0,a:0},max:{r:255,g:255,b:255,h:360,s:100,l:100,a:1},clamp:{r:t=>t>=255?255:t<0?0:t,g:t=>t>=255?255:t<0?0:t,b:t=>t>=255?255:t<0?0:t,h:t=>t%360,s:t=>t>=100?100:t<0?0:t,l:t=>t>=100?100:t<0?0:t,a:t=>t>=1?1:t<0?0:t},toLinear:t=>{const e=t/255;return t>.03928?Math.pow((e+.055)/1.055,2.4):e/12.92},hue2rgb:(t,e,r)=>(r<0&&(r+=1),r>1&&(r-=1),r<1/6?t+(e-t)*6*r:r<1/2?e:r<2/3?t+(e-t)*(2/3-r)*6:t),hsl2rgb:({h:t,s:e,l:r},n)=>{if(!e)return r*2.55;t/=360,e/=100,r/=100;const i=r<.5?r*(1+e):r+e-r*e,a=2*r-i;switch(n){case"r":return If.hue2rgb(a,i,t+1/3)*255;case"g":return If.hue2rgb(a,i,t)*255;case"b":return If.hue2rgb(a,i,t-1/3)*255}},rgb2hsl:({r:t,g:e,b:r},n)=>{t/=255,e/=255,r/=255;const i=Math.max(t,e,r),a=Math.min(t,e,r),s=(i+a)/2;if(n==="l")return s*100;if(i===a)return 0;const o=i-a,l=s>.5?o/(2-i-a):o/(i+a);if(n==="s")return l*100;switch(i){case t:return((e-r)/o+(e