Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Plotting Utility #170

Open
wants to merge 59 commits into
base: develop
Choose a base branch
from

Conversation

jakobsandberg
Copy link
Contributor

In Summary:

  • Added two classes: Canvas, and Plot
    • Canvas is the parent class, and is use for creating the base image that a plot is built on
    • Plot is used for holding method/paramters for a plot, and physically drawing the plot on a canvas object

For an example of how this looks, try the following. Also, try to change the inputs for methods in order to get a feel for how they work (and let me know if things are not obvious).

$canvas = new Canvas();

$plot = $canvas->addPlot(function ($x) { return $x*sin($x/2); }, 0, 5);
$plot->grid(true);
$plot->xRange(0, 20);
$plot->yLabel("This is a working y-label");
$plot->xLabel("Time (seconds)");
$plot->title("Sample Title");
$plot->color("red");
$plot->thickness(5);

$canvas->save();

It would also help to read the descriptions for both classes, the description of the __construct() method for both classes, and the description of the save() method for Canvas.

@coveralls
Copy link

Coverage Status

Coverage decreased (-1.03%) to 98.973% when pulling 3e1bba4 on jakobsandberg:plots into 8494d06 on markrogoyski:develop.

@markrogoyski
Copy link
Owner

Hi Jakob,
Thanks for the pull request. This is really cool. I'm going to spend some time with it to get familiar with how it all works.

@jakobsandberg
Copy link
Contributor Author

Mark,

Sounds great, let me know what you think! Also let me know if you have any
questions.

Perhaps we should make a "plots" branch rather than have it in develop
(unless you think it is ready to be included in the next release).

I definitely think I/we need to figure out what saving/exporting types we
need. Much of this project was inspired by MatPlotLib, which simply saves a
file in a local directly. But as this is Python and not PHP, it might be
handy to have other options as well (printing directly on a page, etc.).

Jakob

On Thu, Oct 13, 2016 at 9:20 AM, Mark Rogoyski [email protected]
wrote:

Hi Jakob,
Thanks for the pull request. This is really cool. I'm going to spend some
time with it to get familiar with how it all works.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#170 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AOlwVT0YFp1zbDG3p-ot14kiCl2ubm8bks5qzlo1gaJpZM4KVuKZ
.

@markrogoyski
Copy link
Owner

Hi Jakob,

Cool stuff. I was able to follow your examples and get it to plot things. I can just basically give it a function and it plots it! Pretty simple and straightforward. I like it.

Now if you don't mind, let's talk about how you can make it even better.

I'd like to talk about design and role of the classes.

It makes sense that there is a Canvas, and you draw Plots on the canvas. And if you add the functionality to plot multiple Plots on a single canvas, that'd be cool and it works with the design.

One thing that seems a little confusing is that addPlot() returns a Plot, and then by calling methods on that object you are modifying the Plot within the Canvas object. Also, I noticed that Plot is a child class of Canvas, but it seems like these are different things with different roles.

Let me know what you think about this idea, taking into consideration the possibility of future functionality to have multiple Plots in a Canvas.

What if the Canvas object had the attributes for canvas size (width, height), x range, grid, title, and labels.
The Plot object then has the callback function, color and thickness.

If a Canvas can contain multiple Plots, they would have to share the same space and be plotted against the same x range. And it wouldn't make sense (I think) to have multiple overlapping titles and labels. So The Canvas controls that stuff and all Plots added to the Canvas conform to those attributes.

The Canvas would then draw the axes, labels, title, etc., and then when it called draw() on each Plot, it would pass in things like width in height as parameters to the Plot's draw method. I imagine the Canvas would have to ask each Plot for their min and max Y values and adjust the y-axis according to the max values.

In code, it would probably look something like this:

$canvas = new Canvas();
$canvas->size(320, 240);
$canvas->xRange(-10, 10);
$canvas->grid(true);
$canvas->yLabel('Y Label goes here');
$canvas->xLabel('X Label goes here');
$canvas->title('Graph Title');

$sin_plot = new Plot(function ($x) { return sin($x); });
$sin_plot->color('red');
$sin_plot->thickness(5);

$cos_plot = new Plot(function ($x) { return cos($x); });
$cos_plot->color('red');
$cos_plot->thickness(5);

$canvas->addPlot($sin_plot);
$canvas->addPlot($cos_plot);

$canvas->draw();

As a quick aside, it might be beneficial to add a fluent interface (Each setter returns $this) to make the initialization take less code:

$canvas = new Canvas();
$canvas->size(320, 240)
       ->xRange(-10, 10)
       ->grid(true)
       ->yLabel('Y Label goes here')
       ->xLabel('X Label goes here')
       ->title('Graph Title');

