#include "graphic.h"

#include "gif.h"
#include "qad_types.h"
#include "uglwidget.h"
#include "ui_graphic.h"
#include "ui_graphic_conf.h"

#include <QActionGroup>
#include <QInputDialog>
#include <QMessageBox>
#include <QMetaObject>
#include <QPanGesture>
#include <QPinchGesture>
#include <QScrollArea>
#include <QTapAndHoldGesture>
#include <QTimer>
#include <float.h>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
#  include <QRandomGenerator>
#endif
#ifdef Q_OS_ANDROID
#  define NO_BUTTONS
#else
#  ifndef FORCE_NO_GL
#	define HAS_GL
#  endif
#endif
#ifdef HAS_GL
#  ifndef GL_MULTISAMPLE
#	define GL_MULTISAMPLE 0x809D
#  endif
#endif

enum DateComponent {
	DateMSecs,
	DateSecs,
	DateMinutes,
	DateHours,
	DateDays,
	DateMonths,
	DateYears,
	DateComponentCount
};

const char _button_prop_name_[] = "_button_";
const double rad2deg_qpie       = 45. / atan(1.);

__GraphicRegistrator__ __graphic_registrator__;

class LegendScrollArea: public QScrollArea {
public:
	LegendScrollArea(QWidget * w): QScrollArea() {
		minimum_hei = 0;
		setFrameShape(QFrame::NoFrame);
		setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
		setWidgetResizable(true);
		setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
		setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
		setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
		setWidget(w);
		viewport()->setAutoFillBackground(false);
		w->setAutoFillBackground(false);
	}
	int minimum_hei;

protected:
	virtual QSize sizeHint() const {
		QSize ret;
		if (!widget()) return ret;
		ret = widget()->sizeHint();
		return ret;
	}
	virtual QSize minimumSizeHint() const { return QSize(1, minimum_hei); }
};


Graphic::Graphic(QWidget * parent): QFrame(parent), canvas(0), line_x_min(this), line_x_max(this), line_y_min(this), line_y_max(this) {
	canvas_gl   = 0;
	timer_pause = timer_record = 0;
	gesture_angle              = 45.;
	leg_update                 = true;
	visible_update = fullscr = need_mouse_pan = m_fakeGL = false;
	gestures =
#ifdef Q_OS_ANDROID
		true;
#else
		false;
#endif
	func_gridMarkX = func_gridMarkY = nullptr;
	ui                              = new Ui::Graphic();
	ui->setupUi(this);
	ui->status->hide();
	ui->scrollLegend->layout()->addWidget(new LegendScrollArea(ui->widgetLegend));
	ui->scrollLegend->hide();
	fillDateFormats();
#ifdef NO_BUTTONS
	ui->widgetLeft->hide();
	ui->widgetRight->hide();
	QList<QToolButton *> btnlist = {ui->graphic_buttonAutofit,
	                                ui->graphic_checkGuides,
	                                ui->graphic_buttonFullscreen,
	                                ui->graphic_checkBorderInputs,
	                                ui->graphic_checkLegend,
	                                ui->graphic_checkPause,
	                                ui->graphic_buttonConfigure,
	                                ui->graphic_buttonRecord,
	                                ui->graphic_buttonClear,
	                                ui->graphic_buttonClose};
	buttons_menu                 = new QMenu(this);
	for (auto * b: btnlist) {
		auto * a = new QAction(this);
		connect(a, SIGNAL(triggered()), b, SLOT(click()));
		connect(a, SIGNAL(triggered(bool)), b, SLOT(setChecked(bool)));
		a->setCheckable(b->isCheckable());
		a->setProperty(_button_prop_name_, (quintptr)b);
		buttons_menu->addAction(a);
	}
#endif
	QActionGroup * agroup = new QActionGroup(this);
	agroup->addAction(ui->graphic_actionGuidesFree);
	agroup->addAction(ui->graphic_actionGuidesTrace);
	ui->graphic_actionGuidesFree->setProperty("_value", (int)Free);
	ui->graphic_actionGuidesTrace->setProperty("_value", (int)Trace);
	connect(agroup, &QActionGroup::triggered, this, &Graphic::actionGuidesTriggered);
	ui->graphic_checkGuides->addAction(ui->graphic_actionGuidesFree);
	ui->graphic_checkGuides->addAction(ui->graphic_actionGuidesTrace);
	ui->graphic_buttonAutofit->addAction(ui->graphic_actionExpandX);
	ui->graphic_buttonAutofit->addAction(ui->graphic_actionExpandY);
	ui->graphic_buttonSaveMenu->addAction(ui->graphic_actionSaveImage);
	ui->graphic_buttonSaveMenu->addAction(ui->graphic_actionExportCSV);
	ui->graphic_buttonSaveMenu->addAction(ui->graphic_actionExportCSV_View);
	line_x_min.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
	line_x_max.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
	((QBoxLayout *)ui->widgetLY->layout())->insertWidget(0, &line_y_min);
	((QBoxLayout *)ui->widgetLY->layout())->addWidget(&line_y_max);
	((QBoxLayout *)ui->widgetLX->layout())->insertWidget(0, &line_x_min);
	((QBoxLayout *)ui->widgetLX->layout())->addWidget(&line_x_max);
	tm.restart();
	grid_numbers_x = grid_numbers_y = 1;
	LN10                            = qLn(10.);
	line_x_min.setClearButtonVisible(true);
	line_x_max.setClearButtonVisible(true);
	line_y_min.setClearButtonVisible(true);
	line_y_max.setClearButtonVisible(true);
	connect(&line_x_min, &EvalSpinBox::valueChanged, this, &Graphic::lineXMinChanged);
	connect(&line_x_max, &EvalSpinBox::valueChanged, this, &Graphic::lineXMaxChanged);
	connect(&line_y_min, &EvalSpinBox::valueChanged, this, &Graphic::lineYMinChanged);
	connect(&line_y_max, &EvalSpinBox::valueChanged, this, &Graphic::lineYMaxChanged);
	connect(ui->actionLeaveFullscreen, &QAction::triggered, this, &Graphic::leaveFullscreen);
	connect(ui->canvas_raster, &UWidget::paintEvent, this, &Graphic::canvasPaintEvent);
	prepareCanvas(ui->canvas_raster);
	icon_exp_x   = QIcon(":/icons/expand_x.png");
	icon_exp_y   = QIcon(":/icons/expand_y.png");
	icon_exp_sx  = QIcon(":/icons/expand_s_x.png");
	icon_exp_sy  = QIcon(":/icons/expand_s_y.png");
	icon_pause_b = QImage(":/icons/pause-back.png");
	icon_pause_f = QImage(":/icons/pause-front.png");
	aupdate = grid = isFit = navigation = true;
	aalias = bufferActive = isOGL = cancel = guides = hasLblX = hasLblY = isHover = false;
	pause_ = only_expand_x = only_expand_y = m_LODOptimization = was_trace = false;
	limit_.setCoords(-std::numeric_limits<double>::max(),
					 -std::numeric_limits<double>::max(),
					 std::numeric_limits<double>::max(),
					 std::numeric_limits<double>::max());
	eminx = eminy = std::numeric_limits<double>::max();
	emaxx = emaxy = -std::numeric_limits<double>::max();
	grad_x = grad_y    = Auto;
	axis_type_x        = Numeric;
	floating_axis_type = Trace;
	min_repaint_int    = 25; // 40 Hz repaint interval
	lastw = lasth = 0;
	inc_x         = 1.;
	// buffer = 0;
	gridx = gridy = 1.;
	history       = 5.;
	visible_time  = -1.;
	thick         = lineThickness(this);
#if QT_VERSION_MAJOR >= 5
	thick *= devicePixelRatio();
#endif
	pause_phase = 0.;
	def_rect.setRect(0., 0., 1., 1.);
	selrect = def_rect;
	margins_.setRect(4, 4, 4, 4);
	curaction = gaMove;
	selbrush.setStyle(Qt::SolidPattern);
	selbrush.setColor(QColor(60, 175, 255, 100));
	text_color = palette().color(QPalette::WindowText);
	grid_pen   = QPen(palette().color(QPalette::Disabled, QPalette::WindowText), 0., Qt::DotLine);
	graphics.append(GraphicType());
	curGraphic = 0;
	selpen     = palette().color(QPalette::WindowText);
	selpen.setStyle(Qt::DashLine);
	back_color = palette().color(QPalette::Base);
	back_color.setAlpha(255);
	buttons_ = AllButtons;
	setOpenGL(false);
	setButtonsPosition(Graphic::Left);
	setAntialiasing(false);
	setCaption("");
	setBorderInputsVisible(false);
	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
	ui->layoutButtons->update();
	ui->widgetTop->addAction(ui->actionLeaveFullscreen);
	updateLegend();
	setRectToLines();
	conf = new GraphicConf(graphics, this);
	connect(conf, &GraphicConf::exportClicked, this, &Graphic::on_graphic_actionExportCSV_triggered);
}


Graphic::~Graphic() {
	for (auto & g: graphics)
		g.destroy();
#ifdef NO_BUTTONS
	delete buttons_menu;
#endif
	delete conf;
	delete ui;
}


void Graphic::changeEvent(QEvent * e) {
	QFrame::changeEvent(e);
	if (e->type() == QEvent::LanguageChange) {
		ui->retranslateUi(this);
		fillDateFormats();
		return;
	}
}


void Graphic::resizeEvent(QResizeEvent *) {
	if (leg_update)
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
		QTimer::singleShot(0, this, [this]() { updateLegend(false); });
#else
		updateLegend(false);
#endif
}


void Graphic::showEvent(QShowEvent *) {
	if (need_createGL && !canvas_gl) {
		// qDebug() << "create GL on show";
		canvas_gl = new UGLWidget();
		ui->layoutCanvas->addWidget(canvas_gl);
		connect(canvas_gl, &UGLWidget::paintSignal, this, &Graphic::canvasPaintEvent);
		prepareCanvas(canvas_gl);
		ui->canvas_raster->hide();
		canvas_gl->show();
		canvas = canvas_gl;
	}
	need_createGL = false;
}


void Graphic::timerEvent(QTimerEvent * e) {
	if (e->timerId() == timer_pause) {
		pause_phase += 0.02;
		if (pause_phase > 1.) pause_phase -= 1.;
		repaintCanvas();
	}
	if (e->timerId() == timer_record) {
		QPixmap im(canvas->size());
		canvas->render(&im);
		record_imgs << im.toImage().convertToFormat(QImage::Format_Indexed8);
	}
}


bool Graphic::eventFilter(QObject * o, QEvent * e) {
	if (o == canvas) {
		switch (e->type()) {
		case QEvent::Gesture:
			if (!navigation || !gestures) break;
			foreach(QGesture * g, ((QGestureEvent *)e)->gestures())
				procGesture(g);
			break;
		case QEvent::KeyPress: {
			int k = ((QKeyEvent *)e)->key();
			if ((k == Qt::Key_Back || k == Qt::Key_Escape) && fullscr) {
				leaveFullscreen();
				return true;
			}
		} break;
		case QEvent::TouchBegin:
			if (!navigation || !gestures) break;
			need_mouse_pan = true;
			break;
		case QEvent::TouchUpdate: {
			if (!navigation || !gestures) break;
			QList<QTouchEvent::TouchPoint> tpl =
#if QT_VERSION_MAJOR <= 5
				((QTouchEvent *)e)->touchPoints();
#else
				((QTouchEvent *)e)->points();
#endif
			if (tpl.size() == 2) {
				need_mouse_pan = false;
				QPointF dp =
#if QT_VERSION_MAJOR <= 5
					tpl[0].scenePos() - tpl[1].scenePos();
#else
					tpl[0].scenePosition() - tpl[1].scenePosition();
#endif
				gesture_angle = rad2deg_qpie * qAtan2(qAbs(dp.y()), qAbs(dp.x()));
			}
		} break;
		default: break;
		}
	}
	if (e->type() == QEvent::ContextMenu) {
		action_source = qobject_cast<QCheckBox *>(o);
	}
	return QFrame::eventFilter(o, e);
}


void Graphic::prepareCanvas(QWidget * w) {
	connect(w, SIGNAL(mouseMoveEvent(QMouseEvent *)), this, SLOT(canvasMouseMoveEvent(QMouseEvent *)));
	connect(w, SIGNAL(mousePressEvent(QMouseEvent *)), this, SLOT(canvasMousePressEvent(QMouseEvent *)));
	connect(w, SIGNAL(mouseReleaseEvent(QMouseEvent *)), this, SLOT(canvasMouseReleaseEvent(QMouseEvent *)));
	connect(w, SIGNAL(mouseDoubleClickEvent(QMouseEvent *)), this, SLOT(canvasMouseDoubleClickEvent(QMouseEvent *)));
	connect(w, SIGNAL(wheelEvent(QWheelEvent *)), this, SLOT(canvasWheelEvent(QWheelEvent *)));
	connect(w, SIGNAL(leaveEvent(QEvent *)), this, SLOT(canvasLeaveEvent(QEvent *)));
	w->grabGesture(Qt::TapAndHoldGesture);
	w->grabGesture(Qt::PanGesture);
	w->grabGesture(Qt::PinchGesture);
	w->setMouseTracking(true);
	w->installEventFilter(this);
}


