Feature #4358 » 0001-Add-support-for-zoom-pan-map-area-updates.patch
src/Wt/Chart/WCartesianChart | ||
---|---|---|
* \note Client side interaction is only available if the chart is drawn
|
||
* on an HTML canvas. This is the default rendering method on modern browsers.
|
||
* \note Some features are currently not supported in interactive mode:
|
||
* - Tooltips are not zoomed or panned along with the chart
|
||
* - Axes set at ZeroValue position will not always be drawn correctly.
|
||
* They may be clipped off outside of the chart area, and when zooming,
|
||
* the axis ticks will change size.
|
||
... | ... | |
WTransform combinedTransform() const;
|
||
WTransform combinedTransform(WTransform xTransform, WTransform yTransform) const;
|
||
void setInitialZoomAndPan();
|
||
void addAreaMask();
|
||
WPointF hv(double x, double y, double width) const;
|
||
WPointF inverseHv(double x, double y, double width) const;
|
src/Wt/Chart/WCartesianChart.C | ||
---|---|---|
const WModelIndex& xIndex,
|
||
WAbstractArea *area)
|
||
{
|
||
if (areas().empty())
|
||
addAreaMask();
|
||
addArea(area);
|
||
}
|
||
... | ... | |
}
|
||
}
|
||
void WCartesianChart::addAreaMask()
|
||
{
|
||
WRectF all = hv(WRectF(0, 0, width_, height_));
|
||
WRectF chart = hv(chartArea_);
|
||
std::vector<WRectF> rects;
|
||
rects.push_back(WRectF(all.topLeft(), WPointF(all.right(), chart.top())));
|
||
rects.push_back(WRectF(WPointF(all.left(), chart.bottom()), all.bottomRight()));
|
||
rects.push_back(WRectF(WPointF(all.left(), chart.top()), chart.bottomLeft()));
|
||
rects.push_back(WRectF(chart.topRight(), WPointF(all.right(), chart.bottom())));
|
||
for (int i = 0; i < rects.size(); ++i) {
|
||
if (rects[i].height() > 0 && rects[i].width() > 0) {
|
||
WRectArea *rect = new WRectArea(rects[i]);
|
||
rect->setHole(true);
|
||
rect->setTransformable(false);
|
||
addArea(rect);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
src/Wt/WAbstractArea | ||
---|---|---|
*/
|
||
bool isHole() const { return hole_; }
|
||
void setTransformable(bool transformable);
|
||
bool isTransformable() const { return transformable_; }
|
||
/*! \brief Sets a link.
|
||
*
|
||
* By setting a link, the area behaves like a WAnchor.
|
||
... | ... | |
};
|
||
bool hole_;
|
||
bool transformable_;
|
||
AnchorImpl *anchor_;
|
||
void createAnchorImpl();
|
||
... | ... | |
WAbstractArea();
|
||
virtual bool updateDom(DomElement& element, bool all);
|
||
virtual std::string updateAreaCoordsJS() = 0;
|
||
void repaint();
|
||
std::string jsRef() { return impl()->jsRef(); }
|
||
private:
|
||
WInteractWidget *impl();
|
||
... | ... | |
friend class WImage;
|
||
friend class Impl::AreaWidget;
|
||
friend class WPaintedWidget;
|
||
};
|
||
}
|
src/Wt/WAbstractArea.C | ||
---|---|---|
WAbstractArea::WAbstractArea()
|
||
: impl_(new Impl::AreaWidget(this)),
|
||
hole_(false),
|
||
transformable_(true),
|
||
anchor_(0)
|
||
{ }
|
||
... | ... | |
repaint();
|
||
}
|
||
void WAbstractArea::setTransformable(bool transformable)
|
||
{
|
||
transformable_ = transformable;
|
||
repaint();
|
||
}
|
||
void WAbstractArea::setLink(const WLink& link)
|
||
{
|
||
createAnchorImpl();
|
src/Wt/WCircleArea | ||
---|---|---|
protected:
|
||
virtual bool updateDom(DomElement& element, bool all);
|
||
virtual std::string updateAreaCoordsJS() WT_CXX11ONLY(override);
|
||
};
|
||
}
|
src/Wt/WCircleArea.C | ||
---|---|---|
return WAbstractArea::updateDom(element, all);
|
||
}
|
||
std::string WCircleArea::updateAreaCoordsJS()
|
||
{
|
||
std::stringstream coords;
|
||
coords << "[" << jsRef() << ",["
|
||
<< x_ << ',' << y_ << ',' << r_ << "]]";
|
||
return coords.str();
|
||
}
|
||
}
|
src/Wt/WImage | ||
---|---|---|
*/
|
||
EventSignal<>& imageLoaded();
|
||
void setTargetJS(std::string targetJS);
|
||
virtual std::string updateAreasJS();
|
||
virtual std::string setAreaCoordsJS();
|
||
private:
|
||
static const char *LOAD_SIGNAL;
|
||
... | ... | |
WLink imageLink_;
|
||
Impl::MapWidget *map_;
|
||
std::bitset<3> flags_;
|
||
std::string targetJS_;
|
||
void resourceChanged();
|
||
... | ... | |
virtual void getDomChanges(std::vector<DomElement *>& result,
|
||
WApplication *app);
|
||
virtual void updateDom(DomElement& element, bool all);
|
||
virtual void defineJavaScript();
|
||
virtual void render(WFlags<RenderFlag> flags);
|
||
virtual DomElementType domElementType() const;
|
||
virtual void propagateRenderOk(bool deep);
|
||
virtual std::string updateAreaCoordsJSON() const;
|
||
friend class WLabel;
|
||
friend class Impl::MapWidget;
|
||
static std::vector<WAbstractArea *> noAreas_;
|
||
};
|
src/Wt/WImage.C | ||
---|---|---|
#include "DomElement.h"
|
||
#ifndef WT_DEBUG_JS
|
||
#include "js/WImage.min.js"
|
||
#endif
|
||
namespace Wt {
|
||
LOGGER("WImage");
|
||
... | ... | |
MapWidget() { }
|
||
protected:
|
||
virtual void render(WFlags<RenderFlag> flags)
|
||
{
|
||
WContainerWidget::render(flags);
|
||
WImage *parent_img = dynamic_cast<WImage *>(parent());
|
||
if (!parent_img->targetJS_.empty())
|
||
parent_img->doJavaScript(parent_img->setAreaCoordsJS());
|
||
}
|
||
virtual void updateDom(DomElement& element, bool all)
|
||
{
|
||
if (all)
|
||
... | ... | |
WInteractWidget::propagateRenderOk(deep);
|
||
}
|
||
void WImage::defineJavaScript()
|
||
{
|
||
WApplication *app = WApplication::instance();
|
||
LOAD_JAVASCRIPT(app, "js/WImage.js", "WImage", wtjs1);
|
||
WStringStream ss;
|
||
ss << "new " WT_CLASS ".WImage("
|
||
<< app->javaScriptClass() << "," << jsRef() << "," << targetJS_ << ");";
|
||
doJavaScript(ss.str());
|
||
}
|
||
void WImage::render(WFlags<RenderFlag> flags)
|
||
{
|
||
if (flags & RenderFull) {
|
||
if (!targetJS_.empty())
|
||
defineJavaScript();
|
||
}
|
||
WInteractWidget::render(flags);
|
||
}
|
||
DomElementType WImage::domElementType() const
|
||
{
|
||
return map_ ? DomElement_SPAN : DomElement_IMG;
|
||
}
|
||
void WImage::setTargetJS(std::string targetJS)
|
||
{
|
||
targetJS_ = targetJS;
|
||
}
|
||
std::string WImage::updateAreasJS()
|
||
{
|
||
WStringStream ss;
|
||
if (!targetJS_.empty()) {
|
||
ss << "jQuery.data(" << jsRef() << ", 'obj').updateAreas();";
|
||
}
|
||
return ss.str();
|
||
}
|
||
std::string WImage::setAreaCoordsJS()
|
||
{
|
||
WStringStream ss;
|
||
if (!targetJS_.empty()) {
|
||
ss << "jQuery.data(" << jsRef() << ", 'obj').setAreaCoordsJSON("
|
||
<< updateAreaCoordsJSON() << ");";
|
||
}
|
||
return ss.str();
|
||
}
|
||
std::string WImage::updateAreaCoordsJSON() const
|
||
{
|
||
WStringStream js;
|
||
const std::vector<WAbstractArea *> &areas = this->areas();
|
||
if (!areas.empty()) {
|
||
for (int i = 0; i < areas.size(); ++i) {
|
||
if (areas[i]->isTransformable()) {
|
||
if (js.empty())
|
||
js << "[";
|
||
else
|
||
js << ",";
|
||
js << areas[i]->updateAreaCoordsJS();
|
||
}
|
||
}
|
||
js << "]";
|
||
}
|
||
return js.str();
|
||
}
|
||
}
|
src/Wt/WPaintedWidget.C | ||
---|---|---|
#include <boost/lexical_cast.hpp>
|
||
#include "Wt/WAbstractArea"
|
||
#include "Wt/WApplication"
|
||
#include "Wt/WCanvasPaintDevice"
|
||
#include "Wt/WEnvironment"
|
||
... | ... | |
WStringStream ss;
|
||
ss << widget_->objJsRef() << ".repaint=function(){";
|
||
ss << canvasDevice->recordedJs_.str();
|
||
if (widget_->areaImage_) {
|
||
widget_->areaImage_->setTargetJS(widget_->objJsRef());
|
||
ss << widget_->areaImage_->updateAreasJS();
|
||
}
|
||
ss << "};";
|
||
el->callJavaScript(ss.str());
|
||
}
|
||
... | ... | |
WStringStream ss;
|
||
ss << widget_->objJsRef() << ".repaint=function(){";
|
||
ss << canvasDevice->recordedJs_.str();
|
||
if (widget_->areaImage_) {
|
||
widget_->areaImage_->setTargetJS(widget_->objJsRef());
|
||
ss << widget_->areaImage_->updateAreasJS();
|
||
}
|
||
ss << "};";
|
||
el->callJavaScript(ss.str());
|
||
}
|
src/Wt/WPolygonArea | ||
---|---|---|
protected:
|
||
virtual bool updateDom(DomElement& element, bool all);
|
||
virtual std::string updateAreaCoordsJS() WT_CXX11ONLY(override);
|
||
};
|
||
}
|
src/Wt/WPolygonArea.C | ||
---|---|---|
return WAbstractArea::updateDom(element, all);
|
||
}
|
||
std::string WPolygonArea::updateAreaCoordsJS()
|
||
{
|
||
std::stringstream coords;
|
||
coords << "[" << jsRef() << ",[";
|
||
for (unsigned i = 0; i < points_.size(); ++i) {
|
||
if (i != 0)
|
||
coords << ',';
|
||
coords << points_[i].x() << ',' << points_[i].y();
|
||
}
|
||
coords << "]]";
|
||
return coords.str();
|
||
}
|
||
}
|
src/Wt/WRectArea | ||
---|---|---|
protected:
|
||
virtual bool updateDom(DomElement& element, bool all);
|
||
virtual std::string updateAreaCoordsJS() WT_CXX11ONLY(override);
|
||
};
|
||
}
|
src/Wt/WRectArea.C | ||
---|---|---|
return WAbstractArea::updateDom(element, all);
|
||
}
|
||
std::string WRectArea::updateAreaCoordsJS()
|
||
{
|
||
std::stringstream coords;
|
||
coords << "[" << jsRef() << ",["
|
||
<< x_ << ',' << y_ << ',' << (x_ + width_) << ',' << (y_ + height_) << "]]";
|
||
return coords.str();
|
||
}
|
||
}
|
src/js/WCartesianChart.js | ||
---|---|---|
return mult([1,0,0,-1,l,b], mult(transform(X), mult(transform(Y), [1,0,0,-1,-l,b])));
|
||
}
|
||
}
|
||
target.combinedTransform = combinedTransform;
|
||
function transformedChartArea() {
|
||
return mult(combinedTransform(), config.area);
|
src/js/WImage.js | ||
---|---|---|
/*
|
||
* Copyright (C) 2015 Emweb bvba, Herent, Belgium.
|
||
*
|
||
* See the LICENSE file for terms of use.
|
||
*/
|
||
/* Note: this is at the same time valid JavaScript and C++. */
|
||
WT_DECLARE_WT_MEMBER
|
||
(1, JavaScriptConstructor, "WImage",
|
||
function(APP, el, target) {
|
||
jQuery.data(el, 'obj', this);
|
||
var self = this;
|
||
var WT = APP.WT;
|
||
var areaCoordsJSON = null;
|
||
this.setAreaCoordsJSON = function(newAreaCoordsJSON) {
|
||
areaCoordsJSON = newAreaCoordsJSON;
|
||
this.updateAreas();
|
||
}
|
||
this.updateAreas = function() {
|
||
this.updateAreaCoords();
|
||
}
|
||
this.updateAreaCoords = function() {
|
||
var combinedTransformJS = target.combinedTransform;
|
||
if (combinedTransformJS === undefined || areaCoordsJSON === null)
|
||
return;
|
||
var combinedTransform = combinedTransformJS();
|
||
var mult = APP.WT.gfxUtils.transform_mult;
|
||
for (var c = 0; c < areaCoordsJSON.length; c++)
|
||
{
|
||
var coordEntry = areaCoordsJSON[c];
|
||
var areaEl = coordEntry[0];
|
||
var points = coordEntry[1];
|
||
var len = points.length;
|
||
var new_coords = "";
|
||
for (var i = 0; i + 1 < len; i += 2) {
|
||
if (i > 0)
|
||
new_coords += ",";
|
||
var p = mult(combinedTransform, points.slice(i, i + 2));
|
||
new_coords += Math.round(p[0]).toString() + "," + Math.round(p[1]).toString();
|
||
}
|
||
if (i < len) // a circle -- copy radius unchanged
|
||
new_coords += ("," + points[i].toString());
|
||
if (areaEl)
|
||
areaEl.coords = new_coords;
|
||
}
|
||
}
|
||
});
|