$sin_plot = new Plot(function ($x) { return sin($x); });
$sin_plot->color('red')
         ->thickness(5);

$cos_plot = new Plot(function ($x) { return cos($x); });
$cos_plot->color('red')
         ->thickness(5);

$canvas->addPlot($sin_plot)
       ->addPlot($cos_plot);

$canvas->draw();

Does this idea make sense? Let me know what you think. It's just an idea, and first I'd like to hear more about your design decisions and where you were thinking of going with it as well.

Some other minor improvements:

File type
I'd go with png rather than jpeg. Jpeg is best suited for photos. Png will produce a smaller file with less compression noise for these types of images. imagepng() looks like the function you want to use in place of imagejpeg().

File name
For the filename when you draw the actual image file, maybe have the user provide that when they instantiate the Canvas, and if they don't provide it, then maybe use PHP's functionality to use a system temporary file, and maybe have the Canvas' draw() or save() method return the filename.

Unit tests
The unit tests don't seem to work, and are not set to run as part of the test suite. Add Plots as a test suite in tests/phpunit.xml. Then see what needs to be updated to get the tests to run.

Random
In Plots, there are two variable names on lines 275 and 288 that have a character that doesn't seem to display.

In Canvas' save() method there is a header function being called. What is the purpose of that?

Btw, I've merged it into a feature branch plot. For any improvements you make, send the pull request to that branch until we decide it is ready to be merged into develop.

Thanks. And again, great stuff!

@jakobsandberg
Copy link
Contributor Author

Mark,

Thanks for taking to time to look through this and give such thorough
feedback.

I definitely understand many of the points you are making. Let me spend a
bit of time to think about the design/classes and I'll get back to you then.

