如何为您的原生应用程序添加简单的 Web 启用 2D/3D 仪表板






4.92/5 (25投票s)
gnuplot 的 Web 化

引言
最近,在看到大量关于基于 Web 的可视化仪表板的广告后,我着手回答以下问题:是否有简单的方法可以为原生 C/C++ 应用程序免费提供简单的 Web 启用 2D/3D 可视化仪表板?
背景
我审查了许多基于 Web 的可视化解决方案(仪表板软件)。其中许多需要支持客户端技术,如 .NET、Flash、Java 等。在我们的特定情况下,我们不想让最终用户承担维护客户端框架的责任。另一个问题是这些技术是否适用于所有各种浏览器实现及其支持的平台。此外,许多解决方案在客户端渲染数据,这种方法对于大型数据集来说性能较差。当我说大型数据集时,我指的是数万个数据点。由于没有开发预算,最大的因素是成本。
那么,如何为原生应用程序提供零成本的 Web 启用 2D-3D 数据可视化解决方案?一种方法是编写自己的可嵌入式 Web 服务器,它可以获取 2D 或 3D 数据,将其渲染成图像,然后将渲染的图像发送到客户端浏览器进行可视化。这听起来可能是一项重大的开发工作,但事实并非如此。事实上,这里提供的解决方案仅用了不到 15 分钟就开发完成了。
解决方案

