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

使用 JSON 公开 C++ 的 Highcharts 数据

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2015 年 7 月 18 日

CPOL

4分钟阅读

viewsIcon

12527

downloadIcon

151

我将向您展示如何使用JSON.h将图表数据导出到网页并在浏览器中绘制图表。

引言

今天我的编码项目是想将我之前的两个项目结合起来。这次我将像以前一样运行一个web服务器,但是今天我将向您展示如何使用JSON.h将图表数据导出到网页并在浏览器中绘制图表。

我将使用的东西

Highcharts

新的网络充斥着各种用于各种新兴应用的JavaScript框架,而人们对此非常兴奋的应用之一就是图表绘制。这个框架通常易于下载并安装到您的网站上(您只需下载文件即可)。您可以在此处阅读Highcharts的介绍:Highcharts

JSON.h

JSON.h是我之前写过的一个项目,您可以在这里查看其源代码:https://github.com/PhillipVoyle/json_h。它是一个用于C++和JSON的序列化框架——如果您喜欢或不喜欢它,请告诉我。我今天将在我的示例中使用它。

boost.asio

Boost是领先的可移植非标准可移植C++库的来源,他们的目标是领先于技术发展,并在标准化之前证明这些技术。今天我将使用他们的异步I/O库,该库对异步操作具有出色的支持,您可以使用此系统编写非常高吞吐量的网络应用程序。如果您有兴趣了解更多关于boost.asio的信息,可以在这里阅读:https://boost.ac.cn/doc/libs/1_58_0/doc/html/boost_asio.html

网页源代码

在深入项目之前,我将向您展示页面的源代码。出于我的目的,我将调用在我的工作站上的控制台应用程序中托管的Web服务器,因此JSON数据将从localhost获取,但对于大多数应用程序,您将希望在此处公开您的域名。好的,就是这样

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>
      Chart
    </title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script src="jquery-1.9.1.js" type="text/javascript"></script>
    <script src="highcharts.js" type="text/javascript"></script>
    <script src="exporting.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function() {
            $.getJSON('https://:8080/Chart.json',
                function(data){
                  $('#container').highcharts(data);
                });
        });
    </script>
  </head>
  <body>
    <div id="container" style="min-width: 800px; height: 800px; margin: 0 auto"></div>
  </body>
</html>

很好!好的,它实际上只是一个基本网页的样板,其中包含一个函数:从这里获取我的图表数据。您可以提供静态页面,但我将提供从表示图表数据的对象序列化的JSON数据。您可以动态生成它,但我将为了清晰起见对其进行硬编码。

C++ Web服务器

正如我之前所说,我将使用boost.asio来提供绘制图表所需的JSON文档。您可以使用任何类型的数据来实现这一点,但系统操作或服务状态的实时统计数据可能是最有用的内容。网页是公开管理对象的便捷位置。无论如何,这是应用程序的源代码。

图表类和JSON.h存根

我在这里编写的类旨在构成图表的紧凑表示,然后将其呈现为JSON,并由上面的网页收集。就像我之前说的那样,我现在正在对图表数据进行硬编码,但您不必这样做。数据来自新西兰国家水文与大气研究所。

class ChartText
{
public:
   string text;
   int x;
};

class ChartXAxis
{
public:
   std::vector<std::string> categories;
};

class PlotLine
{
public:
   int value;
   int width;
   string color;
};

class ChartYAxis
{
public:
   ChartText title;
   std::vector<PlotLine> plotLines;
};

class Legend
{
public:
   string layout;
   string align;
   string verticalAlign;
   int borderWidth;
};

class DataSeries
{
public:
   string name;
   std::vector<float> data;
};

class ChartData
{
public:
   ChartText title;
   ChartText subtitle;
   ChartXAxis xAxis;
   ChartYAxis yAxis;
   Legend legend;
   std::vector<DataSeries> series;
};

BEGIN_CLASS_DESCRIPTOR(ChartText)
   CLASS_DESCRIPTOR_ENTRY(text)
   CLASS_DESCRIPTOR_ENTRY(x)
END_CLASS_DESCRIPTOR()

BEGIN_CLASS_DESCRIPTOR(ChartXAxis)
   CLASS_DESCRIPTOR_ENTRY(categories)
END_CLASS_DESCRIPTOR()

BEGIN_CLASS_DESCRIPTOR(PlotLine)
   CLASS_DESCRIPTOR_ENTRY(value)
   CLASS_DESCRIPTOR_ENTRY(width)
   CLASS_DESCRIPTOR_ENTRY(color)
END_CLASS_DESCRIPTOR()

BEGIN_CLASS_DESCRIPTOR(ChartYAxis)
   CLASS_DESCRIPTOR_ENTRY(title)
   CLASS_DESCRIPTOR_ENTRY(plotLines)
END_CLASS_DESCRIPTOR()

BEGIN_CLASS_DESCRIPTOR(Legend)
   CLASS_DESCRIPTOR_ENTRY(layout)
   CLASS_DESCRIPTOR_ENTRY(align)
   CLASS_DESCRIPTOR_ENTRY(verticalAlign)
   CLASS_DESCRIPTOR_ENTRY(borderWidth)