One quick note: In developing the class hierarchy, I considered three types
of "images':

  1. One plot of a single function (the current implementation)
  2. One plot of multiple functions (as you've described here)
  3. Multiple plots of one or more functions (see this MatPlotLib example:
    http://matplotlib.org/examples/pylab_examples/pythonic_matplotlib.html)

The reason I tried to keep Canvas as minimal as possible is to accommodate
all three types above. (1) and (2) are quite similar, as (2) just requires
drawing additional lines/curves. However, (3) means that we could have
multiple titles and axis labels (up to one for each graph). This is the
reason I designed it such that those parameters are in Plot rather than
Canvas. However, I'm not married to the idea of implementing (3) this way,
and it's possible we can implement it in a more efficient way or simply
build just (1) and (2).

Jakob

On Thu, Oct 13, 2016 at 11:51 PM, Mark Rogoyski [email protected]
wrote:

Hi Jakob,

Cool stuff. I was able to follow your examples and get it to plot things.
I can just basically give it a function and it plots it! Pretty simple and
straightforward. I like it.

Now if you don't mind, let's talk about how you can make it even better.

I'd like to talk about design and role of the classes.

It makes sense that there is a Canvas, and you draw Plots on the canvas.
And if you add the functionality to plot multiple Plots on a single canvas,
that'd be cool and it works with the design.

One thing that seems a little confusing is that addPlot() returns a Plot,
and then by calling methods on that object you are modifying the Plot
within the Canvas object. Also, I noticed that Plot is a child class of
Canvas, but it seems like these are different things with different roles.

Let me know what you think about this idea, taking into consideration the
possibility of future functionality to have multiple Plots in a Canvas.

What if the Canvas object had the attributes for canvas size (width,
height), x range, grid, title, and labels.
The Plot object then has the callback function, color and thickness.

If a Canvas can contain multiple Plots, they would have to share the same
space and be plotted against the same x range. And it wouldn't make sense
(I think) to have multiple overlapping titles and labels. So The Canvas
controls that stuff and all Plots added to the Canvas conform to those
attributes.

The Canvas would then draw the axes, labels, title, etc., and then when it
called draw() on each Plot, it would pass in things like width in height as
parameters to the Plot's draw method. I imagine the Canvas would have to
ask each Plot for their min and max Y values and adjust the y-axis
according to the max values.

In code, it would probably look something like this:

$canvas = new Canvas();$canvas->size(320, 240);$canvas->xRange(-10, 10);$canvas->grid(true);$canvas->yLabel('Y Label goes here');$canvas->xLabel('X Label goes here');$canvas->title('Graph Title');$sin_plot = new Plot(function ($x) { return sin($x); });$sin_plot->color('red');$sin_plot->thickness(5);$cos_plot = new Plot(function ($x) { return cos($x); });$cos_plot->color('red');$cos_plot->thickness(5);$canvas->addPlot($sin_plot);$canvas->addPlot($cos_plot);$canvas->draw();

As a quick aside, it might be beneficial to add a fluent interface (Each
setter returns $this) to make the initialization take less code:

$canvas = new Canvas();$canvas->size(320, 240) ->xRange(-10, 10) ->grid(true) ->yLabel('Y Label goes here') ->xLabel('X Label goes here') ->title('Graph Title');$sin_plot = new Plot(function ($x) { return sin($x); });$sin_plot->color('red') ->thickness(5);$cos_plot = new Plot(function ($x) { return cos($x); });$cos_plot->color('red') ->thickness(5);$canvas->addPlot($sin_plot) ->addPlot($cos_plot);$canvas->draw();

Does this idea make sense? Let me know what you think. It's just an idea,
and first I'd like to hear more about your design decisions and where you
were thinking of going with it as well.

Some other minor improvements:

File type
I'd go with png rather than jpeg. Jpeg is best suited for photos. Png will
produce a smaller file with less compression noise for these types of
images. imagepng() looks like the function you want to use in place of
imagejpeg().

File name
For the filename when you draw the actual image file, maybe have the user
provide that when they instantiate the Canvas, and if they don't provide
it, then maybe use PHP's functionality to use a system temporary file, and
maybe have the Canvas' draw() or save() method return the filename.

Unit tests
The unit tests don't seem to work, and are not set to run as part of the
test suite. Add Plots as a test suite in tests/phpunit.xml. Then see what
needs to be updated to get the tests to run.

Random
In Plots, there are two variable names on lines 275 and 288 that have a
character that doesn't seem to display.

In Canvas' save() method there is a header function being called. What is
the purpose of that?

Btw, I've merged it into a feature branch plot. For any improvements you
make, send the pull request to that branch until we decide it is ready to
be merged into develop.

Thanks. And again, great stuff!


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#170 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AOlwVTEaLVg8RIPaKxYlRx6myiRUwwTuks5qzyZjgaJpZM4KVuKZ
.

@markrogoyski
Copy link
Owner

OK interesting. I didn't consider multiple independent plots drawn together side by side.

@jakobsandberg
Copy link
Contributor Author

Mark,

I've been thinking more about your comments, and I definitely agree with a lot of your points!

First, as you recommended, it makes sense to pass in the canvas height and width as an argument in the draw() method, rather than the current solution of making Plot a parent of Canvas.

Also, I like your approach of defining $sin_plot as a Plot object first, and then passing this as an argument into addPlot(), rather than having addPlot() return a Plot object.

In terms of haveing multiple independent plots, I definitely think this would be an important feature to consider. I think it'd be useful so a user can generate related (but still distinct) plots without requiring them to open and image editor to put them all together (if we only generate single plots).

For example, if we wanted to make an image that shows "Contributions to Math PHP", we could have it contain three subplots: Commits vs. Time, Additions vs. Time, and Deletions vs. Time. In this example, it makes sense to give each subplot its own set of parameters (axis labels, title, etc.). However, also in this example, it could also make sense to give the canvas itself a title, for example, "Contributions to Math PHP". Therefore, I feel like we should give the Canvas object a title parameter, but still keep the other parameters tied to a Plot object.

Consider how this example could look:

// Define example data functions
$fun_commits = function ($x) { return ... ;};
$fun_additions = function ($x) { return ... ;};
$fun_deletes = function ($x) { return ... ;};

$canvas = new Canvas();
$canvas->size(900, 300)
       ->title('Contributions to Math PHP');

$commits = new Plot();
$commits->plot($fun_commits)
        ->color('red')
        ->xRange(0, 10)
        ->grid(true)
        ->title('Daily Commits');
        ->yLabel('Number of Commits')
        ->xLabel('Days')

$additions = new Plot();
$additions->plot($fun_additions)
          ->color('blue')
          ->xRange(0, 10)
          ->grid(true)
          ->title('Daily Additions');
          ->yLabel('Number of Additions')
          ->xLabel('Days')

$deletions = new Plot();
$deletions->plot($fun_deletions)
          ->color('green')
          ->xRange(0, 10)
          ->grid(true)
          ->title('Daily Deletions');
          ->yLabel('Number of Deletions')
          ->xLabel('Days')

$canvas->addSubPlot($commits, 3, 1, 1)
       ->addSubPlot($additions, 3, 1, 2)
       ->addSubPlot($deletions, 3, 1, 3)

$canvas->draw();

In the above example, addSubPlot($commits, 3, 1, 1) means that we are adding the plot $commits to a canvas with 3 x 1 subplots (3 "columns" and 1 "row", i.e. three plots side-by-side) and the $commits plot should be in position 1 (the first) in this 3 x 1 arrangement. Hence, the next two lines addSubPlot($additions, 3, 1, 2) and addSubPlot($deletions, 3, 1, 3) add plots to the 2nd and 3rd position, respectively.

  • An alternative approach would be to give the Canvas object a parameter that represents the 3 x 1 arrangement. This means that we wouldn't need to declare the arrangement in the addSubPlot() method.
  • With the above approach, we can get rid of addSubPlot() altogether and just use a single method addPlot()for canvases with both a single plot and those with multiple plots. To account for a canvas of a single plot, we can give the "arrangement" parameter a value of 1 x 1 in the constructor for Canvas. We can also make it so the parameter of AddPlot (which is replacing addSubPlot()) which handles the position in the 3 x 1 arrangement has a default value of 1.

Using the above approach, we could simply the example:

// Define example data functions
...

$canvas = new Canvas(); // produces a defaul width, height, and arrangment (1 x 1)
$canvas->size(900, 300)
       ->title('Contributions to Math PHP');
       ->arrangement(3, 1); // update the arrangment to account for three plots

$commits = new Plot();
// Set parameters ...

$additions = new Plot();
// Set parameters ...

$deletions = new Plot();
// Set parameters ...

$canvas->addPlot($commits, 1)
       ->addPlot($additions, 2)
       ->addPlot($deletions, 3)

$canvas->draw();

And to demonstrate how this would look for a single plot, consider just plotting commits:

$fun_commits = function ($x) { return ... ;};

$canvas = new Canvas(); // produces a defaul width, height, and arrangment (1 x 1)
$canvas->size(300, 300)

$commits = new Plot();
// Set parameters ...

$canvas->addPlot($commits) // omit the position because it will default to 1

$canvas->draw();

What do you think of this approach?

To make an image that contains a single plot with multiple functions, we would simply use the plot() method on a Plot object multiple times (rather than just once, as in the examples above). Your comment on putting most settings in Canvas vs. Plot inspired this solution, and it is actual quite simple. A single plot should have its own labels, title, grid, etc. but it should be able to have any number of "plots"/functions. Thus, we can simply run plot() additional times, as we don't need to adjust the other parameters (as you demonstrated in your example, where you put those paramters in the Canvas object).

In this way, we could redo your example as follows:

$canvas = new Canvas();
$canvas->size(320, 240);

$plot = new Plot();
$plot->plot(function ($x) { return sin($x); })
     ->plot(function ($x) { return cos($x); })
     ->color('red')
     ->thickness(5);
     ->xRange(-10, 10)
     ->grid(true)
     ->yLabel('Y Label goes here')
     ->xLabel('X Label goes here')
     ->title('Graph Title');

$canvas->addPlot($plot)

$canvas->draw();

Notice that I am using the plot() method three times, and otherwise it is the same as before.

We can extend this even further by building another class e.g. Curve which contains the information specific to one function on a single plot (e.g. the function itself, the thickness, and the color. Thus, I can rewrite your example as follows, except give the sin and cos curve different color and thickness:

$canvas = new Canvas();
$canvas->size(320, 240);

$sin = new Curve();
$sin->function(function ($x) { return sin($x); })
    ->color('red')
    ->thickness(5);

$cos = new Curve();
$cos->function(function ($x) { return cos($x); })
    ->color('blue')
    ->thickness(10);

$plot = new Plot();
$plot->plot($sin)
     ->plot($cos)
     ->xRange(-10, 10)
     ->grid(true)
     ->yLabel('Y Label goes here')
     ->xLabel('X Label goes here')
     ->title('Graph Title');

$canvas->addPlot($plot)

$canvas->draw();

Let me know if any of this is consfusing, or if it seems I'm adding any unecessary level of complexity. I also think it'd be useful to make a logic and consistent terminology, as Canvas, Plot, Curve, etc. are ambiguous and may not be the best options.

With regards to your other comments (file type, file name, unit tests, and random), I completely agree with all your points.

Jakob

@markrogoyski
Copy link
Owner

Hi Jakob,

Thanks for the detailed explanation of how you might organize everything.

For the option of doing multiple subplots, I think I like the second simpler method of declaring the arrangement in the canvas and then just adding the plots.

In the Plot object, I think it would be clearer if you changed the proposed plot() method to be addFunction() or addCurve() or something.

I think if you want to have multiple functions on a single plot and be able to give them different colors and thicknesses, then it does make sense to have those defined individually. I'm not sure if Curve, or Function, or something else is a better name for that class, but that is just a detail you can change later. I think you have the right approach, regardless of what the name ends up being.

Thanks for putting a lot of thought into the design.

@jakobsandberg
Copy link
Contributor Author

Sounds good! I'll use this feedback to make an update. I'll give a bit more thought to appropriate terminology, but as you said, we can adjust this later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants