Demetri Pananos
Demetri Pananos

Reputation: 7404

How can I create a figure with optimal resolution for printing?

I'm rather fond of The Logistic Map' Period Doubling Bifurcation and would like to print it on a canvas.

I can create the plot in python, but need some help preparing figure properties so that it has suitable resolution to be printed. My code right ow produces some jagged lines.

Here is my code:

import numpy as np import matplotlib.pyplot as plt

# overall image properties
width, height, dpi = 2560, 1440, 96
picture_background = 'white'
aspect_ratio = width / height


plt.close('all')
R = np.linspace(3.5,4,5001)

fig = plt.figure(figsize=(width / dpi, height / dpi), frameon=False)
ylim = -0.1,1.1
ax = plt.Axes(fig, [0, 0, 1, 1], xlim = (3.4,4))
ax.set_axis_off()
fig.add_axes(ax)

for r in R:

    x = np.zeros(5001)

    x[0] = 0.1

    for i in range(1,len(x)):

        x[i] = r*x[i-1]*(1-x[i-1])


    ax.plot(r*np.ones(2500),x[-2500:],marker = '.', markersize= 0.01,color = 'grey', linestyle = 'none')

plt.show()  
plt.savefig('figure.eps', dpi=dpi, bbox_inches=0, pad_inches=0, facecolor=picture_background)

Here is what the code produces:

enter image description here

As you can see, some of the lines to the far left of the plot are rather jagged.

How can I create this figure so that the resolution is suitable to be printed on a variety of frame dimensions?

Upvotes: 0

Views: 290

Answers (1)

tacaswell
tacaswell

Reputation: 87376

I think the source of the jaggies is underlying pixel size + that you are drawing this using very small 'point' markers. The pixels that the line are going through are getting fully saturated so you get the 'jaggy'.

A somewhat better way to plot this data is to do the binning ahead of time and then have mpl plot a heat map:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
plt.ion()

width, height, dpi = 2560 / 2, 1440 / 2, 96  # cut to so SO will let me upload result

aspect_ratio = width / height

fig, ax = plt.subplots(figsize=(width / dpi, height / dpi), frameon=False,
                       tight_layout=True)
ylim = -0.1, 1.1
ax.axis('off')


# make heatmap at double resolution
accumulator = np.zeros((height, width), dtype=np.uint64)

burn_in_count = 25000
N = 25000

R = np.linspace(3.5, 4, width)
x = 0.1 * np.ones_like(R)
row_indx = np.arange(len(R), dtype='uint')
# do all of the r values in parallel
for j in range(burn_in_count):
    x = R * x * (1 - x)

for j in range(N):
    x = R * x * (1 - x)
    col_indx = (height * x).astype('int')
    accumulator[col_indx, row_indx] += 1

im = ax.imshow(accumulator, cmap='gray_r',
               norm=mcolors.LogNorm(), interpolation='none')

log scaled logistic map

Note that this is log-scaled, if you just want to see what pixels are hit

use

im = ax.imshow(accumulator>0, cmap='gray_r', interpolation='nearest')

logistic map 'hits'

but these still have issues of the jaggies and (possibly worse) sometimes the narrow lines get aliased out.

This is the sort of problem that datashader or rasterized scatter is intended to solve by re-binning the data at draw time in an intelligent way (see this PR for a prototype datashader/mpl integartion). Both of those are still prototype/proof-of-concept, but usable.

http://matplotlib.org/examples/event_handling/viewlims.html which re-compute the Mandelbrot set on zoom might also be of interest to you.

Upvotes: 2

Related Questions