上图说明了我们的方法。在我们的方法中,我们使用了三个组件
- gnuplot
- 一个嵌入式 Web 服务器
- bubble (插件)
让我们从 gnuplot 开始。gnuplot 是一个功能强大的原生绘图工具,已经存在多年,可在大多数平台上运行,并且文档齐全(有关于 gnuplot 的书籍)。它拥有一个强大的脚本语言,非常适合动态绘图,这是最基本的仪表板解决方案所必需的。此外,它还可以免费分发——无需支付版税。
由于我们要谈论的是为原生应用程序添加功能,因此最简单的方法是嵌入式。我们使用 Snorkel SDK 来开发嵌入式服务器组件。Snorkel (http://snorkelembedded.webs.com/) 是一个嵌入式/应用程序服务器-SDK,它包含一套强大的 API,旨在使此类解决方案的编码变得容易,而且与 gnuplot 一样,它也是免费的。
数据可视化是一个有用的工具,我们很可能会在未来的项目中重复使用它。为了实现可重用性,我们将渲染器编写为插件,即 Snorkel 术语中的 bubble。bubble 将充当我们的嵌入式 Web 服务器和 gnuplot 之间的接口。
对于不熟悉 bubble 这个词的人来说,bubbles 是共享对象,它们将一个或多个函数与文件扩展名(MIME 类型)、URI 和/或专有协议相关联。不要与 CGI 混淆,bubbles 在 Web 服务器内部运行,而不是作为一个单独的进程。
总的来说,bubbles 包含一个导出的函数 bubble_main
。作为加载过程的一部分,Snorkel 运行时会调用每个 bubble 的 bubble_main
例程来执行任何必要的初始化并识别 bubble 的功能目的。正是从这个例程开始,MIME 类型、URI 和用户定义的协议之间的关联才得以建立。
在这个项目中,我们将扩展名 gpng 与 gnuplot 脚本文件关联起来。gnuplot 脚本是一个文本文件,包含一组指令,gnuplot 执行这些指令以生成图像。我们使用 API 函数 snorkel_obj_set
在 bubble 的 bubble_main
例程中注册扩展名-MIME 类型。
. .
51 #define GNUENV "GNUPLOT"
52 #define TERMINAL "set terminal png transparent truecolor enhanced"
53 #define GNU_ARGS " -e "
54 #define EXTENSION "gpng"
55 #define TYPE FILE_PNG
56 #define MODE "wt"
.
.
241
242
243 byte_t SNORKEL_EXPORT
244 bubble_main (snorkel_obj_t server)
245 {
246 snorkel_obj_set (server,
247 snorkel_attrib_mime,
248 EXTENSION,
249 TYPE, encodingtype_binary, gnuplot_gpng);
250 return 1;
251 }
在调用 snorkel_obj_set
时,我们标识了扩展名、文件类型、编码以及负责渲染 gpng 类型 MIME 的例程。基本上,该调用指示服务器使用 gnuplot_gpng
函数渲染所有文件名扩展名为 .gpng 的文件。
gnuplot_gpng
函数处理对 gpng 类型 URI 的传入请求,并将它们的 URL 发送给 gnuplot 进行渲染/执行。gnuplot 将传入的 gpng URL 转换为临时的 PNG(便携式网络图形)文件,并且 gnuplot_gpng
函数在删除这些文件之前将它们流式传输回请求的客户端浏览器。
51 #define GNUENV "GNUPLOT"
52 #define TERMINAL "set terminal png transparent truecolor enhanced"
53 #define GNU_ARGS " -e "
54 #define EXTENSION "gpng"
55 #define TYPE FILE_PNG
56 #define MODE "wt"
57
58 #if !defined(WIN32) && !defined(WIN64)
59 #define _popen popen
60 #define _pclose pclose
61 #define _dir_sep '/'
62 #define sprintf_s snprintf
63 #else
64 #define _dir_sep '\\'
65 #endif
.
.
.
117
118 /**
119 *
120 * gnuplot_gpng
121 * called by server to render gpng (gnuplot script) files as
122 * png
123 *
124 **/
125 call_status_t
126 gnuplot_gpng (snorkel_obj_t http,
127 snorkel_obj_t connection, char *pszurl)
128 {
129 unsigned int canary[] = _CANARY_VALUE;
130 char szwork_dir[512];
131 char sztmpname[256];
132 char szcommand[1024];
133 char *gnuplot = 0;
134 char *psz = 0;
135 FILE *fd = 0;
136 FILE *gnupipe = 0;
137 char *psztmp = 0;
138 char *pszfile = 0;
139
140
141 /*
142 * set our working directory, the parent
143 * directory of the URL
144 *
145 */
146 sztmpname[0] = 0;
147 szcommand[0] = 0;
148 szwork_dir[0] = 0;
149 strncat (szwork_dir, pszurl, sizeof (szwork_dir));
150 pszfile = strrchr (szwork_dir, _dir_sep);
151 if (pszfile)
152 {
153 *pszfile = 0;
154 pszfile++;
155 }
156 else
157 {
158 _stkchk;
159 return ERROR_STRING ("could not determine working directory\r\n");
160 }
161
162
163 /*
164 * get the location of gnuplot
165 */
166 gnuplot = get_pgnuplot ();
167 if (!gnuplot)
168 {
169 _stkchk;
170 return ERROR_STRING ("GNUPLOT not set\r\n");
171 }
172
173 /*
174 *
175 * create a temporary file to receive
176 * our png data
177 *
178 */
179 tmpnam (sztmpname);
180
181 psztmp =
182 (sztmpname[0] == _dir_sep) ? &sztmpname[1] : sztmpname;
183
184 /*
185 *
186 * crank out the command line
187 *
188 */
189 sprintf_s (szcommand, sizeof (szcommand),
190 "%s\"cd '%s';%s;set output '%s';load '%s'\"",
191 gnuplot, szwork_dir, TERMINAL, psztmp, pszfile);
192
193 #if defined(_DEBUG) || defined(DEBUG)
194 printf("%s\n",szcommand);
195 #endif
196
197 /*
198 *
199 * run our gnuplot session
200 *
201 */
202 gnupipe = _popen (szcommand, MODE);
203 if (!gnupipe)
204 {
205 _stkchk;
206 return
207 ERROR_STRING
208 ("error: could not open gnuplot pipe\r\n");
209 }
210 fflush (gnupipe);
211 _pclose (gnupipe);
212
213
214 /*
215 *
216 * all done, stream the plot to
217 * the browser
218 *
219 */
220 sprintf_s (szcommand, sizeof (szcommand), "%s%s", szwork_dir,
221 sztmpname);
222
223
224 if (snorkel_file_stream (connection,
225 szcommand,
226 0,
227 SNORKEL_BINARY) == SNORKEL_ERROR)
228 {
229 remove (szcommand); /* remove the temporary file */
230 _stkchk;
231 return ERROR_STRING ("could not stream data\r\n");
232 }
233
234 /* this is it, we are done... it doesn't get much easier
235 than that */
236 remove (szcommand); /* remove the temporary file */
237 _stkchk;
238
239 return HTTP_SUCCESS;
240 }
让我们仔细看看附带的源代码。我们从第 146-160 行开始,从中提取工作目录和资源名称。接下来,我们调用 get_gnuplot
函数来获取执行 gnuplot 的命令。在第 173-183 行,我们创建了一个临时文件名,后来用它来生成 PNG 文件。
接下来,在第 184-192 行,我们构建命令行并通过打开的管道调用 gnuplot,第 202-211 行。gnuplot 执行脚本并将其渲染到我们的临时文件作为 PNG 图像。然后,我们获取 gnuplot 生成的 PNG 文件,并在第 224-232 行将其流式传输回请求的客户端。完成后,我们删除临时文件并返回成功。
由于我们的 bubble 可以与任何基于 Snorkel 的嵌入式解决方案一起工作,我们从我们之前的文章之一中重用了服务器来测试 bubble。
运行示例
运行示例
在 Linux 上
- 展开源代码/二进制包 dashboard.zip
- 切换目录到 deployment_directory/bin/Linux
- 设置库搜索路径变量 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/deployment_directory/lib/Linux
在 Windows 上
- 展开源代码/二进制包 dashboard.zip
- 打开 DOS/命令提示符并切换目录到 deployment_directory/bin/wintel
启动服务器
- 设置环境变量 GNUPLOT 指向 gnuplot 可执行文件的完全限定路径。注意:gnuplot 不包含在此文章中,但您可以在此处下载其二进制文件(http://sourceforge.net/projects/gnuplot/files)
- 在 deployment_directory/bin/platform 目录下,输入命令 "fsrv -p 8080" 来启动服务器
- 启动您喜欢的浏览器并输入 https://:8080
deployment_directory/bin/platform 中的索引文件引用了位于同一目录中的四个单独的 gpng 文件,并将它们显示在两乘二的表格中。这些文件来自 gnuplot 示例,并为此项目进行了修改。
<html><body>
<table width="90%" cellpadding="0">
<tr><td><img src="finance.gpng" alt=""></td>
<td><img src="finance2.gpng" alt=""></td>
</tr>
<tr><td><img src="iterate.gpng" alt=""></td>
<td><img src="transparent.gpng" alt=""></td>
</tr>
</table></body></html>
您可以通过重命名索引文件(例如,附加一个 %)或删除它来单独显示每个绘图。执行此操作之前,您需要停止正在运行的服务器。隐藏或删除索引文件并启动服务器后,您可以从浏览器中单独选择每个文件 - 一次显示一个。

例如,在浏览器中选择文件 finance2.gpng 会生成以下图像

生成上述图像的 gnuscript 脚本如下所示
set label 1 "Acme Widgets" at graph 0.5, graph 0.9 center front
set label 2 "Courtesy of Bollinger Capital" at graph 0.01, 0.07
set label 3 " www.BollingerBands.com" at graph 0.01, 0.03
set logscale y
set yrange [75:105]
set ytics (105, 100, 95, 90, 85, 80)
set xrange [50:253]
set grid
set lmargin 9
set rmargin 2
set format x ""
set xtics (66, 87, 109, 130, 151, 174, 193, 215, 235)
set multiplot
set title "Change to Bollinger Boxes"
set size 1, 0.7
set origin 0, 0.3
set bmargin 0
set ylabel "price" offset 1
plot 'finance.dat' using 0:3:3:($2>$5?$2:$5):($2>$5?$2:$5) notitle with candlesticks lt 3, \
'finance.dat' using 0:($2<$5?$5:1/0):($2<$5?$5:1/0):
($2<$5?$2:1/0):($2<$5?$2:1/0) notitle with candlesticks lt 2, \
'finance.dat' using 0:($2>$5?$2:1/0):($2>$5?$2:1/0):
($2>$5?$5:1/0):($2>$5?$5:1/0) notitle with candlesticks lt 1, \
'finance.dat' using 0:($2<$5?$2:$5):($2<$5?$2:$5):4:4 notitle with candlesticks lt 3, \
'finance.dat' using 0:9 notitle with lines lt 3, \
'finance.dat' using 0:10 notitle with lines lt 1, \
'finance.dat' using 0:11 notitle with lines lt 2, \
'finance.dat' using 0:8 axes x1y2 notitle with lines lt 4
unset label 1
unset label 2
unset label 3
unset title
set bmargin
set format x
set size 1.0, 0.3
set origin 0.0, 0.0
set tmargin 0
unset logscale y
set autoscale y
set format y "%1.0f"
set ytics 500
set xtics ("6/03" 66, "7/03" 87, "8/03" 109, "9/03" 130,
"10/03" 151, "11/03" 174, "12/03" 193, "1/04" 215, "2/04" 235)
set ylabel "volume (0000)" offset 1
plot 'finance.dat' using 0:($6/10000) notitle with impulses lt 3, \
'finance.dat' using 0:($7/10000) notitle with lines lt 1
需要注意的是,右键单击 gpng 文件并选择“另存为”也会调用 gnuplot_gpng
函数。因此,下载的不是脚本,而是 PNG 图像。
结论
这个项目已经搁置了相当长一段时间,因为我最初的开发估计很大,这是由于对复杂性的误解。实际上,这个项目最终非常容易。
gnuplot 可以绘制几乎任何东西,并且是绘制科学数据的终极工具。我期待在这种形式下将其与其他原生解决方案一起使用。
对于那些可能担心使用 Snorkel 的人来说,它已经在过去几年中进行了开发和测试,并且是其他应用程序/嵌入式服务器解决方案的一个很好的替代方案。如果您在使用 Snorkel SDK、获取 Snorkel SSL 二进制文件、其他平台支持或功能请求方面有任何疑问,请随时通过 wcapers64@gmail.com 与我联系。由于 Snorkel 运行时中尚未公开的元素,它们是一个更大且尚未发布的项目的组成部分,该项目目前正在开发中,因此它是一个闭源解决方案。这是因为我还没有确定是否会免费提供该项目提供的解决方案。但是,作为 CodeProject 会员,我理解回馈社区的价值,并将继续免费提供基本的 Snorkel 二进制文件,以便其他人可以受益于其易用性和性能。请访问 http://snorkelembedded.webs.com/ 获取最新版本的 Snorkel SDK。