Implement proper y-axis labeling

Change-Id: Ie2bdb4b85f168dc611a52303143a6a6fc110e3c3
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Knud Dollereder
2020-06-23 17:41:39 +02:00
parent 351cb8a028
commit 562cf4a867
7 changed files with 283 additions and 11 deletions

View File

@@ -649,6 +649,7 @@ extend_qtc_plugin(QmlDesigner
extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/curveeditor SOURCES_PREFIX components/curveeditor
SOURCES SOURCES
curveeditor.qrc
animationcurve.cpp animationcurve.h animationcurve.cpp animationcurve.h
curveeditor.cpp curveeditor.h curveeditor.cpp curveeditor.h
curveeditormodel.cpp curveeditormodel.h curveeditormodel.cpp curveeditormodel.h
@@ -656,6 +657,7 @@ extend_qtc_plugin(QmlDesigner
curvesegment.cpp curvesegment.h curvesegment.cpp curvesegment.h
keyframe.cpp keyframe.h keyframe.cpp keyframe.h
treeitem.cpp treeitem.h treeitem.cpp treeitem.h
detail/axis.cpp detail/axis.h
detail/colorcontrol.cpp detail/colorcontrol.h detail/colorcontrol.cpp detail/colorcontrol.h
detail/curveeditorstyledialog.cpp detail/curveeditorstyledialog.h detail/curveeditorstyledialog.cpp detail/curveeditorstyledialog.h
detail/curveitem.cpp detail/curveitem.h detail/curveitem.cpp detail/curveitem.h

View File

@@ -44,6 +44,7 @@ SOURCES += \
$$PWD/detail/treemodel.cpp \ $$PWD/detail/treemodel.cpp \
$$PWD/detail/treeview.cpp \ $$PWD/detail/treeview.cpp \
$$PWD/detail/utils.cpp \ $$PWD/detail/utils.cpp \
$$PWD/detail/axis.cpp \
$$PWD/keyframe.cpp \ $$PWD/keyframe.cpp \
$$PWD/treeitem.cpp $$PWD/treeitem.cpp

View File

@@ -123,6 +123,7 @@ struct CurveEditorStyle
double valueAxisWidth = 60.0; double valueAxisWidth = 60.0;
double valueOffsetTop = 10.0; double valueOffsetTop = 10.0;
double valueOffsetBottom = 10.0; double valueOffsetBottom = 10.0;
double labelDensityY = 1.5;
HandleItemStyleOption handleStyle; HandleItemStyleOption handleStyle;
KeyframeItemStyleOption keyframeStyle; KeyframeItemStyleOption keyframeStyle;

View File

@@ -0,0 +1,213 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Design Tooling
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "axis.h"
#include <algorithm>
#include <assert.h>
#include <cmath>
#include <limits>
namespace DesignTools {
// The following is based on: "An Extension of Wilkinson's Algorithm for Positioning Tick Labels on Axes"
// by Justin Talbot, Sharon Lin and Pat Hanrahan.
// The Implementation uses the conventions and variable names found in the paper where:
// Inputs:
// Q Preference ordered list of nice steps
// dmin/dmax Data range
// pt Target label density
// fst Target font size (currently ignored)
// Formats set of label formats (currently ignored)
// Other:
// q Element of Q
// i Index of q in Q
// j Label skipping amount (taking only jth label into account)
// lmin/lmax Labeling sequence range
// v Indicator if labeling should include 0. (1 means yes / 0 no)
double simplicity(int i, std::vector<double> &Q, int j, int v = 1)
{
assert(Q.size() > 1);
return 1.0 - ((static_cast<double>(i) - 1.0) / (static_cast<double>(Q.size()) - 1.0))
- static_cast<double>(j) + static_cast<double>(v);
}
double coverage(double dmin, double dmax, double lmin, double lmax)
{
return 1.0 - 0.5 *
((std::pow(dmax - lmax, 2.0) + std::pow(dmin - lmin, 2.0)) /
(std::pow(0.1 * (dmax - dmin), 2.0)));
}
double coverageMax(double dmin, double dmax, double labelingRange)
{
double dataRange = dmax - dmin;
if (labelingRange > dataRange) {
double range = (labelingRange - dataRange) / 2.;
return 1 - 0.5 * ((std::pow(range, 2.0) * 2.0) / (std::pow(0.1 * (dmax - dmin), 2.0)));
}
return 1;
}
double density(double p, double pt)
{
return 2.0 - std::max(p / pt, pt / p);
}
double densityMax(double p, double pt)
{
if (p >= pt)
return 2 - p / pt;
else
return 1;
}
namespace Legibility {
class Format
{
public:
double legibility() const { return 1.0; }
};
class FontSize
{
public:
double legibility() const { return 1.0; }
};
class Orientation
{
public:
double legibility() const { return 1.0; }
};
class Overlap
{
public:
double legibility() const { return 1.0; }
};
} // End namespace Legibility.
using namespace Legibility;
double legibility(const Format &fmt, const FontSize &fs, const Orientation &ori, const Overlap &ovl)
{
return (fmt.legibility() + fs.legibility() + ori.legibility() + ovl.legibility()) / 4.0;
}
struct LabelingInfo
{
double l;
Format lformat;
};
LabelingInfo optLegibility(int k, double lmin, double lstep)
{
std::vector<double> stepSequence;
for (int i = 0; i < k; ++i)
stepSequence.push_back(lmin + i * lstep);
Format fmt;
FontSize fs;
Orientation ori;
Overlap ovl;
LabelingInfo info;
info.l = legibility(fmt, fs, ori, ovl);
info.lformat = fmt;
return info;
}
Axis Axis::compute(double dmin, double dmax, double height, double pt)
{
Axis result;
auto score = [](double a, double b, double c, double d) {
return a * 0.2 + b * 0.25 + c * 0.5 + d * 0.05;
};
std::vector<double> Q = {1., 5., 2., 2.5, 3.};
//std::vector<double> Q = {1., 5., 2., 2.5, 3., 4., 1.5, 7., 6., 8., 9.};
double best_score = -2.0;
int j = 1;
while (j < std::numeric_limits<int>::max()) {
for (int i = 0; i < static_cast<int>(Q.size()); ++i) {
double q = Q[i];
auto simMax = simplicity(i, Q, j);
if (score(simMax, 1.0, 1.0, 1.0) < best_score) {
j = std::numeric_limits<int>::max() - 1;
break;
}
int k = 2; // label count
while (k < std::numeric_limits<int>::max()) {
auto p = k / height;
auto denMax = densityMax(p, pt);
if (score(simMax, 1.0, denMax, 1.0) < best_score)
break;
auto delta = (dmax - dmin) / (k + 1) / (j * q);
int z = std::ceil(std::log10(delta));
while (z < std::numeric_limits<int>::max()) {
auto lstep = q * static_cast<double>(j) * std::pow(10, z);
auto covMax = coverageMax(dmin, dmax, lstep * (k - 1));
if (score(simMax, covMax, denMax, 1.0) < best_score)
break;
int start = (std::floor(dmax / lstep) - (k - 1)) * j;
int end = std::ceil(dmin / lstep) * j;
for (; start <= end; ++start) {
double lmin = start * lstep / j;
double lmax = lmin + lstep * (k - 1);
double s = simplicity(i, Q, j);
double d = density(p, pt);
double c = coverage(dmin, dmax, lmin, lmax);
if (score(s, c, d, 1.0) < best_score)
continue;
auto info = optLegibility(k, lmin, lstep);
const double cscore = score(s, c, d, info.l);
if (cscore > best_score) {
best_score = cscore;
result = {lmin, lmax, lstep};
}
}
z++;
}
k++;
}
}
j++;
}
return result;
}
} // End namespace DesignTools.

View File

@@ -0,0 +1,42 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Design Tooling
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QToolBar>
#include <QWidget>
namespace DesignTools {
struct Axis
{
static Axis compute(double dmin, double dmax, double height, double pt);
double lmin;
double lmax;
double lstep;
};
} // End namespace DesignTools.

View File

@@ -24,6 +24,7 @@
****************************************************************************/ ****************************************************************************/
#include "graphicsview.h" #include "graphicsview.h"
#include "axis.h"
#include "curveeditormodel.h" #include "curveeditormodel.h"
#include "curveitem.h" #include "curveitem.h"
#include "treeitem.h" #include "treeitem.h"
@@ -35,6 +36,8 @@
#include <QScrollBar> #include <QScrollBar>
#include <cmath> #include <cmath>
#include <iomanip>
#include <sstream>
namespace DesignTools { namespace DesignTools {
@@ -361,15 +364,15 @@ void GraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
if (abscissa.isValid()) if (abscissa.isValid())
drawTimeScale(painter, abscissa); drawTimeScale(painter, abscissa);
painter->fillRect(QRectF(rect.topLeft(), abscissa.bottomLeft()),
m_style.backgroundAlternateBrush);
auto ordinate = valueScaleRect(); auto ordinate = valueScaleRect();
if (ordinate.isValid()) if (ordinate.isValid())
drawValueScale(painter, ordinate); drawValueScale(painter, ordinate);
m_playhead.paint(painter, this); m_playhead.paint(painter, this);
painter->fillRect(QRectF(rect.topLeft(), abscissa.bottomLeft()),
m_style.backgroundAlternateBrush);
m_selector.paint(painter); m_selector.paint(painter);
} }
@@ -460,7 +463,8 @@ void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect) void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect)
{ {
QRectF gridRect = rect.adjusted(m_style.valueAxisWidth + m_style.canvasMargin, QRectF gridRect = rect.adjusted(
m_style.valueAxisWidth + m_style.canvasMargin,
m_style.timeAxisHeight + m_style.canvasMargin, m_style.timeAxisHeight + m_style.canvasMargin,
-m_style.canvasMargin, -m_style.canvasMargin,
-m_style.canvasMargin); -m_style.canvasMargin);
@@ -586,17 +590,24 @@ void GraphicsView::drawValueScale(QPainter *painter, const QRectF &rect)
QFontMetrics fm(painter->font()); QFontMetrics fm(painter->font());
auto paintLabeledTick = [this, painter, rect, fm](double value) { auto paintLabeledTick = [this, painter, rect, fm](double value) {
std::stringstream sstr;
sstr << std::fixed << std::setprecision(10) << value;
sstr >> value;
QString valueText = QString("%1").arg(value); QString valueText = QString("%1").arg(value);
int position = mapValueToY(value); int position = mapValueToY(value);
QRect textRect = fm.boundingRect(valueText); QRect textRect = fm.boundingRect(valueText);
textRect.moveCenter(QPoint(rect.center().x(), position)); textRect.moveCenter(QPoint(rect.center().x(), position));
painter->drawText(textRect, Qt::AlignCenter, valueText); painter->drawText(textRect, Qt::AlignCenter, valueText);
}; };
paintLabeledTick(minimumValue()); double density = 1. / (static_cast<double>(fm.height()) * m_style.labelDensityY);
paintLabeledTick(maximumValue()); Axis axis = Axis::compute(minimumValue(), maximumValue(), rect.height(), density);
const double eps = 1.0e-10;
for (double i = axis.lmin; i <= axis.lmax + eps; i += axis.lstep)
paintLabeledTick(i);
painter->restore(); painter->restore();
} }

View File

@@ -715,6 +715,8 @@ Project {
"curveeditor/keyframe.h", "curveeditor/keyframe.h",
"curveeditor/treeitem.cpp", "curveeditor/treeitem.cpp",
"curveeditor/treeitem.h", "curveeditor/treeitem.h",
"curveeditor/detail/axis.cpp",
"curveeditor/detail/axis.h",
"curveeditor/detail/colorcontrol.cpp", "curveeditor/detail/colorcontrol.cpp",
"curveeditor/detail/colorcontrol.h", "curveeditor/detail/colorcontrol.h",
"curveeditor/detail/curveeditorstyledialog.cpp", "curveeditor/detail/curveeditorstyledialog.cpp",