Changeset - 53e3647e3096
[Not reviewed]
default
0 1 0
Hasan Yavuz ÖZDERYA - 6 years ago 2020-01-08 14:27:51
hy@ozderya.net
fix overlapping value tracker texts by spacing them out

label height is still hard coded
1 file changed with 120 insertions and 11 deletions:
0 comments (0 inline, 0 general)
src/zoomer.cpp
Show inline comments
 
/*
 
  Copyright © 2019 Hasan Yavuz Özderya
 
  Copyright © 2020 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "zoomer.h"
 
#include <qwt_plot.h>
 
#include <QPen>
 
#include <QMouseEvent>
 
#include <QtMath>
 
#include <algorithm>
 

	
 
static const int VALUE_POINT_DIAM = 4;
 
static const int VALUE_TEXT_MARGIN = VALUE_POINT_DIAM + 2;
 

	
 
Zoomer::Zoomer(QWidget* widget, bool doReplot) :
 
    ScrollZoomer(widget)
 
{
 
    is_panning = false;
 

	
 
    setTrackerMode(AlwaysOn);
 

	
 
    // set corner widget between the scrollbars with default background color
 
@@ -126,54 +127,162 @@ QList<const StreamChannel*> Zoomer::visC
 
{
 
    QList<const StreamChannel*> result;
 

	
 
    for (unsigned ci = 0; ci < (unsigned) dispChannels.length(); ci++)
 
    {
 
        if (dispChannels[ci]->visible())
 
            result.append(dispChannels[ci]);
 
    }
 

	
 
    return result;
 
}
 

	
 
const double ValueLabelHeight = 12; // TODO: calculate
 

	
 
struct ChannelValue
 
{
 
    const StreamChannel* ch;
 
    double value;
 
    double y;
 
    double top() const {return y;};
 
    double bottom() const {return y + ValueLabelHeight;};
 
};
 

	
 
static void layoutValues(QList<ChannelValue>& values)
 
{
 
    typedef ChannelValue LayItem;
 
    typedef QList<LayItem*> LayItemList;
 

	
 
    struct LayGroup
 
    {
 
        struct VRange {double top, bottom;};
 
        LayItemList items;
 
        LayGroup(LayItem* initialItem) {items.append(initialItem);}
 
        unsigned numItems() const {return items.size();}
 
        double top() const {return items.first()->top();}
 
        double bottom() const {return items.last()->bottom();}
 
        VRange vRange() const {return {top(), bottom()};}
 
        double overlap(const LayGroup* otrGroup) const
 
            {
 
                auto myr = vRange();
 
                auto otr = otrGroup->vRange();
 

	
 
                double a = myr.bottom - otr.top;
 
                double b = otr.bottom - myr.top;
 
                if (a > 0 and b > 0)
 
                {
 
                    return std::min(a, b);
 
                }
 
                return 0;
 
            }
 
        void moveBy(double y) {for (auto it : items) it->y += y;}
 
        void join(LayGroup* other)
 
            {
 
                // assumes other group is below this one and they overlap
 
                double ovr_h = overlap(other);
 

	
 
                // groups are moved less if they have more items and vice versa
 
                double ratio = double(numItems()) / double(numItems() + other->numItems());
 
                double self_off = ovr_h * (1. - ratio);
 

	
 
                // make sure we don't go out of screen (above) after the shift
 
                double final_top = top() - self_off;
 
                if (final_top < 0)
 
                    self_off += final_top;
 

	
 
                // move groups
 
                moveBy(-self_off);                // up
 
                other->moveBy(ovr_h - self_off);  // down
 

	
 
                // finalize the merge by gettin items from other
 
                do
 
                {
 
                    items.append(other->items.takeFirst());
 
                } while (!other->items.isEmpty());
 
            }
 
    };
 

	
 
    // create initial groups (1 group per item)
 
    QList<LayGroup*> groups;
 
    for (auto& val : values)
 
        groups.append(new LayGroup(&val));
 

	
 
    // sort groups according to their items position
 
    struct {
 
        bool operator()(LayGroup* a, LayGroup* b) const
 
            {
 
                return a->top() < b->top();
 
            }
 
    } compTops;
 

	
 
    std::sort(groups.begin(), groups.end(), compTops);
 

	
 
    // do spacing
 
    bool somethingOverlaps = true;
 
    while (somethingOverlaps and groups.size() > 1)
 
    {
 
        somethingOverlaps = false;
 
        for (int i = 0; i < groups.size() - 1; i++)
 
        {
 
            auto a = groups[i];
 
            auto b = groups[i + 1];
 

	
 
            // make sure nothing is over the top
 
            if (a->top() < 0)
 
                a->moveBy(-a->top());
 

	
 
            // join if groups overlap
 
            if (a->overlap(b))
 
            {
 
                somethingOverlaps = true;
 
                a->join(b);
 
                delete groups.takeAt(i + 1);
 
                break;
 
            }
 
        }
 
    }
 

	
 
    // cleanup
 
    do
 
    {
 
        delete groups.takeFirst();
 
    } while (!groups.isEmpty());
 
};
 

	
 
void Zoomer::drawValues(QPainter* painter) const
 
{
 
    struct ChannelValue
 
    {
 
        const StreamChannel* ch;
 
        double value;
 
    };
 

	
 
    auto tpos = trackerPosition();
 
    if (tpos.x() < 0) return;   // cursor not on window
 

	
 
    // find Y values for current cursor X position
 
    double x = invTransform(tpos).x();
 
    auto channels = visChannels();
 
    QList<ChannelValue> values;
 
    for (auto ch : channels)
 
    {
 
        double value = ch->findValue(x);
 
        if (!std::isnan(value))
 
            values.append({ch, value});
 
        {
 
            auto point = transform(QPointF(x, value));
 
            values.append({ch, value, double(point.y())});
 
        }
 
    }
 

	
 
    // TODO should keep?
 
    if (values.isEmpty())
 
    {
 
        qDebug() << "no value to draw??";
 
        return;
 
    }
 

	
 
    // TODO layout
 
    layoutValues(values);
 

	
 
    painter->save();
 

	
 
    // draw vertical line
 
    auto linePen = rubberBandPen();
 
    linePen.setStyle(Qt::DotLine);
 
    painter->setPen(linePen);
 
    const QRect pRect = pickArea().boundingRect().toRect();
 
    int px = tpos.x();
 
    painter->drawLine(px, pRect.top(), px, pRect.bottom());
 

	
 
    // draw sample values
 
@@ -181,25 +290,25 @@ void Zoomer::drawValues(QPainter* painte
 
    {
 
        double val = value.value;
 
        auto ch = value.ch;
 

	
 
        auto point = transform(QPointF(x, val));
 

	
 
        painter->setBrush(ch->color());
 
        painter->setPen(Qt::NoPen);
 
        painter->drawEllipse(point, VALUE_POINT_DIAM, VALUE_POINT_DIAM);
 

	
 
        painter->setPen(rubberBandPen());
 
        // We give a very small (1x1) rectangle but disable clipping
 
        painter->drawText(QRectF(point.x() + VALUE_TEXT_MARGIN, point.y(), 1, 1),
 
        painter->drawText(QRectF(point.x() + VALUE_TEXT_MARGIN, value.y, 1, 1),
 
                          Qt::AlignVCenter | Qt::TextDontClip,
 
                          QString("%1").arg(val));
 
    }
 

	
 
    painter->restore();
 
}
 

	
 
QRect Zoomer::trackerRect(const QFont& font) const
 
{
 
    if (isActive())
 
    {
 
        return QwtPlotZoomer::trackerRect(font);
0 comments (0 inline, 0 general)