void Graphic::procGesture(QGesture * g) {
	if (!g) return;
	switch (g->gestureType()) {
	case Qt::PanGesture: {
		if (need_mouse_pan) break;
		QPanGesture * pg = (QPanGesture *)g;
		QPointF dp       = -pg->delta();
		dp.rx() /= getScaleX();
		dp.ry() /= getScaleY();
		dp.ry() = -dp.y();
		selrect.translate(dp);
		totalUpdate();
	} break;
	case Qt::PinchGesture: {
		QPinchGesture * pg       = (QPinchGesture *)g;
		Qt::KeyboardModifiers km = Qt::NoModifier;
		if (gesture_angle <= 20.) km = Qt::ControlModifier;
		if (gesture_angle >= 70.) km = Qt::ShiftModifier;
		QPoint cp = pg->centerPoint().toPoint();
		if (!fullscr) cp = mapFromGlobal(cp);
		procZoom(cp, (pg->scaleFactor() - 1.) * 500., km);
		totalUpdate();
	} break;
	case Qt::TapAndHoldGesture: {
		QTapAndHoldGesture * pg = (QTapAndHoldGesture *)g;
		if (pg->state() == Qt::GestureStarted) QMetaObject::invokeMethod(this, [this]() { showMenu(); }, Qt::QueuedConnection);
	} break;
	default: break;
	}
}


void Graphic::procZoom(QPointF view_center, double dzoom, Qt::KeyboardModifiers km) {
	double scl, wid = canvas->width() - gridborder.x() - margins_.width() - margins_.left(),
				hei = canvas->height() - gridborder.y() - margins_.height() - margins_.top();
	double px = view_center.x() - gridborder.x() - margins_.left(), py = hei - view_center.y() + margins_.height();
	px  = px / wid * selrect.width() + selrect.x();
	py  = py / hei * selrect.height() + selrect.y();
	scl = 1. - dzoom / 500.;
	if (km == Qt::NoModifier)
		selrect.setRect(px - (px - selrect.x()) * scl, py - (py - selrect.y()) * scl, selrect.width() * scl, selrect.height() * scl);
	else {
		if (km == Qt::ControlModifier) selrect.setRect(px - (px - selrect.x()) * scl, selrect.y(), selrect.width() * scl, selrect.height());
		if (km == Qt::ShiftModifier) selrect.setRect(selrect.x(), py - (py - selrect.y()) * scl, selrect.width(), selrect.height() * scl);
		if (km == Qt::AltModifier) selrect.translate((dzoom > 0. ? 1. : -1.) * selrect.width() / 2., 0.);
	}
}


void Graphic::totalUpdate() {
	isFit = false;
	emit visualRectChanged();
	repaintCanvas(true);
	setRectToLines();
}


void Graphic::canvasPaintEvent() {
	if (is_lines_update) return;
	int wid = canvas->width(), hei = canvas->height();
	if (canvas->isHidden() || wid <= 1 || hei <= 1) return;
	if (!buffer.isNull()) {
		if (lastw != wid || lasth != hei) buffer = QPixmap();
	}
	if (buffer.isNull()) {
#if QT_VERSION_MAJOR >= 5
		const qreal dpr = canvas->devicePixelRatio();
		buffer          = QPixmap(canvas->size() * dpr);
		buffer.setDevicePixelRatio(dpr);
#else
		buffer = QPixmap(canvas->size());
#endif
	}
	lastw   = wid;
	lasth   = hei;
	font_sz = fontMetrics().size(0, "0");
	font_sz.setHeight(font_sz.height() * 1.);
#ifdef Q_OS_ANDROID
	font_sz.setWidth(font_sz.width() * 6);
#else
	font_sz.setWidth(font_sz.width() * 8);
#endif
	thick = lineThickness(this);
#if QT_VERSION_MAJOR >= 5
	thick *= canvas->devicePixelRatio();
#endif
	if (bufferActive) {
		QPainter p(canvas);
		p.drawPixmap(QPoint(), buffer);
		painter = &p;
		fp_size.clear();
		if (curpos != startpos) drawAction();
		drawGuides();
		return;
	}
	QPainter p;
#ifdef HAS_GL
	if (isOGL && !m_fakeGL) {
		p.fillRect(canvas->rect(), Qt::black);
		glClearColor(0.f, 0.f, 0.f, 0.f);
		p.begin(canvas);
	} else
#endif
		p.begin(&buffer);
	p.fillRect(canvas->rect(), back_color);
	painter = &p;
	p.setFont(font());
	gridborder = QPoint(5, 5);
	if (grid) {
		gridborder += QPoint(font_sz.width(), font_sz.height());
		if (hasLblY) gridborder += QPoint(font_sz.height(), 0);
		if (hasLblX) gridborder += QPoint(0, font_sz.height());
		if (axis_type_x == DateTime) gridborder += QPoint(0, font_sz.height() * 2);
	}
	painter->setClipping(true);
	painter->setClipRect(QRect(gridborder.x(), 0, wid - gridborder.x(), hei - gridborder.y()));
	emit beforeGraphicPaintEvent(painter);
	painter->setClipping(false);
	if (grid) drawGrid();
	p.setRenderHint(QPainter::Antialiasing, aalias);
#ifdef HAS_GL
	if (isOGL && !m_fakeGL) {
		if (aalias)
			glEnable(GL_MULTISAMPLE);
		else
			glDisable(GL_MULTISAMPLE);
	}
#endif
	fp_size.clear();
	if (!aalias) p.translate(-0.5, -0.5);
	drawGraphics();
	drawGuides();
	if (pause_) drawPause();
	emit graphicPaintEvent(painter);
	p.end();
	if (isOGL && !m_fakeGL) return;
	p.begin(canvas);
	p.drawPixmap(QPoint(), buffer);
	p.end();
}


void Graphic::canvasMouseMoveEvent(QMouseEvent * e) {
	isHover  = true;
	curpos   = e->pos();
	curpos_r = canvas2real(curpos);
	QPointF dp;
	emit graphicMouseMoveEvent(curpos_r, e->buttons());
	if (e->buttons() == Qt::NoButton) {
		if (ui->status->isVisible()) ui->status->setText(tr("Cursor: ") + pointCoords(curpos_r));
		if (guides) repaintCanvas();
		return;
	}
	if (!navigation) return;
	if (gestures) {
		if (!need_mouse_pan) return;
		curaction = gaMove;
	} else if (curaction != gaMove && (e->buttons() & Qt::RightButton) == Qt::RightButton)
		return;
	switch (curaction) {
	case gaZoomInRect:
		if (ui->status->isVisible()) {
			ui->status->setText(tr("Selection") + ": " + pointCoords(startpos_r) + " -> " + pointCoords(curpos_r) + ",  " + tr("Size") +
			                    ": " + pointCoords(absPoint(curpos_r - startpos_r)));
		}
		repaintCanvas(true);
		break;
	case gaZoomRangeX:
		if (ui->status->isVisible()) {
			ui->status->setText(tr("Range") + ": " + QString::number(startpos_r.x(), 'f', 3) + " -> " +
			                    QString::number(curpos_r.x(), 'f', 3) + ",  " + tr("Length") + ": " +
			                    QString::number(qAbs(curpos_r.x() - startpos_r.x()), 'f', 3));
		}
		repaintCanvas(true);
		break;
	case gaZoomRangeY:
		if (ui->status->isVisible()) {
			ui->status->setText(tr("Range") + ": " + QString::number(startpos_r.y(), 'f', 3) + " -> " +
			                    QString::number(curpos_r.y(), 'f', 3) + ",  " + tr("Length") + ": " +
			                    QString::number(qAbs(curpos_r.y() - startpos_r.y()), 'f', 3));
		}
		repaintCanvas(true);
		break;
	case gaMove:
		dp = e->pos() - prevpos;
		dp.rx() *= selrect.width() / double(gridborder.x() + 5 - lastw);
		dp.ry() *= selrect.height() / double(lasth - gridborder.y() - 5);
		if (e->modifiers() == Qt::ControlModifier) dp.setY(0.);
		if (e->modifiers() == Qt::ShiftModifier) dp.setX(0.);
		selrect.translate(dp);
		totalUpdate();
		break;
	default: break;
	}
	prevpos = e->pos();
}


void Graphic::canvasMousePressEvent(QMouseEvent * e) {
	emit graphicMousePressEvent(canvas2real(QPointF(e->pos())), e->button());
	if (!navigation) return;
	if (gestures && !need_mouse_pan) return;
	setGuidesCursor();
	prevpos    = e->pos();
	startpos   = prevpos;
	startpos_r = canvas2real(startpos);
	if (cancel || gestures) return;
	if (e->button() == QT_MID_BUTTON) curaction = gaMove;
	if (e->button() == Qt::RightButton) {
		if (bufferActive) {
			curpos   = startpos;
			curpos_r = canvas2real(curpos);
			repaintCanvas(true);
			swapToNormal();
			cancel = true;
			return;
		} else {
			prevaction = curaction;
			setCurrentAction(gaMove);
			return;
		}
	}
	if (e->button() == Qt::LeftButton) {
		if (e->modifiers() == Qt::ControlModifier)
			curaction = gaZoomRangeX;
		else if (e->modifiers() == Qt::ShiftModifier)
			curaction = gaZoomRangeY;
		else
			curaction = gaZoomInRect;
		switch (curaction) {
		case gaZoomInRect:
		case gaZoomRangeX:
		case gaZoomRangeY: swapToBuffer(); break;
		default: break;
		}
	}
	setCurrentAction(curaction);
}


void Graphic::canvasMouseReleaseEvent(QMouseEvent * e) {
	emit graphicMouseReleaseEvent(canvas2real(QPointF(e->pos())), e->button());
	if (gestures) return;
	need_mouse_pan = false;
	if (!navigation) return;
	setGuidesCursor();
	QPointF tlp, brp;
	QRect sr;
	sr = QRect(startpos, curpos).normalized();
	if (cancel) {
		if (e->buttons() == Qt::NoButton) cancel = false;
		return;
	}
	if (e->button() == Qt::RightButton && curaction == gaMove) {
		curaction = prevaction;
		return;
	}
	if (e->button() == Qt::LeftButton && (e->buttons() & Qt::RightButton) != Qt::RightButton) {
		if (curpos != startpos) {
			tlp   = canvas2real(sr.topLeft());
			brp   = canvas2real(sr.bottomRight());
			isFit = false;
			switch (curaction) {
			case gaZoomInRect:
				if (sr.width() <= 1 || sr.height() <= 1) break;
				selrect.setCoords(tlp.x(), brp.y(), brp.x(), tlp.y());
				setRectToLines();
				break;
			case gaZoomRangeX:
				if (sr.width() <= 1) break;
				findGraphicsRect(tlp.x(), brp.x());
				break;
			case gaZoomRangeY:
				if (sr.height() <= 1) break;
				findGraphicsRect(0., 0., brp.y(), tlp.y());
				break;
			default: return;
			}
		}
		swapToNormal();
		repaintCanvas(true);
	}
	if (ui->status->isVisible()) {
		ui->status->setText(tr("Cursor") + ": " + pointCoords(canvas2real(QPointF(e->pos()))));
	}
	emit visualRectChanged();
}


void Graphic::canvasMouseDoubleClickEvent(QMouseEvent *) {
	if (!navigation) return;
	autofit();
}


void Graphic::canvasWheelEvent(QWheelEvent * e) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	emit graphicWheelEvent(canvas2real(e->position()), e->angleDelta().y());
#else
	emit graphicWheelEvent(canvas2real(QPointF(e->pos())), e->delta());
#endif
	if (gestures) return;
	if (!navigation) return;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	procZoom(e->position(), e->modifiers() == Qt::AltModifier ? e->angleDelta().x() : e->angleDelta().y(), e->modifiers());
#else
	procZoom(e->pos(), e->delta(), e->modifiers());
#endif
	totalUpdate();
}


void Graphic::zoom(float factor) {
	double wid = canvas->width() - gridborder.x() - margins_.width() - margins_.left(),
		   hei = canvas->height() - gridborder.y() - margins_.height() - margins_.top();
	double px = wid / 2, py = hei / 2;
	px = px / wid * selrect.width() + selrect.x();
	py = py / hei * selrect.height() + selrect.y();
	selrect.setRect(px - (px - selrect.x()) * factor,
	                py - (py - selrect.y()) * factor,
	                selrect.width() * factor,
	                selrect.height() * factor);
	isFit = false;
	repaintCanvas(true);
	setRectToLines();
}


void Graphic::fullscreen() {
	if (fullscr)
		leaveFullscreen();
	else
		enterFullscreen();
}


void Graphic::canvasLeaveEvent(QEvent *) {
	isHover = false;
	if (guides) repaintCanvas(true);
	if (ui->status->isVisible()) ui->status->setText(tr("Cursor") + ": ( ; )");
}


void Graphic::clear() {
	for (int i = 0; i < graphics.size(); ++i) {
		graphics[i].polyline.clear();
		graphics[i].polyline_pause.clear();
		graphics[i]._lod.clear();
		graphics[i]._lod_pause.clear();
		graphics[i].max_x  = 0.;
		graphics[i].cvrect = QRectF();
	}
	if (isFit) autofit();
}


void Graphic::setAntialiasing(bool enabled) {
	if (aalias == enabled) return;
	aalias = enabled;
	repaintCanvas();
}


void Graphic::setAutoUpdate(bool enabled) {
	aupdate = enabled;
}


void Graphic::setPaused(bool yes) {
	pause_ = yes;
	ui->graphic_checkPause->blockSignals(true);
	ui->graphic_checkPause->setChecked(yes);
	ui->graphic_checkPause->blockSignals(false);
	for (int i = 0; i < graphics.size(); ++i)
		graphics[i].cvrect = QRectF();
	if (!pause_) {
		killTimer(timer_pause);
		timer_pause = 0;
		repaintCanvas(true);
		return;
	}
	for (int i = 0; i < graphics.size(); ++i) {
		graphics[i].polyline_pause = graphics[i].polyline;
		graphics[i].polyline_pause.detach();
		graphics[i]._lod_pause = graphics[i]._lod;
		graphics[i]._lod_pause.detach();
		graphics[i].max_x_pause = graphics[i].max_x;
	}
	timer_pause = startTimer(40);
}