END_CLASS_DESCRIPTOR()

BEGIN_CLASS_DESCRIPTOR(DataSeries)
   CLASS_DESCRIPTOR_ENTRY(name)
   CLASS_DESCRIPTOR_ENTRY(data)
END_CLASS_DESCRIPTOR()

BEGIN_CLASS_DESCRIPTOR(ChartData)
   CLASS_DESCRIPTOR_ENTRY(title)
   CLASS_DESCRIPTOR_ENTRY(subtitle)
   CLASS_DESCRIPTOR_ENTRY(xAxis)
   CLASS_DESCRIPTOR_ENTRY(yAxis)
   CLASS_DESCRIPTOR_ENTRY(legend)
   CLASS_DESCRIPTOR_ENTRY(series)
END_CLASS_DESCRIPTOR()

ChartData g_chartData = {
   {"Mean Monthly Temperatures (°C)", -20},
   {"Source: www.niwa.co.nz", 0},
   {{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}},
   {{"Temperature (°C)"}, {{0, 1, "#808080"}}},
   {"vertical", "right", "middle", 0},
   {
      {"Auckland", {19.1,19.7,18.4,16.1,14.0,11.8,10.9,11.3,12.7,14.2,15.7,17.8}},
      {"Tauranga", {19.4,19.6,18.0,15.5,13.2,10.8,10.2,10.7,12.3,13.9,15.8,18.0}},
      {"Hamilton", {18.4,18.8,17.1,14.5,11.9,9.5,8.9,9.8,11.6,13.2,14.9,16.9}},
      {"Taupo", {17.0,17.1,14.9,12.0,9.4,7.4,6.5,7.2,9.2,11.1,13.1,15.6}},
      {"Wellington", {16.9,17.2,15.8,13.7,11.7,9.7,8.9,9.4,10.8,12.0,13.5,15.4}},
      {"Christchurch", {17.5,17.2,15.5,12.7,9.8,7.1,6.6,7.9,10.3,12.2,14.1,16.1}},
      {"Dunedin", {15.3,15.0,13.7,11.7,9.3,7.3,6.6,7.7,9.5,10.9,12.4,13.9}},
      {"Antarctica, Scott Base", {-4.5,-11.6,-20.6,-24.2,-25.7,-26.0,-28.7,-30.0,-27.6,-20.8,-11.3,-4.5}}
   }
};

头文件和应用程序代码

好的,我将使用几周前编写的类似Web服务器,但我已对其进行了改进,以使用两个方便的功能,即boost::bind和std::shared_from_this()。如果您阅读了我之前的帖子,您会看到我在这里不再需要使用一些lambda表达式。首先,我将向您展示头文件和响应代码。如下所示,只有一个合理的URL /Chart.json,它以text/json mime类型返回图表数据。另一个关注点是保持活动行为,只要浏览器可用,它就允许继续使用会话。

class http_headers
{
   std::string method;
   std::string url;
   std::string version;
   
   int nRequests = 0;
   
   typedef std::map<std::string, std::string, nocase_less> map_t;
   map_t headers;

public:
   http_headers()
   {
   }
   
   std::string get_response()
   {
      std::stringstream ssOut;

      if((url == "/Chart.json") && (method == "GET"))
      {
         g_currentStatus.hitCount ++;
         std::string sJSON = ToJSON(g_chartData);
         
         ssOut << "HTTP/1.1 200 OK" << std::endl;
         ssOut << "content-type: text/json" << std::endl;
         ssOut << "content-length: " << sJSON.length() << std::endl;
         ssOut << std::endl;
         ssOut << sJSON;
      }
      else
      {
         std::string sHTML = "<html><body><h1>404 Not Found</h1><p>There's nothing here.</p></body></html>";
         ssOut << "HTTP/1.1 404 Not Found" << std::endl;
         ssOut << "content-type: text/html" << std::endl;
         ssOut << "content-length: " << sHTML.length() << std::endl;
         ssOut << std::endl;
         ssOut << sHTML;
      }
      return ssOut.str();
   }
   
   int content_length()
   {
      auto request = headers.find("content-length");
      if(request != headers.end())
      {
         std::stringstream ssLength(request->second);
         int content_length;
         ssLength >> content_length;
         return content_length;
      }
      return 0;
   }
   
   bool keepAlive()
   {
      auto connection = headers.find("connection");
      if(connection != headers.end())
      {
         return nocase_eq(connection->second, "keep-alive");
      }
      return false;
   }
   
   void on_read_header(const std::string& line)
   {
      std::stringstream ssHeader(line);
      std::string headerName;
      std::getline(ssHeader, headerName, ':');
      boost::algorithm::trim_left(headerName);
      boost::algorithm::trim_right(headerName);
      
      std::string value;
      std::getline(ssHeader, value);
      boost::algorithm::trim_left(value);
      boost::algorithm::trim_right(value);
      headers[headerName] = value;
   }
   
