Unit Testing, Code Coverage & Linting: Power BI Custom Visual Development Fundamentals #20

This video explains how to set up Unit Testing, Code Coverage and Linting on your Power BI Custom Visual.
This video is part of a series, please check the playlist below for the set.

Continue reading Unit Testing, Code Coverage & Linting: Power BI Custom Visual Development Fundamentals #20

Adding Data Bound Properties: Power BI Custom Visual Development Fundamentals #16

Learn how to add data properties to your Power BI Custom Visual.
This video is part of a course, please check the playlist below for the set.

Playlist @ https://goo.gl/qbHDSq
Code @ https://goo.gl/7C4LeM
Dataset @ https://goo.gl/DEaZYY
D3 v3.x Docs @ https://goo.gl/xHZQ18

Transcript

Hello again and welcome back to this series on developing Power BI Custom Visuals.

Today I’ll show you how to add data bound settings to the properties pane of your visual.

These data bound properties allow the user to configure aspects of your visual that are related to a specific bit of data, for example, a specific series or a specific category.

In this video, such a property will enable users to choose a specific colour for each specific category bar.

So, let’s get to it, right now.

Just like in the last video about simple properties, we can begin by going to the capabilities.json file so we can declare another property group.

        "dataColors":{
            "displayName": "Data Colors",
            "properties": {
                "fill":{
                    "displayName": "Color",
                    "type": {
                        "fill": {
                            "solid": {
                                "color": true
                            }
                        }
                    }
                }
            }
        }

This time, we’re adding a new property group called data colors, that contains a single property called fill and that the user will see as color.

The type of this property is a bit out of the ordinary but it basically tells Power BI to render a color picker for this property.

The value you get from that color picker is a typical hexadecimal color code, the same format that you can use in html.

Now from the point of view of the capabilities file, there is no real difference between a simple property and a data bound property.

What makes that difference is the way we handle the property in code.

In this case, instead of a single setting that we read and write, we want to get and persist a different color value per each data category that we have on-screen, regardless of how many categories there are.

To make that happen, we need to treat this property in a different way than before.

Instead of just adding a new simple setting, what we want to do is to associate a color value to each of data points.

Luckily, we have already done that.

The color property here is already what allows each bar to have its own color, so that’s one step less.

We can therefore go right ahead and populate these colors in the properties pane.

And you guess it, we do this in the unnecessarily named enumerate object instances function.

Now that’s a mouthful every single time.

 case "dataColors":

                    break;

But anyway, in here we can add another block to handle rendering of the data colors property group.

This block needs to be somewhat different though, as again, instead of rendering a single property on the pane, we need to render one unique property per category in our data.

The thing is, those categories and those colors currently live in the view model object, which is only populated at update time.

However, this function runs outside of the update cycle, so it doesn’t have access to the view model to begin with.

Well, that’s something we need to fix.

We need to make the view model available at the visual instance level.

private viewModel: ViewModel;

We can do this by adding a new instance variable here called viewmodel.

this.viewModel = this.getViewModel(options);

And then in the update function, we change the local viewmodel variable to the instance variable.

This will break things here a bit, so let’s just fix them before we move on.

And there you go, the view model is now accessible everywhere in the visual.

                    if (this.viewModel) {
                        for (let dp of this.viewModel.dataPoints) {
                            properties.push({
                                objectName: propertyGroupName,
                                displayName: dp.category,
                                properties: {
                                    fill: dp.colour
                                },
                                selector: dp.identity.getSelector()
                            })
                        }
                    }

This mean we can go back to the enumerate something something function and add the remaining code.

So there are a couple of things different here.

First, instead of just pushing a single property to power bi, we’re instead pushing one property per each live data point that we have.

Our data points do map to categories in this visual, so this is about right.

Second, for each property that we push, we are defining a custom display name for it, using the category name.

If we didn’t do this, we would see the default display name of color in every single property and that would be quite useless.

Third, we are now providing this selector value as well.

This is where the magic happens.

The selector is a bit of information that allows Power BI to store the color value we provide here against the specific slice of data that this data point identifies.

This is how Power BI can know that a certain property value belongs to a certain category as opposed to being a simple property with a single value.

In this way, Power BI can store multiple values for this property and will be able to return them to you, associated with each category, every time you ask.

In fact, let’s see this in action.