void Graphic::setHistorySize(double val) {
	history = val;
	double x;
	for (int i = 0; i < graphics.size(); ++i) {
		QPolygonF & pol(graphics[i].polyline);
		if (pol.isEmpty() || history <= 0.) continue;
		graphics[i].cvrect = QRectF();
		x                  = pol.back().x() - history;
		for (int j = pol.size() - 2; j >= 0; --j)
			if (pol[j].x() < x) {
				pol.erase(pol.begin(), pol.begin() + j);
				break;
			}
	}
}


void Graphic::setMaxVisibleTime(double val) {
	visible_time = val;
	if (isFit) autofit();
}


void Graphic::setAutoXIncrement(double val) {
	inc_x = val;
}


void Graphic::setLimit(const QRectF & val) {
	limit_ = val;
}


void Graphic::setMargins(const QRect & val) {
	margins_ = val;
	repaintCanvas();
}


void Graphic::setMargins(int left_, int right_, int top_, int bottom_) {
	setMargins(QRect(left_, bottom_, right_, top_));
}


void Graphic::setLeftMargin(int value) {
	margins_.moveLeft(value);
	setMargins(margins_);
}


void Graphic::setRightMargin(int value) {
	margins_.setWidth(value);
	setMargins(margins_);
}


void Graphic::setTopMargin(int value) {
	margins_.setHeight(value);
	setMargins(margins_);
}


void Graphic::setBottomMargin(int value) {
	margins_.moveTop(value);
	setMargins(margins_);
}


void Graphic::setMinimumRepaintInterval(const int & val) {
	min_repaint_int = val;
}


void Graphic::setOnlyExpandY(bool yes) {
	ui->graphic_actionExpandY->setChecked(yes);
}


void Graphic::setOnlyExpandX(bool yes) {
	ui->graphic_actionExpandX->setChecked(yes);
}


void Graphic::setGesturesNavigation(bool yes) {
	gestures = yes;
}


Graphic::GraphicsData Graphic::graphicsData() const {
	GraphicsData ret;
	ret.resize(graphics.size());
	for (int i = 0; i < graphics.size(); ++i)
		ret[i] = graphics[i].polyline;
	return ret;
}


QByteArray Graphic::graphicsDataRaw() const {
	QByteArray ret;
	QDataStream s(&ret, QIODevice::WriteOnly);
	s << graphicsData();
	return ret;
}


void Graphic::setGraphicsData(const Graphic::GraphicsData & gd) {
	setGraphicsCount(gd.size());
	for (int i = 0; i < gd.size(); ++i) {
		setGraphicData(gd[i], i, false);
	}
	updateGraphics();
}


void Graphic::setGraphicsDataRaw(const QByteArray & ba) {
	if (ba.isEmpty()) {
		clear();
		return;
	}
	Graphic::GraphicsData gd;
	QDataStream s(ba);
	s >> gd;
	setGraphicsData(gd);
}


void Graphic::setGridNumbersMultiplierX(double value) {
	grid_numbers_x = value;
	updateGraphics();
}


void Graphic::setGridNumbersMultiplierY(double value) {
	grid_numbers_y = value;
	updateGraphics();
}


void Graphic::setGraduationX(Graduation value) {
	grad_x = value;
	updateGraphics();
}


void Graphic::setGraduationY(Graduation value) {
	grad_y = value;
	updateGraphics();
}


void Graphic::setGraduationStepX(double sx) {
	gridx = sx;
	updateGraphics();
}


void Graphic::setGraduationStepY(double sy) {
	gridy = sy;
	if (aupdate) repaintCanvas();
}


void Graphic::setGraduationSteps(double sx, double sy) {
	gridx = sx;
	gridy = sy;
	if (aupdate) repaintCanvas();
}


void Graphic::setAxisType(AxisType t) {
	axis_type_x = t;
	if (aupdate) repaintCanvas();
}


void Graphic::setFloatingAxisType(FloatingAxisType t) {
	floating_axis_type = t;
	setGuidesCursor();
	if (aupdate) repaintCanvas();
}


void Graphic::setFloatingAxisEnabled(bool on) {
	ui->graphic_checkGuides->setChecked(on);
}


void Graphic::setButtons(Graphic::Buttons b) {
	buttons_ = b;
	ui->graphic_buttonAutofit->setVisible(b.testFlag(Autofit));
	ui->graphic_checkGuides->setVisible(b.testFlag(CursorAxis));
	ui->graphic_buttonFullscreen->setVisible(b.testFlag(Fullscreen));
	ui->graphic_checkBorderInputs->setVisible(b.testFlag(BorderInputs));
	ui->graphic_checkLegend->setVisible(b.testFlag(Legend));
	ui->graphic_buttonClear->setVisible(b.testFlag(Clear));
	ui->graphic_buttonConfigure->setVisible(b.testFlag(Configure));
	ui->graphic_buttonSaveMenu->setVisible(b.testFlag(Save) || b.testFlag(Export));
	ui->graphic_actionExportCSV->setVisible(b.testFlag(Export));
	ui->graphic_actionExportCSV_View->setVisible(b.testFlag(Export));
	ui->graphic_actionSaveImage->setVisible(b.testFlag(Save));
	ui->graphic_buttonClose->setVisible(b.testFlag(Close));
	ui->graphic_checkPause->setVisible(b.testFlag(Pause));
	ui->graphic_buttonRecord->setVisible(b.testFlag(Record));
	if (ui->graphic_buttonAutofit->isVisible() || ui->graphic_checkGuides->isVisible() || ui->graphic_buttonConfigure->isVisible() ||
	    ui->graphic_checkPause->isVisible()) {
		ui->verticalSpacer->changeSize(0, 30, QSizePolicy::Preferred, QSizePolicy::Preferred);
	} else {
		ui->verticalSpacer->changeSize(0, 0, QSizePolicy::Preferred, QSizePolicy::Preferred);
	}
	ui->layoutButtons->update();
}


void Graphic::setButtonsPosition(Graphic::Alignment a) {
	align = a;
	ui->widgetLeft->hide();
	ui->widgetRight->hide();
#ifdef NO_BUTTONS
	return;
#endif
	switch (a) {
	case Graphic::Left:
		ui->widgetLeft->setLayout(ui->layoutButtons);
		ui->widgetLeft->show();
		break;
	case Graphic::Right:
		ui->widgetRight->setLayout(ui->layoutButtons);
		ui->widgetRight->show();
		break;
	}
}


void Graphic::addPoint(const QPointF & p, int graphic, bool update_) {
	if (!checkGraphicIndex(graphic)) return;
	GraphicType & t(graphics[graphic]);
	if (!t.cvrect.isNull() && !pause_) {
		if (t.cvrect.top() < p.y()) t.cvrect.setTop(p.y());
		if (t.cvrect.bottom() > p.y()) t.cvrect.setBottom(p.y());
		if (t.cvrect.right() < p.x()) t.cvrect.setRight(p.x());
		if (t.cvrect.left() > p.x()) t.cvrect.setLeft(p.x());
	}
	if (t.polyline.size() == 0) t.max_x = p.x();
	if (t.max_x < p.x()) t.max_x = p.x();
	t.polyline << p;
	tick(graphic, true, update_);
}


void Graphic::addPoint(const QPointF & p, bool update) {
	addPoint(p, curGraphic, update);
}


void Graphic::addPoint(double x, double y, int graphic, bool update) {
	addPoint(QPointF(x, y), graphic, update);
}


void Graphic::addPoint(double x, double y, bool update) {
	addPoint(QPointF(x, y), update);
}


void Graphic::addPoint(double y, int graphic, bool update) {
	if (!checkGraphicIndex(graphic)) return;
	if (graphics[graphic].polyline.isEmpty()) {
		addPoint(QPointF(0.0, y), graphic, update);
	} else {
		addPoint(QPointF(graphics[graphic].max_x + inc_x, y), graphic, update);
	}
}


void Graphic::addPoint(double y, bool update) {
	addPoint(y, curGraphic, update);
}


void Graphic::addPoints(const QPolygonF & pts, int graphic, bool update_) {
	if (!checkGraphicIndex(graphic)) return;
	if (pts.isEmpty()) return;
	GraphicType & t(graphics[graphic]);
	if (!t.cvrect.isNull() && !pause_) {
		for (const QPointF & p: pts) {
			if (t.cvrect.top() < p.y()) t.cvrect.setTop(p.y());
			if (t.cvrect.bottom() > p.y()) t.cvrect.setBottom(p.y());
			if (t.cvrect.right() < p.x()) t.cvrect.setRight(p.x());
			if (t.cvrect.left() > p.x()) t.cvrect.setLeft(p.x());
		}
	}
	if (t.polyline.size() == 0) t.max_x = pts.at(0).x();
	for (const QPointF & p: pts) {
		if (t.max_x < p.x()) t.max_x = p.x();
	}
	t.polyline << pts;
	tick(graphic, true, update_);
}


void Graphic::addPoints(const QPolygonF & pts, bool update) {
	addPoints(pts, curGraphic, update);
}


void Graphic::addPoints(const QVector<double> & pts, int graphic, bool update_) {
	if (!checkGraphicIndex(graphic)) return;
	QPolygonF ps;
	ps.reserve(pts.size());
	double stx = 0;
	if (!graphics[graphic].polyline.isEmpty()) stx = graphics[graphic].max_x;
	for (int i = 0; i < pts.size(); ++i) {
		ps << QPointF(stx + i * inc_x, pts[i]);
	}
	addPoints(ps, graphic, update_);
}


void Graphic::addPoints(const QVector<double> & pts, bool update) {
	addPoints(pts, curGraphic, update);
}


void Graphic::setGraphicData(const QVector<QPointF> & g, int graphic, bool update_) {
	if (!checkGraphicIndex(graphic)) return;
	GraphicType & t(graphics[graphic]);
	t.cvrect = QRectF();
	t.polyline.clear();
	t.polyline = g;
	if (t.polyline.size() == 0) {
		t.max_x = 0.;
		tick(graphic, false, update_);
		return;
	}
	t.max_x = t.polyline[0].x();
	for (const auto & p: t.polyline) {
		if (t.max_x < p.x()) t.max_x = p.x();
	}
	tick(graphic, false, update_);
}


void Graphic::setGraphicData(const QVector<QPointF> & g) {
	setGraphicData(g, curGraphic);
}


void Graphic::setGraphicProperties(const QString & name, const QColor & color, Qt::PenStyle style, double width, bool visible) {
	setGraphicProperties(curGraphic, name, color, style, width, visible);
}


void Graphic::setGraphicProperties(int graphic,
                                   const QString & name,
                                   const QColor & color,
                                   Qt::PenStyle style,
                                   double width,
                                   bool visible) {
	if (!checkGraphicIndex(graphic)) return;
	graphics[graphic].name = name;
	graphics[graphic].pen.setColor(color);
	graphics[graphic].pen.setStyle(style);
	graphics[graphic].pen.setWidth(width);
	graphics[graphic].visible = visible;
	updateLegend();
}


void Graphic::addGraphic(const QString & name, const QColor & color, Qt::PenStyle style, double width, bool visible) {
	addGraphic(GraphicType(name, color, style, width, visible));
}


void Graphic::addGraphic(const GraphicType & gd) {
	graphics << gd;
	updateLegend();
	repaintCanvas();
}


void Graphic::setVisualRect(const QRectF & rect) {
	selrect = rect;
	isFit   = false;
	repaintCanvas();
}


void Graphic::setDefaultRect(const QRectF & rect) {
	def_rect = rect;
	if (isFit) autofit();
}


void Graphic::autofit() {
	isFit        = true;
	bool isEmpty = true;
	for (const GraphicType & t: graphics) {
		const QPolygonF & pol(pause_ ? t.polyline_pause : t.polyline);
		if (!pol.isEmpty()) {
			isEmpty = false;
			break;
		}
	}
	if (isEmpty) grect = def_rect;
	selrect = grect;
	findGraphicsRect();
	repaintCanvas();
}


void Graphic::saveImage(QString filename) {
	ppath = filename;
	QPixmap im;
	QString custom_title;
	if (func_image_title) custom_title = func_image_title();
	if (custom_title.isEmpty()) {
		im = QPixmap(canvas->size());
		canvas->render(&im);
	} else {
		QSize sz   = canvas->size();
		QRectF tbr = fontMetrics().boundingRect(QRect(0, 0, sz.width(), 1), Qt::TextWordWrap, custom_title);
		sz.setHeight(sz.height() + tbr.height());
		im = QPixmap(canvas->size());
		im.fill(back_color);
		QPainter p(&im);
		p.setPen(text_color);
		p.drawText(tbr, Qt::TextWordWrap, custom_title);
		p.end();
		canvas->render(&im, QPoint(0, tbr.height()));
	}
	im.save(ppath);
}


