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





0/5 (0投票)
我将向您展示如何使用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
}
};
输出
这就是您将看到的内容——我没有对图表进行太多自定义——您可以自己尝试一下 :-)
总结和最后的思考
您可以将这种方法用于不仅仅是绘图——我对这种事情最喜欢的白日梦是内存管理对象,您可以通过Web工具查看和编辑它们,而不是编写UI。特定对象的状况,诸如此类的事情。我很想知道您的想法。下次我将从序列化中休息一下,并写一些不同的东西。
感谢阅读。
本文最初发布在这里:https://dabblingseriously.wordpress.com/