So now we can see a list of all the categories here, and we can select a color for each.

Thing is, it’s not applying any color other than the default and that’s because we’re not yet reading the color that the user is manually selecting.

But that’s fine, before we fix this, I want to show you how Power BI provides that manual color to you.

So if you switch here to data view mode, and you expand the metadata view, you’ll see exactly nothing.

This is a consequence of using that selector when populating the properties.

When you do that, what Power BI does is, instead of showing your property in the metadata view, it attaches it directly to the appropriate place in whatever data view you are requesting.

In our case, we are requesting a categorical data view and we are saying, by using the selectorId, that each value maps to a single category.

Meaning, we are literally data binding that setting.

Based on that, Power BI has enough information to bring in the user selected color right next to the category itself.

Or well, at least, in this objects array, in the same index position of the equivalent category values array.

It’s kind of the same thing anyway.

So, this whole thing means that we can read these data bound settings at the same time as we’re reading the rest of the view model data.

let objects = categories.objects;

First we get that objects array from the category array.

Now we need to keep in mind this objects array may or may not be there to begin with.

We need to check for its existence every time we want to access it.

colour: objects && objects[i] && DataViewObjects.getFillColor(objects[i], {
                        objectName: "dataColors",
                        propertyName: "fill"
                    }, null) || this.host.colorPalette.getColor(categories.values[i]).value,

And that’s precisely what we’re going to do right now.

So we’re doing something similar here to what we did in the updatesettings function.

The only difference is that instead of grabbing properties from the metadata view, we’re now grabbing them straight from the categories view, via that object array.

Of course, we’re checking whether the objects array is there to begin with and if it isn’t, or if it is but is has no relevant data, then we just default to the palette color we had before.

So let’s see what this did.

And there you have it, you can select the colors in the chart to your heart’s content.

You can also revert the color back if you’re not happy with it and use the palette color instead.

That’s it for today.

If you have any questions, let me know and I’ll get back to you.

In the next video in this series, you will learn how to add some nice little data tooltips to this visual.

Until then, take care and see you next time.

Adding Simple Properties: Power BI Custom Visual Development Fundamentals #15

Learn how to add simple properties to your Power BI Custom Visual.
This video is part of a course, please check the playlist below for the set.

Playlist @ https://goo.gl/qbHDSq
Code @ https://goo.gl/7C4LeM
Dataset @ https://goo.gl/DEaZYY
D3 v3.x Docs @ https://goo.gl/xHZQ18

Transcript

Hello again and welcome back to this series on developing Power BI Custom Visuals.

Today you will learn how to add simple settings to the properties pane of your visual.

These properties allow the user to change certain aspects of your visual, such as font sizes and colours and whether you’re drawing puppies or kittens or whatever else you want really.

In this example, you will use them to hide and show the axis that you added in the previous video.

So let’s get to it, right now.

So the first thing is, we’re going to is install a new helper package via npm.

This is completely optional, but doing so will make our lives easier down the line.

The package in question is the power bi data view utilities package and like the name suggests, it contains a few utility classes that help us extract information out from the data views.

Before we install this though, we need to stop the pbiviz utility for a minute using control-c, then y.

Now we can run the npm command to install this package.

This is now installed so we just need to reference a couple of new files on the project configuration.

"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.d.ts"

First get let’s go to the tsconfig project file and add the package’s typescript declaration file.

This file has no executable code if you check it, it just declares what types exist in the compiled code file.

Which is exactly what we’re going to add to the pbiviz file list.

So here we add the javascript file from the package that contains the compiled javascript code.

"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js"

Now you will notice this pattern a lot when adding more and more packages.

This is not always the case, but a lot of times, developers package their final product onto two files.

One is a compiled and minified javascript file that you can use anywhere, regardless if you’re using typescript or any other javascript superset.

And the other one, in our case, is a typescript definition file that simply declares what new types can the typescript compiler assume to exist, so that you can use them in your typescript code while keeping the benefits of static typing.

Remember, static typing is a typescript thing, not a javascript one.

Anyway, we’ve added the new files so we can run the pbiviz utility again and see if everything works.

There you go, everything is still working.

Now let’s get those properties going.

Switch to Visual Studio Code, visual.ts

So the first thing we need to, is to go over to the capabilities.json file.

Again, this is where you tell Power BI what your visual can do.