void Graphic::exportGraphics(QString filename, QChar decimal_point, bool view_only) {
	ppath = filename;
	QFile f(filename);
	if (!f.open(QIODevice::ReadWrite)) {
		QMessageBox::critical(this, tr("Export graphics"), tr("Can`t open file \"%1\"!").arg(filename));
		return;
	}
	QApplication::setOverrideCursor(Qt::WaitCursor);
	f.resize(0);
	QTextStream ts(&f);
	ts << "#";
	for (int i = 0; i < graphics.size(); ++i) {
		GraphicType & g(graphics[i]);
		if (!g.visible) continue;
		ts << ";" << (g.name + " " + (label_x.isEmpty() ? "X" : label_x)) << ";" << (g.name + " " + (label_y.isEmpty() ? "Y" : label_y));
	}
	ts << "\n";
	QVector<GraphicType> view_gr;
	auto * source = &graphics;
	if (view_only) {
		for (const auto & g: graphics) {
			GraphicType gt;
			gt.visible = g.visible;
			gt.polyline.reserve(g.polyline.size() / 8);
			for (const auto & i: g.polyline) {
				if (selrect.contains(i)) gt.polyline << i;
			}
			view_gr << gt;
		}
		source = &view_gr;
	}
	bool has_data = true;
	int ind       = 0;
	QString line;
	while (has_data) {
		has_data = false;
		line.clear();
		line += QString::number(ind + 1);
		for (int i = 0; i < source->size(); ++i) {
			const GraphicType & g(source->at(i));
			if (!g.visible) continue;
			if (ind >= g.polyline.size()) {
				line += ";;";
				continue;
			}
			has_data = true;
			line += ";";
			if (func_gridMarkX) {
				line += func_gridMarkX(g.polyline[ind].x()).replace('.', decimal_point);
			} else {
				line += QString::number(g.polyline[ind].x(), 'g', 15).replace('.', decimal_point);
			}
			line += ";";
			if (func_gridMarkY) {
				line += func_gridMarkY(g.polyline[ind].y()).replace('.', decimal_point);
			} else {
				line += QString::number(g.polyline[ind].y(), 'g', 12).replace('.', decimal_point);
			}
		}
		++ind;
		line += "\n";
		if (has_data) ts << line;
	}
	QApplication::restoreOverrideCursor();
}


void Graphic::setOpenGL(bool on) {
#ifdef HAS_GL
	isOGL         = on;
	need_createGL = false;
	if (on && !m_fakeGL) {
		if (!canvas_gl) {
			if (!isVisible())
				need_createGL = true;
			else {
				// qDebug() << "create GL on setter";
				canvas_gl = new UGLWidget();
				ui->layoutCanvas->addWidget(canvas_gl);
				connect(canvas_gl, &UGLWidget::paintSignal, this, &Graphic::canvasPaintEvent);
				prepareCanvas(canvas_gl);
			}
		}
		if (canvas_gl) {
			ui->canvas_raster->hide();
			canvas_gl->show();
			canvas = canvas_gl;
		}
	} else {
		if (canvas_gl) canvas_gl->hide();
		ui->canvas_raster->show();
		canvas = ui->canvas_raster;
	}
#else
	isOGL = false;
	ui->canvas_raster->show();
	canvas = ui->canvas_raster;
#endif
	repaintCanvas();
}


void Graphic::update() {
	repaintCanvas();
}

void Graphic::updateGraphics() {
	findGraphicsRect();
	repaintCanvas(true);
}


void Graphic::setCurrentGraphic(int arg) {
	if (arg < 0 || arg >= graphics.size()) return;
	curGraphic = arg;
}


#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
#  define _G_QRAND_ QRandomGenerator::global()->generate()
#else
#  define _G_QRAND_ qrand()
#endif

void Graphic::setGraphicsCount(int count, bool update) {
	if (count < 0) return;
	bool changed = false;
	while (graphics.size() < count) {
		GraphicType gt(tr("y(x)"));
		if (loaded_configs.size() > graphics.size())
			gt = loaded_configs[graphics.size()];
		else
			gt.pen.setColor(QColor::fromHsv((graphics.size() * 55) % 360, 255, 255 - _G_QRAND_ % 115));
		graphics.append(gt);
		changed = true;
	}
	while (graphics.size() > count) {
		if (action_source == graphics.back().pb) action_source = nullptr;
		graphics.back().destroy();
		graphics.pop_back();
		changed = true;
	}
	if (update && changed) updateLegend();
}

#undef _G_QRAND_


void Graphic::removeGraphic(int arg, bool update) {
	if (arg < 0 || arg >= graphics.size()) return;
	graphics[arg].destroy();
	graphics.remove(arg);
	if (update) updateLegend();
}


void Graphic::setCustomGridMarkFuncs(std::function<QString(double)> fx, std::function<QString(double)> fy) {
	func_gridMarkX = fx;
	func_gridMarkY = fy;
}


void Graphic::setCustomSaveImageTitleFunc(std::function<QString()> func) {
	func_image_title = func;
}


void Graphic::findGraphicsRect(double start_x, double end_x, double start_y, double end_y) {
	double cx, cy, maxX, minX, maxY, minY, vx;
	bool isRangeX = (start_x != end_x), isRangeY = (start_y != end_y);
	bool can_fast   = (start_x == 0 && end_x == 0 && start_y == 0 && end_y == 0);
	bool anyVisible = false, isTimeLimit = (visible_time > 0.) && !(isRangeX || isRangeY);
	vx   = -std::numeric_limits<double>::max();
	minY = minX = std::numeric_limits<double>::max();
	maxY = maxX = -std::numeric_limits<double>::max();
	foreach(const GraphicType & t, graphics) {
		if (!t.visible) continue;
		if (vx < (pause_ ? t.max_x_pause : t.max_x)) vx = (pause_ ? t.max_x_pause : t.max_x);
	}
	vx -= visible_time;
	for (int g = 0; g < graphics.size(); g++) {
		GraphicType & t(graphics[g]);
		if (!t.visible) continue;
		const QPolygonF & pol(pause_ ? t.polyline_pause : t.polyline);
		if (pol.isEmpty()) continue;
		bool f = true;
		if (t.cvrect.isNull() || !can_fast) {
			for (int i = 0; i < pol.size(); i++) {
				cx = pol[i].x();
				cy = pol[i].y();
				if ((start_x > cx || end_x < cx) && isRangeX) continue;
				if ((start_y > cy || end_y < cy) && isRangeY) continue;
				if ((cx < vx) && isTimeLimit) continue;
				if (f) {
					t.cvrect.setRect(cx, cy, 0, 0);
					f = false;
				} else {
					if (t.cvrect.top() < cy) t.cvrect.setTop(cy);
					if (t.cvrect.bottom() > cy) t.cvrect.setBottom(cy);
					if (t.cvrect.right() < cx) t.cvrect.setRight(cx);
					if (t.cvrect.left() > cx) t.cvrect.setLeft(cx);
				}
			}
			if (f) continue;
		}
		anyVisible = true;
		if (maxY < t.cvrect.top()) maxY = t.cvrect.top();
		if (minY > t.cvrect.bottom()) minY = t.cvrect.bottom();
		if (maxX < t.cvrect.right()) maxX = t.cvrect.right();
		if (minX > t.cvrect.left()) minX = t.cvrect.left();
		if (!can_fast) t.cvrect = QRectF();
	}
	if (!anyVisible) {
		grect = def_rect;
		setRectToLines();
		return;
	}
	if (maxX > limit_.right()) maxX = limit_.right();
	if (minX > limit_.right()) minX = limit_.right();
	if (minX < limit_.left()) minX = limit_.left();
	if (maxX < limit_.left()) maxX = limit_.left();
	if (maxY > limit_.bottom()) maxY = limit_.bottom();
	if (minY > limit_.bottom()) minY = limit_.bottom();
	if (minY < limit_.top()) minY = limit_.top();
	if (maxY < limit_.top()) maxY = limit_.top();
	if (minX > maxX) qSwap<double>(minX, maxX);
	if (minY > maxY) qSwap<double>(minY, maxY);
	if (qAbs<double>(minX - maxX) < 1E-60) {
		minX -= defaultRect().width() / 2;
		maxX += defaultRect().width() / 2;
	}
	if (qAbs<double>(minY - maxY) < 1E-60) {
		minY -= defaultRect().height() / 2;
		maxY += defaultRect().height() / 2;
	}
	if (only_expand_x) {
		if (minX > eminx) minX = eminx;
		if (maxX < emaxx) maxX = emaxx;
	}
	if (only_expand_y) {
		if (minY > eminy) minY = eminy;
		if (maxY < emaxy) maxY = emaxy;
	}
	eminx = minX;
	emaxx = maxX;
	eminy = minY;
	emaxy = maxY;
	if (isRangeX)
		selrect.setRect(start_x, minY, end_x - start_x, maxY - minY);
	else if (isRangeY)
		selrect.setRect(minX, start_y, maxX - minX, end_y - start_y);
	else
		grect.setRect(minX, minY, maxX - minX, maxY - minY);
	grect = grect.normalized();
	if (isFit) {
		if (visible_time > 0.) {
			if (grect.width() > visible_time) grect.setLeft(grect.right() - visible_time);
		}
		selrect = grect;
	}
	setRectToLines();
}


void Graphic::drawAction() {
	int wid = canvas->width(), hei = canvas->height() - gridborder.y(), sx = startpos.x(), sy = startpos.y(), cx = curpos.x(),
		cy = curpos.y();
	painter->setPen(selpen);
	painter->setBrush(selbrush);
	switch (curaction) {
	case gaZoomInRect: {
		QSizeF rsz = QRectF(startpos_r, curpos_r).normalized().size();
		painter->drawRect(QRect(startpos, curpos));
		fp_size = " x " + pointCoords(QPointF(rsz.width(), rsz.height()));
	} break;
	case gaZoomRangeX:
		painter->drawLine(sx, hei, sx, 0);
		painter->drawLine(cx, hei, cx, 0);
		painter->fillRect(sx, 0, cx - sx, hei, selbrush);
		fp_size = " x " + pointCoords(QPointF(qAbs(startpos_r.x() - curpos_r.x()), 0.), true, false);
		break;
	case gaZoomRangeY:
		painter->drawLine(gridborder.x(), sy, wid, sy);
		painter->drawLine(gridborder.x(), cy, wid, cy);
		painter->fillRect(gridborder.x(), sy, wid - gridborder.x(), cy - sy, selbrush);
		fp_size = " x " + pointCoords(QPointF(0., qAbs(startpos_r.y() - curpos_r.y())), false, true);
		break;
	default: break;
	}
}


