D3 is a
powerful JavaScript library for visualizing data. In my opinion, this is just a paradise for a web developer, seemingly inaccessible to a Qt programmer. But the flexibility of the Qt framework allows you to integrate a web-frontend into a thick client using the
Qt Web Bridge mechanism. Such applications are called hybrid (
Qt Hybrid Apps ).
For JavaScript programmers, the good news is that their solutions can be easily integrated into Desktop applications, which could potentially increase the target audience of users of the libraries being developed (in any case, this is true for the world of Qt applications).
The screenshot below shows the
Dependency Wheel widget (Circle of Dependencies), which is drawn with the help of D3.js and the data and display is managed with the help of Qt. When a pointer is located above the corresponding arc, its interrelationships are “highlighted”, and the rest become semi-significant. This widget can be used to visualize various kinds of dependencies (for example, libraries).
')
Unlike the original JS solution, the diagram dynamically changes the size to fit the widget, and the data is set on the Qt side, rather than by loading a JSON file.
The article is more focused on Qt-programmers, but it can also be interesting for JS programmers.
The idea of ​​hybrid applications
The starting point of the idea of ​​hybrid applications is a number of limitations inherent in native applications:
- additional costs for the implementation and maintenance of client parts of the system;
- writing a unique user interface is sometimes a non-trivial task;
- inability to reuse existing web application APIs.
Hybrid applications solve these problems due to the fact that:
- deployment is done as in web applications;
- complex interfaces are created using web technologies (HTML, CSS, SVG, Canvas);
- reuse existing web application APIs
The hybrid application architecture suggests that
- Qt-application acts as a browser;
- user interaction and application logic is programmed in JavaScript;
- additional functionality is implemented in C ++ in the Qt-part of the application.
Thus, hybrid applications implement the idea of ​​a thin client.
One example of hybrid applications in Qt is
WebKit Image Analyzer .
In the example considered in the article, only part of the hybrid application approach will be used: component mapping due to JavaScript. In this case, all the necessary JS files will be located in the resources, as in the classic StandAlone application (standalone and does not require an intranet / Internet connection to work).
Project structure
The general structure of the project files is shown in the figure:
The base directory contains:
- d3viewer.h and d3viewer.cpp - the definition and implementation of the base viewer class D3Viewer , inherited from QWidget and encapsulating the interaction with QWebView .
- d3webpage.h and d3webpage.cpp - the definition and implementation of D3WebPage - a QWebPage successor (to support error messages and debug information in QWebPage :: javaScriptConsoleMessage ).
In the charts / pie directory:
- dependencywheelwidget.h and dependencywheelwidget.cpp - definition and implementation of the base viewer class, inherited from QWidget and encapsulating interaction with QWebView .
The resources directory is divided into two: js and html. The html contains the page that will be loaded in the widget and contains all the Qt interaction code, js contains the js files necessary for the DependencyWheel to work: common for D3 is d3.min.js and example-specific is d3.dependencyWheel. js.
Class diagram
In order to reduce the volume of the article using VisualParadigm, a simple diagram was created: it omits the attributes and methods of classes that are not directly related to the described technology. Details of the implementation can be found in the source code, link to which is located at the end of the article.
Qt <-> JS Interaction
In hybrid applications, a special object is embedded in JavaScript, the method call of which is processed on the Qt side:
void D3Viewer::addContextObject(const QString &name, QObject *object) { frame()->addToJavaScriptWindowObject( name, object ); //frame() - QWebFrame }
This method is called in the D3Viewer-derived classes in the constructor before loading the page:
addContextObject("api", this);
Further, the interaction of Qt with JS is possible through four mechanisms:
- By referring to the properties of the object.
for this you need to define a property in the object, which is a context object in JS ("api"):
public: Q_PROPERTY(float padding READ padding WRITE setPadding) public slots: float padding(); //getter void setPadding(const float padding); //setter
After that, you can access these properties from JS:
var chart = d3.chart.dependencyWheel() .width(api.width) .height(api.height) .margin(api.margin) .padding(api.padding);
- Signal processing Qt in JS, for this in JS, you must connect the appropriate handler function to the signal.
api.update.connect(redraw);
- Calling Qt slots in JS, for example, when processing a click on an element:
g.append("svg:path") .style("fill", fill) .style("stroke", fill) .attr("d", arc) .on("mouseover", fade(0.1)) .on("mouseout", fade(1)) .on('click', function (d) { api.itemClicked(packageNames[d.index]) } );
- By calling other Qt methods in JS, for this method declaration must be preceded by the Q_INVOKABLE macro.
Q_INVOKABLE void thisMethodIsInvokableInJavaScript();
- Direct execution of the JS code.
void D3Viewer::evaluateScript(const QString &script) { frame()->evaluateJavaScript(script); }
In the example, methods 4 and 5 are not used.
Debugging JavaScript in a hybrid application
To debug a JS application (like a DOM overview, view network activity, downloadable resources, etc.), you need to set the following property in the D3Viewer designer:
#ifdef QT_DEBUG // page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); #endif
Then at run time, the context menu item “Inspect” will be available in the context menu (right-click on QWebView).
Having chosen which window of Web-inspector is displayed.
In this window, on the Scripts tab, you can enable debugging.
Setting a breakpoint is done by clicking on the corresponding line number on the left.
PS In Qt 4.8.6 I never managed to intercept breakpoint. In 5.3.0 everything is working properly.
disadvantages
Any solution has both advantages and disadvantages. And in this case, D3.js will have to pay its price for the “prettiness”.
Source
The source code of the sample is available
here .
The example was built and run under Qt 4.8.6 and 5.3.0.