"objects": {
},

We’re now going to add an entry called objects.

This entry will contain a list of all the custom properties in your visual, grouped by a property header.

        "xAxis": {
            "displayName": "X Axis",
            "properties": {
                "show": {
                    "displayName": "Show X Axis",
                    "type": { "bool": true }
                }
            }
        },

For starters, we’re going to add a property to the visual that allows the user to enable or disable the X Axis.

So there are a few elements to this.

The “xAxis” element refers to the internal name of the property group.

This is the identifier that we can use, when we want to access this property group in code.

And indeed, that’s something that we will do in just a minute.

The display name on the other hand is the label that the end-user sees for this property group.

You can put here whatever makes sense to show on the interface.

Now each property group can have one or more properties in it, and we define that group with this “properties” element.

For now, we will start with a single property to allow the user to enable or disable the X Axis.

As you can probably guess, this “show” is the internal identifier of that property.

Likewise the displayname is the visible label in the properties pane itself.

What is new here is the type of the property.

Depending on this type, Power BI will render the property differently on-screen and you can play around with this if you want.

For the show x-axis property, we’re defining it as a boolean.

This will mean we’re either showing the axis or not showing the axis, and for this Power BI will render a neat toggle button that you can just click on.

Now, just defining this property in the capabilities file does not make it show up in the properties pane.

Power BI instead uses this property list to later inquire your visual whether you actually want to display each property or keep them to yourself and if you do want to display a certain property, then what’s the current value for it that you want the user to see.

So we need to make these decisions in the visual instead.

Let’s go back to the visual code and look at the settings object we created in the last video.

This is where we defined some defaults for the border and padding.

We’re going to add some new settings to reflect the need for showing and hiding the axes but we’re going to make things a little different now.

        private settings = {
            axis: {
                x: {
                    padding: {
                        default: 50,
                        value: 50
                    },
                },
                y: {
                    padding: {
                        default: 50,
                        value: 50
                    }
                }
            },
            border: {
                top: {
                    default: 10,
                    value: 10
                }
            }
        }

Before that though, I’m going to refactor this code into something a bit more useful.

What I’m doing here is splitting each setting into a default value and a current value.

This simple pattern will let us assign new values to each setting without worrying about losing the defaults.

Now by doing this, we’ve broken the code miserably, so let’s go ahead and fix it.

There you go, all working now.

                   show: {
                        default: true,
                        value: true
                    }

Let’s get back to the settings object and add a new settings to show x the axis.

This will have a default value of true and a current value of true as well although this doesn’t matter very much, as we will update this value upon loading the visual.

However, before we can get a setting from the user, we have to expose these settings in the properties pane, so that the user can change them in the first place.

The Power BI API has a peculiar way of doing this.

        public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {

            return null;
        }

Let’s add a new function to the end of the visual code.

enumerateObjectInstances.

That’s a mouth full.

Now this fella is an API function that Power BI calls as if by magic, every single time it needs to display or update a property in the properties pane.

The driver for those calls is in fact, the list of property groups that we define in the capabilities.json file.

So what this mean is that, every single time Power BI needs to render the properties pane, it will go through your declared property groups in capabilities.json and then call this function one time per each of those property groups.

It then expects this function to return the list of properties that you actually want to display, along with their current values and any extra configuration, such as minimum as maximum value restrictions.

Now this whole thing happens outside of the update loop.

It’s perfectly possible to see the main update function running once and then see this function running as many times as needed to keep the properties pane updated.

So now that you know that, let’s add some code to this function.

            let propertyGroupName = options.objectName;
            let properties: VisualObjectInstance[] = [];

            switch (propertyGroupName) {
                case "xAxis":
                    properties.push({
                        objectName: propertyGroupName,
                        properties: {
                            show: this.settings.axis.x.show.value
                        },
                        selector: null
                    });
		break;
            };

            return properties;

So we’re doing a couple of things here.

As I said, this function gets called once per property group, so we need to check for what exact property group this function is getting called each time.

Then, based on that, we fill up this array of Visual Object Instances with the property information that we want to show to the user.

Right now, we only want to make one property visible and that’s the “show” property, so, we’re returning that, along with the current value of that property.

So this VisualObjectInstance array is literally the mini view model that Power BI uses to render those properties on-screen, in the properties pane.

