// This file is part of fityk program. Copyright (C) Marcin Wojdyr
// Licence: GNU General Public License version 2
// $Id: plot.cpp 317 2007-07-15 00:39:46Z wojdyr $
/// In this file:
/// FPlot, the base class for MainPlot and AuxPlot
#include <wx/wxprec.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/fontdlg.h>
#include <wx/numdlg.h>
#include <wx/confbase.h>
#include "plot.h"
#include "cmn.h"
#include "gui.h" //ftk
#include "../data.h"
#include "../logic.h"
using namespace std;
void Scale::set(fp m, fp M, int pixels)
{
fp h = 0;
if (logarithm) {
M = log(max(M, 1e-1));
m = log(max(m, 1e-1));
}
h = M - m;
origin = reversed ? M : m;
if (h == 0)
h = 0.1;
scale = pixels / (reversed ? -h : h);
}
//===============================================================
// BufferedPanel
//===============================================================
void BufferedPanel::refresh(bool now)
{
clear_and_draw();
Refresh(false);
if (now)
Update();
}
bool BufferedPanel::resize_buffer(wxDC &dc)
{
wxCoord w, h;
dc.GetSize(&w, &h);
if (!buffer.Ok()
|| w != buffer.GetWidth() || h != buffer.GetHeight()) {
memory_dc.SelectObject(wxNullBitmap);
buffer = wxBitmap(w, h);
memory_dc.SelectObject(buffer);
return true;
}
return false;
}
void BufferedPanel::clear_and_draw()
{
if (!buffer.Ok())
return;
memory_dc.SetLogicalFunction(wxCOPY);
memory_dc.SetBackground(wxBrush(backgroundCol));
memory_dc.Clear();
draw(memory_dc);
}
/// called from wxPaint event handler
void BufferedPanel::buffered_draw()
{
wxPaintDC dc(this);
if (resize_buffer(dc))
clear_and_draw();
dc.Blit(0, 0, buffer.GetWidth(), buffer.GetHeight(), &memory_dc, 0, 0);
}
//===============================================================
// FPlot (plot with data and fitted curves)
//===============================================================
void FPlot::draw_dashed_vert_line(int X, int style)
{
if (X != INT_MIN) {
wxClientDC dc(this);
dc.SetLogicalFunction (wxINVERT);
if (style == wxSHORT_DASH)
dc.SetPen(*wxBLACK_DASHED_PEN);
else {
wxPen pen = *wxBLACK_DASHED_PEN;
pen.SetStyle(style);
dc.SetPen(pen);
}
int h = GetClientSize().GetHeight();
dc.DrawLine (X, 0, X, h);
}
}
void FPlot::draw_crosshair(int X, int Y)
{
wxClientDC dc(this);
dc.SetLogicalFunction (wxINVERT);
dc.SetPen(*wxBLACK_DASHED_PEN);
dc.CrossHair(X, Y);
}
bool FPlot::vert_line_following_cursor (MouseActEnum ma, int x, int x0)
{
if (ma == mat_start) {
draw_dashed_vert_line(x0);
vlfc_prev_x0 = x0;
}
else {
if (vlfc_prev_x == INT_MIN)
return false;
draw_dashed_vert_line(vlfc_prev_x); //clear (or draw again) old line
}
if (ma == mat_move || ma == mat_start) {
draw_dashed_vert_line(x);
vlfc_prev_x = x;
}
else if (ma == mat_redraw) {
draw_dashed_vert_line(vlfc_prev_x0);
}
else { // mat_stop
draw_dashed_vert_line(vlfc_prev_x0); //clear
vlfc_prev_x = vlfc_prev_x0 = INT_MIN;
}
return true;
}
void draw_line_with_style(wxDC& dc, int style,
wxCoord X1, wxCoord Y1, wxCoord X2, wxCoord Y2)
{
wxPen pen = dc.GetPen();
int old_style = pen.GetStyle();
pen.SetStyle(style);
dc.SetPen(pen);
dc.DrawLine (X1, Y1, X2, Y2);
pen.SetStyle(old_style);
dc.SetPen(pen);
}
/// draw x axis tics
void FPlot::draw_xtics (wxDC& dc, View const &v, bool set_pen)
{
if (set_pen) {
dc.SetPen(wxPen(xAxisCol));
dc.SetTextForeground(xAxisCol);
}
dc.SetFont(ticsFont);
// get tics text height
wxCoord h;
dc.GetTextExtent(wxT("1234567890"), 0, &h);
vector<double> minors;
vector<double> x_tics = scale_tics_step(v.left, v.right, x_max_tics,
minors, xs.logarithm);
//if x axis is visible tics are drawed at the axis,
//otherwise tics are drawed at the bottom edge of the plot
int Y = GetClientSize().GetHeight() - h;
if (x_axis_visible && !ys.logarithm && ys.px(0) >= 0 && ys.px(0) < Y)
Y = ys.px(0);
for (vector<double>::const_iterator i = x_tics.begin();
i != x_tics.end(); ++i) {
int X = xs.px(*i);
dc.DrawLine (X, Y, X, Y - x_tic_size);
wxString label = s2wx(S(*i));
if (label == wxT("-0"))
label = wxT("0");
wxCoord w;
dc.GetTextExtent (label, &w, 0);
dc.DrawText (label, X - w/2, Y + 1);
if (x_grid) {
draw_line_with_style(dc, wxDOT, X,0, X,Y);
draw_line_with_style(dc, wxDOT, X, Y+1+h,
X, GetClientSize().GetHeight());
}
}
//draw minor tics
if (xminor_tics_visible)
for (vector<double>::const_iterator i = minors.begin();
i != minors.end(); ++i) {
int X = xs.px(*i);
dc.DrawLine (X, Y, X, Y - x_tic_size);
}
}
/// draw y axis tics
void FPlot::draw_ytics (wxDC& dc, View const &v, bool set_pen)
{
if (set_pen) {
dc.SetPen(wxPen(xAxisCol));
dc.SetTextForeground(xAxisCol);
}
dc.SetFont(ticsFont);
//if y axis is visible, tics are drawed at the axis,
//otherwise tics are drawed at the left hand edge of the plot
int X = 0;
if (y_axis_visible && xs.px(0) > 0
&& xs.px(0) < GetClientSize().GetWidth()-10)
X = xs.px(0);
vector<double> minors;
vector<double> y_tics = scale_tics_step(v.bottom, v.top, y_max_tics,
minors, ys.logarithm);
for (vector<double>::const_iterator i = y_tics.begin();
i != y_tics.end(); ++i) {
int Y = ys.px(*i);
dc.DrawLine (X, Y, X + y_tic_size, Y);
wxString label = s2wx(S(*i));
if (label == wxT("-0"))
label = wxT("0");
if (x_axis_visible && label == wxT("0"))
continue;
wxCoord w, h;
dc.GetTextExtent (label, &w, &h);
dc.DrawText (label, X + y_tic_size + 1, Y - h/2);
if (y_grid) {
draw_line_with_style(dc, wxDOT, 0,Y, X,Y);
draw_line_with_style(dc, wxDOT, X + y_tic_size + 1 + w + 1, Y,
GetClientSize().GetWidth(), Y);
}
}
//draw minor tics
if (yminor_tics_visible)
for (vector<double>::const_iterator i = minors.begin();
i != minors.end(); ++i) {
int Y = ys.px(*i);
dc.DrawLine (X, Y, X + y_tic_size, Y);
}
}
double FPlot::get_max_abs_y (double (*compute_y)(vector<Point>::const_iterator,
Sum const*),
vector<Point>::const_iterator first,
vector<Point>::const_iterator last,
Sum const* sum)
{
double max_abs_y = 0;
for (vector<Point>::const_iterator i = first; i < last; i++) {
if (i->is_active) {
double y = fabs(((*compute_y)(i, sum)));
if (y > max_abs_y) max_abs_y = y;
}
}
return max_abs_y;
}
void FPlot::draw_data (wxDC& dc,
double (*compute_y)(vector<Point>::const_iterator,
Sum const*),
Data const* data,
Sum const* sum,
wxColour const& color, wxColour const& inactive_color,
int Y_offset,
bool cumulative)
{
Y_offset *= (GetClientSize().GetHeight() / 100);
wxPen const activePen(color.Ok() ? color : activeDataCol);
wxPen const inactivePen(inactive_color.Ok() ? inactive_color
: inactiveDataCol);
wxBrush const activeBrush(activePen.GetColour(), wxSOLID);
wxBrush const inactiveBrush(inactivePen.GetColour(), wxSOLID);
if (data->is_empty())
return;
vector<Point>::const_iterator first = data->get_point_at(ftk->view.left),
last = data->get_point_at(ftk->view.right);
//if (last - first < 0) return;
bool active = first->is_active;
dc.SetPen (active ? activePen : inactivePen);
dc.SetBrush (active ? activeBrush : inactiveBrush);
int X_ = INT_MIN, Y_ = INT_MIN;
// first line segment -- lines should be drawed towards points
// that are outside of plot
if (line_between_points && first > data->points().begin() && !cumulative) {
X_ = xs.px (ftk->view.left);
int Y_l = ys.px ((*compute_y)(first - 1, sum));
int Y_r = ys.px ((*compute_y)(first, sum));
int X_l = xs.px ((first - 1)->x);
int X_r = xs.px (first->x);
if (X_r == X_l)
Y_ = Y_r;
else
Y_ = Y_l + (Y_r - Y_l) * (X_ - X_l) / (X_r - X_l);
}
Y_ -= Y_offset;
double y = 0;
//drawing all points (and lines); main loop
for (vector<Point>::const_iterator i = first; i < last; i++) {
int X = xs.px(i->x);
if (cumulative)
y += (*compute_y)(i, sum);
else
y = (*compute_y)(i, sum);
int Y = ys.px(y) - Y_offset;
if (X == X_ && Y == Y_)
continue;
if (i->is_active != active) {
//half of line between points should be active and half not.
//draw first half here and change X_, Y_; the rest will be drawed
//as usually.
if (line_between_points) {
int X_mid = (X_ + X) / 2, Y_mid = (Y_ + Y) / 2;
dc.DrawLine (X_, Y_, X_mid, Y_mid);
X_ = X_mid, Y_ = Y_mid;
}
active = i->is_active;
if (active) {
dc.SetPen (activePen);
dc.SetBrush (activeBrush);
}
else {
dc.SetPen (inactivePen);
dc.SetBrush (inactiveBrush);
}
}
if (point_radius > 1)
dc.DrawCircle (X, Y, point_radius - 1);
if (line_between_points) {
if (X_ != INT_MIN)
dc.DrawLine (X_, Y_, X, Y);
X_ = X, Y_ = Y;
}
else {//no line_between_points
if (point_radius == 1)
dc.DrawPoint (X, Y);
}
if (draw_sigma) {
dc.DrawLine (X, ys.px(y - i->sigma) - Y_offset,
X, ys.px(y + i->sigma) - Y_offset);
}
}
//the last line segment, toward next point
if (line_between_points && last < data->points().end() && !cumulative) {
int X = xs.px (ftk->view.right);
int Y_l = ys.px ((*compute_y)(last - 1, sum));
int Y_r = ys.px ((*compute_y)(last, sum));
int X_l = xs.px ((last - 1)->x);
int X_r = xs.px (last->x);
if (X_r != X_l) {
int Y = Y_l + (Y_r - Y_l) * (X - X_l) / (X_r - X_l) - Y_offset;
dc.DrawLine (X_, Y_, X, Y);
}
}
}
void FPlot::change_tics_font()
{
wxFontData data;
data.SetInitialFont(ticsFont);
data.SetColour(xAxisCol);
wxFontDialog dialog(0, data);
if (dialog.ShowModal() == wxID_OK)
{
wxFontData retData = dialog.GetFontData();
ticsFont = retData.GetChosenFont();
xAxisCol = retData.GetColour();
refresh(false);
}
}
void FPlot::set_scale()
{
View const &v = ftk->view;
xs.set(v.left, v.right, GetClientSize().GetWidth());
ys.set(v.top, v.bottom, GetClientSize().GetHeight());
}
int FPlot::get_special_point_at_pointer(wxMouseEvent& event)
{
// searching the closest peak-top and distance from it, d = dx + dy < 10
int nearest = -1;
int min_dist = 10;
for (vector<wxPoint>::const_iterator i = special_points.begin();
i != special_points.end(); i++) {
int d = abs(event.GetX() - i->x) + abs(event.GetY() - i->y);
if (d < min_dist) {
min_dist = d;
nearest = i - special_points.begin();
}
}
return nearest;
}
void FPlot::read_settings(wxConfigBase *cf)
{
cf->SetPath(wxT("Visible"));
x_axis_visible = cfg_read_bool (cf, wxT("xAxis"), true);
y_axis_visible = cfg_read_bool (cf, wxT("yAxis"), false);
xtics_visible = cfg_read_bool (cf, wxT("xtics"), true);
ytics_visible = cfg_read_bool (cf, wxT("ytics"), true);
xminor_tics_visible = cfg_read_bool (cf, wxT("xMinorTics"), true);
yminor_tics_visible = cfg_read_bool (cf, wxT("yMinorTics"), false);
x_grid = cfg_read_bool (cf, wxT("xgrid"), false);
y_grid = cfg_read_bool (cf, wxT("ygrid"), false);
cf->SetPath(wxT("../Colors"));
xAxisCol = cfg_read_color(cf, wxT("xAxis"), wxColour(wxT("WHITE")));
cf->SetPath(wxT(".."));
ticsFont = cfg_read_font(cf, wxT("ticsFont"),
wxFont(8, wxFONTFAMILY_DEFAULT,
wxFONTSTYLE_NORMAL,
wxFONTWEIGHT_NORMAL));
}
void FPlot::save_settings(wxConfigBase *cf) const
{
cf->SetPath(wxT("Visible"));
cf->Write (wxT("xAxis"), x_axis_visible);
cf->Write (wxT("yAxis"), y_axis_visible);
cf->Write (wxT("xtics"), xtics_visible);
cf->Write (wxT("ytics"), ytics_visible);
cf->Write (wxT("xMinorTics"), xminor_tics_visible);
cf->Write (wxT("yMinorTics"), yminor_tics_visible);
cf->Write (wxT("xgrid"), x_grid);
cf->Write (wxT("ygrid"), y_grid);
cf->SetPath(wxT("../Colors"));
cfg_write_color (cf, wxT("xAxis"), xAxisCol);
cf->SetPath(wxT(".."));
cfg_write_font (cf, wxT("ticsFont"), ticsFont);
}
BEGIN_EVENT_TABLE(FPlot, wxPanel)
END_EVENT_TABLE()
//===============================================================
// utilities
//===============================================================
/// returns major and minor tics positions
vector<double> scale_tics_step (double beg, double end, int max_tics,
vector<double> &minors, bool log)
{
vector<double> result;
minors.clear();
if (beg >= end || max_tics <= 0)
return result;
if (log) {
if (beg <= 0)
beg = 1e-1;
if (end <= beg)
end = 2*beg;
double min_logstep = (log10(end/beg)) / max_tics;
bool with_2_5 = (min_logstep < log10(2.));
double logstep = ceil(min_logstep);
double step0 = pow(10, logstep * ceil(log10(beg) / logstep));
for (int i = 2; i <= 9; ++i) {
double v = step0/10. * i;
if (v > beg && v < end) {
if (with_2_5 && (i == 2 || i == 5))
result.push_back(v);
else
minors.push_back(v);
}
}
for (double t = step0; t < end; t *= pow(10,logstep)) {
result.push_back(t);
for (int i = 2; i <= 9; ++i) {
double v = t * i;
if (v > beg && v < end)
if (with_2_5 && (i == 2 || i == 5))
result.push_back(v);
else
minors.push_back(v);
}
}
}
else { // !log
double min_step = (end - beg) / max_tics;
double s = pow(10, floor (log10 (min_step)));
int minor_div = 5; //ratio of major-step to minor-step
// now s <= min_step
if (s >= min_step)
;
else if (s * 2 >= min_step) {
s *= 2;
minor_div = 2;
}
else if (s * 2.5 >= min_step)
s *= 2.5;
else if (s * 5 >= min_step)
s *= 5;
else
s *= 10;
for (double t = s * ceil(beg / s); t < end; t += s) {
if (t > beg) {
// make sure that 0 is not displayed as e.g. -2.7893e-17
if (is_zero(t))
t = 0.;
result.push_back(t);
}
for (int i = 1; i < minor_div; ++i) {
double v = t + s * i / minor_div;
if (v > beg && v < end)
minors.push_back(v);
}
}
}
return result;
}
syntax highlighted by Code2HTML, v. 0.9.1