void Graphic::drawGrid() {
	int gbx = gridborder.x(), gby = gridborder.y(), cwid = canvas->width(), chei = canvas->height();
	double px, py, range, step, start;
	int wid = cwid - gbx - 5, hei = chei - gby - 5, cx, cy, cnt, right = cwid + gbx;
	QRect rect;
	QPair<QString, QString> str;

	range = selrect.bottom() - selrect.top();
	if (grad_y == Graphic::Auto)
		step = splitRange(range, hei / gridy / font_sz.height() / 1.4);
	else
		step = gridy;
	start = roundTo(canvas2realY(-hei), step) - step;
	py    = start + step;
	cy    = 0;
	cx    = gbx - 5;
	grid_pen.setWidth(qMax<int>(qMax<int>(qRound(thick / 1.4), 1), grid_pen.width()));
#if QT_VERSION_MAJOR >= 5
	grid_pen.setCosmetic(true);
#endif
	QFont sf = font();
	QFont nf = sf;
	sf.setPointSizeF(qMax<qreal>(sf.pointSizeF() / 1.6, 7.));
	QFontMetrics fm(nf), sfm(sf);
	if (step > 0.) {
		cnt = 1000;
		while (cnt-- > 0) {
			py -= step;
			if (fabs(py) < step * .5) py = 0.;
			cy = real2canvasY(py);
			if (cy < 0) continue;
			if (cy > hei + 5) break;
			painter->setPen(grid_pen);
			painter->drawLine(gbx, cy, cwid, cy);
			painter->setPen(text_color);
			cy += font_sz.height() / 4.;
			int dx = font_sz.height() / 8.;
			if (func_gridMarkY) {
				str.first = func_gridMarkY(py * grid_numbers_y);
				str.second.clear();
			} else {
				str = gridMark(py * grid_numbers_y);
				if (!str.second.isEmpty()) {
					rect = sfm.boundingRect(str.second);
					painter->setFont(sf);
					painter->drawText(cx - rect.width() - dx, cy - font_sz.height() / 2.5, str.second);
					dx += rect.width() + font_sz.height() / 6.;
				}
			}
			rect = fm.boundingRect(str.first);
			painter->setFont(nf);
			painter->drawText(cx - rect.width() - dx, cy, str.first);
		}
	}
	cy = real2canvasY(0.);
	if (cy >= 0 && cy <= (hei + 5)) {
		QPen _p(grid_pen);
		_p.setStyle(Qt::SolidLine);
		painter->setPen(_p);
		painter->drawLine(gbx, cy, cwid, cy);
	}
	if (hasLblY) {
		painter->setPen(text_color);
		painter->save();
		painter->translate(5, hei);
		painter->rotate(-90.);
		painter->drawText(0, 0, hei, font_sz.height(), Qt::AlignCenter, label_y);
		painter->restore();
	}

	cy = chei - font_sz.height() / 4;
	if (hasLblX) cy -= font_sz.height();
	range = selrect.right() - selrect.left();
	QString df;
	if (axis_type_x == Graphic::Numeric) {
		if (grad_x == Graphic::Auto)
			step = splitRange(range, wid / gridx / font_sz.width() * 1.4);
		else
			step = gridx;
		start = roundTo(canvas2realX(right), step);
		px    = start + step;
		if (step > 0.) {
			cnt = 1000;
			while (cnt-- > 0) {
				px -= step;
				if (fabs(px) < step * .5) px = 0.;
				cx = real2canvasX(px);
				if (cx > right) continue;
				if (cx < gbx) break;
				painter->setPen(grid_pen);
				painter->drawLine(cx, hei + 5, cx, 0);
				painter->setPen(text_color);
				int dx = -font_sz.height() / 4.;
				painter->setFont(nf);
				if (func_gridMarkX) {
					str.first = func_gridMarkX(px * grid_numbers_x);
					str.second.clear();
				} else {
					str = gridMark(px * grid_numbers_x);
				}
				rect = fm.boundingRect(str.first);
				painter->drawText(cx + dx, cy, str.first);
				if (!str.second.isEmpty()) {
					dx += rect.width() + font_sz.height() / 6.;
					rect = sfm.boundingRect(str.second);
					painter->setFont(sf);
					painter->drawText(cx + dx, cy - font_sz.height() / 4., str.second);
				}
			}
		}
		cx = real2canvasX(0.);
		if (cx <= right && cx >= gbx) {
			QPen _p(grid_pen);
			_p.setStyle(Qt::SolidLine);
			painter->setPen(_p);
			painter->drawLine(cx, hei + 5, cx, 0);
		}
	} else {
		int dt_add[DateComponentCount];
		int dt_add_lo[DateComponentCount];
		for (int i = 0; i < DateComponentCount; ++i)
			dt_add_lo[i] = dt_add[i] = 0;
		DateFormats formats;
		step = splitRangeDate(range, wid / gridx / font_sz.width() / 1.5, formats, dt_add);
		for (int i = 0; i < DateComponentCount - 1; ++i)
			dt_add_lo[i + 1] = dt_add[i];
		bool is_years = formats.center.isEmpty();
		if (step > 0.) {
			int up_y = cy - font_sz.height() * 2;
			int ce_y = cy - font_sz.height();
			int lo_y = cy;
			int ddx = 0, pcx = 0;
			QDateTime cd = QDateTime::fromMSecsSinceEpoch(canvas2realX(gbx) * grid_numbers_x), cdp, cdc, cdl, cdlp;
			QString ds;
			// qDebug() << step << range << int(wid / gridx / font_sz.width() * 1.4) << cd;
			if (!is_years) {
				roundDateTime(cd, dt_add);
				cdp = cdl = cdlp = cd;
				roundDateTime(cdl, dt_add_lo);
				roundDateTime(cdlp, dt_add_lo);
			} else {
				cd.setTime(QTime(0, 0, 0));
				cd.setDate(QDate(roundTo(cd.date().year(), dt_add[6]) - dt_add[6], 1, 1));
			}
			// qDebug() << cd << cur_scl[0] << cur_scl[1] << cur_scl[2] << cur_scl[3] << cur_scl[4] << cur_scl[5] << cur_scl[6];
			struct Anchor {
				int x_start, x_end;
				QDateTime date;
			};
			QVector<Anchor> areas_ce, areas_lo;
			cnt            = 1000;
			int area_start = gbx, area_start_lo = gbx;
			pcx = real2canvasX(cd.toMSecsSinceEpoch() / grid_numbers_x);
			while (cnt-- > 0) {
				addDateTime(cd, dt_add);
				bool need_text = true;
				if (!is_years) {
					cdc = cd;
					roundDateTime(cdc, dt_add);
					cx = real2canvasX(cd.toMSecsSinceEpoch() / grid_numbers_x);
					if (cx < gbx) continue;
					if (cdp != cdc) {
						int cxc = real2canvasX(cdc.toMSecsSinceEpoch() / grid_numbers_x);
						// qDebug() << cx << cxc << ddx << pcx;
						if ((qAbs(cxc - cx) < ddx) && (cx != cxc)) need_text = false;
						areas_ce << Anchor{area_start, qMin(cxc, right), cdp};
						area_start = areas_ce.back().x_end;
						cd = cdl = cdp = cdc;
						cx             = cxc;
						roundDateTime(cdl, dt_add_lo);
						if (cdlp != cdl) {
							areas_lo << Anchor{area_start_lo, qMin(cxc, right), cdlp};
							cdlp          = cdl;
							area_start_lo = areas_lo.back().x_end;
						}
					} else {
						ddx = (cx - pcx) * 0.95;
					}
					pcx = cx;
				} else {
					cx = real2canvasX(cd.toMSecsSinceEpoch() / grid_numbers_x);
					if (cx < gbx) continue;
				}
				if (cx > right) break;
				painter->setPen(grid_pen);
				painter->drawLine(cx, hei + 5, cx, 0);
				if (need_text) {
					painter->setPen(text_color);
					int dx = -font_sz.height() / 4.;
					painter->setFont(nf);
					painter->drawText(cx + dx, up_y, cd.toString(formats.upper));
				}
			}
			if (!is_years) {
				if (area_start < right) areas_ce << Anchor{area_start, right, cdc};
				if (area_start_lo < right) areas_lo << Anchor{area_start_lo, right, cdl};
				// qDebug() << areas_lo.size() << formats.upper << areas_lo[0].date;
				painter->setPen(grid_pen);
				for (const auto & a: areas_ce) {
					painter->drawLine(a.x_start, hei + 5, a.x_start, ce_y);
				}
				for (const auto & a: areas_lo) {
					painter->drawLine(a.x_start, ce_y, a.x_start, lo_y);
				}

				painter->setPen(text_color);
				painter->setFont(nf);
				auto pfm = painter->fontMetrics();
				for (const auto & a: areas_ce) {
					ds            = a.date.toString(formats.center);
					auto str_rect = pfm.boundingRect(ds);
					painter->drawText(a.x_start + (a.x_end - a.x_start - str_rect.width()) / 2, ce_y, ds);
				}
				for (const auto & a: areas_lo) {
					ds            = a.date.toString(formats.lower);
					auto str_rect = pfm.boundingRect(ds);
					painter->drawText(a.x_start + (a.x_end - a.x_start - str_rect.width()) / 2, lo_y, ds);
				}
			}
		}
	}
	painter->setPen(text_color);
	painter->setFont(nf);
	if (hasLblX) {
		painter->setPen(text_color);
		painter->drawText(gbx, chei - font_sz.height(), wid, font_sz.height(), Qt::AlignCenter, label_x);
	}

	QPen outer_pen(grid_pen.color(), qMax<int>(thick, grid_pen.width()));
#if QT_VERSION_MAJOR >= 5
	outer_pen.setCosmetic(true);
#endif
	painter->setPen(outer_pen);
	painter->drawRect(gbx, -1, wid + 6, hei + 6);
}


QPair<QString, QString> Graphic::gridMark(double v) const {
	QPair<QString, QString> ret;
	if ((qAbs(v) >= 1E+4 || qAbs(v) <= 1E-4) && v != 0.) {
		int p = qFloor(qLn(qAbs(v)) / LN10);
		v /= qPow(10., p);
		if (v == 10.) {
			v = 1.;
			p += 1;
		}
		ret.first  = QString::fromUtf8("%1·10").arg(v);
		ret.second = QString::number(p);
	} else
		ret.first = QString::number(v, 'g', 8);
	return ret;
}


void Graphic::fillDateFormats() {
	date_formats.clear();
	QString tr_ms = tr("ms"), tr_s = tr("s"), tr_m = tr("m"), tr_h = tr("h");
	auto trFunc = [&](const DateFormats & src) -> DateFormats {
		DateFormats ret;
		ret.upper  = QString(src.upper).replace("%1", tr_ms).replace("%2", tr_s).replace("%3", tr_m).replace("%4", tr_h);
		ret.center = QString(src.center).replace("%1", tr_ms).replace("%2", tr_s).replace("%3", tr_m).replace("%4", tr_h);
		ret.lower  = QString(src.lower).replace("%1", tr_ms).replace("%2", tr_s).replace("%3", tr_m).replace("%4", tr_h);
		return ret;
	};
	date_formats << trFunc(DateFormats{"zzz '%1'", "h '%4' mm '%3' ss '%2'", "yyyy MMM dd(ddd)"});
	date_formats << trFunc(DateFormats{"ss '%2'", "h '%4' mm '%3'", "yyyy MMM dd(ddd)"});
	date_formats << trFunc(DateFormats{"mm '%3'", "h '%4'", "yyyy MMM dd(ddd)"});
	date_formats << trFunc(DateFormats{"h '%4'", "dd(ddd)", "yyyy MMM"});
	date_formats << trFunc(DateFormats{"dd(ddd)", "MMM", "yyyy"});
	date_formats << trFunc(DateFormats{"MMM", "yyyy", ""});
	date_formats << trFunc(DateFormats{"yyyy", "", ""});
}


void Graphic::askForExport(bool view_only) {
	QString f = QFileDialog::getSaveFileName(this, tr("Export graphics"), ppath, "CSV(*.csv)");
	if (f.isEmpty()) return;
	QStringList items;
	items << "."
		  << ",";
	bool ok;
	QString item = QInputDialog::getItem(this, tr("Select decimal point"), tr("Decimal point:"), items, 0, false, &ok);
	if (ok && !item.isEmpty()) exportGraphics(f, item.front(), view_only);
}


bool Graphic::checkGraphicIndex(int index) const {
	if (index < 0 || index >= graphics.size()) return false;
	return true;
}


void Graphic::graphicVisibleChanged() {
	if (isFit)
		autofit();
	else {
		repaintCanvas();
	}
	emit graphicSettingsChanged();
}


void Graphic::drawGraphics() {
	if (isHover && ui->status->isVisible()) {
		ui->status->setText(tr("Cursor: ") + pointCoords(canvas2real(QPointF(curpos))));
	}
	QPointF srp = -selrect.topLeft();
	double sclx, scly, wid = canvas->width(), hei = canvas->height();
	int cwid = (wid - gridborder.x() - margins_.left() - margins_.width());
	sclx     = cwid / selrect.width();
	scly     = (hei - gridborder.y() - margins_.top() - margins_.height()) / selrect.height();
	painter->setClipping(true);
	painter->setClipRect(QRect(gridborder.x(), 0, wid - gridborder.x(), hei - gridborder.y()));
	painter->translate(gridborder.x() + margins_.left(), hei - gridborder.y() - margins_.top());
	painter->scale(sclx, -scly);
	painter->translate(srp);
	QTransform mat = painter->transform();
	painter->resetTransform();
	painter->setWorldMatrixEnabled(false);
	QPen pen;
	for (int i = 0; i < graphics.size(); ++i) {
		GraphicType & t(graphics[i]);
		QPolygonF & src_pol(pause_ ? t.polyline_pause : t.polyline);
		if (!t.visible || src_pol.isEmpty()) continue;
		QVector<QPolygonF> & src_lod(pause_ ? t._lod_pause : t._lod);
		int lod = 0;
		if (m_LODOptimization) {
			int gpcnt   = src_pol.size();
			qreal range = src_pol.back().x() - src_pol.front().x();
			qreal ppp   = (gpcnt * selrect.width() / qMax(range, 1.E-9) / cwid);
			lod         = qBound<int>(0, qFloor(log2(ppp) - 1), src_lod.size());
			// qDebug() << "draw lod" << lod << src_lod[lod - 1].size();
		}
		t.last_lod = lod;
		QPolygonF & rpol(lod == 0 ? src_pol : src_lod[lod - 1]);
		int ind_start = -1, ind_end = -1;
		if (m_LODOptimization) {
			qreal xs = selrect.left(), xe = selrect.right(), _offset = 2. / cwid * selrect.width();
			xs -= _offset;
			xe += _offset;
			for (int i = 0; i < rpol.size(); ++i) {
				qreal px = rpol[i].x();
				if (px < xs) continue;
				if (ind_start < 0) ind_start = qMax(0, i - 1);
				if (px > xe && ind_end < 0) {
					ind_end = qMin(rpol.size(), i + 1);
					break;
				}
			}
			if (ind_start < 0) ind_start = 0;
			if (ind_end < 0) ind_end = rpol.size();
			// qDebug() << "bound" << ind_start << ind_end << rpol.size();
		} else {
			ind_start = 0;
			ind_end   = rpol.size();
		}
		int polsize = ind_end - ind_start;
		if (polsize > 0) {
			QPolygonF cpol;
			if (m_LODOptimization && polsize < rpol.size()) {
				cpol.resize(polsize);
				memcpy(cpol.data(), &(rpol[ind_start]), polsize * sizeof(QPointF));
				// qDebug() << "copy" << polsize;
			} else {
				cpol = rpol;
			}
			pen = t.pen;
			if (qRound(pen.widthF()) == pen.widthF())
				pen.setWidth(pen.width() * thick);
			else
				pen.setWidthF(pen.widthF() * thick);
			pen.setCosmetic(true);
			if (t.lines) {
				painter->setPen(pen);
				if (t.fill) {
					painter->setBrush(t.fill_color);
					painter->drawPolygon(mat.map(cpol));
				} else
					painter->drawPolyline(mat.map(cpol));
			}
			if (t.points) {
				if (qRound(t.pointWidth) == t.pointWidth)
					pen.setWidth(qRound(t.pointWidth * thick));
				else
					pen.setWidthF(t.pointWidth * thick);
				painter->setPen(pen);
				painter->drawPoints(mat.map(cpol));
			}
		}
	}
	painter->setWorldMatrixEnabled(true);
}


QString Graphic::pointCoords(QPointF point, bool x, bool y) {
	QString ret = "(";
	if (x) {
		if (axis_type_x == Numeric)
			ret += QString::number(point.x(), 'f', 3);
		else
			ret +=
#if QT_VERSION_MAJOR <= 5
				QDateTime::fromMSecsSinceEpoch(point.x()).toString(Qt::SystemLocaleShortDate);
#else
				locale().toString(QDateTime::fromMSecsSinceEpoch(point.x()), QLocale::ShortFormat);
#endif
	}
	if (y) {
		if (ret.size() > 1) ret += " ; ";
		ret += QString::number(point.y(), 'f', 3);
	}
	ret += ")";
	return ret;
}