We’re also setting this selector setting to null.

The selector setting relates to data bound properties, which is, by the way, the subject of the next video, but for now, leave it as null and don’t worry about it.

So let’s see what this did.

Okay, so we now have a new property group on the properties pane called X Axis.

We also have a new push slider here, which curiously is at the property group level, instead of being inside the property group.

This seems odd, but there is nothing wrong about this.

The properties pane has this convention, where, if the first property that you define is a boolean type and it’s called show, then, instead of popping inside the property group, it will in fact show in the header of the property and you when enable or disable it, it will automatically enable or disable all the other properties in the pane.

This is the same behaviour as all other properties you see in the visual pane.

This is a neat convention, which we happen to be using here as our first property is indeed a boolean called show.

This is all good, however our show property doesn’t seem to be working very well yet.

That’s because we are pushing the property value to the properties pane here, yet nowhere in our code are we getting the new value back from power bi when the user sets it.

So the way Power BI provides the new setting value to your visual, is by forcing an update every single time something changes and providing all the new property values in the metadata view.

This is cool as it means, you don’t have to worry about forcing updates yourself if anything changes, as power bi will just piggy back on the whole update cycle for you.

The only thing we need to do here then, is to improve our update function so it can read these settings as they become available.

So let’s get that done.

Let’s get back to the visual code.

import DataViewObjects = powerbi.extensibility.utils.dataview.DataViewObjects;

Now let’s add an import statement up top so we can use a helper object from that library we installed in the beginning of this video.

That package does not export its types by default so we need to import them on a case by case basis.

        private updateSettings(options: VisualUpdateOptions) {

        }

Now let’s go to the bottom and add a new function.

This updatesettings function will grab those new settings from the metadata as they come and update our internal settings object as appropriate.

            this.settings.axis.x.show.value = DataViewObjects.getValue(
                options.dataViews[0].metadata.objects, {
                    objectName: "xAxis",
                    propertyName: "show"
                },
                this.settings.axis.x.show.default);

To do that, we can use the DataViewObjects helper we just imported.

This helper does the grunt work of inspecting the metadata view for a specific property and then getting its value back for you.

And if the property is not there to begin with, it can also return a default value for you instead.

All that’s left is to wire this up in the update function.

this.updateSettings(options);

We’ll add this right in the top, so we’re sure that the settings are always up-to-date every time the update code runs.

So let’s see what this did.

And there you go, the property now holds its value.

So now that we have a working property to show or hide the X axis, we can go back to the code and make some use it.

let xAxisPadding = this.settings.axis.x.show.value ? this.settings.axis.x.padding.value : 0;

So what we’re going to do here, is to take into account both the show property and the internal padding setting, to decide whether to take into account the axis or not.

If the user wants to see the axis, we will retain the padding value.

If not, we use a padding of value of zero just for this update, which will make the axis render off-screen, while everything else will adapt like magic.

Now let’s replace all the uses of the x padding value from settings with this local variable instead.

Let’s see what this did.

And there you go, now the chart adapts accordingly.

Cool, isn’t it.

And that’s it, now you’ve learned how to add a simple property to let the user setup the visual to their own taste.

We’ll stop here today, but in the git hub code base you will also find another property for the y axis and additional properties to configure the borders of the visual as well.

Still, I do recommend you try to implement these by yourself using the steps you have learned today.

Anyway, if you have questions, let me know and I’ll get back to you.

In next video in this series, you will learn how to add data bound settings to the properties pane to let the user himself control the colours of the bars.

Until then, take care and see you next time.

Adding Axes: Power BI Custom Visual Development Fundamentals #14

Learn how to add axes (as in axis of course) to your Power BI Custom Visual.
This video is part of a course, please check the playlist below for the set.

Resources

Playlist @ https://goo.gl/qbHDSq
Code @ https://goo.gl/7C4LeM
Dataset @ https://goo.gl/DEaZYY
D3 v3.x Docs @ https://goo.gl/xHZQ18

Transcript

Hello again and welcome back to this series on developing custom visuals for Power BI.

Today, you will focus on drawing axis on a a custom visual.

Using our growing little bar chart as an example, you will see how to add a categorical axis for the bars and a continuous axis for the values.

So let’s get to it right now.

        private settings = {
            axis: {
                x: {
                    padding: 50
                }
            }
        }

