Creating a Zooming Mandelbrot Fractal with Python in Power BI
I recently had the time to read "Make Your Own Mandelbrot" by Tariq Rashid. If you know a little Python and are interested in the exciting and strange world of fractals, I would highly recommend you read this book. It's not very long, and you can probably get through it in a day. Amazon has a kindle version you can download too. After you're finished, you can create this!
Tariq also explains how you can 'zoom in' on the fractal as well. Here is a closer look:
In this article, I'll go over how to create some user inputs that allow for zooming in on the fractal with Power BI.
First off, if you don't have Python yet, you need to install it first. The installation process is outside the scope of this article, but there are plenty of resources on how to install Python on the web. A good place to start is on the Python main download page. I'm using Python 2.7 for this demonstration.
Alien Tip: After installing Python, you also need to enable Python in the preview settings if you haven't already. We are not going to be using Python in the Power Query editor - just the visuals. If you are not sure how to enable Python in Power BI, read my other blog about setting up Python.
Open up a blank page to get started. Select the Python visual to insert a Python module.
However, to actually build a Python visual, you need something to feed into it. Since we only have a blank page and no data, we can easily just create a filler table with DAX that we can pass in.
But let's not create just any table. I wanted the ability to zoom in on the fractal based on a user input value. To do this, I inserted a New Parameter under the 'What If' section of the Modeling tab. This will create a table for whatever starting values I give it. Just to start off, I set my table to go from 1 to 2, with increments of 1.
This gives us a table like the following:
Now that we have a table, go back to the Python visual and pass in the Zoom parameter.
Power BI automatically creates a dataframe for us, but we don't need to use it just yet. You can simply paste in your Mandelbrot code right below where the prompt tells you to. Click 'Run' and you should see your fractal in Power BI!
The full Python code for the Mandelbrot fractal is outside the scope of this article. Tariq would probably do a much better job at explaining the concepts than me anyway. It's about the price of a coffee and he makes it a simple and enjoyable read! There are other resources on the web as well. IBM has an article how to create one, though the code will be a bit different than this one, and the author goes into detail about how to optimize the code.
Setting Up The Zoom
Now that there is an image to work with, I we can work on the zoom feature. There are probably a number of ways to do this - mine is certainly not the best way by any means, but it is simple and makes sense to me.
Create a new measure which returns the zoom coordinates
Pass in this new measure in the Python visual
1. Creating the Zoom Measure
This measure will return a comma separated string value for the coordinates of the complex plane (which I called 'Zoom Coordinates').
Zoom Coordinates = var z = Zoom[Zoom Value]
var firstLevel = "-2.25, 0.75, -1.5, 1.5, 40"
var secondLevel = "-0.22, -0.21, -0.70, -0.69, 120"
return SWITCH(z, 1,firstLevel, 2, secondLevel)
In the above DAX forumula, the firstLevel, "-2.25, 0.75, -1.5, 1.5, 40" corresponds to the imaginary numbers of (-2.25 - 1.5i) & (0.75 + 15i), with 40 iterations.
How to Zoom
There are 5 numbers within the variable. I'll call them positions 1 to 5:
Pos 1 (X Min): Reducing this number zooms the left side of the plot.
Pos 2 (X Max): Increasing this number zooms the right side of the plot.
Pos 3 (Y Min): Reducing this number zooms the top side of the plot.
Pos 4 (Y Max): Increasing this number zooms the bottom side of the plot.
Pos 5: Increasing this number results in a more detailed plot.
We can visualize it like this:
You essentially need to make the plot area smaller. Of course, just making the plot area smaller would result in just a bigger distorted image. To prevent this, increase the position 5 number and we'll get more detail (for example, from 40 to 120).
Alien Tip: When adjusting the coordinates, try to keep a WIDTH to HEIGHT ratio of 1. For example, (-2.25 - 0.75) / (-1.5 - 1.5) = 1.0. To move left, subtract from both Pos 1 & Pos 2. To move up, add to both Pos 3 & Pos 4.
2. Passing in the Measure to Python
I fed this variable into the Python script, which splits the string and converts each value to a float number. These numbers are then used for the zoomH and zoomV variables. Again, there may have been a better way to set this up, but having the numbers as a string variable in the DAX formula allowed me to have all the numbers in a single location, without needing to create individual columns each with their own IF statements.
Getting the first row of the dataset (in case there are multiple rows) with dataset.iloc.
Splitting this with the split() function.
Creating a new list called zoomCoordValues.
Appending and converting the string values to float values to the list with a simple for loop.
import numpy as np import matplotlib.pyplot as plt
# Get first row of dataset
zoomCoord = dataset.iloc
# Create array of string values by splitting on the commas zoomCoord = zoomCoord.split(',')
# Setting up new array zoomCoordValues = 
# Appending float values to new array for i in range(0,len(zoomCoord)): zoomCoordValues.append(float(zoomCoord[i]))
# Use Zoom Values Instead of Hard Coded Values
zoomH = zoomCoordValues[0:2] zoomV = zoomCoordValues[2:4] iterations = int(zoomCoordValues)
At this point I've created a working zoom... but with only 2 zoom levels. From here, I could've created a function to modify the coordinates based on the zoom level, but with the Mandelbrot fractal there's a bit of randomness mixed in with the structure. So a zoom function may end up zooming in on absolutely nothing. I decided to just test points with trial and error. It may not be the most efficient way, but I was able to get some fairly interesting views.
I created a total of 12 zooms (starting from the 3rd).
Here are all the zoom coordinates and number of iterations if you'd like to test them out yourself. There was no super advanced, mathematical way of coming up with these coordinates. Just simple trial and error, referring to the plot area (above), and keeping a width to height ratio of 1.0.
var firstLevel = "-2.25, 0.75, -1.5, 1.5, 40" var secondLevel = "-0.22, -0.21, -0.70, -0.69, 120" var thirdLevel = "-0.20, -0.19, -0.68, -0.67, 160" var fourthLevel = "-0.20, -0.195, -0.68, -0.675, 200" var fifthLevel = "-0.20, -0.1975, -0.68, -0.6775, 230" var sixthLevel = "-0.20, -0.1985, -0.68, -0.6785, 280" var seventhLevel = "-0.1995, -0.1985, -0.6798, -0.6788, 330" var eighthLevel = "-0.1995, -0.19875, -0.6798, -0.67905, 380" var ninethLevel = "-0.1995, -0.19895, -0.6798, -0.67925, 430" var tenthLevel = "-0.1993, -0.19905, -0.67965, -0.6794, 550" var eleventhLevel = "-0.19919, -0.199155, -0.6795495, -0.6795145, 600" var twelfthLevel = "-0.199175, -0.1991705, -0.6795295, -0.679525, 700"
Other Methods To Accomplish this
"But....wait a minute, could we have just created a table in a spreadsheet with all the coordinates?" Yes, we certainly could have. Everything we've done so far, we've accomplished without any outside sources. The disadvantage of using an outside source, is that we lose the ability to have a single value slicer that the "what if" parameter gives us. At any rate, let's take a look at how we can accomplish this by referring to an outside spreadsheet.
With our good friend Excel, I copied and pasted the above coordinates. Then I split them into individual columns:
After importing the table with Power Query, I replaced the "Zoom Coordinates" measure with the xMin, xMax, yMin, yMax and Iterations fields, in that order.
Next, we can clean up the Python code a bit. I removed the split() function and the loop for the list, since we don't need it now that we are directly passing in the fields.
import numpy as np import matplotlib.pyplot as plt
zoomCoord = dataset.iloc
zoomH = zoomCoord[0:2] zoomV = zoomCoord[2:4] iterations = int(zoomCoord)
From here you just need to bring in the filter for the zoom levels. I made mine as a list, since it's the easiest way to quickly select a single value. We lost the ability to have a single select, but if we accidently choose more than one zoom level, the dataset.iloc will take the first row.
As of this writing, Python visuals are not supported in web view so unfortunately I can't place the dashboard here, but I encourage you to try this on your own. It was really exciting for me to dive into the interesting designs of the Mandelbrot fractal. Some of them make me feel like I'm peering into the universe! What do you think? Let me know in the comments!
Building an Access Database? Check out my ebook on Amazon!