diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 345c756034b..24c345d1e4b 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -649,6 +649,7 @@ extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/curveeditor SOURCES + curveeditor.qrc animationcurve.cpp animationcurve.h curveeditor.cpp curveeditor.h curveeditormodel.cpp curveeditormodel.h @@ -656,6 +657,7 @@ extend_qtc_plugin(QmlDesigner curvesegment.cpp curvesegment.h keyframe.cpp keyframe.h treeitem.cpp treeitem.h + detail/axis.cpp detail/axis.h detail/colorcontrol.cpp detail/colorcontrol.h detail/curveeditorstyledialog.cpp detail/curveeditorstyledialog.h detail/curveitem.cpp detail/curveitem.h diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri b/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri index e759ed6bde7..d42544f3fad 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.pri @@ -44,6 +44,7 @@ SOURCES += \ $$PWD/detail/treemodel.cpp \ $$PWD/detail/treeview.cpp \ $$PWD/detail/utils.cpp \ + $$PWD/detail/axis.cpp \ $$PWD/keyframe.cpp \ $$PWD/treeitem.cpp diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h b/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h index 54653d38862..d5d584fe04d 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditorstyle.h @@ -123,6 +123,7 @@ struct CurveEditorStyle double valueAxisWidth = 60.0; double valueOffsetTop = 10.0; double valueOffsetBottom = 10.0; + double labelDensityY = 1.5; HandleItemStyleOption handleStyle; KeyframeItemStyleOption keyframeStyle; diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/axis.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/axis.cpp new file mode 100644 index 00000000000..3001aaf36d9 --- /dev/null +++ b/src/plugins/qmldesigner/components/curveeditor/detail/axis.cpp @@ -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 +#include +#include +#include + +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 &Q, int j, int v = 1) +{ + assert(Q.size() > 1); + return 1.0 - ((static_cast(i) - 1.0) / (static_cast(Q.size()) - 1.0)) + - static_cast(j) + static_cast(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 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 Q = {1., 5., 2., 2.5, 3.}; + //std::vector 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::max()) { + for (int i = 0; i < static_cast(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::max() - 1; + break; + } + + int k = 2; // label count + while (k < std::numeric_limits::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::max()) { + auto lstep = q * static_cast(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. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/axis.h b/src/plugins/qmldesigner/components/curveeditor/detail/axis.h new file mode 100644 index 00000000000..ee8c197748d --- /dev/null +++ b/src/plugins/qmldesigner/components/curveeditor/detail/axis.h @@ -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 +#include + +namespace DesignTools { + +struct Axis +{ + static Axis compute(double dmin, double dmax, double height, double pt); + + double lmin; + double lmax; + double lstep; +}; + +} // End namespace DesignTools. diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp index 4fe3ed20d8a..60aa7060426 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "graphicsview.h" +#include "axis.h" #include "curveeditormodel.h" #include "curveitem.h" #include "treeitem.h" @@ -35,6 +36,8 @@ #include #include +#include +#include namespace DesignTools { @@ -361,15 +364,15 @@ void GraphicsView::drawForeground(QPainter *painter, const QRectF &rect) if (abscissa.isValid()) drawTimeScale(painter, abscissa); + painter->fillRect(QRectF(rect.topLeft(), abscissa.bottomLeft()), + m_style.backgroundAlternateBrush); + auto ordinate = valueScaleRect(); if (ordinate.isValid()) drawValueScale(painter, ordinate); m_playhead.paint(painter, this); - painter->fillRect(QRectF(rect.topLeft(), abscissa.bottomLeft()), - m_style.backgroundAlternateBrush); - 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) { - QRectF gridRect = rect.adjusted(m_style.valueAxisWidth + m_style.canvasMargin, - m_style.timeAxisHeight + m_style.canvasMargin, - -m_style.canvasMargin, - -m_style.canvasMargin); + QRectF gridRect = rect.adjusted( + m_style.valueAxisWidth + m_style.canvasMargin, + m_style.timeAxisHeight + m_style.canvasMargin, + -m_style.canvasMargin, + -m_style.canvasMargin); if (!gridRect.isValid()) return; @@ -586,17 +590,24 @@ void GraphicsView::drawValueScale(QPainter *painter, const QRectF &rect) QFontMetrics fm(painter->font()); 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); - int position = mapValueToY(value); - QRect textRect = fm.boundingRect(valueText); textRect.moveCenter(QPoint(rect.center().x(), position)); + painter->drawText(textRect, Qt::AlignCenter, valueText); }; - paintLabeledTick(minimumValue()); - paintLabeledTick(maximumValue()); + double density = 1. / (static_cast(fm.height()) * m_style.labelDensityY); + 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(); } diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index c3db90d2e0f..dbd37279e5e 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -715,6 +715,8 @@ Project { "curveeditor/keyframe.h", "curveeditor/treeitem.cpp", "curveeditor/treeitem.h", + "curveeditor/detail/axis.cpp", + "curveeditor/detail/axis.h", "curveeditor/detail/colorcontrol.cpp", "curveeditor/detail/colorcontrol.h", "curveeditor/detail/curveeditorstyledialog.cpp",