Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtCharts/qabstractaxis.h>
#include <QtCharts/qlogvalueaxis.h>
#include <QtCharts/qvalueaxis.h>
#include <QtCore/qmath.h>
#include <QtGui/qtextdocument.h>
#include <QtWidgets/qgraphicslayout.h>
#include <private/abstractchartlayout_p.h>
#include <private/abstractdomain_p.h>
#include <private/cartesianchartaxis_p.h>
#include <private/chartpresenter_p.h>
#include <private/linearrowitem_p.h>
#include <private/qabstractaxis_p.h>
QT_BEGIN_NAMESPACE
CartesianChartAxis::CartesianChartAxis(QAbstractAxis *axis, QGraphicsItem *item , bool intervalAxis)
: ChartAxisElement(axis, item, intervalAxis)
{
Q_ASSERT(item);
}
CartesianChartAxis::~CartesianChartAxis()
{
}
void CartesianChartAxis::createItems(int count)
{
if (arrowItems().size() == 0) {
QGraphicsLineItem *arrow = new LineArrowItem(this, this);
arrow->setAcceptedMouseButtons({});
arrow->setPen(axis()->linePen());
arrowGroup()->addToGroup(arrow);
}
if (intervalAxis() && gridItems().size() == 0) {
for (int i = 0 ; i < 2 ; i ++){
QGraphicsLineItem *item = new QGraphicsLineItem(this);
item->setAcceptedMouseButtons({});
item->setPen(axis()->gridLinePen());
gridGroup()->addToGroup(item);
QGraphicsRectItem *shades = new QGraphicsRectItem(this);
shades->setAcceptedMouseButtons({});
shades->setPen(axis()->shadesPen());
shades->setBrush(axis()->shadesBrush());
shadeGroup()->addToGroup(shades);
}
}
QGraphicsTextItem *title = titleItem();
title->setFont(axis()->titleFont());
title->setDefaultTextColor(axis()->titleBrush().color());
title->setHtml(axis()->titleText());
for (int i = 0; i < count; ++i) {
QGraphicsLineItem *arrow = new QGraphicsLineItem(this);
arrow->setAcceptedMouseButtons({});
QGraphicsLineItem *grid = new QGraphicsLineItem(this);
grid->setAcceptedMouseButtons({});
QGraphicsTextItem *label;
if (axis()->type() == QAbstractAxis::AxisTypeValue) {
label = new ValueAxisLabel(this);
connect(static_cast<ValueAxisLabel *>(label), &ValueAxisLabel::valueChanged,
this, &ChartAxisElement::valueLabelEdited);
if (labelsEditable())
static_cast<ValueAxisLabel *>(label)->setEditable(true);
#if QT_CONFIG(charts_datetime_axis)
} else if (axis()->type() == QAbstractAxis::AxisTypeDateTime) {
DateTimeAxisLabel *dateTimeLabel = new DateTimeAxisLabel(this);
label = dateTimeLabel;
connect(dateTimeLabel, &DateTimeAxisLabel::dateTimeChanged,
this, &ChartAxisElement::dateTimeLabelEdited);
if (labelsEditable())
dateTimeLabel->setEditable(true);
dateTimeLabel->setFormat(static_cast<QDateTimeAxis*>(axis())->format());
#endif
} else {
label = new QGraphicsTextItem(this);
}
label->setAcceptedMouseButtons({});
label->document()->setDocumentMargin(ChartPresenter::textMargin());
arrow->setPen(axis()->linePen());
grid->setPen(axis()->gridLinePen());
label->setFont(axis()->labelsFont());
label->setDefaultTextColor(axis()->labelsBrush().color());
label->setRotation(axis()->labelsAngle());
arrowGroup()->addToGroup(arrow);
gridGroup()->addToGroup(grid);
labelGroup()->addToGroup(label);
if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) {
QGraphicsRectItem *shades = new QGraphicsRectItem(this);
shades->setPen(axis()->shadesPen());
shades->setBrush(axis()->shadesBrush());
shadeGroup()->addToGroup(shades);
}
}
}
void CartesianChartAxis::updateMinorTickItems()
{
int currentCount = minorArrowItems().size();
int expectedCount = 0;
if (axis()->type() == QAbstractAxis::AxisTypeValue) {
QValueAxis *valueAxis = qobject_cast<QValueAxis *>(axis());
if (valueAxis->tickType() == QValueAxis::TicksFixed) {
expectedCount = valueAxis->minorTickCount() * (valueAxis->tickCount() - 1);
expectedCount = qMax(expectedCount, 0);
} else {
const qreal interval = valueAxis->tickInterval();
const qreal anchor = valueAxis->tickAnchor();
const qreal max = valueAxis->max();
const qreal min = valueAxis->min();
const int _minorTickCount = valueAxis->minorTickCount();
// Find the closest major tick <= the min of the range, even if it's not drawn!
// This is where we'll start counting minor ticks from, because minor ticks
// might need to be drawn even before the first major tick.
const qreal ticksFromAnchor = (anchor - min) / interval;
const qreal firstMajorTick = anchor - std::ceil(ticksFromAnchor) * interval;
const qreal deltaMinor = interval / qreal(_minorTickCount + 1);
qreal minorTick = firstMajorTick + deltaMinor;
int minorCounter = 0;
while (minorTick < min) {
minorTick += deltaMinor;
minorCounter++;
}
QList<qreal> points;
// Calculate the points on axis value space. Conversion to graphical points
// will be done on axis specific geometry update function
while (minorTick <= max) {
if (minorCounter < _minorTickCount) {
expectedCount++;
minorCounter++;
points << (minorTick - min);
} else {
minorCounter = 0;
}
minorTick += deltaMinor;
}
setDynamicMinorTickLayout(points);
}
} else if (axis()->type() == QAbstractAxis::AxisTypeLogValue) {
QLogValueAxis *logValueAxis = qobject_cast<QLogValueAxis *>(axis());
int minorTickCount = logValueAxis->minorTickCount();
if (minorTickCount < 0)
minorTickCount = qMax(qFloor(logValueAxis->base()) - 2, 0);
expectedCount = minorTickCount * (logValueAxis->tickCount() + 1);
expectedCount = qMax(expectedCount, logValueAxis->minorTickCount());
} else {
// minor ticks are not supported
return;
}
int diff = expectedCount - currentCount;
if (diff > 0) {
for (int i = 0; i < diff; ++i) {
QGraphicsLineItem *minorGridLineItem = new QGraphicsLineItem(this);
minorGridLineItem->setPen(axis()->minorGridLinePen());
minorGridGroup()->addToGroup(minorGridLineItem);
QGraphicsLineItem *minorArrowLineItem = new QGraphicsLineItem(this);
minorArrowLineItem->setPen(axis()->linePen());
minorArrowGroup()->addToGroup(minorArrowLineItem);
}
} else {
QList<QGraphicsItem *> minorGridItemsList = minorGridItems();
QList<QGraphicsItem *> minorArrowItemsList = minorArrowItems();
for (int i = 0; i > diff; --i) {
if (!minorGridItemsList.isEmpty())
delete minorGridItemsList.takeLast();
if (!minorArrowItemsList.isEmpty())
delete minorArrowItemsList.takeLast();
}
}
}
void CartesianChartAxis::deleteItems(int count)
{
QList<QGraphicsItem *> lines = gridItems();
QList<QGraphicsItem *> labels = labelItems();
QList<QGraphicsItem *> shades = shadeItems();
QList<QGraphicsItem *> axis = arrowItems();
for (int i = 0; i < count; ++i) {
if (lines.size() == 1 || (((lines.size() + 1) % 2) && lines.size() > 0))
delete(shades.takeLast());
delete(lines.takeLast());
delete(labels.takeLast());
delete(axis.takeLast());
}
}
void CartesianChartAxis::updateLayout(const QList<qreal> &layout)
{
int diff = ChartAxisElement::layout().size() - layout.size();
if (diff > 0)
deleteItems(diff);
else if (diff <= 0)
createItems(-diff);
updateMinorTickItems();
if (animation()) {
switch (presenter()->state()) {
case ChartPresenter::ZoomInState:
animation()->setAnimationType(AxisAnimation::ZoomInAnimation);
animation()->setAnimationPoint(presenter()->statePoint());
break;
case ChartPresenter::ZoomOutState:
animation()->setAnimationType(AxisAnimation::ZoomOutAnimation);
animation()->setAnimationPoint(presenter()->statePoint());
break;
case ChartPresenter::ScrollUpState:
case ChartPresenter::ScrollLeftState:
animation()->setAnimationType(AxisAnimation::MoveBackwordAnimation);
break;
case ChartPresenter::ScrollDownState:
case ChartPresenter::ScrollRightState:
animation()->setAnimationType(AxisAnimation::MoveForwardAnimation);
break;
case ChartPresenter::ShowState:
animation()->setAnimationType(AxisAnimation::DefaultAnimation);
break;
}
animation()->setValues(ChartAxisElement::layout(), layout);
presenter()->startAnimation(animation());
} else {
setLayout(layout);
updateGeometry();
}
}
bool CartesianChartAxis::emptyAxis() const
{
return axisGeometry().isEmpty()
|| gridGeometry().isEmpty()
|| qFuzzyIsNull(max() - min());
}
void CartesianChartAxis::setGeometry(const QRectF &axis, const QRectF &grid)
{
m_gridRect = grid;
setAxisGeometry(axis);
if (emptyAxis()) {
prepareGeometryChange();
return;
}
const QList<qreal> layout = calculateLayout();
updateLayout(layout);
}
QSizeF CartesianChartAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
Q_UNUSED(which);
Q_UNUSED(constraint);
return QSizeF();
}
void CartesianChartAxis::setDateTimeLabelsFormat(const QString &format)
{
if (max() <= min()
|| layout().size() < 1
|| axis()->type() != QAbstractAxis::AxisTypeDateTime) {
return;
}
#if QT_CONFIG(charts_datetime_axis)
for (int i = 0; i < layout().size(); i++)
static_cast<DateTimeAxisLabel *>(labelItems().at(i))->setFormat(format);
#endif
}
void CartesianChartAxis::handleArrowPenChanged(const QPen &pen)
{
const auto arrows = arrowItems();
for (QGraphicsItem *item : arrows)
static_cast<QGraphicsLineItem *>(item)->setPen(pen);
}
void CartesianChartAxis::handleGridPenChanged(const QPen &pen)
{
const auto items = gridItems();
for (QGraphicsItem *item : items)
static_cast<QGraphicsLineItem *>(item)->setPen(pen);
}
void CartesianChartAxis::handleMinorArrowPenChanged(const QPen &pen)
{
const auto items = minorArrowItems();
for (QGraphicsItem *item : items)
static_cast<QGraphicsLineItem *>(item)->setPen(pen);
}
void CartesianChartAxis::handleMinorGridPenChanged(const QPen &pen)
{
const auto items = minorGridItems();
for (QGraphicsItem *item : items)
static_cast<QGraphicsLineItem *>(item)->setPen(pen);
}
void CartesianChartAxis::handleGridLineColorChanged(const QColor &color)
{
const auto items = gridItems();
for (QGraphicsItem *item : items) {
QGraphicsLineItem *lineItem = static_cast<QGraphicsLineItem *>(item);
QPen pen = lineItem->pen();
pen.setColor(color);
lineItem->setPen(pen);
}
}
void CartesianChartAxis::handleMinorGridLineColorChanged(const QColor &color)
{
const auto items = minorGridItems();
for (QGraphicsItem *item : items) {
QGraphicsLineItem *lineItem = static_cast<QGraphicsLineItem *>(item);
QPen pen = lineItem->pen();
pen.setColor(color);
lineItem->setPen(pen);
}
}
void CartesianChartAxis::handleShadesBrushChanged(const QBrush &brush)
{
const auto items = shadeItems();
for (QGraphicsItem *item : items)
static_cast<QGraphicsRectItem *>(item)->setBrush(brush);
}
void CartesianChartAxis::handleShadesPenChanged(const QPen &pen)
{
const auto items = shadeItems();
for (QGraphicsItem *item : items)
static_cast<QGraphicsRectItem *>(item)->setPen(pen);
}
void CartesianChartAxis::updateLabelsValues(QValueAxis *axis)
{
const QList<qreal> &layout = ChartAxisElement::layout();
if (layout.isEmpty())
return;
if (axis->tickType() == QValueAxis::TicksFixed) {
for (int i = 0; i < layout.size(); ++i) {
qreal value = axis->isReverse()
? min() + ((layout.size() - 1 - i) * (max() - min()) / (layout.size() - 1))
: min() + (i * (max() - min()) / (layout.size() - 1));
static_cast<ValueAxisLabel *>(labelItems().at(i))->setValue(value);
}
} else {
const qreal anchor = axis->tickAnchor();
const qreal interval = axis->tickInterval();
const qreal ticksFromAnchor = (anchor - min()) / interval;
const qreal firstMajorTick = anchor - std::floor(ticksFromAnchor) * interval;
int i = axis->isReverse() ? labelItems().size()-1 : 0;
qreal value = firstMajorTick;
while (value <= max()) {
static_cast<ValueAxisLabel *>(labelItems().at(i))->setValue(value);
value += interval;
i += axis->isReverse() ? -1 : 1;
}
}
}
void CartesianChartAxis::updateLabelsDateTimes()
{
if (max() <= min() || layout().size() < 1)
return;
#if QT_CONFIG(charts_datetime_axis)
for (int i = 0; i < layout().size(); i++) {
qreal value = min() + (i * (max() - min()) / (layout().size() - 1));
static_cast<DateTimeAxisLabel *>(labelItems().at(i))->setValue(
QDateTime::fromMSecsSinceEpoch(value));
}
#endif
}
QT_END_NAMESPACE
#include "moc_cartesianchartaxis_p.cpp"