void Graphic::drawGuides() {
	if (!guides || !isHover) return;
	int wid = canvas->width(), hei = canvas->height();
	painter->setRenderHint(QPainter::Antialiasing, false);
	QPen gpen(grid_pen.color(), qMax<int>(qRound(thick / 1.4), 1));
#if QT_VERSION_MAJOR >= 5
	gpen.setCosmetic(true);
#endif
	painter->setPen(gpen);
	painter->resetTransform();
	painter->setClipping(true);
	painter->setClipRect(QRect(gridborder.x(), 0, wid - gridborder.x(), hei - gridborder.y()));
	QPoint apos  = curpos;
	QPointF rpos = canvas2real(apos);
	QString str;
	str                  = pointCoords(rpos) + fp_size;
	bool trace_found     = false;
	auto trace_free_func = [&](QPointF cursor) {
		double min_dist = -1;
		int gr = -1, mag_dist = fontHeight(this) * 2;
		QPointF point, scale  = getScale(), dp;
		for (int g = 0; g < graphics.size(); ++g) {
			auto & t(graphics[g]);
			if (t.visible) {
				QPolygonF & src_pol(pause_ ? t.polyline_pause : t.polyline);
				QVector<QPolygonF> & src_lod(pause_ ? t._lod_pause : t._lod);
				QPolygonF & pol(t.last_lod == 0 ? src_pol : src_lod[t.last_lod - 1]);
				double dist = 0.;
				for (int i = 0; i < pol.size(); ++i) {
					point = pol[i];
					if (!selrect.contains(point)) continue;
					dp = point - cursor;
					dp = QPointF(dp.x() * scale.x(), dp.y() * scale.y());
					if (dp.manhattanLength() <= mag_dist) {
						dist = QVector2D(dp).lengthSquared();
						if (min_dist > dist || min_dist < 0) {
							min_dist = dist;
							gr       = g;
							rpos     = point;
						}
					}
				}
			}
		}
		if (gr >= 0) {
			apos        = real2canvas(rpos).toPoint();
			str         = " " + graphics[gr].name + ": " + pointCoords(rpos) + fp_size;
			trace_found = true;
			emit graphicTraceEvent(gr, rpos);
		}
	};
	if (floating_axis_type == Trace) {
		trace_free_func(rpos);
	}
	if (was_trace && !trace_found) emit graphicTraceEvent(-1, QPointF());
	was_trace = trace_found;
	painter->drawLine(0, apos.y(), wid, apos.y());
	painter->drawLine(apos.x(), 0, apos.x(), hei);
	QPoint p = apos + QPoint(font_sz.height() / 4., -font_sz.height() / 4.);
	QFontMetrics fm(font());
	QRect r = fm.boundingRect(str);
	if (r.width() + apos.x() > wid - font_sz.height() / 2.) p.setX(apos.x() - r.width() - font_sz.height() / 4.);
	if (apos.y() - r.height() < font_sz.height() / 8.) p.setY(apos.y() + r.height() - font_sz.height() / 8.);
	painter->setPen(text_color);
	QRect trect = painter->fontMetrics().boundingRect(str);
	QColor tcol = back_color;
	tcol.setAlphaF(0.65);
	painter->fillRect(trect.translated(p), tcol);
	painter->drawText(p, str);
}


void Graphic::drawPause() {
	painter->setClipping(false);
	painter->save();
	painter->resetTransform();
	painter->translate(canvas->width() - icon_pause_b.width() - 6, 6);
	double o = (0.5 - pause_phase) * 2;
	painter->setOpacity(o * o);
	painter->drawImage(0, 0, icon_pause_b);
	painter->setOpacity(1.);
	painter->drawImage(0, 0, icon_pause_f);
	painter->restore();
	painter->setClipping(true);
}

double roundToSteps(double value, const QVector<double> & steps) {
	double ret = value, min_err = -1.;
	for (double v: steps) {
		double sv  = qRound64(value / v) * v;
		double err = qAbs<double>(value - sv);
		if (min_err < 0 || min_err > err) {
			min_err = err;
			ret     = sv;
		}
	}
	if (ret < steps[0]) ret = steps[0];
	return ret;
}

double roundToNearest(double value, const QVector<double> & values) {
	double ret = value, min_err = -1.;
	for (double v: values) {
		double err = qAbs<double>(value - v);
		if (min_err < 0 || min_err > err) {
			min_err = err;
			ret     = v;
		}
	}
	if (ret < values[0]) ret = values[0];
	return ret;
}


double Graphic::splitRange(double range, int count) {
	double digits, step, tln;
	range = qAbs<double>(range);
	tln   = qFloor(qLn(range) / LN10);
	for (int i = 0; i <= 5; ++i) {
		digits = qPow(10., tln - i);
		step   = qRound(range / count / digits);
		if (step > 0.) {
			digits = qPow(10., tln - i - 1);
			step   = qRound(range / count / digits);
			break;
		}
	}
	step = roundToSteps(step, {5., 10.}) * digits;
	return step;
}


double Graphic::splitRangeDate(double range, int count, DateFormats & formats, int step[7]) {
	static const qint64 to_sec = 1000LL, to_min = 1000LL * 60, to_hour = 1000LL * 60 * 60, to_day = 1000LL * 60 * 60 * 24,
						to_month = 1000LL * 60 * 60 * 24 * 30, to_year = 1000LL * 60 * 60 * 24 * 30 * 12;
	double ret       = splitRange(range, count);
	int format_index = DateYears;
	if (ret < to_sec / 1) {
		format_index    = DateMSecs;
		step[DateMSecs] = qRound64(ret);
	} else if (ret < to_min / 2) {
		format_index   = DateSecs;
		step[DateSecs] = roundToNearest(ret / to_sec, {1, 2, 5, 10, 15, 20, 30});
	} else if (ret < to_hour) {
		format_index      = DateMinutes;
		step[DateMinutes] = roundToNearest(ret / to_min, {1, 2, 5, 10, 15, 20, 30});
	} else if (ret < to_day) {
		format_index    = DateHours;
		step[DateHours] = roundToNearest(ret / to_hour, {1, 2, 3, 4, 6, 8, 12});
	} else if (ret < to_month / 1.6) {
		format_index   = DateDays;
		step[DateDays] = roundToNearest(ret / to_day, {1, 2, 5, 10});
	} else if (ret < to_year) {
		format_index     = DateMonths;
		step[DateMonths] = roundToNearest(ret / to_month, {1, 2, 3, 4, 6});
	} else {
		format_index    = DateYears;
		step[DateYears] = qRound64(ret / to_year);
	}
	formats = date_formats[format_index];
	return ret;
}


double Graphic::roundTo(double value, double round_to) {
	if (round_to == 0.) return value;
	return qRound64(value / round_to) * round_to;
}


void Graphic::roundDateTime(QDateTime & dt, int * c) {
	QDate d(dt.date());
	QTime t(dt.time());
	if (c[DateMSecs] != 0) t.setHMS(t.hour(), t.minute(), t.second());
	if (c[DateSecs] != 0) t.setHMS(t.hour(), t.minute(), 0);
	if (c[DateMinutes] != 0) t.setHMS(t.hour(), 0, 0);
	if (c[DateHours] != 0) {
		t.setHMS(0, 0, 0);
		d.setDate(d.year(), d.month(), d.day());
	}
	if (c[DateDays] != 0) {
		t.setHMS(0, 0, 0);
		d.setDate(d.year(), d.month(), 1);
	}
	if (c[DateMonths] != 0 || c[DateYears] != 0) {
		t.setHMS(0, 0, 0);
		d.setDate(d.year(), 1, 1);
	}
	dt = QDateTime(d, t);
}


void Graphic::addDateTime(QDateTime & dt, int * c, qint64 mul) {
	if (c[DateMSecs] != 0) dt = dt.addMSecs(mul * c[DateMSecs]);
	if (c[DateSecs] != 0) dt = dt.addSecs(mul * c[DateSecs]);
	if (c[DateMinutes] != 0) dt = dt.addSecs(mul * c[DateMinutes] * 60);
	if (c[DateHours] != 0) dt = dt.addSecs(mul * c[DateHours] * 60 * 60);
	if (c[DateDays] != 0) dt = dt.addDays(mul * c[DateDays]);
	if (c[DateMonths] != 0) dt = dt.addMonths(mul * c[DateMonths]);
	if (c[DateYears] != 0) dt = dt.addYears(mul * c[DateYears]);
}


double Graphic::canvas2realX(double px) const {
	int gbx     = gridborder.x() + margins_.left();
	int wid     = lastw - gbx - margins_.width();
	double cx   = px - gbx;
	double sclx = selrect.width() / (double)wid;
	return cx * sclx + selrect.x();
}


double Graphic::canvas2realY(double py) const {
	int gby     = gridborder.y() + margins_.top();
	int hei     = lasth - gby - margins_.height();
	double cy   = lasth - py - gby;
	double scly = selrect.height() / (double)hei;
	return cy * scly + selrect.y();
}


double Graphic::real2canvasX(double px) const {
	int gbx     = gridborder.x() + margins_.left();
	int wid     = lastw - gbx - margins_.width();
	double sclx = selrect.width() / (double)wid;
	return (px - selrect.x()) / sclx + gbx;
}


double Graphic::real2canvasY(double py) const {
	int gby     = gridborder.y() + margins_.top();
	int hei     = lasth - gby - margins_.height();
	double scly = selrect.height() / (double)hei;
	return lasth - gby - (py - selrect.y()) / scly;
}


QPolygonF Graphic::real2canvas(const QPolygonF & real_polygon) const {
	QPolygonF ret;
	for (const auto & p: real_polygon) {
		ret << real2canvas(p);
	}
	return ret;
}


QPolygonF Graphic::canvas2real(const QPolygonF & canvas_polygon) const {
	QPolygonF ret;
	for (const auto & p: canvas_polygon) {
		ret << canvas2real(p);
	}
	return ret;
}


double Graphic::getScaleX() const {
	int gbx = gridborder.x() + margins_.left();
	int wid = lastw - gbx - margins_.width();
	return (double)wid / selrect.width();
}

double Graphic::getScaleY() const {
	int gby = gridborder.y() + margins_.top();
	int hei = lasth - gby - margins_.height();
	return (double)hei / selrect.height();
}


void Graphic::setCurrentAction(GraphicAction action) {
	curaction = action;
	switch (action) {
	case gaNone: setGuidesCursor(); break;
	case gaZoomInRect: setCanvasCursor(Qt::CrossCursor); break;
	case gaZoomRangeX: setCanvasCursor(Qt::SplitHCursor); break;
	case gaZoomRangeY: setCanvasCursor(Qt::SplitVCursor); break;
	case gaMove: setCanvasCursor(Qt::SizeAllCursor); break;
	}
}


void Graphic::setCanvasCursor(QCursor cursor) {
	ui->canvas_raster->setCursor(cursor);
#ifdef HAS_GL
	if (canvas_gl) canvas_gl->setCursor(cursor);
#endif
}


void Graphic::setGuidesCursor() {
	if (guides) {
		setCanvasCursor(floating_axis_type == Free ? Qt::BlankCursor : Qt::CrossCursor);
	} else
		setCanvasCursor(Qt::ArrowCursor);
}


void Graphic::swapToBuffer() {
	QImage timg;
#ifdef HAS_GL
	if (isOGL && canvas_gl) {
		timg = canvas_gl->grabFrameBuffer();
		QPainter p(&buffer);
		p.drawImage(0, 0, timg);
		p.end();
	}
#endif
	bufferActive = true;
}


void Graphic::setRectToLines() {
	is_lines_update = true;
	if (line_x_min.isVisible() && line_x_max.isVisible() && line_y_min.isVisible() && line_y_max.isVisible()) {
		line_x_min.blockSignals(true);
		line_x_max.blockSignals(true);
		line_y_min.blockSignals(true);
		line_y_max.blockSignals(true);
		if (!line_x_min.hasFocus()) {
			if (isFit)
				line_x_min.setValue(grect.left());
			else
				line_x_min.setValue(selrect.left());
		}
		if (!line_x_max.hasFocus()) {
			if (isFit)
				line_x_max.setValue(grect.right());
			else
				line_x_max.setValue(selrect.right());
		}
		if (!line_y_min.hasFocus()) {
			if (isFit)
				line_y_min.setValue(grect.bottom());
			else
				line_y_min.setValue(selrect.bottom());
		}
		if (!line_y_max.hasFocus()) {
			if (isFit)
				line_y_max.setValue(grect.top());
			else
				line_y_max.setValue(selrect.top());
		}
		//		line_x_min.setDefaultText(QString::number(grect.left()).toUpper());
		//		line_x_max.setDefaultText(QString::number(grect.right()).toUpper());
		//		line_y_min.setDefaultText(QString::number(grect.bottom()).toUpper());
		//		line_y_max.setDefaultText(QString::number(grect.top()).toUpper());
		line_x_min.blockSignals(false);
		line_x_max.blockSignals(false);
		line_y_min.blockSignals(false);
		line_y_max.blockSignals(false);
	}
	is_lines_update = false;
}


void Graphic::checkLines() {
	isFit = (line_x_min.isCleared() && line_x_max.isCleared() && line_y_min.isCleared() && line_y_max.isCleared());
	repaintCanvas(true);
}


void Graphic::tick(int index, bool slide, bool update_) {
	if (slide) {
		GraphicType & t(graphics[index]);
		if (history > 0.)
			while (t.polyline.size() > 1) {
				if (fabs(t.polyline.back().x() - t.polyline.front().x()) <= history) break;
				/// TODO: [Graphic] fast autofit while addPoint(double y, ...)
				if (!t.cvrect.isNull()) {
					QPointF fp(t.polyline.first());
					if (qFuzzyCompare(t.cvrect.left(), fp.x()) || qFuzzyCompare(t.cvrect.right(), fp.x()) ||
					    qFuzzyCompare(t.cvrect.top(), fp.y()) || qFuzzyCompare(t.cvrect.bottom(), fp.y())) {
						t.cvrect = QRectF();
					}
				}
				t.polyline.pop_front();
			}
	} else {
		calcLOD(index);
	}
	if (isFit) findGraphicsRect();
	if (aupdate && update_) repaintCanvas();
}