The first thing to do is actually make some room in the visual to put the x-axis on.

This means padding the visual contents on the bottom side so they go up a bit and allow enough space for the axis to live in.

As I don’t like to leave magic numbers around the place, I’m going to add a new property to the visual class called settings.

This will be a simple json object that will hold a collection of general settings for the visual, starting with a padding of 50 pixels.

I may refactor this into something far more beautiful on a future video, but for now, this will do just fine.

So now that we some padding defined, we just need to push the bars up by this amount.

The way you do this in D3 is by telling the yScale object that it should start drawing the lower value data points at a higher place in the SVG canvas.

.range([height - this.settings.axis.x.padding, 0]);

Since we know by how many pixels we want to push the bars up, we just to include those pixels into the calculation of the minimum y range value.

Now if you’re thinking like, wait what is he talking about, then I recommend you go and review video 9 of this series, where I explain the principle behind D3 scales and the relationship between domain values and range values.

Otherwise, let’s take a look of what this did.

So, as you may see here, if you’re really paying attention, the bars have grown a lit bit upwards.

Okay, so it’s a bit hard to see, but we can compare and see that the bar sizes are a bit bigger than the vanilla chart, even taking the stretching here into account.

That’s because the D3 scale is now drawing the data points of these bars slightly above what they were before.

However, this does not mean that the scale is now drawing each bar 20 pixels above the previous value.

Instead, the scale is now considering that the minimum data point of zero should be drawn at exactly 20 pixels from the bottom and then scaling all the data points accordingly.

Which is great, however, the bar is still filling down the whole thing, and that’s really not the scale’s fault.

To fix this, we need to go down to the rendering code and find the bit where we are defining the height of the SVG rectangle object for each bar.

height: d => height - yScale(d.value) - this.settings.axis.x.padding,

Then, we just subtract the padding that we defined for the x-axis.

And that’s it, let’s see what this it.

There you have it, as you can see the bars are now shorter and consistent with the vanilla visual as well.

Time to go make our axis happen.

private xAxisGroup: d3.Selection<SVGElement>;

We will start by creating a placeholder SVG group object.

This will hold all the rendering bits of the x-axis and will also allow us to easily position the axis on the visual.

this.xAxisGroup = this.svg.append("g")
                .classed("x-axis", true);

Of course, we need to instantiate this, so let’s go down to the constructor and add a bit more code.

Again, same as with the bar group, this is just an empty SVG group element, it does not render anything on its own, however we are setting a CSS class of x-axis, just in case we want to style it later down the process.

With this done, let’s go down to the rendering code, right after the bit where we are defining the x scale.

let xAxis = d3.svg.axis()
                .scale(xScale)
                .orient("bottom")
		.tickSize(1);

This is the best spot to define our x-axis, and we can do this with this bit of code.

This creates a D3 axis function that is able to render an axis on-screen.

It does this all on its own and using SVG elements in the html.

That’s why this is part of the SVG namespace in D3 version 3.

We then associate the axis with a scale so it is able to infer what type of axis we want to render.

Right now, we are associating this axis with a categorical scale, so the axis object will know that it needs to render a categorical axis as opposed to a continuous axis or a time axis or any other type of axis.

It will also know the range of values it needs to show on-screen.

We also set the orientation of the axis so it renders neatly as a bottom axis.

Having said that, a bottom style axis is the default in D3 so you don’t need to define this if you don’t want to but it’s always good to make things explicit.

Finally, we set the ticksize to 1.

This affects the size of main line in the axis and any ticks that it has.

Although I’m not a fan of magic numbers, I’m happy leaving this one here for now as we rarely need to change this at all.

Now this code, by itself, does not render the x-axis anywhere on-screen.

It only sets up the D3 object that is able to render the axis for us whenever we need to.

xAxis(this.xAxisGroup);

To make the actual rendering happen, we need to run the D3 axis function over the SVG placeholder we have prepared for it.

So let’s see what this did.

Okay, well, that did something, we have an x-axis.

It’s just not on a very good spot yet.

Let’s go back to the code and fix that.

this.xAxisGroup.call(xAxis);

First, I’m going to replace the D3 call code with something that is functionally equivalent.

Now this bit of code does exactly what the previous bit did, it’s just another way of writing the same thing.

