65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (25投票s)

2010年8月6日

CPOL

7分钟阅读

viewsIcon

60458

downloadIcon

2732

gnuplot 的 Web 化

dashboard_image3.png

引言

最近,在看到大量关于基于 Web 的可视化仪表板的广告后,我着手回答以下问题:是否有简单的方法可以为原生 C/C++ 应用程序免费提供简单的 Web 启用 2D/3D 可视化仪表板?

背景

我审查了许多基于 Web 的可视化解决方案(仪表板软件)。其中许多需要支持客户端技术,如 .NET、Flash、Java 等。在我们的特定情况下,我们不想让最终用户承担维护客户端框架的责任。另一个问题是这些技术是否适用于所有各种浏览器实现及其支持的平台。此外,许多解决方案在客户端渲染数据,这种方法对于大型数据集来说性能较差。当我说大型数据集时,我指的是数万个数据点。由于没有开发预算,最大的因素是成本。

那么,如何为原生应用程序提供零成本的 Web 启用 2D-3D 数据可视化解决方案?一种方法是编写自己的可嵌入式 Web 服务器,它可以获取 2D 或 3D 数据,将其渲染成图像,然后将渲染的图像发送到客户端浏览器进行可视化。这听起来可能是一项重大的开发工作,但事实并非如此。事实上,这里提供的解决方案仅用了不到 15 分钟就开发完成了。

解决方案

dashboard_image4.png

上图说明了我们的方法。在我们的方法中,我们使用了三个组件

  • 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 上

  1. 展开源代码/二进制包 dashboard.zip
  2. 切换目录到 deployment_directory/bin/Linux
  3. 设置库搜索路径变量 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/deployment_directory/lib/Linux

在 Windows 上

  1. 展开源代码/二进制包 dashboard.zip
  2. 打开 DOS/命令提示符并切换目录到 deployment_directory/bin/wintel

启动服务器

  1. 设置环境变量 GNUPLOT 指向 gnuplot 可执行文件的完全限定路径。注意:gnuplot 不包含在此文章中,但您可以在此处下载其二进制文件(http://sourceforge.net/projects/gnuplot/files
  2. deployment_directory/bin/platform 目录下,输入命令 "fsrv -p 8080" 来启动服务器
  3. 启动您喜欢的浏览器并输入 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>

您可以通过重命名索引文件(例如,附加一个 %)或删除它来单独显示每个绘图。执行此操作之前,您需要停止正在运行的服务器。隐藏或删除索引文件并启动服务器后,您可以从浏览器中单独选择每个文件 - 一次显示一个。

dashboard_image1.png

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

dashboard_image2.png

生成上述图像的 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。

© . All rights reserved.