void Graphic::calcLOD(int index) {
	if (!m_LODOptimization) return;
	GraphicType & t(graphics[index]);
	t._lod.clear();
	t._lod.reserve(32);
	int pcnt = t.polyline.size();
	// qDebug() << "calcLOD" << index;
	while (pcnt >= 10) {
		QPolygonF & pl(t._lod.isEmpty() ? t.polyline : t._lod.back());
		t._lod.append(QPolygonF());
		QPolygonF & cl(t._lod.back());
		if (pl.size() > 4) {
			cl << pl.front();
			int qcnt    = (pl.size() + 1) / 4;
			pcnt        = qcnt * 2 + 2;
			int pc      = 4;
			qreal mx[2] = {0., 0.}, my[2] = {0., 0.}, my_x[2] = {0., 0.}, px, py;
			for (int i = 0; i < qcnt; ++i) {
				int j = i * 4 + 1;
				if (i == qcnt - 1) pc = pl.size() - j - 1;
				mx[0] = mx[1] = my_x[0] = my_x[1] = pl[j].x();
				my[0] = my[1] = pl[j].y();
				for (int k = 1; k < pc; ++k) {
					px    = pl[j + k].x();
					py    = pl[j + k].y();
					mx[0] = qMin(mx[0], px);
					mx[1] = qMax(mx[1], px);
					if (my[0] > py) {
						my[0]   = py;
						my_x[0] = px;
					}
					if (my[1] < py) {
						my[1]   = py;
						my_x[1] = px;
					}
				}
				qreal dx = (mx[1] - mx[0]) / 4., cx = (mx[1] + mx[0]) / 2.;
				if (my_x[1] >= my_x[0])
					cl << QPointF(cx - dx, my[0]) << QPointF(cx + dx, my[1]);
				else
					cl << QPointF(cx - dx, my[1]) << QPointF(cx + dx, my[0]);
			}
			cl << pl.back();
		} else
			cl = pl;
		// qDebug() << "lod" << t._lod.size() << "->" << cl.size();
	}
}


void Graphic::repaintCanvas(bool force) {
	if (tm.elapsed() < min_repaint_int && !force) return;
	tm.restart();
	canvas->update();
}


void Graphic::on_graphic_buttonAutofit_clicked() {
	autofit();
}


void Graphic::on_graphic_buttonConfigure_clicked() {
	conf->graphicItems.clear();
	int all_checks[2] = {-1, -1};
	auto addAllChecks = [](int & out, bool is_vis) {
		if (out < 0)
			out = is_vis ? Qt::Checked : Qt::Unchecked;
		else {
			if (is_vis && out == Qt::Unchecked) out = Qt::PartiallyChecked;
			if (!is_vis && out == Qt::Checked) out = Qt::PartiallyChecked;
		}
	};
	for (const auto & g: graphics) {
		GraphicConf::GraphicItem item;
		item.icon = g.icon;
		item.name = g.name;
		conf->graphicItems.append(item);
		addAllChecks(all_checks[0], g.lines);
		addAllChecks(all_checks[1], g.points);
	}
	conf->ui->checkLinesAll->blockSignals(true);
	conf->ui->checkPointsAll->blockSignals(true);
	conf->ui->checkLinesAll->setCheckState((Qt::CheckState)all_checks[0]);
	conf->ui->checkPointsAll->setCheckState((Qt::CheckState)all_checks[1]);
	conf->ui->checkLinesAll->blockSignals(false);
	conf->ui->checkPointsAll->blockSignals(false);
	conf->ui->colorGrid->setColor(grid_pen.color());
	conf->ui->comboStyleGrid->setCurrentIndex((int)grid_pen.style());
	conf->ui->spinWidthGrid->setValue(grid_pen.widthF());
#ifdef HAS_GL
	conf->ui->checkOGL->setChecked(isOGL);
#else
	conf->ui->checkOGL->setEnabled(false);
	conf->ui->checkOGL->setChecked(false);
#endif
	conf->ui->checkAAlias->setChecked(aalias);
	conf->ui->checkInputs->setChecked(borderInputsVisible());
	conf->ui->checkStatus->setChecked(statusVisible());
	conf->ui->checkLegend->setChecked(legendVisible());
	conf->ui->groupGrid->setChecked(grid);
	conf->ui->checkGridAutoX->setChecked(grad_x == Auto);
	conf->ui->checkGridAutoY->setChecked(grad_y == Auto);
	conf->ui->colorBackground->setColor(back_color);
	conf->ui->colorText->setColor(text_color);
	conf->ui->spinGridStepX->setValue(gridx);
	conf->ui->spinGridStepY->setValue(gridy);
	conf->ui->spinMarginL->setValue(margins_.left());
	conf->ui->spinMarginT->setValue(margins_.height());
	conf->ui->spinMarginR->setValue(margins_.width());
	conf->ui->spinMarginB->setValue(margins_.top());
	conf->readParams();
	if (conf->exec() == QDialog::Rejected) return;
	grid_pen = QPen(conf->ui->colorGrid->color(), conf->ui->spinWidthGrid->value(), (Qt::PenStyle)conf->ui->comboStyleGrid->currentIndex());
	back_color = conf->ui->colorBackground->color();
	text_color = conf->ui->colorText->color();
	grad_x     = conf->ui->checkGridAutoX->isChecked() ? Auto : Fixed;
	grad_y     = conf->ui->checkGridAutoY->isChecked() ? Auto : Fixed;
	gridx      = conf->ui->spinGridStepX->value();
	gridy      = conf->ui->spinGridStepY->value();
	setOpenGL(conf->ui->checkOGL->isChecked());
	setAntialiasing(conf->ui->checkAAlias->isChecked());
	setBorderInputsVisible(conf->ui->checkInputs->isChecked());
	setStatusVisible(conf->ui->checkStatus->isChecked());
	setLegendVisible(conf->ui->checkLegend->isChecked());
	setMargins(conf->ui->spinMarginL->value(),
	           conf->ui->spinMarginR->value(),
	           conf->ui->spinMarginT->value(),
	           conf->ui->spinMarginB->value());
	setGridEnabled(conf->ui->groupGrid->isChecked());
	updateLegend();
	repaintCanvas();
	for (int i = 0; i < qMin<int>(graphics.size(), loaded_configs.size()); ++i) {
		loaded_configs[i] = graphics[i];
		loaded_configs[i].removeData();
	}
}


void Graphic::on_graphic_buttonFullscreen_clicked() {
	fullscreen();
}


void Graphic::on_graphic_actionSaveImage_triggered() {
	QString f = QFileDialog::getSaveFileName(this,
	                                         tr("Save Image"),
	                                         ppath,
	                                         "PNG(*.png);;JPEG(*.jpg *.jpeg);;BMP(*.bmp);;TIFF(*.tiff *.tif);;PPM(*.ppm)");
	if (f.isEmpty()) return;
	saveImage(f);
}


void Graphic::on_graphic_actionExportCSV_triggered() {
	askForExport(false);
}


void Graphic::on_graphic_actionExportCSV_View_triggered() {
	askForExport(true);
}


void Graphic::on_graphic_buttonRecord_clicked(bool checked) {
	if (checked) {
		record_imgs.clear();
		timer_record = startTimer(100);
	} else {
		killTimer(timer_record);
		timer_record = 0;
		if (record_imgs.isEmpty()) return;
		QString f = QFileDialog::getSaveFileName(this, tr("Save GIF"), ppath, "GIF(*.gif)");
		if (!f.isEmpty()) {
			qApp->setOverrideCursor(Qt::BusyCursor);
			GifWriter gif_writer;
			int frame_delay = 10;
			if (GifBegin(&gif_writer,
			             f.toUtf8(),
			             static_cast<uint32_t>(record_imgs.first().width()),
			             static_cast<uint32_t>(record_imgs.first().height()),
			             static_cast<uint32_t>(frame_delay))) {
				for (const QImage & im: record_imgs) {
					if (!GifWriteFrame(&gif_writer,
					                   im.convertToFormat(QImage::Format_RGBA8888).constBits(),
					                   static_cast<uint32_t>(im.width()),
					                   static_cast<uint32_t>(im.height()),
					                   static_cast<uint32_t>(frame_delay))) {
						GifEnd(&gif_writer);
						qDebug() << "GifWriteFrame ERROR";
					}
				}
				GifEnd(&gif_writer);
			}
			qApp->restoreOverrideCursor();
		}
		record_imgs.clear();
	}
}


void Graphic::on_graphic_checkGuides_toggled(bool checked) {
	guides = checked;
	setGuidesCursor();
	repaintCanvas();
}


void Graphic::updateLegend(bool es) {
	QPixmap pix(60, 22);
	for (int i = 0; i < graphics.size(); i++) {
		pix.fill(back_color);
		QPainter p(&pix);
		QPen pen = graphics[i].pen;
		if (qRound(pen.widthF()) == pen.widthF())
			pen.setWidth(pen.width() * thick);
		else
			pen.setWidthF(pen.widthF() * thick);
		p.setPen(pen);
		p.drawLine(0, pix.height() / 2, pix.width(), pix.height() / 2);
		p.end();
		graphics[i].icon = QIcon(pix);
	}
	if (!ui->scrollLegend->isVisibleTo(this)) {
		if (es) emit graphicSettingsChanged();
		return;
	}
	leg_update = false;
	int ps     = 100;
	for (int r = 0; r < ui->layoutLegend->rowCount(); ++r)
		for (int c = 0; c < ui->layoutLegend->columnCount(); ++c) {
			QLayoutItem * li = ui->layoutLegend->itemAtPosition(r, c);
			if (!li) continue;
			if (!li->widget()) continue;
			ui->layoutLegend->removeWidget(li->widget());
		}
	ui->layoutLegend->invalidate();
	for (int i = 0; i < graphics.size(); i++) {
		if (graphics[i].init()) {
			graphics[i].pb->addActions(
				{ui->actionCheck_all, ui->actionUncheck_all, ui->actionInvert_selection, ui->actionSelect_only_this});
			graphics[i].pb->installEventFilter(this);
			connect(graphics[i].pb, SIGNAL(toggled(bool)), this, SLOT(graphicVisibleChange(bool)));
			graphics[i].pb->setContextMenuPolicy(Qt::ActionsContextMenu);
		}
		graphics[i].pb->setIconSize(pix.size());
		graphics[i].pb->setIcon(graphics[i].icon);
		graphics[i].pb->setChecked(graphics[i].visible);
		graphics[i].pb->setText(graphics[i].name);
		graphics[i].pb->setProperty("graphic_num", i);
		int cps = graphics[i].pb->sizeHint().width() + 4;
		if (cps > ps) ps = cps;
	}
	LegendScrollArea * leg_sa = (LegendScrollArea *)ui->scrollLegend->layout()->itemAt(0)->widget();
	int maxcol                = qMax<int>(leg_sa->width() / ps - 1, 1);
	int row = 0, col = 0;
	bool lv = ui->scrollLegend->isVisibleTo(this);
	ui->scrollLegend->hide();
	for (int i = 0; i < graphics.size(); i++) {
		ui->layoutLegend->addWidget(graphics[i].pb, row, col);
		graphics[i].pb->show();
		if (leg_sa->minimum_hei == 0) {
			leg_sa->minimum_hei = ui->widgetLegend->sizeHint().height();
		}
		col++;
		if (col > maxcol) {
			col = 0;
			row++;
		}
	}
	ui->gridLayout->invalidate();
	ui->scrollLegend->setVisible(lv);
	leg_sa->updateGeometry();
	leg_update = true;
	if (es) emit graphicSettingsChanged();
}


void Graphic::updateLegendChecks() {
	for (int i = 0; i < graphics.size(); i++) {
		if (!graphics[i].pb) continue;
		bool pbs = graphics[i].pb->blockSignals(true);
		graphics[i].pb->setChecked(graphics[i].visible);
		graphics[i].pb->blockSignals(pbs);
	}
	emit graphicSettingsChanged();
}


void Graphic::graphicVisibleChange(bool checked) {
	if (visible_update) return;
	QCheckBox * cb      = qobject_cast<QCheckBox *>(sender());
	int i               = cb->property("graphic_num").toInt();
	graphics[i].visible = checked;
	graphicVisibleChanged();
}


void Graphic::lineXMinChanged(double value) {
	selrect.setLeft(value);
	checkLines();
}


void Graphic::lineXMaxChanged(double value) {
	selrect.setRight(value);
	checkLines();
}


void Graphic::lineYMinChanged(double value) {
	selrect.setBottom(value);
	checkLines();
}


void Graphic::lineYMaxChanged(double value) {
	selrect.setTop(value);
	checkLines();
}


void Graphic::on_actionCheck_all_triggered() {
	visible_update = true;
	for (auto & g: graphics) {
		g.visible = true;
		g.pb->setChecked(true);
	}
	visible_update = false;
	graphicVisibleChanged();
}


void Graphic::on_actionUncheck_all_triggered() {
	visible_update = true;
	for (auto & g: graphics) {
		g.visible = false;
		g.pb->setChecked(false);
	}
	visible_update = false;
	graphicVisibleChanged();
}


void Graphic::on_actionInvert_selection_triggered() {
	visible_update = true;
	for (auto & g: graphics) {
		g.visible = !g.visible;
		g.pb->setChecked(g.visible);
	}
	visible_update = false;
	graphicVisibleChanged();
}


void Graphic::on_actionSelect_only_this_triggered() {
	visible_update = true;
	for (auto & g: graphics) {
		g.visible = (g.pb == action_source);
		g.pb->setChecked(g.visible);
	}
	visible_update = false;
	graphicVisibleChanged();
	action_source = nullptr;
}