this.xAxisGroup
                .call(xAxis)
                .attr({
                    transform: "translate(0, " + (height - this.settings.axis.x.padding) + ")"
                });

The bonus is, we can now keep calling other things on the axis group immediately after we render the axis on it.

And here we’re making use of the D3 attribute function to move the axis placeholder itself down to the bottom by applying a transform attribute.

Nothing to it, let’s see the result.

Look at that, we now have a categorical x-axis.

Thing is, it’s using default settings, so it’s a bit all over the place.

Let’s go and fix that.

.style({
                    fill: "#777777"
                })

First, that black there is somewhat strong on the eyes, so let’s make it a nice shade of grey instead.

.selectAll("text")

Now those labels were all over the place so we must do something about them.

The D3 axis function renders those labels as SVG text elements inside whatever container you give to it, so we can grab the lot by using the selectAll function.

This returns a D3 collection that lets you continue to apply attributes and style to all those text elements at the same time, just like we do when rendering the bars, for example.

So with that in mind, we just need to do a couple of things with the labels.

.attr({
                    transform: "rotate(-35)",
                })

First, we need to rotate them just a bit so that they don’t overlap and are easier to read.

                .style({
                    "text-anchor": "end",
                    "font-size": "x-small"
                });

And second, we just need to style the labels so that the text justifies to the right and the font isn’t as big as is it.

Now I’m going cheat here and use a relative size for the font, so I can get away with not creating settings for this yet, as that is in fact the theme of the next video.

So let’s see what this looks like now.

And there we go.

Now it looks like a proper axis.

Not only that, look at how it adjusts to the bars dynamically when you maximize it.

Cool, isn’t it?

So now that you understand how to add an x-axis, adding the y-axis should be a pice of cake.

Let’s go through the motions again.

y: {
                    padding: 50
                }

First, let’s get back to our settings variable and add a setting for some y-axis padding.

I’m going to use the same padding value for now, we can change it if we find we need more than this.

private yAxisGroup: d3.Selection<SVGElement>;

Now let’s go the visual variables and add a new placeholder SVG group for the y-axis as well…

this.yAxisGroup = this.svg.append("g")
                .classed("y-axis", true);

Let’s also instantiate this svg group in the constructor…

let yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left")
                .tickSize(1);

And let’s define the D3 axis function for this y-axis.

We do this almost the exact same way as we did for the x-axis.

The only difference is that we’re now telling D3 to orient this axis left instead of bottom.

this.yAxisGroup
                .call(yAxis)
                .attr({
                    transform: "translate(" + this.settings.axis.y.padding + ",0)"
                })
                .style({
                    fill: "#777777"
                })
                .selectAll("text")
                .style({
                    "text-anchor": "end",
                    "font-size": "x-small"
                });

The last thing to do is to actually render the axis on-screen.

Now for the y-axis, we will position it horizontally using the padding value we defined.

A left side axis starts rendering from the right, so this means that this will fill up all the area that we defined as padding.

So let’s see what this did.

Hmm, interesting.

So there is still an issue here that we need to address.

We still need to push and scale both the bars and the x-axis to the right so we have room for the y-axis.

We can do this is one go by affecting the xScale object in the rendering code.

.rangeRoundBands([this.settings.axis.y.padding, width], this.xPadding);

We just need to say that the data points need to start being rendered from the y-axis padding position and not from zero anymore.

Since the other D3 functions are re-using these, they will just pick this up and well, scale accordingly.

Let’s see what this did.

Almost, almost there.

Now if you notice, the top value on the y-axis is really risking getting a hair-cut there as it’s getting drawn right on the edge of the visual.

Fixing this one is straightforward, so let’s take care of this and finish the job.

border: {
                top: 10
            }

First, let’s add a border setting so we can say how many pixels of space we want to free up at the top.

Ten is fine for our purposes.

.range([height - this.settings.axis.x.padding, 0 + this.settings.border.top]);

Now let’s go to where we set the yScale target range and say that we want the maximum domain data value to render just below the top of the visual, which in screen coordinates is at zero, meaning we need to add our border value to it.

So let’s see what this did.

And there you have it.

Two nice axes on our visual.

That’s it for today.

If you have any questions, let me know and I’ll get back to you.

Next, you will learn how to add simple settings to the properties pane to let the user control whether these axis will show up or not.

Until then, take care and see you next time.