plotting directly using the renderer

Hello everyone, Hello John,

    > I would like to ask you (right before I'll mess everything
    > up :wink: if all this at least could work: What I would try
    > for unification is writing the dictionary 'markerd' in
    > lines.py (I'd prefer it right in front of 'class Line2D'
    > as global - but there it isn't readable for a program
    > later, isn't it?) and change the functions a bit like: def
    > _make_triangle_down(size): return reshape([[0, -size,

Hi Martin,

Sorry for the delay getting back to you.

Perhaps the best approach is to predefine some paths in a new module,
eg data.py and define your path dictionary like so

    from matplotlib import agg

    markerd = {}

    # triangle up
    path = agg.path_storage()
    path.move_to(0, 1.0)
    path.line_to(-1.0, -1.0)
    path.line_to(1.0, -1.0)
    path.end_poly()
    markerd['^'] = path

    # square
    path = agg.path_storage()
    path.move_to(-1.0, -1.0)
    path.line_to(-1.0, 1.0)
    path.line_to(1.0, 1.0)
    path.line_to(1.0, -1.0)
    path.end_poly()
    markerd['s'] = path

We should think about what property of the marker should be conserved
(radius in points, area, area of circumscribed circle), because I
don't think we're terribly consistent on that one currently. In the
current implementation, the markersize of the triangle and square
gives the side, for the circle it gives the diameter. I'm not sure
what the right answer is here, but while you are doing this
refactoring it would be good to rationalize this and document any
changes in API_CHANGES.

In lines.py you could transform this path to scale it to the requested
size; something like

    def _draw_square(self, renderer, gc, xt, yt):
        side = renderer.points_to_pixels(self._markersize)
        s = side*0.5
        rgbFace = self._get_rgb_face()

        if self._newstyle:
            path = markerd['s']
            scale = agg.trans_affine_scaling(s,s)
            transpath = agg.conv_transform_path(path, scale)
            renderer.draw_markers(gc, transpath, rgbFace, xt, yt, self._transform)
        
We would then need to modify renderer.draw_markers to take a
transformed path "transpath" rather than a path but this would be easy
-- I'm including a modified src/_backend_agg.cpp
RendererAgg::draw_markers function below if you want to go this route.

For code that doesn't want to use the path object, it is easy to
extract the moveto/lineto command as well as the vertices like so

for i in range(path.total_vertices()):
    command, x, y = path.vertex(i)
    print command, x, y

With a little thought and care, you should be able to do away with all
the _draw_square, _draw_triangle, etc, in Line2D and replace them with
a single _draw_path, eg

    def _draw_path(self, renderer, gc, path, xt, yt):
        s = renderer.points_to_pixels(self._markersize)
        scale = agg.trans_affine_scaling(s,s)
        transpath = agg.conv_transform_path(path, translation)
        renderer.draw_markers(gc, transpath, rgbFace, xt, yt, self._transform)

where the calling function pulls the path out of the dict.

Another advantage of this approach is that it could supports arbitrary
paths (including splines) and user extensible markers. Eg, the user
should be able to do

  markerd['somekey'] = somepath

and then be able to call

  plot(rand(N), marker='somekey')

and have everything work automagically -- your task is growing :slight_smile:

Seriously, though, I think this would be a very nice addition, and not
too much work.

    > Another question would be: Are those new for-loops in
    > _draw_marker() too annoying (in the sense of speed)? (I
    > quess they are vital for the _make_triangle_down()-like

I don't think the for loop is a problem because it is one per line
object, so as long as you don't create a ton of separate lines, you'll
be OK.

JDH

Here is the modified code from _backend_agg.cpp you will need

Py::Object
RendererAgg::draw_markers(const Py::Tuple& args) {
  //_draw_markers_cache(gc, path, rgbFace, xo, yo, transform)
  theRasterizer->reset_clipping();
  
  _VERBOSE("RendererAgg::_draw_markers");
  args.verify_length(6);
  
  GCAgg gc = GCAgg(args[0], dpi);

  agg::conv_transform<agg::path_storage> *ppath;
  swig_type_info * descr = SWIG_TypeQuery("agg.conv_transform<agg::path_storage> *");
  assert(descr);
  if (SWIG_ConvertPtr(args[1].ptr(),(void **)(&ppath), descr, 0) == -1)
    throw Py::TypeError("Could not convert agg.conv_transform<agg::path_storage>");

  facepair_t face = _get_rgba_face(args[2], gc.alpha);

  Py::Object xo = args[3];
  Py::Object yo = args[4];
  
  PyArrayObject *xa = (PyArrayObject *) PyArray_ContiguousFromObject(xo.ptr(), PyArray_DOUBLE, 1, 1);
  
  if (xa==NULL)
    throw Py::TypeError("RendererAgg::_draw_markers_cache expected numerix array");
  
  PyArrayObject *ya = (PyArrayObject *) PyArray_ContiguousFromObject(yo.ptr(), PyArray_DOUBLE, 1, 1);
  
  if (ya==NULL)
    throw Py::TypeError("RendererAgg::_draw_markers_cache expected numerix array");
  
  Transformation* mpltransform = static_cast<Transformation*>(args[5].ptr());
  
  double a, b, c, d, tx, ty;
  try {
    mpltransform->affine_params_api(&a, &b, &c, &d, &tx, &ty);
  }
  catch(...) {
    throw Py::ValueError("Domain error on affine_params_api in RendererAgg::_draw_markers_cache");
  }
  
  agg::trans_affine xytrans = agg::trans_affine(a,b,c,d,tx,ty);
  
  size_t Nx = xa->dimensions[0];
  size_t Ny = ya->dimensions[0];
  
  if (Nx!=Ny)
    throw Py::ValueError(Printf("x and y must be equal length arrays; found %d and %d", Nx, Ny).str());
  
  double heightd = double(height);

  ppath->rewind(0);
  //ppath->flip_y(0,0);
  //typedef agg::conv_curve<agg::path_storage> curve_t;
  typedef agg::conv_curve<agg::conv_transform<agg::path_storage> > curve_t;
  curve_t curve(*ppath);

  //maxim's suggestions for cached scanlines
  agg::scanline_storage_aa8 scanlines;
  theRasterizer->reset();

  agg::int8u* fillCache = NULL;
  unsigned fillSize = 0;
  if (face.first) {
    theRasterizer->add_path(curve);
    agg::render_scanlines(*theRasterizer, *slineP8, scanlines);
    fillSize = scanlines.byte_size();
    fillCache = new agg::int8u[fillSize]; // or any container
    scanlines.serialize(fillCache);
  }
  
  agg::conv_stroke<curve_t> stroke(curve);
  stroke.width(gc.linewidth);
  stroke.line_cap(gc.cap);
  stroke.line_join(gc.join);
  theRasterizer->reset();
  theRasterizer->add_path(stroke);
  agg::render_scanlines(*theRasterizer, *slineP8, scanlines);
  unsigned strokeSize = scanlines.byte_size();
  agg::int8u* strokeCache = new agg::int8u[strokeSize]; // or any container
  scanlines.serialize(strokeCache);
  theRasterizer->reset_clipping();
  
  if (gc.cliprect==NULL) {
    rendererBase->reset_clipping(true);
  }
  else {
    int l = (int)(gc.cliprect[0]) ;
    int b = (int)(gc.cliprect[1]) ;
    int w = (int)(gc.cliprect[2]) ;
    int h = (int)(gc.cliprect[3]) ;
    rendererBase->clip_box(l, height-(b+h),l+w, height-b);
  }

  double thisx, thisy;
  for (size_t i=0; i<Nx; i++) {
    thisx = *(double *)(xa->data + i*xa->strides[0]);
    thisy = *(double *)(ya->data + i*ya->strides[0]);
    
    if (mpltransform->need_nonlinear_api())
      try {
  mpltransform->nonlinear_only_api(&thisx, &thisy);
      }
      catch(...) {
  continue;
      }
    
    xytrans.transform(&thisx, &thisy);

    thisy = heightd - thisy; //flipy

    thisx = (int)thisx + 0.5;
    thisy = (int)thisy + 0.5;
    
    agg::serialized_scanlines_adaptor_aa8 sa;
    agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl;
    
    if (face.first) {
      //render the fill
      sa.init(fillCache, fillSize, thisx, thisy);
      rendererAA->color(face.second);
      agg::render_scanlines(sa, sl, *rendererAA);
    }
    
    //render the stroke
    sa.init(strokeCache, strokeSize, thisx, thisy);
    rendererAA->color(gc.color);
    agg::render_scanlines(sa, sl, *rendererAA);
    
  } //for each marker

  Py_XDECREF(xa);
  Py_XDECREF(ya);
  
  if (face.first)
    delete fillCache;
  delete strokeCache;
  return Py::Object();
   
}