void Graphic::on_graphic_buttonClose_clicked() {
	emit closeRequest(this);
}


void Graphic::on_graphic_buttonClear_clicked() {
	clear();
	emit cleared();
}


void Graphic::enterFullscreen() {
	if (fullscr) return;
	fullscr = true;
	canvas->hide();
#ifdef Q_OS_ANDROID
	tm_fscr.restart();
	QDialog dlg;
	dlg.setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
	dlg.layout()->setContentsMargins(0, 0, 0, 0);
	dlg.layout()->addWidget(canvas);
	QPushButton * btn = new QPushButton("Leave fullscreen");
	dlg.layout()->addWidget(btn);
	connect(btn, SIGNAL(clicked(bool)), this, SLOT(leaveFullscreen()));
	canvas->show();
	dlg.showFullScreen();
	dlg.exec();
	dlg.layout()->removeWidget(canvas);
	leaveFullscreen();
	return;
#else
	layout()->removeWidget(ui->widgetTop);
	ui->widgetTop->setParent(0);
	ui->widgetTop->showFullScreen();
	ui->widgetTop->setFocus();
	ui->widgetTop->raise();
	canvas->show();
#endif
}


void Graphic::leaveFullscreen() {
#ifdef Q_OS_ANDROID
	if (tm_fscr.elapsed() < 100) return;
#endif
	if (!fullscr) return;
	fullscr = false;
#ifdef Q_OS_ANDROID
	canvas->showNormal();
	canvas->hide();
	ui->layoutCanvas->addWidget(canvas);
	canvas->show();
#else
	layout()->addWidget(ui->widgetTop);
	canvas->show();
#endif
}


void Graphic::showMenu() {
#ifdef NO_BUTTONS
	for (auto * a: buttons_menu->actions()) {
		QToolButton * b = (QToolButton *)(a->property(_button_prop_name_).toULongLong());
		if (!b) {
			a->setVisible(false);
			continue;
		}
		a->blockSignals(true);
		a->setVisible(!b->isHidden());
		a->setText(b->toolTip());
		a->setIcon(b->icon());
		a->setChecked(b->isChecked());
		a->blockSignals(false);
	}
	buttons_menu->popup(QCursor::pos());
#endif
}


QString Graphic::caption() const {
	return ui->labelCaption->text();
}


QString Graphic::graphicName(int index) const {
	if (!checkGraphicIndex(index)) return {};
	return graphics[index].name;
}


QColor Graphic::graphicColor(int index) const {
	if (!checkGraphicIndex(index)) return Qt::black;
	return graphics[index].pen.color();
}


Qt::PenStyle Graphic::graphicStyle(int index) const {
	if (!checkGraphicIndex(index)) return Qt::NoPen;
	return graphics[index].pen.style();
}


double Graphic::graphicLineWidth(int index) const {
	if (!checkGraphicIndex(index)) return 0.;
	return graphics[index].pen.widthF();
}


double Graphic::graphicPointWidth(int index) const {
	if (!checkGraphicIndex(index)) return 0.;
	return graphics[index].pointWidth;
}


QColor Graphic::graphicFillColor(int index) const {
	if (!checkGraphicIndex(index)) return Qt::black;
	return graphics[index].fill_color;
}


bool Graphic::graphicVisible(int index) const {
	if (!checkGraphicIndex(index)) return false;
	return graphics[index].visible;
}


bool Graphic::graphicLinesEnabled(int index) const {
	if (!checkGraphicIndex(index)) return false;
	return graphics[index].lines;
}


bool Graphic::graphicPointsEnabled(int index) const {
	if (!checkGraphicIndex(index)) return false;
	return graphics[index].points;
}


bool Graphic::graphicFillEnabled(int index) const {
	if (!checkGraphicIndex(index)) return false;
	return graphics[index].fill;
}


QPen Graphic::graphicPen(int index) const {
	if (!checkGraphicIndex(index)) return {};
	return graphics[index].pen;
}


bool Graphic::borderInputsVisible() const {
	return ui->widgetLX->isVisible();
}


bool Graphic::statusVisible() const {
	return ui->status->isVisible();
}


bool Graphic::legendVisible() const {
	return ui->scrollLegend->isVisible();
}


QVector<QPointF> Graphic::graphicData(int index) const {
	if (!checkGraphicIndex(index)) return {};
	return graphics[index].polyline;
}


QByteArray Graphic::save() {
	//	QByteArray ba;
	//	QDataStream s(&ba, QIODevice::ReadWrite);
	//	s << openGL() << antialiasing() << borderInputsVisible() << statusVisible() << legendVisible();
	//	s << graphics;
	//	return ba;

	// version '2':
	ChunkStream cs;
	cs.add(1, antialiasing()).add(2, openGL()).add(3, borderInputsVisible()).add(4, statusVisible()).add(5, legendVisible());
	cs.add(6, backgroundColor()).add(7, textColor()).add(8, margins());
	cs.add(9, gridPen()).add(10, graduationX()).add(11, graduationY()).add(12, graduationStepX()).add(13, graduationStepY());
	cs.add(14, graphics);
	cs.add(15, isFit).add(16, visualRect());
	if (backgroundColor() == palette().color(QPalette::Base) && textColor() == palette().color(QPalette::WindowText) &&
	    gridColor() == palette().color(QPalette::Disabled, QPalette::WindowText))
		cs.add(17, true);
	cs.add(18, gridEnabled());
	return cs.data().prepend('2');
}


void Graphic::load(QByteArray ba) {
	if (ba.isEmpty()) return;
	char ver        = ba[0];
	bool has_opengl = isOGL;
	switch (ver) {
	case '2': { // version '2':
		ba.remove(0, 1);
		QRectF vrect;
		ChunkStream cs(ba);
		bool def_colors = false;
		while (!cs.atEnd()) {
			switch (cs.read()) {
			case 1: aalias = cs.getData<bool>(); break;
			case 2: has_opengl = cs.getData<bool>(); break;
			case 3: setBorderInputsVisible(cs.getData<bool>()); break;
			case 4: setStatusVisible(cs.getData<bool>()); break;
			case 5: setLegendVisible(cs.getData<bool>()); break;
			case 6:
				if (!def_colors) back_color = cs.getData<QColor>();
				break;
			case 7:
				if (!def_colors) text_color = cs.getData<QColor>();
				break;
			case 8: margins_ = cs.getData<QRect>(); break;
			case 9:
				if (!def_colors) grid_pen = cs.getData<QPen>();
				break;
			case 10: grad_x = cs.getData<Graduation>(); break;
			case 11: grad_y = cs.getData<Graduation>(); break;
			case 12: gridx = cs.getData<double>(); break;
			case 13: gridy = cs.getData<double>(); break;
			case 14: graphics = cs.getData<QVector<GraphicType>>(); break;
			case 15: isFit = cs.getData<bool>(); break;
			case 16: vrect = cs.getData<QRectF>(); break;
			case 17:
				if (cs.getData<bool>()) {
					text_color = palette().color(QPalette::WindowText);
					grid_pen   = QPen(palette().color(QPalette::Disabled, QPalette::WindowText), 0., Qt::DotLine);
					back_color = palette().color(QPalette::Base);
					def_colors = true;
				}
				break;
			case 18: grid = cs.getData<bool>(); break;
			default: break;
			}
		}
		if (!isFit) setVisualRect(vrect);
	} break;
	default: { // old version 0:
		QDataStream s(ba);
		bool a;
		s >> has_opengl;
		s >> aalias;
		s >> a;
		setBorderInputsVisible(a);
		s >> a;
		setStatusVisible(a);
		s >> a;
		s >> graphics;
		setLegendVisible(a);
	} break;
	}
	loaded_configs = graphics;
	for (auto & i: loaded_configs)
		i.removeData();
	if (has_opengl != isOGL) setOpenGL(has_opengl);
	updateLegend();
	repaintCanvas(true);
}


GraphicType Graphic::graphic(int arg) {
	if (!checkGraphicIndex(arg)) return {};
	return graphics[arg];
}


void Graphic::setAllGraphics(const QVector<GraphicType> & g, bool update) {
	graphics = g;
	if (update) {
		updateLegend();
		repaintCanvas();
	}
}


void Graphic::setCaption(const QString & str) {
	ui->labelCaption->setText(str);
	ui->labelCaption->setVisible(str.length() > 0);
	if (aupdate) repaintCanvas();
}


void Graphic::setLabelX(const QString & str) {
	label_x = str;
	hasLblX = (str.length() > 0);
	if (aupdate) repaintCanvas();
}


void Graphic::setLabelY(const QString & str) {
	label_y = str;
	hasLblY = (str.length() > 0);
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicName(const QString & str, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].name = str;
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicName(const QString & str) {
	setGraphicName(str, curGraphic);
}


void Graphic::setBackgroundColor(const QColor & color) {
	back_color = color;
	if (aupdate) repaintCanvas();
	updateLegend();
}


void Graphic::setTextColor(const QColor & color) {
	text_color = color;
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicColor(const QColor & color, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].pen.setColor(color);
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicColor(const QColor & color) {
	setGraphicColor(color, curGraphic);
}


void Graphic::setGridColor(const QColor & color) {
	grid_pen.setColor(color);
	if (aupdate) repaintCanvas();
}


void Graphic::setSelectionColor(const QColor & color) {
	selpen.setColor(color);
}


void Graphic::setGraphicStyle(const Qt::PenStyle & style, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].pen.setStyle(style);
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicStyle(const Qt::PenStyle & style) {
	setGraphicStyle(style, curGraphic);
}


void Graphic::setGridStyle(const Qt::PenStyle & style) {
	grid_pen.setStyle(style);
	if (aupdate) repaintCanvas();
}


void Graphic::setSelectionStyle(const Qt::PenStyle & style) {
	selpen.setStyle(style);
}


void Graphic::setGraphicVisible(bool visible, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].visible = visible;
	updateLegendChecks();
	if (isFit) {
		autofit();
	} else if (aupdate) {
		repaintCanvas();
	}
}


void Graphic::setGraphicVisible(bool visible) {
	setGraphicVisible(visible, curGraphic);
}


void Graphic::setGraphicLineWidth(double w, int index) {
	if (!checkGraphicIndex(index)) return;
	if (qRound(w) == w)
		graphics[index].pen.setWidth(qRound(w));
	else
		graphics[index].pen.setWidthF(w);
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicLineWidth(double w) {
	setGraphicLineWidth(w, curGraphic);
}


void Graphic::setGraphicPointWidth(double w, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].pointWidth = w;
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicPointWidth(double w) {
	setGraphicPointWidth(w, curGraphic);
}


void Graphic::setGraphicFillColor(const QColor & w, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].fill_color = w;
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicFillColor(const QColor & w) {
	setGraphicFillColor(w, curGraphic);
}


void Graphic::setGraphicLinesEnabled(bool w, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].lines = w;
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicLinesEnabled(bool w) {
	setGraphicLinesEnabled(w, curGraphic);
}


void Graphic::setGraphicPointsEnabled(bool w, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].points = w;
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicPointsEnabled(bool w) {
	setGraphicPointsEnabled(w, curGraphic);
}


void Graphic::setGraphicFillEnabled(bool w, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].fill = w;
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicFillEnabled(bool w) {
	setGraphicFillEnabled(w, curGraphic);
}


void Graphic::setGraphicPen(const QPen & pen, int index) {
	if (!checkGraphicIndex(index)) return;
	graphics[index].pen = pen;
	updateLegend();
	if (aupdate) repaintCanvas();
}


void Graphic::setGraphicPen(const QPen & pen) {
	setGraphicPen(pen, curGraphic);
}


void Graphic::setGridPen(const QPen & pen) {
	grid_pen = pen;
	if (aupdate) repaintCanvas();
}


void Graphic::setSelectionPen(const QPen & pen) {
	selpen = pen;
}


void Graphic::setSelectionBrush(const QBrush & brush) {
	selbrush = brush;
}


void Graphic::setNavigationEnabled(bool on) {
	navigation = on;
}


void Graphic::setLODOptimization(bool yes) {
	m_LODOptimization = yes;
}


void Graphic::setGridEnabled(bool enabled) {
	grid = enabled;
	repaintCanvas();
}


void Graphic::setBorderInputsVisible(bool visible) {
	ui->widgetLX->setVisible(visible);
	ui->widgetLY->setVisible(visible);
	ui->graphic_checkBorderInputs->setChecked(visible);
	if (visible) setRectToLines();
}


void Graphic::setStatusVisible(bool visible) {
	ui->status->setVisible(visible);
}


void Graphic::setLegendVisible(bool visible) {
	ui->scrollLegend->setVisible(visible);
	ui->graphic_checkLegend->setChecked(visible);
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
	QTimer::singleShot(0, this, [this]() { updateLegend(); });
#else
	updateLegend();
#endif
}


void Graphic::on_graphic_actionExpandX_triggered(bool checked) {
	only_expand_x = checked;
	ui->graphic_actionExpandX->setIcon(checked ? icon_exp_x : icon_exp_sx);
}


void Graphic::on_graphic_actionExpandY_triggered(bool checked) {
	only_expand_y = checked;
	ui->graphic_actionExpandY->setIcon(checked ? icon_exp_y : icon_exp_sy);
}


void Graphic::on_graphic_checkBorderInputs_toggled(bool checked) {
	setBorderInputsVisible(checked);
}


void Graphic::on_graphic_checkLegend_toggled(bool checked) {
	setLegendVisible(checked);
}


void Graphic::on_graphic_checkPause_toggled(bool checked) {
	setPaused(checked);
}


void Graphic::actionGuidesTriggered(QAction * a) {
	ui->graphic_checkGuides->setChecked(true);
	setFloatingAxisType((FloatingAxisType)a->property("_value").toInt());
}
