forked from qt-creator/qt-creator
Implement proper y-axis labeling
Change-Id: Ie2bdb4b85f168dc611a52303143a6a6fc110e3c3 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
213
src/plugins/qmldesigner/components/curveeditor/detail/axis.cpp
Normal file
213
src/plugins/qmldesigner/components/curveeditor/detail/axis.cpp
Normal 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.
|
42
src/plugins/qmldesigner/components/curveeditor/detail/axis.h
Normal file
42
src/plugins/qmldesigner/components/curveeditor/detail/axis.h
Normal 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.
|
@@ -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,10 +463,11 @@ 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.timeAxisHeight + m_style.canvasMargin,
|
m_style.valueAxisWidth + m_style.canvasMargin,
|
||||||
-m_style.canvasMargin,
|
m_style.timeAxisHeight + m_style.canvasMargin,
|
||||||
-m_style.canvasMargin);
|
-m_style.canvasMargin,
|
||||||
|
-m_style.canvasMargin);
|
||||||
|
|
||||||
if (!gridRect.isValid())
|
if (!gridRect.isValid())
|
||||||
return;
|
return;
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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",
|
||||||
|
Reference in New Issue
Block a user