author | Francesco Abbate <francesco.bbt@gmail.com> | 2013年02月03日 23:45:28 +0100 |
---|---|---|
committer | Francesco Abbate <francesco.bbt@gmail.com> | 2013年02月03日 23:45:28 +0100 |
commit | 474642a4e98d378a5ab3cdb124c182135f5a5493 (patch) | |
tree | a02d7ac0d7cdf45695ccb5bbbe87bdbb1c809fa0 /agg-plot/plot.cpp | |
parent | e88806a5a5760aba7f5e2cd633ac3c3de2c22682 (diff) | |
download | gsl-shell-474642a4e98d378a5ab3cdb124c182135f5a5493.tar.gz |
-rw-r--r-- | agg-plot/plot.cpp | 660 |
diff --git a/agg-plot/plot.cpp b/agg-plot/plot.cpp new file mode 100644 index 00000000..95f3f743 --- /dev/null +++ b/agg-plot/plot.cpp @@ -0,0 +1,660 @@ +#include "plot.h" + +static double compute_scale(const agg::trans_affine& m) +{ + return m.scale() / 480.0; +} + +static double +std_line_width(double scale, double w = 1.0) +{ +#if 0 + const double dsf = M_LN10; + double ls = log(scale) / dsf; + return exp(round(ls) * dsf) * w * 1.5; +#else + return w * 1.5; +#endif +} + +void plot::commit_pending_draw() +{ + push_drawing_queue(); + m_need_redraw = false; + m_changes_pending.clear(); +} + +void plot::add(sg_object* vs, agg::rgba8& color, bool outline) +{ + item d(vs, color, outline); + list<item> *new_node = new list<item>(d); + m_drawing_queue = list<item>::push_back(m_drawing_queue, new_node); + RM::acquire(vs); +} + +void plot::push_drawing_queue() +{ + item_list* layer = current_layer(); + for (list<item> *c = m_drawing_queue; c != 0; c = c->next()) + { + layer->add(c->content()); + } + + while (m_drawing_queue) + m_drawing_queue = list<item>::pop(m_drawing_queue); +} + +void plot::clear_drawing_queue() +{ + while (m_drawing_queue) + { + item& d = m_drawing_queue->content(); + RM::dispose(d.vs); + m_drawing_queue = list<item>::pop(m_drawing_queue); + } +} + +static bool area_is_valid(const agg::trans_affine& b) +{ + const double thresold = 40.0; + return (b.sx > thresold && b.sy > thresold); +} + +void plot::draw_virtual_canvas(canvas_type& canvas, plot_layout& layout, const agg::rect_i* clip) +{ + before_draw(); + draw_legends(canvas, layout); + + if (area_is_valid(layout.plot_area)) + { + draw_axis(canvas, layout, clip); + draw_elements(canvas, layout); + } +}; + +void plot::draw_simple(canvas_type& canvas, plot_layout& layout, const agg::rect_i* clip) +{ + before_draw(); + draw_axis(canvas, layout, clip); + draw_elements(canvas, layout); +}; + +void plot::draw_element(item& c, canvas_type& canvas, const agg::trans_affine& m) +{ + sg_object& vs = c.content(); + vs.apply_transform(m, 1.0); + + if (c.outline) + canvas.draw_outline(vs, c.color); + else + canvas.draw(vs, c.color); +} + +agg::trans_affine plot::get_model_matrix(const plot_layout& layout) +{ + agg::trans_affine m = m_trans; + trans_affine_compose (m, layout.plot_active_area); + return m; +} + +void plot::clip_plot_area(canvas_type& canvas, const agg::trans_affine& area_mtx) +{ + if (this->clip_is_active()) + { + agg::rect_base<int> clip = rect_of_slot_matrix<int>(area_mtx); + canvas.clip_box(clip); + } +} + +void plot::draw_elements(canvas_type& canvas, const plot_layout& layout) +{ + const agg::trans_affine m = get_model_matrix(layout); + + this->clip_plot_area(canvas, layout.plot_active_area); + + for (unsigned k = 0; k < m_layers.size(); k++) + { + item_list& layer = *(m_layers[k]); + for (unsigned j = 0; j < layer.size(); j++) + { + draw_element(layer[j], canvas, m); + } + } + + canvas.reset_clipping(); +} + +void plot::compute_user_trans() +{ + agg::rect_base<double> r; + + if (m_use_units && m_pad_units) + { + int ixi, ixs, iyi, iys; + double xd, yd; + m_ux.limits(ixi, ixs, xd); + r.x1 = ixi * xd; + r.x2 = ixs * xd; + + m_uy.limits(iyi, iys, yd); + r.y1 = iyi * yd; + r.y2 = iys * yd; + } + else + { + r = m_rect.is_defined() ? m_rect.rect() : agg::rect_base<double>(0.0, 0.0, 1.0, 1.0); + } + + double dx = r.x2 - r.x1, dy = r.y2 - r.y1; + double fx = (dx == 0 ? 1.0 : 1/dx), fy = (dy == 0 ? 1.0 : 1/dy); + this->m_trans = agg::trans_affine(fx, 0.0, 0.0, fy, -r.x1 * fx, -r.y1 * fy); +} + +void plot::draw_grid(const axis_e dir, const units& u, + const agg::trans_affine& user_mtx, + agg::path_storage& ln) +{ + const double eps = 1.0e-3; + const bool isx = (dir == x_axis); + + int jinf = u.begin(), jsup = u.end(); + for (int j = jinf+1; j < jsup; j++) + { + double uq = u.mark_value(j); + double x = (isx ? uq : 0), y = (isx ? 0.0 : uq); + user_mtx.transform(&x, &y); + const double q = (isx ? x : y); + + if (q >= -eps && q <= 1.0 + eps) + { + ln.move_to(isx ? q : 0.0, isx ? 0.0 : q); + ln.line_to(isx ? q : 1.0, isx ? 1.0 : q); + } + } +} + + +double plot::draw_axis_m(axis_e dir, units& u, + const agg::trans_affine& user_mtx, + ptr_list<draw::text>& labels, double scale, + agg::path_storage& mark, agg::path_storage& ln) +{ + const double ppad = double(axis_label_prop_space) / 1000.0; + const double text_label_size = get_default_font_size(text_axis_labels, scale); + const double eps = 1.0e-3; + + // used to store the bounding box of text labels + opt_rect<double> bb; + agg::rect_base<double> r; + + bool isx = (dir == x_axis); + + const axis& ax = get_axis(dir); + double hj = ax.labels_hjustif(), vj = ax.labels_vjustif(); + double langle = ax.labels_angle(); + + category_map::iterator clabels(ax.categories); + units_iterator ulabels(u, ax.format_tag, ax.label_format()); + + label_iterator* ilabels = (ax.use_categories ? (label_iterator*) &clabels : (label_iterator*) &ulabels); + + double uq; + const char* text; + while (ilabels->next(uq, text)) + { + double x = (isx ? uq : 0.0), y = (isx ? 0.0 : uq); + user_mtx.transform(&x, &y); + + double q = (isx ? x : y); + + if (q < -eps || q > 1.0 + eps) + continue; + + draw::text* label = new draw::text(text, text_label_size, hj, vj); + + label->set_point(isx ? q : -ppad, isx ? -ppad : q); + label->angle(langle); + + agg::bounding_rect_single(*label, 0, &r.x1, &r.y1, &r.x2, &r.y2); + bb.add<rect_union>(r); + + labels.add(label); + + mark.move_to(isx ? q : 0.0 , isx ? 0.0 : q); + mark.line_to(isx ? q : -0.01, isx ? -0.01 : q); + } + + this->draw_grid(dir, u, user_mtx, ln); + + double label_size; + if (bb.is_defined()) + { + const agg::rect_base<double>& br = bb.rect(); + label_size = (isx ? br.y2 - br.y1 : br.x2 - br.x1); + } + else + { + label_size = 0.0; + } + + return label_size; +} + +double plot::draw_xaxis_factors(units& u, + const agg::trans_affine& user_mtx, + ptr_list<draw::text>& labels, + ptr_list<factor_labels>* f_labels, double scale, + agg::path_storage& mark, agg::path_storage& ln) +{ + const double text_label_size = get_default_font_size(text_axis_labels, scale); + + const double y_spacing = 0.05; + const unsigned layers_number = f_labels->size(); + double p_lab = - double(layers_number) * y_spacing; + for (unsigned layer = 0; layer < layers_number; layer++) + { + factor_labels* factor = f_labels->at(layer); + for (int k = 0; k < factor->labels_number(); k++) + { + double x_lab_a = factor->mark(k); + double x_lab_b = factor->mark(k+1); + + double x_a = x_lab_a, y_a = 0.0; + user_mtx.transform(&x_a, &y_a); + double q_a = x_a; + + double x_lab = (x_lab_a + x_lab_b) / 2, y_lab = 0.0; + user_mtx.transform(&x_lab, &y_lab); + double q_lab = x_lab; + + mark.move_to(q_a, p_lab); + mark.line_to(q_a, p_lab + y_spacing); + + const char* text = factor->label_text(k); + draw::text* label = new draw::text(text, text_label_size, 0.5, -0.2); + + label->set_point(q_lab, p_lab); + label->angle(0.0); + + labels.add(label); + } + + double x_lab = factor->mark(layers_number); + double x_a = x_lab, y_a = 0.0; + user_mtx.transform(&x_a, &y_a); + double q_a = x_a; + mark.move_to(q_a, p_lab); + mark.line_to(q_a, p_lab + y_spacing); + + p_lab += y_spacing; + } + + this->draw_grid(x_axis, u, user_mtx, ln); + + return y_spacing * layers_number; +} + +static inline double approx_text_height(double text_size) +{ + return text_size * 1.5; +} + +plot_layout plot::compute_plot_layout(const agg::trans_affine& canvas_mtx, bool do_legends) +{ + plot_layout layout; + + const double sx = canvas_mtx.sx, sy = canvas_mtx.sy; + const double ppad = double(canvas_margin_prop_space) / 1000.0; + const double fpad = double(canvas_margin_fixed_space); + const double size_frac_x = 0.125, size_frac_y = 0.05; + + double dxl, dxr, dyb, dyt; + + dxl = dxr = fpad + ppad * sx; + dyb = dyt = fpad + ppad * sy; + + if (!str_is_null(&m_title)) + { + const double scale = compute_scale(canvas_mtx); + const double ptpad = double(axis_title_prop_space) / 1000.0; + const double title_text_size = get_default_font_size(text_plot_title, scale); + const double th = approx_text_height(title_text_size); + + double x = 0.5, y = 1.0; + canvas_mtx.transform(&x, &y); + y -= ptpad + dyt + title_text_size; + + layout.title_pos = plot_layout::point(x, y); + layout.title_font_size = title_text_size; + + dyt += 2 * ptpad + th; + } + + for (int k = 0; k < 4 && do_legends; k++) + { + plot* mp = m_legend[k]; + + if (mp) + { + agg::rect_base<double> bb; + mp->get_bounding_rect(bb); + + double bb_dx = bb.x2 - bb.x1, bb_dy = bb.y2 - bb.y1; + double dx, dy; + double px, py; + switch (k) + { + case right: + dx = max(sx * size_frac_x, bb_dx); + dy = dx * bb_dy / bb_dx; + px = sx - dx - ppad * sx - dxr; + py = (sy - dy) / 2; + dxr += dx + 2 * ppad * sx; + break; + case left: + dx = max(sx * size_frac_x, bb_dx); + dy = dx * bb_dy / bb_dx; + px = ppad * sx + dxr; + py = (sy - dy) / 2; + dxl += dx + 2 * ppad * sx; + break; + case bottom: + dy = sy * size_frac_y; + dx = dy * bb_dx / bb_dy; + py = ppad * sy + dyb; + px = (sx - dx) / 2; + dyb += dy + 2 * ppad * sy; + break; + case top: + dy = sy * size_frac_y; + dx = dy * bb_dx / bb_dy; + py = sy - dy - ppad * sy - dyt; + px = (sx - dx) / 2; + dyt += dy + 2 * ppad * sy; + break; + default: + /* */ + ; + } + + if (px >= 0 && py >= 0 && px + dx < sx && py + dy < sy) + { + const double x0 = canvas_mtx.tx + px, y0 = canvas_mtx.ty + py; + layout.legend_area[k] = agg::trans_affine(dx, 0.0, 0.0, dy, x0, y0); + } + else + { + plot_layout::set_area_undefined(layout.legend_area[k]); + } + } + } + + double x0 = canvas_mtx.tx + dxl, y0 = canvas_mtx.ty + dyb; + double ssx = sx - (dxl + dxr), ssy = sy - (dyb + dyt); + layout.plot_area = agg::trans_affine(ssx, 0.0, 0.0, ssy, x0, y0); + + return layout; +} + +void plot::draw_legends(canvas_type& canvas, const plot_layout& layout) +{ + if (!str_is_null(&m_title)) + { + const plot_layout::point& pos = layout.title_pos; + draw::text title(m_title.cstr(), layout.title_font_size, 0.5, 0.0); + title.set_point(pos.x, pos.y); + title.apply_transform(identity_matrix, 1.0); + canvas.draw(title, colors::black); + } + + for (int k = 0; k < 4; k++) + { + plot* mp = m_legend[k]; + const agg::trans_affine& mtx = layout.legend_area[k]; + + if (mp && plot_layout::is_area_defined(mtx)) + { + agg::rect_i clip = rect_of_slot_matrix<int>(mtx); + plot_layout mp_layout = mp->compute_plot_layout(mtx, false); + mp->draw_simple(canvas, mp_layout, &clip); + } + } +} + +// Draw the axis elements and labels and set layout.plot_active_area +// to the actual plotting are matrix. +void plot::draw_axis(canvas_type& canvas, plot_layout& layout, const agg::rect_i* clip) +{ + if (!m_use_units) + { + layout.plot_active_area = layout.plot_area; + return; + } + + double scale = compute_scale(layout.plot_area); + + if (clip) + canvas.clip_box(*clip); + + const agg::trans_affine& m = layout.plot_active_area; + + agg::path_storage box; + sg_object_gen<agg::conv_transform<agg::path_storage> > boxtr(box, m); + trans::stroke_a boxvs(&boxtr); + + box.move_to(0.0, 0.0); + box.line_to(0.0, 1.0); + box.line_to(1.0, 1.0); + box.line_to(1.0, 0.0); + box.close_polygon(); + + agg::path_storage mark; + sg_object_gen<agg::conv_transform<agg::path_storage> > mark_tr(mark, m); + trans::stroke_a mark_stroke(&mark_tr); + + agg::path_storage ln; + sg_object_gen<agg::conv_transform<agg::path_storage> > ln_tr(ln, m); + trans::dash_a lndash(&ln_tr); + trans::stroke_a lns(&lndash); + + const double label_text_size = get_default_font_size(text_axis_title, scale); + const double plpad = double(axis_label_prop_space) / 1000.0; + const double ptpad = double(axis_title_prop_space) / 1000.0; + + ptr_list<draw::text> labels; + + double dy_label = 0, pdy_label = 0; + if (this->m_xaxis_hol) + pdy_label = draw_xaxis_factors(m_ux, m_trans, labels, this->m_xaxis_hol, scale, mark, ln); + else + dy_label = draw_axis_m(x_axis, m_ux, m_trans, labels, scale, mark, ln); + + double dx_label = draw_axis_m(y_axis, m_uy, m_trans, labels, scale, mark, ln); + + double ppad_left = plpad, ppad_right = plpad; + double ppad_bottom = plpad + pdy_label, ppad_top = plpad; + double dx_left = dx_label, dx_right = 0.0; + double dy_bottom = dy_label, dy_top = 0.0; + + if (!str_is_null(&m_y_axis.title)) + { + dx_left += approx_text_height(label_text_size); + ppad_left += ptpad; + } + + if (!str_is_null(&m_x_axis.title)) + { + dy_bottom += approx_text_height(label_text_size); + ppad_bottom += ptpad; + } + + const double sx = layout.plot_area.sx, sy = layout.plot_area.sy; + const double x0 = layout.plot_area.tx, y0 = layout.plot_area.ty; + + const double xppad = (ppad_left + ppad_right); + const double lsx = (dx_left + dx_right + xppad * sx) / (1 + xppad); + + const double yppad = (ppad_bottom + ppad_top); + const double lsy = (dy_bottom + dy_top + yppad * sy) / (1 + yppad); + + const double sxr = sx - lsx; + const double syr = sy - lsy; + + const double aax = x0 + dx_left + ppad_left * sxr; + const double aay = y0 + dy_bottom + ppad_bottom * syr; + layout.set_plot_active_area(sxr, syr, aax, aay); + + for (unsigned j = 0; j < labels.size(); j++) + { + draw::text* label = labels[j]; + label->apply_transform(m, 1.0); + canvas.draw(*label, colors::black); + } + + lndash.add_dash(7.0, 3.0); + + lns.width(std_line_width(scale, 0.15)); + canvas.draw(lns, colors::black); + + mark_stroke.width(std_line_width(scale, 0.75)); + canvas.draw(mark_stroke, colors::black); + + boxvs.width(std_line_width(scale, 0.75)); + canvas.draw(boxvs, colors::black); + + if (!str_is_null(&m_x_axis.title)) + { + double labx = m.sx * 0.5 + m.tx; + double laby = y0; + + const char* text = m_x_axis.title.cstr(); + draw::text xlabel(text, label_text_size, 0.5, 0.0); + xlabel.set_point(labx, laby); + xlabel.apply_transform(identity_matrix, 1.0); + + canvas.draw(xlabel, colors::black); + } + + if (!str_is_null(&m_y_axis.title)) + { + double labx = x0; + double laby = m.sy * 0.5 + m.ty; + + const char* text = m_y_axis.title.cstr(); + draw::text ylabel(text, label_text_size, 0.5, 1.0); + ylabel.set_point(labx, laby); + ylabel.angle(M_PI/2.0); + ylabel.apply_transform(identity_matrix, 1.0); + + canvas.draw(ylabel, colors::black); + } + + if (clip) + canvas.reset_clipping(); +} + +void plot::set_axis_labels_angle(axis_e axis_dir, double angle) +{ + get_axis(axis_dir).set_labels_angle(angle); + m_need_redraw = true; + compute_user_trans(); +} + +void plot::set_units(bool use_units) +{ + if (m_use_units != use_units) + { + m_use_units = use_units; + m_need_redraw = true; + compute_user_trans(); + } +} + +void plot::update_units() +{ + if (m_rect.is_defined()) + { + const rect_base<double>& r = m_rect.rect(); + m_ux = units(r.x1, r.x2); + m_uy = units(r.y1, r.y2); + } + else + { + m_ux = units(); + m_uy = units(); + } + + compute_user_trans(); +} + +void plot::set_limits(const agg::rect_base<double>& r) +{ + m_rect.set(r); + update_units(); + m_need_redraw = true; +} + +void plot::unset_limits() +{ + m_rect.clear(); + update_units(); + m_need_redraw = true; +} + +void plot::layer_dispose_elements(plot::item_list* layer) +{ + unsigned n = layer->size(); + for (unsigned k = 0; k < n; k++) + { + RM::dispose(layer->at(k).vs); + } +} + +bool plot::push_layer() +{ + if (m_layers.size() >= max_layers) + return false; + + item_list *new_layer = new(std::nothrow) item_list(); + if (new_layer) + { + before_draw(); + push_drawing_queue(); + m_layers.add(new_layer); + return true; + } + + return false; +} + +bool plot::pop_layer() +{ + if (m_layers.size() <= 1) + return false; + + unsigned n = m_layers.size(); + item_list* layer = m_layers[n-1]; + m_layers.inc_size(-1); + layer_dispose_elements(layer); + delete layer; + + clear_drawing_queue(); + m_need_redraw = true; + + return true; +} + +void plot::clear_current_layer() +{ + item_list* current = current_layer(); + clear_drawing_queue(); + layer_dispose_elements(current); + current->clear(); + m_changes_pending = m_changes_accu; + m_changes_accu.clear(); +} + +int plot::current_layer_index() +{ + return m_layers.size(); +} |