I see that set_xscale accepts a base parameter, but I also want to scale with a factor; i.e. if the base is 4 and the factor is 10, then:
40, 160, 640, ...
Also, the documentation says that the sub-grid values represented by subsx should be integers, but I will want floating-point values.
What is the cleanest way to do this?
I'm not aware of any built-in method to apply a scaling factor after the exponent, but you could create a custom tick locator and formatter by subclassing matplotlib.ticker.LogLocator and matplotlib.ticker.LogFormatter.
Here's a fairly quick-and-dirty hack that does what you're looking for:
from matplotlib import pyplot as plt
from matplotlib.ticker import LogLocator, LogFormatter, ScalarFormatter, \
is_close_to_int, nearest_long
import numpy as np
import math
class ScaledLogLocator(LogLocator):
def __init__(self, *args, scale=10.0, **kwargs):
self._scale = scale
LogLocator.__init__(self, *args, **kwargs)
def view_limits(self, vmin, vmax):
s = self._scale
vmin, vmax = LogLocator.view_limits(self, vmin / s, vmax / s)
return s * vmin, s * vmax
def tick_values(self, vmin, vmax):
s = self._scale
locs = LogLocator.tick_values(self, vmin / s, vmax / s)
return s * locs
class ScaledLogFormatter(LogFormatter):
def __init__(self, *args, scale=10.0, **kwargs):
self._scale = scale
LogFormatter.__init__(self, *args, **kwargs)
def __call__(self, x, pos=None):
b = self._base
s = self._scale
# only label the decades
if x == 0:
return '$\mathdefault{0}$'
fx = math.log(abs(x / s)) / math.log(b)
is_decade = is_close_to_int(fx)
sign_string = '-' if x < 0 else ''
# use string formatting of the base if it is not an integer
if b % 1 == 0.0:
base = '%d' % b
else:
base = '%s' % b
scale = '%d' % s
if not is_decade and self.labelOnlyBase:
return ''
elif not is_decade:
return ('$\mathdefault{%s%s\times%s^{%.2f}}$'
% (sign_string, scale, base, fx))
else:
return (r'$%s%s\times%s^{%d}$'
% (sign_string, scale, base, nearest_long(fx)))
For example:
fig, ax = plt.subplots(1, 1)
x = np.arange(1000)
y = np.random.randn(1000)
ax.plot(x, y)
ax.set_xscale('log')
subs = np.linspace(0, 1, 10)
majloc = ScaledLogLocator(scale=10, base=4)
minloc = ScaledLogLocator(scale=10, base=4, subs=subs)
fmt = ScaledLogFormatter(scale=10, base=4)
ax.xaxis.set_major_locator(majloc)
ax.xaxis.set_minor_locator(minloc)
ax.xaxis.set_major_formatter(fmt)
ax.grid(True)
# show the same tick locations with non-exponential labels
ax2 = ax.twiny()
ax2.set_xscale('log')
ax2.set_xlim(*ax.get_xlim())
fmt2 = ScalarFormatter()
ax2.xaxis.set_major_locator(majloc)
ax2.xaxis.set_minor_locator(minloc)
ax2.xaxis.set_major_formatter(fmt2)
Related
Currently my code returns no errors, and generates a weird looking set of points, totally not a Mandelbulb. I've looked over the formulas multiple times and everything seems right, but I could definitely be overlooking something. Any ideas? Just to note, I'm quite inexperienced with Python (I work in Java a lot though so I get the main ideas). Here's my code:
import bpy
import numpy as np
import math
def mandelbulb(x, y, z, iterations):
c = x + y*1j + z*1j
z = c
r = 0
for i in range(iterations):
r2 = x*x + y*y + z*z
if r2 > 2:
return math.sqrt(r2)
theta = math.atan2(math.sqrt(x*x + y*y), z)
phi = math.atan2(y, x)
r = math.sqrt(abs(x*x + y*y + z*z))
x = r*r*r*r * math.cos(4*theta) * math.cos(4*phi) + c.real
y = r*r*r*r * math.cos(4*theta) * math.sin(4*phi) + c.imag
z = r*r*r*r * math.sin(4*theta)
return 0
def generate_mesh(size, iterations):
vertices = []
for x in np.linspace(-2, 2, size):
for y in np.linspace(-2, 2, size):
for z in np.linspace(-2, 2, size):
value = mandelbulb(x, y, z, iterations)
if value >= 2:
vertices.append((x, y, z))
return vertices, []
def create_mesh_object(vertices, faces, name):
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(vertices, [], faces)
mesh.update()
object = bpy.data.objects.new(name, mesh)
bpy.context.collection.objects.link(object)
def execute(size, iterations):
vertices, faces = generate_mesh(size, iterations)
create_mesh_object(vertices, faces, "Mandelbulb")
class MandelbulbOperator(bpy.types.Operator):
bl_idname = "object.mandelbulb_operator"
bl_label = "Mandelbulb Operator"
bl_options = {'REGISTER', 'UNDO'}
size: bpy.props.IntProperty(
name="Size",
default=32,
min=1,
max=256,
step=1
)
iterations: bpy.props.IntProperty(
name="Iterations",
default=64,
min=1,
max=512,
step=1
)
def execute(self, context):
execute(self.size, self.iterations)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.label(text="Create a 3D Mandelbulb")
layout.prop(self, "size")
layout.prop(self, "iterations")
def register():
bpy.utils.register_class(MandelbulbOperator)
def unregister():
bpy.utils.unregister_class(MandelbulbOperator)
if __name__ == "__main__":
register()
I tried messing with values, such as size and iterations but nothing seemed to change the look of the result, and changing the iterations straight up did nothing. I've also tried using variations on the main Mandelbulb formula but to no avail. Any suggestions are welcome.
From the following objected oriented class for polynomial, we can get a symbolic expression of the polynomial.
#Classes and Object Oriented Programming
import numpy as np
import matplotlib.pyplot as plt
class Polynomial(object):
def __init__(self, roots, leading_term):
self.roots = roots
self.leading_term = leading_term
self.order = len(roots)
def __repr__(self):
string = str(self.leading_term)
for root in self.roots:
if root == 0:
string = string + "x"
elif root > 0:
string = string + "(x - {})".format(root)
else:
string = string + "(x + {})".format(-root)
return string
def __mul__(self, other):
roots = self.roots + other.roots
leading_term = self.leading_term * other.leading_term
return Polynomial(roots, leading_term)
def explain_to(self, caller):
print("Hello, {}. {}.".format(caller,self.explanation))
print("My roots are {}.".format(self.roots))
# Creating a symbolic expression of the polynomial
>>> p = Polynomial((1,2,-3),2)
>>> print(p)
2(x - 1)(x - 2)(x + 3)
>>> q = Polynomial((1,1,0,-2), -1)
>>> print(q)
-1(x - 1)(x - 1)x(x + 2)
For plotting purpose, I am willing to avoid the following approach and directly want to covert the symbolic expressipon into a collable expression in the polynomial class itself.
# Plotting the polynomials
def p(x):
return 2*(x-1)*(x-2)*(x+3)
def q(x):
return -1*(x-1)*(x-1)*x*(x+2)
x = np.linspace(-5,5,100)
plt.plot(x, p(x),'r--')
plt.plot(x, q(x), 'g--')
If I get you right, you want something like this using __call__.
Here is how to use __call__ in your class:
import numpy as np
import matplotlib.pyplot as plt
class Polynomial(object):
def __init__(self, roots, leading_term):
self.roots = roots
self.leading_term = leading_term
self.order = len(roots)
def __repr__(self):
string = str(self.leading_term)
for root in self.roots:
if root == 0:
string = string + "x"
elif root > 0:
string = string + "(x - {})".format(root)
else:
string = string + "(x + {})".format(-root)
return string
def __call__(self,x):
res = 1
res *= self.leading_term
for root in self.roots:
res *= (x - root)
return res
def __mul__(self, other):
roots = self.roots + other.roots
leading_term = self.leading_term * other.leading_term
return Polynomial(roots, leading_term)
def explain_to(self, caller):
print("Hello, {}. {}.".format(caller,self.explanation))
print("My roots are {}.".format(self.roots))
It works like this:
>>> p = Polynomial((1,2,-3),2)
>>> print(p)
2(x - 1)(x - 2)(x + 3)
>>> print(p(0), p(1), p(2), p(3) ,p(4))
12 0 0 24 84
>>> x = np.linspace(-4, 4, 100)
>>> fig = plt.figure()
>>> plt.plot(x, p(x), '-')
Here is a solution that relies on sympy. Note that instead of using lambdify, coefficients are extracted and passed into np.poly1d. As a result, the callable function is vectorized and should be fairly efficient.
import numpy as np
from sympy import Poly, sympify
from matplotlib import pyplot as plt
poly_string = "2*x**3 + 4"
coeffs = Poly(sympify(poly_string)).all_coeffs()
p_callable = np.poly1d(np.array(coeffs).astype(np.float))
xs = np.linspace(-10,10,100)
ys = p_callable(xs)
plt.plot(xs, ys)
You can do that purely in numpy (np)
p = 2*np.poly([1,2,-3])
q = -1*np.poly([1,1,0,-2])
x = np.linspace(-5,5,100)
plt.plot(x, np.polyval(p,x),'r--')
plt.plot(x, np.polyval(q,x), 'g--')
plt.grid(); plt.legend("pq"); plt.show()
I'm trying to scale the x axis of a plot with math.log(1+x) instead of the usual 'log' scale option, and I've looked over some of the custom scaling examples but I can't get mine to work! Here's my MWE:
import matplotlib.pyplot as plt
import numpy as np
import math
from matplotlib.ticker import FormatStrFormatter
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
class CustomScale(mscale.ScaleBase):
name = 'custom'
def __init__(self, axis, **kwargs):
mscale.ScaleBase.__init__(self)
self.thresh = None #thresh
def get_transform(self):
return self.CustomTransform(self.thresh)
def set_default_locators_and_formatters(self, axis):
pass
class CustomTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def __init__(self, thresh):
mtransforms.Transform.__init__(self)
self.thresh = thresh
def transform_non_affine(self, a):
return math.log(1+a)
def inverted(self):
return CustomScale.InvertedCustomTransform(self.thresh)
class InvertedCustomTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def __init__(self, thresh):
mtransforms.Transform.__init__(self)
self.thresh = thresh
def transform_non_affine(self, a):
return math.log(1+a)
def inverted(self):
return CustomScale.CustomTransform(self.thresh)
# Now that the Scale class has been defined, it must be registered so
# that ``matplotlib`` can find it.
mscale.register_scale(CustomScale)
z = [0,0.1,0.3,0.9,1,2,5]
thick = [20,40,20,60,37,32,21]
fig = plt.figure(figsize=(8,5))
ax1 = fig.add_subplot(111)
ax1.plot(z, thick, marker='o', linewidth=2, c='k')
plt.xlabel(r'$\rm{redshift}$', size=16)
plt.ylabel(r'$\rm{thickness\ (kpc)}$', size=16)
plt.gca().set_xscale('custom')
plt.show()
The scale consists of two Transform classes, each of which needs to provide a transform_non_affine method. One class needs to transform from data to display coordinates, which would be log(a+1), the other is the inverse and needs to transform from display to data coordinates, which would in this case be exp(a)-1.
Those methods need to handle numpy arrays, so they should use the respective numpy functions instead of those from the math package.
class CustomTransform(mtransforms.Transform):
....
def transform_non_affine(self, a):
return np.log(1+a)
class InvertedCustomTransform(mtransforms.Transform):
....
def transform_non_affine(self, a):
return np.exp(a)-1
There's no need to define classes yourself even the answer from #ImportanceOfBeingErnest does work.
You can use either FunctionScale or FunctionScaleLog to do this in one line. Take the FunctionScaleLog as example:
plt.gca().set_xscale("functionlog", functions=[lambda x: x + 1, lambda x: x - 1])
And with your full code:
import matplotlib.pyplot as plt
import numpy as np
z = [0, 0.1, 0.3, 0.9, 1, 2, 5]
thick = [20, 40, 20, 60, 37, 32, 21]
fig = plt.figure(figsize=(8, 5))
ax1 = fig.add_subplot(111)
ax1.plot(z, thick, marker="o", linewidth=2, c="k")
plt.xlabel(r"$\rm{redshift}$", size=16)
plt.ylabel(r"$\rm{thickness\ (kpc)}$", size=16)
# Below is my code
plt.gca().set_xscale("functionlog", functions=[lambda x: x + 1, lambda x: x - 1])
plt.gca().set_xticks(np.arange(0, 6))
plt.gca().set_xticklabels(np.arange(0, 6))
And the result is:
UPDATED:
I would like to plot real time y values generated randomly from Random_Generation_List.py. I have imported the python file and have them in the same folder. The points are only being printed and show only a vertical line on the graph. How do I fix this to make the points plot onto the graph in real time? Like a point every 0.001?
Random_Generation_List:
import random
import threading
def main():
for count in range(12000):
y = random.randint(0,1000)
print(y)
def getvalues():
return [random.randint(0,1000) for count in range(12000)]
def coordinate():
threading.Timer(0.0001, coordinate).start ()
coordinate()
main()
Real_Time_Graph
import time
from collections import deque
from matplotlib import pyplot as plt
from matplotlib import style
import Random_Generation_List
start = time.time()
class RealtimePlot:
def __init__(self, axes, max_entries = 100):
self.axis_x = deque(maxlen=max_entries)
self.axis_y = deque(maxlen=max_entries)
self.axes = axes
self.max_entries = max_entries
self.lineplot, = axes.plot([], [], "g-")
self.axes.set_autoscaley_on(True)
def add(self, x, y):
self.axis_x.append(x)
self.axis_y.append(y)
self.lineplot.set_data(self.axis_x, self.axis_y)
self.axes.set_xlim(self.axis_x[0], self.axis_x[-1] + 1e-15)
self.axes.relim(); self.axes.autoscale_view() # rescale the y-axis
def animate(self, figure, callback, interval = 50):
def wrapper(frame_index):
self.add(*callback(frame_index))
self.axes.relim(); self.axes.autoscale_view() # rescale the y-axis
return self.lineplot
def main():
style.use('dark_background')
fig, axes = plt.subplots()
display = RealtimePlot(axes)
axes.set_xlabel("Seconds")
axes.set_ylabel("Amplitude")
values = Random_Generation_List.getvalues()
print(values)
while True:
display.add(time.time() - start, values)
plt.pause(0.001)
display.animate(fig, lambda frame_index: (time.time() - start, values))
plt.show()
if __name__ == "__main__": main()
Error Message:
raise RuntimeError('xdata and ydata must be the same length')
RuntimeError: xdata and ydata must be the same length
I'm building a Python tool for visualizing data structures in 3D. The code below is the full program, it's even set up to run a default test model with some random data; you just need numpy and matplotlib. Basically, you declare a Node, connect it to other Nodes, and it makes pretty 3D networks. I'd like to be able to call switchNode() and have it flip the color of a node between black and white. With the way it works right now, every time a Node is instantiated, the plot is added to with another data point. I'm not familiar enough with matplotlib's animation tools to know the best way of doing this (my attempt at following the example from another post is commented out on line 83, and hoped someone could offer me some tips. Thanks!!
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.animation as anim
# user-defined variables
debug = False
axisOn = False
labelsOn = True
maxNodes = 20
edgeColor = 'b'
dottedLine = ':'
darkGrey = '#191919'
lightGrey = '#a3a3a3'
plotBackgroundColor = lightGrey
fontColor = darkGrey
gridColor = 'k'
numFrames = 200
# global variables
fig = None
hTable = []
ax = None
numNodes = 0
# initialize plot
def initPlot():
global ax, fontColor, fig
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d', axisbg=plotBackgroundColor)
if axisOn == False:
ax.set_axis_off()
else:
ax.set_axis_on()
fontColor = darkGrey
# gives n random float values between vmin and vmax
def randrange(n, vmin, vmax):
return (vmax - vmin) * np.random.rand(n) + vmin
# builds an empty node with a given value, helper method for makeNode
def makeNodeS(value):
global hTable, numNodes
n = Node(value)
hTable.append(n)
numNodes = len(hTable)
return n
# builds a node with given parameters
def makeNode(value, location, color, marker):
n = makeNodeS(value)
n.setLoc(location)
n.setStyle(color, marker)
if debug:
print("Building node {} at {} with color = {}, marker = {}, and associations = {}.".format(value, location, color, marker, n.assocs))
return n
# aggregate nodes in hTable and plot them in 3D
def plotNodes():
global hTable
if debug:
print("Plotting Graph...")
for elem in hTable:
if debug:
print(" Plotting node {}...".format(elem.value))
global fig, numFrames
scat = ax.scatter(elem.location[0], elem.location[1], elem.location[2], c=elem.color, marker=elem.marker)
for c in elem.assocs:
if (getNode(c).value != elem.value):
if elem.count in getNode(c).assocs: # if the two nodes are associated to each other, draw solid line
ax.plot([elem.location[0], getNode(c).location[0]], [elem.location[1], getNode(c).location[1]], [elem.location[2], getNode(c).location[2]], edgeColor)
if debug:
print(" Plotting double edge between {} and {}...".format(elem.value, getNode(c).value))
else:
ax.plot([elem.location[0], getNode(c).location[0]], [elem.location[1], getNode(c).location[1]], [elem.location[2], getNode(c).location[2]], edgeColor + dottedLine)
if debug:
print(" Plotting single edge from {} to {}...".format(elem.value, getNode(c).value))
#ani = anim.FuncAnimation(fig, update_plot, frames=xrange(numFrames), fargs=(['b', 'w'], scat))
# build single connection from node A to node B
def sConnect(nodeA, nodeB):
nodeA.addAssoc(nodeB)
if debug:
print(" Drawing single connection from node {} to node {}...".format(nodeA.value, nodeB.value))
# build double connection from node A to node B, and from node B to node A
def dConnect(nodeA, nodeB):
if debug:
print("\nDouble node connection steps:")
sConnect(nodeA, nodeB)
sConnect(nodeB, nodeA)
# update scatter with new color data
def update_plot(i, data, scat):
scat.set_array(data[i])
return scat
# returns the node with given count
def getNode(count):
global hTable
n = hTable[count-1]
return n
# set up axis info
def defineAxis():
ax.set_xlabel('X Label')
ax.xaxis.label.set_color(lightGrey)
ax.tick_params(axis='x', colors=lightGrey)
ax.set_ylabel('Y Label')
ax.yaxis.label.set_color(lightGrey)
ax.tick_params(axis='y', colors=lightGrey)
ax.set_zlabel('Z Label')
ax.zaxis.label.set_color(lightGrey)
ax.tick_params(axis='z', colors=lightGrey)
# randomly populate nodes and connect them
def test():
for i in range (0, maxNodes):
rand = np.random.rand(2)
if (0 <= rand[0] <= 0.25):
q = makeNode(i, np.random.rand(3), 'r', '^')
elif (0.25 < rand[0] <= 0.5):
q = makeNode(i, np.random.rand(3), 'b', 'o')
elif (0.5 < rand[0] <= 0.75):
q = makeNode(i, np.random.rand(3), 'g', 'v')
elif (0.75 < rand[0]):
q = makeNode(i, np.random.rand(3), 'w', 'o')
if (0 < i < maxNodes-1):
if (rand[1] <= 0.2):
dConnect(q, getNode(q.count-1))
elif (rand[1] < 0.5):
sConnect(q, getNode(q.count-1))
# randomly populate binary nodes and connect them
def test2():
for i in range (0, maxNodes):
rand = np.random.rand(2)
if (0 <= rand[0] <= 0.80):
q = makeNode(i, np.random.rand(3), 'k', 'o')
else:
q = makeNode(i, np.random.rand(3), 'w', 'o')
if (i > 0):
if (rand[1] <= 0.2):
dConnect(q, getNode(q.count-1))
elif (rand[1] > 0.2):
sConnect(q, getNode(q.count-1))
# switches a binary node between black and white
def switchNode(count):
q = getNode(count)
if (q.color == 'b'):
q.color = 'w'
else:
q.color = 'b'
# main program
def main():
## MAIN PROGRAM
initPlot()
test2()
plotNodes()
defineAxis()
plt.show()
# class structure for Node class
class Node(str):
value = None
location = None
assocs = None
count = 0
color = None
marker = None
# initiate node
def __init__(self, val):
self.value = val
global numNodes
numNodes += 1
self.count = numNodes
self.assocs = []
self.color = 'b'
self.marker = '^'
# set node location and setup 3D text label
def setLoc(self, coords):
self.location = coords
global labelsOn
if labelsOn:
ax.text(self.location[0], self.location[1], self.location[2], self.value, color=fontColor)
# define node style
def setStyle(self, color, marker):
self.color = color
self.marker = marker
# define new association
def addAssoc(self, newAssociation):
self.assocs.append(newAssociation.count)
if debug:
print(" Informing node association: Node {} -> Node {}".format(self.value, newAssociation.value))
main()
Scatter returns a collection and you can change the colors of the points in a collection with set_facecolor. Here's an example you can adapt for your code:
plt.figure()
n = 3
# Plot 3 white points.
c = [(1,1,1), (1,1,1), (1,1,1)]
p = plt.scatter(np.random.rand(n), np.random.rand(n), c = c, s = 100)
# Change the color of the second point to black.
c[1] = (0,0,0)
p.set_facecolor(c)
plt.show()