   void on_read_request_line(const std::string& line)
   {
      std::stringstream ssRequestLine(line);
      ssRequestLine >> method;
      ssRequestLine >> url;
      ssRequestLine >> version;
      
      std::cout << "request for resource: " << url << std::endl;
   }
   
   void clear()
   {
      method = "";
      url = "";
      version = "";
      headers.clear();
   }
   
   void on_read_line(const std::string& line)
   {
      if((method == "") || (url == "")  || (version == ""))
      {
         clear();
         on_read_request_line(line);
      }
      else
      {
         on_read_header(line);
      }
   }
};

入口点和会话类

同样,您可以在下面看到我已经改进了上周类的行为,本周它简短得多,您会注意到使用了shared_from_this()。下面的代码启动一个会话并等待连接。当连接到达时,它会启动一个新的连接,并开始逐行与已连接的方交互。

class session: public enable_shared_from_this<session>
{
public:
   http_headers headers;
   tcp::socket socket;
   boost::asio::streambuf buf;
   
   session():socket(iosrv)
   {
      std::cout << "new session (idle)" << std::endl;
   }
   ~session()
   {
      std::cout << "destroy session" << std::endl;
   }
   
   void complete_write(
      const boost::system::error_code& ec,
      std::size_t bytes,
      shared_ptr<std::string> buf)
   {
      if(headers.keepAlive())
      {
         headers.clear();
         begin_read();
      }
   }
   
   void begin_write(std::string string)
   {
      auto str = std::make_shared<std::string>(string);
      async_write(
         socket,
         boost::asio::buffer(*str),
         bind(
            &session::complete_write,
            shared_from_this(),
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred,
            str));
   }
   
   void complete_read(const boost::system::error_code& ec, std::size_t bytes)
   {
      if(bytes > 0)
      {
         std::string line, ignore;
         std::istream stream {&buf};
         std::getline(stream, line, '\r');
         std::getline(stream, ignore, '\n');
         if(line == "")
         {
            begin_write(headers.get_response());
         }
         else
         {
            headers.on_read_line(line);
            begin_read();
         }
      }
   }
   
   void begin_read()
   {
      async_read_until(
         socket,
         buf,
         '\n',
         bind(
            &session::complete_read,
            shared_from_this(),
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
   }
   
   static void initiate()
   {
      auto sesh = make_shared<session>();
      sesh->begin_accept(acceptor);
   }
   
   void complete_accept(const boost::system::error_code& ec)
   {
      std::cout << "accept session" << std::endl;
      initiate();
      begin_read();
   }
   
   void begin_accept(tcp::acceptor& acceptor)
   {
      acceptor.async_accept(socket,
         bind(
            &session::complete_accept,
            shared_from_this(),
            boost::asio::placeholders::error));
   }
};

int main(int argc, const char * argv[]) {
   acceptor.listen();
   session::initiate();
   iosrv.run();
   return 0;
}

其余部分在顶部

这是文件的其余部分,列在顶部——如果您想自己编写代码,您也需要这些东西,但它们并不是真正必不可少的。这里的内容是包含文件和一些方便的东西,用于使我的映射区分大小写但不区分大小写。

#include <boost/asio.hpp>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/algorithm/string.hpp>
#include <json/JSON.h>

#include <memory>
#include <vector>

using namespace boost::system;
using namespace boost::asio;
using namespace boost::asio::ip;
using namespace std;
using namespace boost::asio::placeholders;

io_service iosrv;
tcp::endpoint ep(ip::address::from_string("127.0.0.1"), 8080);
tcp::acceptor acceptor(iosrv, ep);

bool nocase_compare_char(const unsigned char& c1, const unsigned char& c2)
{
   return tolower (c1) < tolower (c2);
}

bool nocase_eq(const std::string& s1, const std::string& s2)
{
   return std::lexicographical_compare
     (s1.begin (), s1.end (),   // source range
     s2.begin (), s2.end (),   // dest range
     [](unsigned char a , unsigned char b){return tolower(a) == tolower(b);});  // comparison
}

bool nocase_compare(const std::string& s1, const std::string& s2)
{
   bool bResult =  std::lexicographical_compare
     (s1.begin (), s1.end (),   // source range
     s2.begin (), s2.end (),   // dest range
     nocase_compare_char);  // comparison
   
   return bResult;
}

struct nocase_less : std::binary_function<std::string, std::string, bool>
{
   bool operator() (const std::string & s1, const std::string & s2) const
   {
      return nocase_compare(s1, s2);  // comparison
   }
};

输出

这就是您将看到的内容——我没有对图表进行太多自定义——您可以自己尝试一下 :-)

Screen Shot 2015-07-18 at 6.00.24 pm

总结和最后的思考

您可以将这种方法用于不仅仅是绘图——我对这种事情最喜欢的白日梦是内存管理对象,您可以通过Web工具查看和编辑它们,而不是编写UI。特定对象的状况,诸如此类的事情。我很想知道您的想法。下次我将从序列化中休息一下,并写一些不同的东西。

感谢阅读。

本文最初发布在这里:https://dabblingseriously.wordpress.com/

© . All rights reserved.