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

Images as Axis Labels #759

Merged
merged 15 commits into from
Feb 11, 2021
Merged

Images as Axis Labels #759

merged 15 commits into from
Feb 11, 2021

Conversation

bclehmann
Copy link
Member

New Contributors:
please review CONTRIBUTING.md

Purpose:
#446 #716
Allows bitmaps to be used as axis labels.

New Functionality:
Describe what this pull request does using code and/or images.
image

Code:

plt.XAxis.ImageLabel(new Bitmap("Images/theta.bmp"));
plt.XAxis2.ImageLabel(new Bitmap("Images/theta.bmp"));
plt.YAxis.ImageLabel(new Bitmap("Images/d_theta_dt.bmp"));
plt.YAxis2.ImageLabel(new Bitmap("Images/d_theta_dt.bmp"));

I had wanted to use a LaTeX package for the demo but as it turns out there isn't a Nuget package which does LaTeX rendering that can easily interop with System.Drawing, so the images are shipped with the demo, which isn't ideal.

One may also want to remove the labels on XAxis2 and YAxis2, as they were only added for debugging purposes.

@bclehmann
Copy link
Member Author

I'm also wondering if this feature would make sense for the title as well, such as in this picture from matplotlib:

image

@bclehmann
Copy link
Member Author

I believe the reason it fails tests on Linux and Mac is because they have a different bitmap format than Windows (the header is different) which means that this test will only pass on Windows. I had hoped using the resources instead of the filesystem would help but it turns out the same problem persists.

So I guess I can either find a way to specify a cross-platform bitmap to pass into System.Drawing, for example by stripping out the whole header and just specifying the pixels in a byte[], similar to how heatmaps are done. Or one can draw the bitmap with System.Drawing at runtime. Fortunately I chose a pretty simple demo so the second option is not that much work.

I think either way the code for generating the image shouldn't be shown as part of the demo, as a) it'd be confusing and b) it's not relevant.

@StendProg
Copy link
Contributor

StendProg commented Feb 8, 2021

Hi @bclehmann,
You shoud try different bitmaps formats.
Say png, or bmp with different color maps.
I think problem is on concrete .bmp format you use, just try to change something.

@bclehmann
Copy link
Member Author

You shoud try different bitmaps formats.
Say png, or bmp with different color maps.
I think problem is on concrete .bmp format you use, just try to change something.

Yeah, that's a lot easier. For some reason I got it in my head that the Bitmap class wouldn't support importing from a different file format.

@swharden
Copy link
Member

I added a jpeg example, and also demonstrated how transparency in PNGs is respected. This looks great! Thanks @bclehmann

Regarding the matplotlib-style example, I think the example used in this PR is better because it's simpler. I don't want this recipe be to flashy, because at a glance it looks like this library supports LaTeX but fundamentally it doesn't, so I think a subtle recipe is the way to go here 👍

break the Render method into multiple functions

use switch expressions where possible

add XML documentation to public fields
@swharden
Copy link
Member

@bclehmann I refactored the AxisLabel model but left all the logic intact. I'm a bit confused about the two 3s and one 4 in the image offset calculations though... Is the intent to pad images by a fraction of how large they are?

private void RenderImageLabel(Graphics gfx, float x, float y, int rotation)
{
// TODO: use ImagePadding instead of fractional padding
float xOffset = Edge switch
{
Edge.Left => 0,
Edge.Right => -3 * ImageLabel.Width / 2,
Edge.Bottom => -ImageLabel.Width / 2,
Edge.Top => -ImageLabel.Width / 2,
_ => throw new NotImplementedException()
};
float yOffset = Edge switch
{
Edge.Left => -ImageLabel.Height / 2,
Edge.Right => -ImageLabel.Height / 2,
Edge.Bottom => -3 * ImageLabel.Height / 2,
Edge.Top => ImageLabel.Height / 4,
_ => throw new NotImplementedException()
};
gfx.TranslateTransform(x, y);
gfx.DrawImage(ImageLabel, xOffset, yOffset);
gfx.ResetTransform();
}

I added an ImagePadding field. Depending on your answer to the question above, you or I can modify that method to use it instead. I think it makes sense to let the user define the padding in pixels (with a default of 5 or something)

/// <summary>
/// Padding (in pixels) around the image label
/// </summary>
public float ImagePadding = 5;

Also, do you think images should be rotated automatically for vertical axes like text labels are? I guess a bool could let the user control whether this happens or not. It's a pretty niche feature, but while we're in here maybe it's a good time to add it in.

Interested in your thoughts!

@bclehmann
Copy link
Member Author

I didn't bring up that matplotlib example because I wanted to steer the demo that way, I brought it up because I noticed that while this PR allows images as axis labels it doesn't allow them for the plot title.

@bclehmann
Copy link
Member Author

Is the intent to pad images by a fraction of how large they are?

The numbers seem arbitrary but this is for positioning, if you replaced the fraction with 1/2 it would clip the plotting window. Image padding in pixels may make sense for aesthetic reasons (although I might suggest having separate padding for x and y).

Also, do you think images should be rotated automatically for vertical axes like text labels are? I guess a bool could let the user control whether this happens or not. It's a pretty niche feature, but while we're in here maybe it's a good time to add it in.

I believe it's confusing if images are rotated by default, where it's not confusing for text. Text doesn't prescribe how it should be layouted other than the order of the characters. Images have a top and a bottom and changing those to left and right is disorienting. For example, it would be confusing to render the d_theta/dt sideways. Now it may be a decent idea as an optional setting, although I can't say that I can think of a situation where the user would want their image rotated, and I think if they wanted to draw it sideways they'd rotate it themselves.

This commit fixes a bug where setting the title color accidentally sets the top axis tick label color
@swharden
Copy link
Member

Sounds good! I implemented options for custom padding (in pixels) and give the user the ability to customize it separately for each side of the bitmap.

/// <summary>
/// Display a custom image as the axis label instead of text
/// </summary>
/// <param name="img">The image to display where the label should go</param>
/// <param name="padInside">pixels of padding between the inner image edge and the data area</param>
/// <param name="padOutside">pixels of padding between the outer image edge and the figure edge</param>
public void ImageLabel(Bitmap img, float padInside = 5, float padOutside = 5)

Good points about rotation - It's so easy to use System.Drawing to rotate a bitmap anyway, if a user wants a rotated image axis they have an easy way to do it themselves.

This PR is looking pretty good! Thanks for all your worn on this @bclehmann! 👍

@swharden swharden merged commit d205530 into ScottPlot:master Feb 11, 2021
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