<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Luiz Eduardo Zappa on Medium]]></title>
        <description><![CDATA[Stories by Luiz Eduardo Zappa on Medium]]></description>
        <link>https://medium.com/@luizzappa?source=rss-7f4b86735a16------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Vvf8SANLqE1OfF7V43zYeA.jpeg</url>
            <title>Stories by Luiz Eduardo Zappa on Medium</title>
            <link>https://medium.com/@luizzappa?source=rss-7f4b86735a16------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 06 Apr 2026 02:24:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@luizzappa/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Nuxt 3 | Repository pattern: organising and managing your calls to APIs (Typescript)]]></title>
            <link>https://medium.com/@luizzappa/nuxt-3-repository-pattern-organising-and-managing-your-calls-to-apis-with-typescript-acd563a4e046?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/acd563a4e046</guid>
            <category><![CDATA[nuxtjs]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[vuejs]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sun, 16 Jul 2023 23:02:44 GMT</pubDate>
            <atom:updated>2023-10-29T22:44:43.319Z</atom:updated>
            <content:encoded><![CDATA[<p>In Nuxt 2 the use of axios was straightforward and allowed a good management of API calls. When I started to develop with Nuxt 3 I found it difficult to organise with the composables useFetchand useAsyncData(<a href="https://nuxt.com/docs/getting-started/data-fetching">which uses </a><a href="https://nuxt.com/docs/getting-started/data-fetching">ofetchlibrary behind</a>).</p><p>This article is a step by step on how to use the <strong>repository pattern </strong>to organise your API calls in Nuxt 3. Looking for an example, I just found <a href="https://www.vuemastery.com/blog/api-management-in-nuxt-3-with-typescript/">this article</a> from Vue Mastery which is very enlightening, but has some problems, like generating duplicate requests when using SSR (Server-Side Rendering). I based this article to write this tutorial correcting the flaws I found.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/974/1*ac4HERftoEpMt1bKXWYAZg.png" /><figcaption>Accessing API in a clean way</figcaption></figure><h3>The repository pattern</h3><p>Before we go into the details of the implementation, a short theoretical introduction to this pattern is important for those who are not familiar with it.</p><p>The repository pattern is a design pattern that provides <strong>an abstraction layer </strong>between the application’s business logic and the data persistence layer (typically a database or web service).</p><p>The main idea behind the repository pattern is to encapsulate the logic for retrieving and storing data within dedicated repository classes. These repositories act as a bridge between the application and the datasource, providing consistent and uniform interface to interact whit the data.</p><p>To avoid getting too ethereal, take a look at the diagram below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tSiermdYobiCO4mJX96gtg.png" /><figcaption>Without VS With repository pattern</figcaption></figure><p>In the<strong> first diagram</strong> this <strong>pattern is not used</strong>. All the logic to access (and transform) the source data is in the application layer. So, if there is a change in the URL or in the format that the data comes from, we will have to make the same change in all pages that consume that endpoint, <strong>hurting the DRY</strong> (don’t repeat yourself) principle .</p><p>The <strong>second diagram</strong> introduces the <strong>repository layer </strong>between the application and the datasources. All the logic of accessing (and transforming) the data is<strong> encapsulated in this layer</strong>, so the application layer does not need to know the implementation details of accessing the datasource.</p><p>This provides some advantages to our code:</p><ul><li><strong>Abstraction</strong>: shields the application from the underlying data access implementation details. It allows the application to work with a consistent set of methods and operations, regardless of the specific database technology.</li><li><strong>Separation of concerns</strong>: by isolating the data access logic into dedicated repository classes, this pattern helps maintain a separation of concerns between the business logic and data persistence concerns. This improves code readability, maintainability and testability.</li><li><strong>Single responsibility principle</strong>: each repository class focuses on a specific entity within the domain model.</li><li><strong>Testability</strong>: repositories classes can be easily mocked or stubbed during unit testing.</li></ul><h3>Implementing the repository pattern in Nuxt 3</h3><p>All code below is in <a href="https://github.com/luizzappa/nuxt3-repository-pattern">this GitHub repository</a>. The datasource will be a fake API so we don’t have to worry about building an API web service or a database.</p><p>As we will be using the $fecth method of the ofetch library, we have to install it to be able to import it. In your terminal install the library and save in development:</p><pre>npm install ofetch --save-dev</pre><p>The repository layer will be in the <strong>repository</strong> folder. I’ll follow the same organisation as the Vue Mastery tutorial, creating a factory class at the root folder and for each repository, a new file will be created inside the <strong>modules</strong> subfolder.</p><p>So, let’s go, in the root folder of your Nuxt 3 project create the repository folder:</p><pre>mkdir repository</pre><p>Inside this folder we create the <strong>factory.ts</strong> file which will have our abstract class that all repositories will extend from:</p><pre>// [FILE]: repository/factory.ts<br><br>// 3rd&#39;s<br>import { $Fetch, FetchOptions } from &#39;ofetch&#39;;<br><br>/*<br> The FetchFactory acts as a wrapper around an HTTP client. <br> It encapsulates the functionality for making API requests asynchronously <br> through the call function, utilizing the provided HTTP client.<br>*/<br>class FetchFactory&lt;T&gt; {<br>  private $fetch: $Fetch;<br><br>  constructor(fetcher: $Fetch) {<br>    this.$fetch = fetcher;<br>  }<br><br>  /**<br>   * The HTTP client is utilized to control the process of making API requests.<br>   * @param method the HTTP method (GET, POST, ...)<br>   * @param url the endpoint url<br>   * @param data the body data<br>   * @param fetchOptions fetch options<br>   * @returns <br>   */<br>  async call(<br>    method: string,<br>    url: string,<br>    data?: object,<br>    fetchOptions?: FetchOptions&lt;&#39;json&#39;&gt;<br>  ): Promise&lt;T&gt; {<br>    return this.$fetch&lt;T&gt;(<br>      url, <br>      { <br>        method, <br>        body: data, <br>        ...fetchOptions <br>      }<br>    )<br>  }<br>}<br><br>export default FetchFactory;</pre><p>This class receives an HTTP client that will be used to make the requests. In this case we are using the $fetch method from the ofetch library, which is the one Nuxt 3 uses behind the scene.</p><p>Now let’s create the repositories. They will be inside the <strong>modules</strong> subfolder, so create the modules folder inside the repository folder:</p><pre>mkdir modules</pre><p>For simplicity, in this example we will use only one domain: <strong>products</strong>. Then we will have only one repository. But the idea is that for each domain (example: users, cart, …) a separate repository is created.</p><p>Let’s create the <strong>product repository</strong>. Create the <strong>products.ts</strong> file inside the modules subfolder.</p><pre>// [FILE]: repository/modules/products.ts<br><br>// 3rd&#39;s<br>import { FetchOptions } from &#39;ofetch&#39;;<br>import { AsyncDataOptions } from &#39;#app&#39;;<br><br>// locals<br>import FetchFactory from &#39;../factory&#39;;<br><br>type IProduct = {<br>  id: number;<br>  title: string;<br>  price: number;<br>  description: string;<br>  category: string;<br>  image: string;<br>  rating: {<br>    rate: number;<br>    count: number;<br>  }<br>}<br><br>class ProductsModule extends FetchFactory&lt;IProduct[]&gt; {<br>  private RESOURCE = &#39;/products&#39;;<br><br>  /**<br>   * Return the products as array <br>   * @param asyncDataOptions options for `useAsyncData`<br>   * @returns <br>   */<br>  async getProducts(<br>    asyncDataOptions?: AsyncDataOptions&lt;IProduct[]&gt;<br>  ) {<br><br>    return useAsyncData(<br>      () =&gt; {<br>        const fetchOptions: FetchOptions&lt;&#39;json&#39;&gt; = {<br>          headers: {<br>            &#39;Accept-Language&#39;: &#39;en-US&#39;<br>          }<br>        };<br>        return this.call(<br>          &#39;GET&#39;,<br>          `${this.RESOURCE}`,<br>          undefined, // body<br>          fetchOptions<br>        )<br>      },<br>      asyncDataOptions<br>    ) <br>  }<br>}<br><br>export default ProductsModule;</pre><p>We create the product repository by extending the FetchFactory class. Notice that we wrap the call method with the composable useAsyncData. This is necessary so that we <strong>don’t have network calls duplication</strong> (<a href="https://nuxt.com/docs/getting-started/data-fetching#network-calls-duplication">see Nuxt documentation</a>).</p><p>As an example I added the variable fetchOptions in case some method needs additional parameters, in the example I modified the header. Also, it is possible to pass options to the useAsyncData function when calling this method. As we will see, this will be useful later on.</p><p>Now let’s manage our respositories through a Nuxt plugin. To do this, inside the <strong>plugins</strong> directory of our Nuxt project, create a file called <strong>api.ts</strong>:</p><pre>// [File]: plugins/api.ts<br><br>// 3rd&#39;s<br>import { $fetch, FetchOptions } from &#39;ofetch&#39;;<br><br>// locals<br>import ProductsModule from &#39;~/repository/modules/products&#39;;<br><br>interface IApiInstance {<br>  products: ProductsModule;<br>}<br><br>export default defineNuxtPlugin((nuxtApp) =&gt; {<br>  const config = useRuntimeConfig();<br><br>  const fetchOptions: FetchOptions = {<br>    baseURL: config.public.apiBaseUrl<br>  };<br><br>  // Create a new instance of $fecther with custom option<br>  const apiFecther = $fetch.create(fetchOptions);<br><br>  // An object containing all repositories we need to expose<br>  const modules: IApiInstance = {<br>    products: new ProductsModule(apiFecther),<br>  };<br><br>  return {<br>    provide: {<br>      api: modules<br>    }<br>  };<br>});</pre><p>Here we create an instance of $fetch called apiFetcher and pass it to our product repository constructor. The base url value comes from an environment variable, let’s add it to our project.</p><p>In the root directory of our Nuxt project create a file called<strong> .env</strong> which will contain the base URL of our api.</p><pre># [File]: .env<br><br>API_BASE_URL=https://fakestoreapi.com</pre><p>Now we need to pass this variable when we start Nuxt. To do this, in the <strong>package.json</strong> file change the dev script to the following:</p><pre>[FILE]: package.json<br><br>&quot;scripts&quot;: {<br>   ...<br>    &quot;dev&quot;: &quot;nuxt dev --dotenv .env&quot;,<br>   ...<br>  }</pre><p>In your terminal run the command below so that Nuxt generates the type files and typescript no longer complains about the baseURL attribute of our plugin.</p><pre>npm run postinstall</pre><p>Then we need to expose the API base URL variable in the Nuxt settings. In the nuxt.config.ts file add the following snippet:</p><pre>// [FILE]: nuxt.config.ts<br><br>// ...<br><br>  runtimeConfig: {<br>    public: {<br>      apiBaseUrl: process.env.API_BASE_URL<br>    }<br>  }<br><br>// ...</pre><p>Okay, now we will use this plugin on a Nuxt page. Inside the <strong>pages</strong> directory I will create the <strong>index.vue</strong> file:</p><pre>// [FILE]: pages/index.vue<br><br>&lt;template&gt;<br>  &lt;h1&gt;Products list&lt;/h1&gt;<br>  &lt;div <br>    v-if=&quot;pending&quot;<br>    class=&quot;spinner-wrapper&quot;<br>  &gt;<br>    &lt;span class=&quot;loader&quot;&gt;&lt;/span&gt;<br>  &lt;/div&gt;<br>  &lt;div <br>    v-else<br>    class=&quot;product-wrapper&quot;<br>  &gt;<br>    &lt;div<br>      v-for=&quot;product in productsList&quot;<br>      :key=&quot;product.id&quot;<br>      class=&quot;card&quot;<br>    &gt;<br>      &lt;div class=&quot;title&quot;&gt;{{ product.title }}&lt;/div&gt; <br>      &lt;div class=&quot;thumbnail&quot;&gt;&lt;img :src=&quot;product.image&quot;&gt;&lt;/div&gt;<br>      &lt;div class=&quot;description&quot;&gt;{{ product.description }}&lt;/div&gt;<br>      &lt;div class=&quot;wrapper-meta&quot;&gt;<br>        &lt;span class=&quot;price&quot;&gt;${{ product.price }}&lt;/span&gt;<br>        &lt;span class=&quot;rate&quot;&gt;☆ {{ product.rating.rate }}&lt;/span&gt;<br>      &lt;/div&gt;<br>    &lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup lang=&quot;ts&quot;&gt;<br>const { $api } = useNuxtApp();<br><br>const {<br>    data: productsList,<br>    pending,<br>    error<br>  } = await $api.products.getProducts();<br>&lt;/script&gt;<br><br>&lt;style lang=&quot;css&quot; scoped&gt;<br>:root {<br>  --card-height: 324px;<br>  --card-width: 288px;<br>  --spinner-size: 14px;<br>  --spinner-color: gray;<br>}<br><br>.spinner-wrapper {<br>  display: flex;<br>  flex-wrap: wrap;<br>  flex-direction: column;<br>  gap: 3rem;<br>  align-items: center;<br>}<br><br>.loader {<br>  color: var(--spinner-color);<br>  font-size: var(--spinner-size);<br>  width: 1em;<br>  height: 1em;<br>  border-radius: 50%;<br>  position: relative;<br>  display: block;<br>  text-indent: -9999em;<br>  animation: mulShdSpin 1.3s infinite linear;<br>  transform: translateZ(0);<br>}<br><br>@keyframes mulShdSpin {<br>  0%,<br>  100% {<br>    box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em,<br>      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;<br>  }<br>  12.5% {<br>    box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em,<br>      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;<br>  }<br>  25% {<br>    box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0,<br>      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;<br>  }<br>  37.5% {<br>    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em,<br>      0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;<br>  }<br>  50% {<br>    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em,<br>      0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;<br>  }<br>  62.5% {<br>    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,<br>      0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;<br>  }<br>  75% {<br>    box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em,<br>      2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em,<br>      -2em -2em 0 0;<br>  }<br>  87.5% {<br>    box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,<br>      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;<br>  }<br>}<br><br>.product-wrapper {<br>  display: grid;<br>  grid-template-columns: repeat(auto-fit, calc(350px));<br>  grid-gap: 32px;<br>  justify-content: center;<br>  padding: initial;<br>}<br><br>.card {<br>  background: #fff;<br>  border-radius: 19px;<br>  box-shadow: 20px 20px 60px #d9d9d9, -20px -20px 60px #fff;<br>  margin: 20px;<br>  padding: 1rem;<br>  text-align: center;<br>  display: flex;<br>  flex-direction: column;<br>  height: var(--card-height);<br>  width: var(--card-width);<br>  overflow: hidden;<br>  position: relative;<br>  font-family: sans-serif;<br>  font-size: 14px;<br>  font-weight: 400;<br>}<br><br>.card .title {<br>  font-weight: 700;<br>  color: rgb(197, 131, 8);<br>}<br><br>.card .thumbnail {<br>  align-items: center;<br>  display: flex;<br>  justify-content: center;<br>  padding-top: 10px;<br>  padding-left: 30px;<br>  padding-right: 30px;<br>}<br><br>.card .thumbnail &gt; img {<br>  height: 150px;<br>  object-fit: contain;<br>}<br><br>.card .description {<br>  margin-top: 1rem;<br>  font-size: 13px;<br>}<br><br>.card .wrapper-meta {<br> display: flex;<br> flex-direction: row;<br> justify-content: space-around;<br> font-size: 14px;<br> font-weight: 700;<br> margin-top: 2rem;<br>}<br>&lt;/style&gt;</pre><p>The relevant part of this code is inside the script tag. We take the reference to the created plugin ( $api) and call the getProductsmethod. The values returned are those of the composable useAsyncData (<a href="https://nuxt.com/docs/api/composables/use-async-data#return-values">see documentation</a>). They are already reactive by default.</p><p>That way API is initially called on the backend, what if we wanted it to be called only on the client side? For that, we can set the server option to false:</p><pre>// To API be called only on the client side<br><br>const {<br>    data: productsList,<br>    pending,<br>    error<br>  } = await $api.products.getProducts({<br>        server: false<br>      });</pre><p><strong>Attention</strong>: the use of the composable useAsyncData inside the onMounted hook will only work when a hot reload of the page happens. To work around this behaviour we should also set the server option to false, as in the previous example.</p><p>All the code is in <a href="https://github.com/luizzappa/nuxt3-repository-pattern">this GitHub repository</a>.</p><h3>Conclusion</h3><p>We were able to abstract all datasource access logic from the application layer. For example, if we wanted to replace the fake API, we could just change it in the repository and our application would be unchanged (as long as we respect the contract).</p><p>The repository pattern is also widely used in the backend when building REST API services that access databases, for example.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=acd563a4e046" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to find out the coordinates of vertices of a shape in fabric.js?]]></title>
            <link>https://medium.com/@luizzappa/how-to-find-out-the-coordinates-of-vertices-of-a-shape-in-fabric-js-a871109085c1?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/a871109085c1</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[fabricjs]]></category>
            <category><![CDATA[javascript-development]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sat, 25 Feb 2023 13:28:59 GMT</pubDate>
            <atom:updated>2023-02-25T13:43:27.875Z</atom:updated>
            <content:encoded><![CDATA[<p>A common question I see among fabric.js developers is <strong>how to get the coordinates of an object’s vertex</strong>. We’ll talk in detail about that in this article and along we’ll build the following scenario: a parallelogram with circles at each vertices, and these circles are positioned dynamically when we move, resize, shear or rotate the parallelogram.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/484/1*7Soum73yebhRt0aJYH9JUA.gif" /><figcaption>Vertices of an object in the fabric.js</figcaption></figure><p>We’ll work with fabric.Polygon because it’s possible to <strong>directly access the points (vertices)</strong>. In a fabric.Rect or fabric.Triangle we can not get this information directly. It would be possible with a little geometry, but since we can build rectangles and triangles with a polygon, it ends up being a <strong>more general solution</strong>.</p><p>Let’s go ahead and create the polygon:</p><pre>const points = [<br>    { x: -50, y: 150 },<br>    { x: 100, y: 150 },<br>    { x: 50, y: 50 },<br>    { x: -100, y: 50 }<br>  ];<br><br>const polygon = new fabric.Polygon(<br>  points, <br>  {<br>    top: 20<br>  }<br>);</pre><p>The points define the parallelogram below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/245/1*rr0iYcvDslKbNrcjr6ZTYQ.png" /><figcaption>Our parallelogram</figcaption></figure><p>These points are defined<strong> in the plane of the object itself</strong>, <strong>they are not coordinates relative to the canvas </strong>(remember that in fabric.js the Y axis grows downwards). <strong>What we need to do is transform these coordinates to the plane of the canvas</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*60yDQWOFxmvlPDYP4wmouQ.png" /><figcaption>Representation of the points when viewed in relation to each of the planes</figcaption></figure><p>If to achieve this goal you thought of transformation matrix, you got it right! We need to calculate the transformation matrix of the canvas and of the object, multiply each other so we can map the points from one plane to another.</p><blockquote>If the concept of transformation matrix is not very clear, <strong>I explain in depth </strong><a href="https://medium.com/@luizzappa/the-transformation-matrix-in-fabric-js-fb7f733d0624"><strong>in this article </strong></a><strong>what are transformation matrices, Cartesian planes, the how (and why) matrices can map a point from one plane to another</strong>. I strongly recommend reading it.</blockquote><p>Let’s create a function that takes as argument a fabric.Polygon and returns an array with the points of this polygon with the coordinates relative to the canvas (in other words, in the canvas plane).</p><pre>function getPolyVertices(poly) {<br>  const points = poly.points,<br>    vertices = [];<br><br>  points.forEach((point) =&gt; {<br>    const x = point.x - poly.pathOffset.x,<br>      y = point.y - poly.pathOffset.y;<br><br>    vertices.push(<br>      fabric.util.transformPoint(<br>        { x: x, y: y },<br>        fabric.util.multiplyTransformMatrices(<br>          poly.canvas.viewportTransform,<br>          poly.calcTransformMatrix()<br>        )<br>      )<br>    );<br>  });<br><br>  return vertices;<br>}</pre><p>Let’s understand this funciton better. Initially it stores the points of the polygon. Then it iterates over them <strong>applying a transformation to each of them </strong>with the transformation matrix resulting from the multiplication of the transformation matrices of the canvas (viewportTransform) and the object. Remember that this transformation matrix that allows us to map a point from one plane to another.</p><p>What may not be very clear is this part:</p><pre>const x = point.x - poly.pathOffset.x,<br>      y = point.y - poly.pathOffset.y;</pre><p>What the hell is this pathOffset and why are we subtracting it from the point? It’s the <strong>difference between the centre of the shape and the origin (0, 0) of the object’s plane</strong>. The image below shows visually the pathOffset calculation. The orange point is the centre of the shape and the red one the origin of the object’s plane. On the X axis, since the centre and origin are at the same position, pathOffset.x = 0. On the Y axis, the centre is offset 100 from the origin, so pathOffset.y = 100.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/479/1*urU8N3ORmIFE4F_HobeEkA.png" /><figcaption>pathOffset calculation</figcaption></figure><p>By subtracting the pathOffset from each vertex, we are actually <strong>changing the origin of the object’s plane to be the centre of the shape</strong>. Visually, the result:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YmmVIzNOKYtZqU4_VLA3eA.png" /><figcaption>Origin change</figcaption></figure><p>And why do we have to use the origin in the centre of the shape? The translate used in the transformation matrix calculation is <strong>always relative to the centre point of the object</strong>. As we are multiplying these vertices by the transformation matrix, we must use the same “base”, otherwise the result of the calculation would be incorrect.</p><p>Okay, we’ve gone through the hardest part. Now it’s just use the vertices positions in the canvas plane that were returned by the getPolyVertices function to render the circles.</p><pre>const polyVertices = getPolyVertices(polygon);<br><br>const circles = [];<br><br>polyVertices.forEach((vertice) =&gt; {<br>  const circ = new fabric.Circle({<br>    radius: 5,<br>    originX: &quot;center&quot;,<br>    originY: &quot;center&quot;,<br>    fill: &quot;blue&quot;,<br>    left: vertice.x,<br>    top: vertice.y<br>  });<br>  circles.push(circ);<br>  canvas.add(circ);<br>});</pre><p>The code above is straightforward. It’s iterating on each of the vertex positions and creating a circle. In addition, it’s storing the reference of those in an array.</p><p>Finally, we create a function to update the position of these circles every time the parallelogram is moved, resized, skewed or rotated.</p><pre>function updateCirclesPosition() {<br>  const newVertices = getPolyVertices(polygon);<br>  newVertices.forEach((vertice, idx) =&gt; {<br>    const circ = circles[idx];<br>    circ.left = vertice.x;<br>    circ.top = vertice.y;<br>  });<br>}<br><br>polygon.on(&quot;scaling&quot;, updateCirclesPosition);<br>polygon.on(&quot;skewing&quot;, updateCirclesPosition);<br>polygon.on(&quot;rotating&quot;, updateCirclesPosition);<br>polygon.on(&quot;moving&quot;, updateCirclesPosition);</pre><p>There we have our parallelogram with circles at the vertices that dynamically update their position.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Ffabricjs-get-poly-vertices-2bw6ws%3Ffile%3D%2Fsrc%2Findex.ts&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2F2bw6ws&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2F2bw6ws%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/97f936168d8e83de8ee73a046d81abcf/href">https://medium.com/media/97f936168d8e83de8ee73a046d81abcf/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a871109085c1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The transformation matrix in fabric.js]]></title>
            <link>https://medium.com/@luizzappa/the-transformation-matrix-in-fabric-js-fb7f733d0624?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/fb7f733d0624</guid>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[javascript-development]]></category>
            <category><![CDATA[fabricjs]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sun, 08 Jan 2023 16:19:18 GMT</pubDate>
            <atom:updated>2023-01-08T17:12:32.811Z</atom:updated>
            <content:encoded><![CDATA[<p>Recently I’m using the excellent <a href="http://fabricjs.com/">fabric.js library</a> for a side project. I came across the <strong>transformation matrix</strong>. The <strong>purpose </strong>of this article is to detail <strong>how the transformation matrix works and especially how did we arrive at the transformation matrix, why is it in the format we know?</strong> Although this is an explanation in the context of fabric.js, it’s extensible to other contexts (perhaps with slight implementation differences).</p><p>Since not everyone has a background in mathematics (it’s actually high school math, but some people might barely remember), I will start by explaining all the fundamentals so that any reader can follow this article. Feel free to skip some part if you find it too basic.</p><h3>Base vectors of a Cartesian plane</h3><p>We already saw what <strong>Cartesian planes</strong> are in the <a href="https://medium.com/@luizzappa/display-angle-while-rotating-an-object-in-fabric-js-1156370e2b77">previous article</a> (if you are not familiar whit this concept and with <strong>vectors</strong>, I <strong>strongly recommend</strong> check out this article <strong>before continuing reading</strong>).</p><p>In a Cartesian coordiante system there are <strong>standard unit vectors</strong> in the direction of the <strong>X</strong> and <strong>Y</strong> axis. The unit vector in the <strong>X</strong> axis is represented as <strong>î</strong> (<em>hat i</em>) and the unit vector in <strong>Y</strong> axis as <strong>ĵ</strong> (<em>hat j</em>).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/258/1*ueiLVkw5OZ4VFHMt3zAqXg.png" /><figcaption>Standard unit vectors</figcaption></figure><blockquote><strong>Unit vectors</strong>: vectores that have a length of 1. By convention every unit vector is represented with a hat. For instance: â, î,…</blockquote><p>The unit vectors <strong>î</strong> and <strong>ĵ </strong>are the bases of our coordinate sytem, and <strong>we can represent any vector in terms of them</strong>.</p><p>Let’s look at an example. Here we will use the <strong>top left origin</strong>, that is, the positive diretion of the Y axis is downards, like the canvas in the fabric.js. Representing the point (2, 2) in this plane:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/252/1*c-09fNoZWVFsUUjQVZfV-Q.png" /><figcaption>Plot point (2, 2)</figcaption></figure><p>Now let’s draw a vector from the origin point (0, 0) of our plane to the point (2, 2):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/277/1*ume1PA4zeyn_lH0OKyvQbA.png" /></figure><p>We can represent the vector [2, 2] in terms of the base of our plane:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/289/1*lJaeOLmePfjBniv51P-CHQ.png" /><figcaption>Vector [2, 2] in terms of <strong>î</strong> and <strong>ĵ</strong></figcaption></figure><p>That is, the vector [2, 2] is a linear combination of two <strong>î</strong> vectors and two <strong>ĵ </strong>vectors. Any vector in this plane can be represented as a linear combination of <strong>î</strong> and <strong>ĵ </strong>(the basis of our plane).</p><h3>Linear transformation</h3><p>A central concept for understading the transformation matrix is linear transformation:</p><blockquote><strong>Linear transformation </strong>alters our plane such that <strong>parallel lines stay parallel</strong> and <strong>equally spaced</strong>, and the <strong>origin doesn’t move</strong>.</blockquote><p>Let’s see in practice a linear transformation applied to our vector [2, 2]. For this, I created an animation using fabric.js (you can play around <a href="https://codesandbox.io/s/linear-transformation-ptm5gk">here</a>). When performing a rotation transformation with shear, we can see after transformation our vector remains equal to two steps in the direction of the transformed <strong>X</strong> axis (two <strong>î</strong> vectors) and two steps in the direction of the transformed <strong>Y</strong> (two <strong>ĵ </strong>vectors).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5YZtm06DqjSZF46EyA8Hkw.gif" /><figcaption>Linear transformation in action</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fTuRHF04b5G_GyPSD3DKXw.png" /></figure><p>The <strong>blue grids (dark and light) </strong>represent our <strong>transformed plane</strong>, and the <strong>light gray grids</strong> represent <strong>our untransformed plane</strong>. The values of <strong>î</strong>, <strong>ĵ</strong>, <strong>v</strong> vectors are calculated relative to the untransformed plane. Notice that the values of these vectors change relative to the untransformed plane. For example, the vector <strong>î</strong> before transformation was [1 0] and after transformation is [0.9397 -0.342]. But even with the transformation, the vector <strong>v</strong> remained equal to 2<strong>î</strong> + 2<strong>ĵ</strong> and the origin remained the same. That is, this is a <strong>linear transformation</strong>.</p><h3>Linear transformation with matrices</h3><p>As we have seen, to describe a linear transformation we only need to keep track where the vectors <strong>î</strong> and 2x2 transformation matrix<strong> </strong>go and calculate the coordinates with respect to the untransformed plane.</p><p>The matrix is nothing more than packing these two vectors together to make the calculation easier. It’s called a two-by-two matrix that describes a 2D linear transformation. The first column of this matrix describes the vector <strong>î </strong>after the transformation and the second column of the matrix describes the vector <strong>ĵ</strong> after the transformation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/173/1*dgyTtNhmV3paTURa_tuopQ.png" /><figcaption>2x2 transformation matrix</figcaption></figure><p>If we want to find out where a vector is located after the transformation, all we need to do is multiply our original vector by the 2x2 transformation matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/534/1*AiR9yEdM5u3x4mUsBZMdCA.png" /><figcaption>Applying transformation matrix to original vector to find final position</figcaption></figure><p>Note that we find the final position of the vector after the transformation without having to work with the Cartesian plane. This makes the calculation process much easier and faster. In an animated way, we can map the calculation from the Cartesian plane to matrices as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/730/1*RoyWJR23ROaeWzOwxPBBgA.gif" /><figcaption>Cartesian plane calculation vs matrix-wise</figcaption></figure><p>If you need a matrices review, I recommend these videos:</p><ul><li><a href="https://www.youtube.com/watch?v=yRwQ7A6jVLk">Introduction to matrices</a></li><li><a href="https://www.youtube.com/watch?v=vzt9c7iWPxs">Multiplying matrices</a></li><li><a href="https://www.youtube.com/watch?v=sT9IgHjjhiY">Inverse matrix introduction</a></li></ul><p>Let us now see some specific matrices and their respective transformations.</p><h3>Transformation: scaling</h3><p>When applying a scale on the <strong>X</strong> axis and a scale on the <strong>Y</strong> axis, the unit vectors <strong>î</strong> and <strong>ĵ</strong> after the transformation will be:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/325/1*2_HJ5cx7q01HvcsWKZyIXQ.png" /><figcaption>Scaling transformation</figcaption></figure><p>We can pack this transformation into a matrix that is called a scaling matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/183/1*8uuZFmzxxYjBGPi95hzSCQ.png" /><figcaption>Scaling matrix</figcaption></figure><p>For example, applying scaleX = 0.8 and scaleY = 0.8:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IRnJ4DBfUNdkexkayA0zAg.gif" /><figcaption>Scaling transformation in fabric.js</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NnubB3bPkwj2FMYJcIFYYA.png" /></figure><h3>Transformation: flip</h3><p>This operation is similar to scaling. We just need to invert one of the coordinates for horizontal or vertical inversion (or both) to reflect about the origin.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/405/1*lxHu4Tdz1wzxHUpJ0Tr_6Q.png" /><figcaption>Flip transformation</figcaption></figure><p>The flip in the transformation matrix boils down to the inversion of the sign on the respective scale axis:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*04O8KYOiMHFIxw9mzGlbtQ.png" /><figcaption>Flip matrix</figcaption></figure><p>For instance, applying flipY:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6YiMUYJ_izcBiHSPr_l-QQ.gif" /><figcaption>Flip transformation in fabric.js</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qaYXVHvLsmt0kdkgJS4aIw.png" /></figure><h3>Transformation: skewing</h3><p>First, let’s understand how shear occurs. By performing a shear along the <strong>X</strong> axis (skewX) from an alpha angle at the point(x, y), our transformed point will be offset by a delta X value:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/315/1*LjSmcXmtgbk4JX9wQtObeg.png" /><figcaption>skewX of alpha</figcaption></figure><p>To calculate the value of delta X, just a little geometry:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/348/1*Dls9LJhmbAKortlsUC4b5g.png" /><figcaption>Calculation of delta X</figcaption></figure><p>We know the tangent of alpha (= skewX) and the value of y, we can write delta X in terms of both:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/429/1*QJhj0zh427A5BVL4CFKdtQ.png" /></figure><p>That is, a shear transform ( skewX) on the <strong>X</strong> axis applied to a point(x, y) will transform it into:</p><blockquote><strong>(x, y) = (x + y * Tan(skewX), y)</strong></blockquote><p>The deduction of the shear formula along the Y axis is the same. In order to not get too long, I won’t disassemble it, but the result after a skewY transformation at the point(x, y) will be:</p><blockquote><strong>(x, y) = (x, y + x * Tan(skewY))</strong></blockquote><p>The more attentive reader may be wondering what happens if we apply skewX and skewY at the same time. We have to determine an order of operation, otherwise we will have a circular reference. In the case of the canvas (and fabric.js), first we apply the skewY and then the skewX:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/670/1*D0CA7804gn6Uf2RHm3hGlQ.png" /></figure><p>So, we can write the shear transformation in the unit vectors as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/475/1*D0sAL2GP5wQISCBInY4gdA.png" /><figcaption>Skew transformation</figcaption></figure><p>We can pack this transformation into a matrix that is called shear matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/345/1*2MBY4_a2V1Z5KNm9lphhrw.png" /><figcaption>Shear matrix</figcaption></figure><p>For example, applying skewX = -20 and skewY = -10:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QJ5jjkvg3BTN1i2rA1s3Hg.gif" /><figcaption>Shear transformation in fabric.js</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CMf9rrqhzk1Q5IdqdK9LgA.png" /></figure><h3>Transformation: rotation</h3><p>As expected, the transformation due to a rotation is a function of the angle of the rotation. The calculation of vectors values is found through trigonometry:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/349/1*P7KbZNLWRZF69veY09R5gw.png" /><figcaption>Rotation transformation</figcaption></figure><p>We can pack this transformation into a matrix that is called rotation matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/221/1*kn-qOocP7bYAxUr1Pi4X2g.png" /><figcaption>Rotation matrix</figcaption></figure><h3>Transformation: translate</h3><p>Translation moves our coordinate system to another location, i.e. changes the origin. And by definition, by changing the origin, it’s not a linear transformation, so we cannot describe this transformation in our linear transformation matrix.</p><p>To represent this transformation, we create a vector from the origin of the plane to the new origin of the translated plane:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/318/1*zjrukfOCwHObYze8eFfRSw.png" /><figcaption>Vector pointing to the translated plane</figcaption></figure><p>It may seem a little strange that the vectors <strong>î</strong> and <strong>ĵ </strong>continue with the values [1 0] and [0 1], but here we are talking about different planes.</p><p>And how do we represent translation in matrix format? We can combine our two-by-two linear transformation matrix with the translation into a single matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/479/1*m8-e6fIWn3OOlz-iD0oFdg.png" /><figcaption>Translation matrix</figcaption></figure><p>Notice that an extra line appeared at the end of our matrix, we have increased the matrix to make it compatible with translation operations.</p><p>Now if we are going to multiply a vector by this transformation matrix, we must also add one more line in the matrix that represents this vector to allow the matrix operations:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/228/1*6-NnGDUGWhVQ3KEGnQCn3g.png" /></figure><h3>Composing multiple transformations</h3><p>We can compose a serie of transformations in sequence. For example, a scaling, then a rotation and finally a shear: (<em>note that we are now working with the augmented transformation matrix to accommodate the translation</em>)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/336/1*adC495VNqq66TmcEf7haDQ.png" /><figcaption>Transformation matrices</figcaption></figure><p>For this, we multiply each of the transformation matrices to arrive at the final transformation matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/566/1*fQu5huEgvXdSEqIvyCDKoA.png" /><figcaption>Final transformation matrix</figcaption></figure><blockquote>The matrix multiplication order makes difference in the final result. That is, matrix A times matrix B is different from matrix B times matrix A.</blockquote><p>The order of multiplication is important, the first transformation start rightmost. For example, if we reversed the order of multiplication, the final result would be different:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BeMNdnpCs62HrEAQug89_g.png" /><figcaption>Different results when we change the order of multiplication</figcaption></figure><p>But why when we do something like code below in fabric.js the end result is always the same?</p><pre>const rect1= new fabric.Rect({ <br>  width: 100, <br>  height: 100, <br>  scaleX: 1,<br>  scaleY: 1,<br>  skewX: 1,<br>  skewY: 1,<br>  angle: 0<br>});<br><br>const rect2= new fabric.Rect({ <br>  width: 100, <br>  height: 100, <br>  scaleX: 1,<br>  scaleY: 1,<br>  skewX: 1,<br>  skewY: 1,<br>  angle: 0<br>});<br><br>canvas.add(rect1, rect2);<br><br>rect1.set({scaleX: 2});<br>rect1.set({angle: 30});<br>rect1.set({skewX: 45});<br><br>rect2.set({skewX: 45});<br>rect2.set({angle: 30});<br>rect2.set({scaleX: 2});<br><br>canvas.renderAll();</pre><p>We are applying the transformations in different order, but the end result is the same. The reason is that there is a <strong>fixed transformation order for objects, regardless of whether we set one of the transformations before or after</strong>. The order used internally by fabric.js is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z-KifQefOCf2ES9HI03FDw.png" /><figcaption>Fabric.js transformation order</figcaption></figure><p>First apply skew, then scaling, rotation and finally translation. We can change this order when working with groups, we’ll see later in this article.</p><h3>Undoing transformations</h3><p>We can undo certain transformations directly in the final transformation matrix. For this we use a property of matrices which is: <strong>the multiplication of matrix (M) by its inverse (M-¹) produces an identity matrix (I)</strong>. Note that the order of multiplication in this case produces the same result:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/204/1*fSiPu7zaPxHMVXYgI1sVPA.png" /><figcaption>Matrix times its inverse</figcaption></figure><p>The identity matrix does not change the result of a matrix multiplication. That is, matrix A multiplied by identity matrix (I) remains matrix A: (<em>again the order of multiplication is indifferent</em>)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/89/1*G1QGv29lZmh2h918bo2Muw.png" /><figcaption>Matrix times identity</figcaption></figure><p>So if we have the following transformation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/352/1*94ZkDeFwrQaJ3fXWrXYCEg.png" /></figure><p>And we want to remove the scale transformation, just multiply by the inverse of the scale matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/460/1*iNkvRIuByk9nBKcmGHPyxw.png" /><figcaption>Removing scale</figcaption></figure><p>One important thing is that we must multiply the inverse of the transformation matrix next to the original matrix. For example, if we wanted to remove skew, the first case is incorrect and the second correct:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/547/1*lTrsbemJMrzy8Vb7HN5EPg.png" /><figcaption>Removing skew</figcaption></figure><p>Fabric.js has functions to assist with these operations:</p><ul><li><strong>fabric.util.invertTransform</strong>: calculates the inverse of a transformation matrix.</li><li><strong>fabric.util.multiplyTransformMatrices</strong>: multiplies two transformation matrices.</li><li><strong>fabric.util.transformPoint</strong>: apply transform to a point.</li></ul><p>As we saw, for transformations in a single object fabric.js abstracts the entire transformation process. But when we work with different planes, knowing these operations is important.</p><h3>The transformation matrix in fabric.js</h3><p>The transformation matrix in fabric.js is represented in an array of length 6 as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/346/1*KujYr4lEnSXSP3m8leDJrg.png" /></figure><p>There are a few ways to calculate the transformation matrix of an object in fabric.js. It depends on which plane we are referring to:</p><pre>const rect = new fabric.Rect({ <br>  width: 100, <br>  height: 100, <br>  scaleX: 1,<br>  scaleY: 1,<br>  skewX: 1,<br>  skewY: 1,<br>  angle: 0<br>});<br><br>// Transformation matrix in the object plane<br>rect.calcOwnMatrix();<br>// Transformation matrix from the canvas plane to the object plane<br>rect.calcTransformMatrix();</pre><p><strong>calcOwnMatrix</strong>: returns the transformation matrix in the object plane, without considering external transformations.</p><p><strong>calcTransformMatrix</strong>: returns the transformation matrix from the canvas plane to the object plane. If the object is inside a group, it will consider these transformations as well.</p><p>Later on we will see in more details the multiple planes that exist on the canvas and these concepts will become clearer.</p><p>Let’s see the transformation matrix in action in fabric.js:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Ftransformation-matrix-echhfe&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2Fechhfe&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2Fechhfe%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/fb786ecf41ab480b72cf2124a3bc8584/href">https://medium.com/media/fb786ecf41ab480b72cf2124a3bc8584/href</a></iframe><h3>The different Cartesian planes in fabric.js</h3><p>We will work with different Cartesian planes and see how the transformation matrix allows us to locate points on these different planes.</p><p>Let’s start with an example. Adding a rectangle to the canvas:</p><pre>const rect = new fabric.Rect({<br>  width: 50,<br>  height: 50,<br>  fill: &#39;red&#39;,<br>  strokeWidth: 0,<br>  originX: &#39;left&#39;,<br>  originY: &#39;top&#39;,<br>  left: 25,<br>  top: 25<br>});<br>canvas.add(rect);</pre><p>Our canvas is a Cartesian plane (origin is top left, so the Y axis grows downwards). When we add a rectangle, behind the scene we are <strong>defining a new plane</strong> and set of 4 points inside it that form our rectangle:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/391/1*RSy_AsLAeBDHzNtGVGJ1rw.png" /><figcaption>Canvas plane and rect plane</figcaption></figure><p>Now we have two planes:</p><ul><li>The canvas plane (has gray arrows as axes)</li><li>The rectangle plane (orange arrows as axes)</li></ul><p>Note that <strong>the plane of an object has its center as the origin</strong>, so it is located at the point (50, 50). This is a fabricj.js design choice. Each of these planes has a transformation matrix that allow us to apply the existing transformations in that plane to a point. If we calculate the transformation matrix of the object and the canvas:</p><pre>// Canvas plane tranformation matrix<br>canvas.viewportTransform<br>// [1, 0, 0, 1, 0, 0]<br><br>// Object plane tranformation matrix <br>rect.calcTransformMatrix();<br>// [1, 0, 0, 1, 50, 50]</pre><p>As we can see, in the plane of the object, translateX and translateY point to the center of the object which is the center of the plane.</p><p>In the previous image we are observing the points in relation to the canvas plane. What it we observed the same points but in relation to the plane of the rectangle? The representation would look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/397/1*1F1m0uNd7nN27s__kPEHqQ.png" /><figcaption>The points relative to the plane of the rectangle</figcaption></figure><p>Let’s apply what we learn. I have two rectangles with two different transformations (one with scale and one with rotation):</p><pre>const rect_1 = new fabric.Rect({<br>  width: 50, <br>  height: 50, <br>  fill: &#39;red&#39;, <br>  left: 10, <br>  top: 10,<br>  scaleX: 3<br>});<br><br>const rect_2 = new fabric.Rect({<br>  width: 50, <br>  height: 50, <br>  fill: &#39;red&#39;, <br>  left: 10, <br>  top: 100,<br>  angle: 30<br>});<br><br>canvas.add(rect_1, rect_2);<br></pre><p>I want to create a line from the center to the top right corner. How could I do this using transformation matrix?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/495/1*m04jCbQoCcMA4w975IsB9w.png" /><figcaption>Our goal</figcaption></figure><p>Try to think a little before looking at the solution…</p><p>Let’s look at one of the ways to achieve this. We first look at the untransformed plane of the object. We want to draw a vector from the origin of this plane, point(0, 0), to the point(25, -25):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/230/1*qSls-hKvCLyIShBQtpx8kg.png" /><figcaption>Object’s untransformed plane</figcaption></figure><p>When we are creating the line, we are on the canvas plane. That is, we need to find these points on the canvas plane. To do this, just multiply by the transformation matrix of each of the rectangles:</p><pre>// Points of interest in the untransformed plane of objects<br>const centerPoint = new fabric.Point(0, 0),<br>  cornerPoint = new fabric.Point(25, -25);<br><br>// The same points of interest now on the canvas plane<br>// As each rectangle has a different transformation,<br>// to map to the canvas plane we need to multiply by<br>// each of the transformation matrices.<br>const rect_1Center = centerPoint.transform(rect_1.calcTransformMatrix()),<br>  rect_1CornerPoint = cornerPoint.transform(rect_1.calcTransformMatrix());<br><br>const rect_2Center = centerPoint.transform(rect_2.calcTransformMatrix()),<br>  rect_2CornerPoint = cornerPoint.transform(rect_2.calcTransformMatrix());  <br><br>// Then we create the lines using the points on the canvas plane<br>const line_1 = new fabric.Line(<br>  [<br>    rect_1Center.x, // line origin.x<br>    rect_1Center.y, // line origin.y<br>    rect_1CornerPoint.x, // line destination.x<br>    rect_1CornerPoint.y // line destination.y<br>  ], <br>  { stroke: &#39;blue&#39;, strokeWidth: 1, originX: &#39;center&#39;, originY: &#39;center&#39;}<br>);<br><br>const line_2 = new fabric.Line(<br>  [<br>    rect_2Center.x, <br>    rect_2Center.y, <br>    rect_2CornerPoint.x, <br>    rect_2CornerPoint.y<br>  ], <br>  { stroke: &#39;blue&#39;, strokeWidth: 1, originX: &#39;center&#39;, originY: &#39;center&#39;}<br>);<br><br>canvas.add(line_1, line_2);</pre><p>We also work with different planes when an object is inside a group. Each group represents a new plane and the transformation existing in the group is applied to all objects (or other groups) contained within it.</p><p>For example, let’s add two rectangles to a group and apply a scaleY equal to 2 to the group.</p><pre>const rect_1 = new fabric.Rect({<br>  width: 50, <br>  height: 50, <br>  fill: &#39;red&#39;, <br>  left: 10, <br>  top: 10,<br>  scaleX: 3<br>});<br><br>const rect_2 = new fabric.Rect({<br>  width: 50, <br>  height: 50, <br>  fill: &#39;red&#39;, <br>  left: 10, <br>  top: 100,<br>  angle: 30<br>});<br><br>const group = new fabric.Group(<br>  [rect_1, rect_2],<br>  {<br>    left: 30,<br>    top: 100,<br>    scaleY: 2<br>  }<br>)<br><br>canvas.add(group);</pre><p>Each of the rectangles and the group will have their respective transformation matrices ( calcOwnMatrix ), which not consider any external transformation (from another plane):</p><pre>console.log(rect_1.calcOwnMatrix()); <br>// [3, 0, 0, 1, 12.75, -54.333647796503186]<br>console.log(rect_2.calcOwnMatrix()); <br>// [0.8660254037844387, 0.49999999999999994, -0.49999999999999994, 0.8660254037844387, -54.416352203496814, 45]<br>console.log(group.calcOwnMatrix()); <br>// [1, 0, 0, 2, 119.25, 259.6672955930064]</pre><p>To find the total existing transformation in each of the rectangles, we must multiply all the transformation matrices that affect them. That is, we must multiply the transformation matrix of the group and that of the rectangle itself:</p><pre>console.log(fabric.util.multiplyTransformMatrices(<br>  group.calcOwnMatrix(), <br>  rect_1.calcOwnMatrix()<br>));<br>// [3, 0, 0, 2, 132, 151]<br><br>console.log(fabric.util.multiplyTransformMatrices(<br>  group.calcOwnMatrix(), <br>  rect_2.calcOwnMatrix()<br>));<br>// [0.8660254037844387, 0.9999999999999999, -0.49999999999999994, 1.7320508075688774, 64.83364779650319, 349.6672955930064]</pre><p>A point of attention when multiplying the transformation matrices is in the <strong>order of multiplication</strong>. The first transformation must be rightmost, so the last argument is the rectangle transformation matrix since it is the first transformation. This process of multiplying transformation matrices would get tedious if we had a lot of nested groups. Fabric.js has a method that calculate this for us:</p><pre>console.log(rect_1.calcTransformMatrix());<br>// [3, 0, 0, 2, 132, 151]<br><br>console.log(rect_2.calcTransformMatrix());<br>// [0.8660254037844387, 0.9999999999999999, -0.49999999999999994, 1.7320508075688774, 64.83364779650319, 349.6672955930064]</pre><p>Altough fabric.js provides us with many out-of-the-box resources, knowing how to work with transformation matrices allows us to develop interesting things.</p><p>Below are some video references on the subject that present the topic in a very visual way:</p><p><a href="https://www.youtube.com/watch?v=HgQzOmnBGCo">Matrices and Transformations — Math for Gamedev</a></p><p><a href="https://www.youtube.com/watch?v=kYB8IZa5AuE">Linear transformation and matrices</a></p><p><a href="https://www.youtube.com/watch?v=XkY2DOUCWMU">Matrix multiplication as compostion</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fb7f733d0624" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Display angle while rotating an object in fabric.js (introducing vectors)]]></title>
            <link>https://medium.com/@luizzappa/display-angle-while-rotating-an-object-in-fabric-js-1156370e2b77?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/1156370e2b77</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[fabricjs]]></category>
            <category><![CDATA[web]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sun, 11 Dec 2022 23:55:03 GMT</pubDate>
            <atom:updated>2023-02-25T00:20:44.824Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article we’ll enrich what we have developed in the <a href="https://medium.com/@luizzappa/custom-icon-and-cursor-in-fabric-js-controls-4714ba0ac28f">last article</a> (where we modified the cursor and the rotation icon). The purpose is to display the angle of the object as it’s rotated. For this, I will briefly present a review of vectors, which greatly facilitate development in fabric.js. Based on what we’ll learn in theory, we’ll develope this feature. In the end we will have:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/373/1*YIH5T39VEwIH575Ap19S4g.gif" /><figcaption>Display angle while rotating</figcaption></figure><h3>How to represent a point?</h3><p>First of all we must understand how to represent a point in 2D space and what a Cartesian system is. I like the battleship analogy. How do we refer to the position of a ship?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/452/1*F4n8Af_RRC6Lnsle0wCf6g.png" /><figcaption>Cartesian plane</figcaption></figure><p>We have two axes (I’ll call the horizontal as <strong>X</strong> an the vertical as <strong>Y</strong>) and we identify the ship’s position based on them. For example, there is part of a ship at point (2,4), the <strong>red dot</strong>. We can interpret this representation as starting from origin point (0,0), we move 2 squares in the direction of the X axis and 4 squares in the direction of the Y axis.</p><p>The Cartesian system is represented by two perpendicular lines (coordinate axis) and the point where they meet is the origin, point (0, 0).</p><h3>What is a vector?</h3><p>Simplistically, we can say that the vector is an arrow that starts from an origin (the <strong>tail</strong>) to a destinatin (the <strong>head</strong>). Using this definition, we can represent the position of the ship based on a vector:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/455/1*BYfBPvCoGIiscWK8rgr2FQ.png" /><figcaption>Vector representation</figcaption></figure><p>The <strong>coordinates of a vector</strong> is a pair of numbers that “give instructions” for how to get from the origin of the vector to its head. The first number (2) represents how far to walk along the X-axis, and the second number (4) represents how far to walk along the Y-axis.</p><p>The convention for distinguishing vectors from points is to represent vectors vertically with square brackets and points horizontally with parentheses:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/464/1*Mh4Xc7Tzy-T77gZ4XxolHQ.png" /><figcaption>Point and vector representations</figcaption></figure><p>We represent the vectors with letters with an arrow on top:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/88/1*H_-uYqY9ThHfWXDwlwjv6Q.png" /></figure><h3>The size of a vector</h3><p>An important property of vectors is their size. Still in the previous example, how we calculate the distance from the origin of the Cartesian plane to the red point. The answer to this question is the size of the orange vector.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/422/1*ue5L92b-KXXbYH1kbsPKDA.png" /></figure><p>To calculate the distance, d, just apply the Pythagorean formula:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/228/1*fFfXrzBzVk2uOfVXheKRzg.png" /></figure><p>Substituting the values we have:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/233/1*s3r29k-wW1rbBKiEjE-1Xw.png" /></figure><p>So:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/175/1*cMSnperZUxwCNIy0Nck2cg.png" /><figcaption>The size of the vector</figcaption></figure><blockquote>The size of the vector is also called the <strong>magnitude</strong>.</blockquote><h3>Operations with vectors: addition and subtraction</h3><p>Let’s look at the basic operations with vectors. If we want to leave the red point and go to the green one, how would we represent this displacement with vectors?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/444/1*TETVLT7ulIbCe-NJn5dX9w.png" /></figure><p>An important feature of vectors is that they can be <strong>shifted to any part of our Cartesian plane</strong>. To represent this displacement (from red dot to the green one), we will consider the red point as our origin, and move four squares backwards on the X axis and one square upwards on the Y axis. That is, the representation of our vector would be:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/448/1*NZcsz3nCT3DjEoBqaBw06g.png" /></figure><p>And how would we represent the vector starting from the origin of the Cartesian system, point (0, 0), to our final point (the green one)? We would just move two squares backwards along the X axis and five squares up along the Y axis. The representation would be the yellow vector:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/412/1*VZxb7ZiJo1jjp6DLOkqYAw.png" /></figure><p>We have just visualized how to <strong>operate on a vector addition</strong>. The yellow vector is the sum of the orange and purple vectors:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/172/1*uPR1w-wXkpzkEGVYocvVsA.png" /></figure><p>We can generalize vector addition as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/201/1*ULZgaZBV4ugDR3Qpj77sng.png" /><figcaption>Sum of vectors</figcaption></figure><p>Let’s see how this operation is in fabric.js:</p><pre>const orangeVector = new fabric.Point(2, 4),<br>  origin = new fabric.Point(2, 4),<br>  destination = new fabric.Point(-2, 5),<br>  purpleVector = fabric.util.createVector(origin, destination);<br><br>console.log(purpleVector);<br>// Point {x: -4, y: 1 }<br><br>// Addition<br>const yellowVector = orangeVector.add(purpleVector);<br><br>console.log(yellowVector);<br>// Point {x: -2, y: 5 }</pre><p>The createVector function takes the tail (origin) and head (destination) of the vector as arguments. Note that for the orange vector this function was not used, because as the origin is the point(0, 0), the vector will be numerically equal to the destination point (head). So, for simplicity we already assign directly to the point value instead of doing fabric.util.createVector(new fabric.Point(0, 0), new fabric.Point(2, 4)) which result in the same value.</p><p>The cool thing about this operation is that we can locate our end point (the green one), just by making relative moviments between vectors (the purple vector has the red point as its origin. This vector does not know the origin of our Cartesian plane). We could perform various “relative moves” with vectors and still find our final position relative to the origin of the Cartesian plane:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/382/1*yAwpyHRellj6RbWtHbR_SQ.png" /></figure><p>Knowing the addition operation, the subtraction is straightforward. For example, if we wanted to subtract the purple vector [-4 1]from the orange one, it would be enough to add the inverse of the purple vector [4 -1]:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/405/1*ct-HMHTJEnUnyxTU38CHhQ.png" /></figure><p>Now let’s look at the subtraction operation in fabric.js:</p><pre>const orangeVector = new fabric.Point(2, 4),<br>  origin = new fabric.Point(2, 4),<br>  destination = new fabric.Point(-2, 5),<br>  purpleVector = fabric.util.createVector(origin, destination);<br><br>console.log(purpleVector);<br>// Point {x: -4, y: 1 }<br><br>// Subtraction<br>const yellowVector = orangeVector.subtract(purpleVector);<br><br>console.log(yellowVector);<br>// Point {x: 6, y: 3 }</pre><h3>Operations with vectors: multiplication and division</h3><p>Multiplication and division operations are best understood by looking at the Cartesian plane. What would happen if we multiplied the vector v by two?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/383/1*k2v7TQhDZ8AoWxn-4plTHw.png" /></figure><p>We will have a vector that will be twice the size of the original vector:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/437/1*FFqbXgZV7NPTy0RfPZ_yug.png" /></figure><p>The logic for the division is the same. If we divided the vector by two, we would have a new vector that is half of the original vector:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/386/1*i0teYfT0Rr8TdOwEpmXI4g.png" /></figure><p>That is, we can generalize these operations as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/170/1*VNEOdIfHytB2K-luJJp-og.png" /><figcaption>Scaling a vector</figcaption></figure><blockquote>These operations are commonly known as <strong>scaling</strong>.</blockquote><p>Let’s look at the two operations in fabric.js:</p><pre>const orangeVector = new fabric.Point(2, 4);<br><br>/* Multiplication */<br>const twoTimesOrange = orangeVector.scalarMultiply(2);<br>console.log(twoTimesOrange);<br>// Point {x: 4, y: 8}<br><br>/* Division */<br>const halfOrange = orangeVector.scalarDivide(2);<br>console.log(halfOrange);<br>// Point {x: 1, y: 2 }</pre><p>Attention, in fabric.js there is another multiplication and division methods called <strong>multiply </strong>and<strong> divide</strong>. These types of multiplication and division are <strong>different</strong>, it’s a <a href="https://en.wikipedia.org/wiki/Hadamard_product_(matrices)">Hadamard product</a>, that is, the element X will be multiplied (or divided) by the corresponding X, and the same goes for the Y. This operation does not have the same result (and interpretation) as what we saw above. Let’s see an example of it:</p><pre>const v1 = new fabric.Point(2, 4),<br>  v2 = new fabric.Point (4, 5);<br><br>/* Multiplication */<br>const v1Timesv2 = v1.multiply(v2);<br>console.log(v1Timesv2);<br>// Point {x: 8, y: 20}<br><br>/* Division */<br>const v1Dividev2 = v1.divide(v2);<br>console.log(v1Dividev2);<br>// Point {x: 0.5, y: 0.8}</pre><h3>Rotating vectors</h3><p>The orange vector is rotated 63° degrees counterclockwise with respect to the X axis. And if we wanted to rotate it another 30° clockwise, how would we do it?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/487/1*dsNcCKkv0orPfJatUbG9yQ.png" /></figure><blockquote>In fabric.js, <strong>counterclockwise rotations</strong> have angles with <strong>positive values</strong> and <strong>clockwise rotations</strong> have angles with <strong>negative values</strong>.</blockquote><p>Mathematically, to find the rotation we should use the following formula:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/765/1*qDn-7-08c-qne88HnSqGUA.png" /></figure><blockquote>If you are interested in the mathematical proof of this formula, <a href="https://matthew-brett.github.io/teaching/rotation_2d.html">checkout this article</a>.</blockquote><p>As you can see, the formula is not so friendly. But when we encapsulate it in a function, it becomes mucho more intelligible.</p><p>For example, let’s rotate this vector in fabric.js. As you can see from the code, it’s pretty easy to keep track of what’s going on.</p><pre>const origin = new fabric.Point(0, 0),<br>  destination = new fabric.Point(2, 4),<br>  rotationAngle = fabric.util.degreesToRadians(-30);<br><br>const initialVector = fabric.util.createVector(origin, destination);<br>const rotatedVector = initialVector.rotate(rotationAngle);<br><br>console.log(rotatedVector);<br>// Point {x: 3.732050807568877, y: 2.464101615137755}</pre><p>When working with angles, most functions use <strong>radians instead of degrees</strong> (which we are more used to). The same goes to fabric.js. Because of this, fabric.js has two help functions that convert between these two units of measurement: fabric.util.degreesToRadians and fabric.util.radiansToDegrees.</p><h3>Implementing the feature</h3><p>Okay, enought theory, let’s get to practice and see how to implement this new feature using vectors.</p><p>First of all let’s create the angle display and then we’ll use vectors to position it.</p><p>Let’s create two more properties on our state object to store the current angle and wheter we’re in the middle of a rotation:</p><pre>const state = {<br>  lastAngleRotation: null,<br>  currentAngle: null,<br>  isRotating: false,<br>}</pre><p>To update these properties, we will extend the listener for when a rotation event is occurring. Also, when mouse up, we indicate that the rotating is over.</p><pre>canvas.on(&#39;object:rotating&#39;, function (e) {<br>  // ...<br>  state.isRotating = true;<br>  state.currentAngle = obj.angle;<br>});<br><br>canvas.on(&#39;mouse:up&#39;, function(opt) {<br>  state.isRotating = false;<br>})</pre><p>We could create the display using fabric.js objects (such as Text and the background with a Rect). However, this way the object’s controls will be in front of our display. Because of this, we will draw directly on the canvas:</p><pre>canvas.on(&#39;after:render&#39;, function(opt) {<br>  state.isRotating &amp;&amp; renderRotateLabel(opt.ctx, canvas);<br>})</pre><p>Every time we are in the middle of a rotation we will render the display on the canvas. Let’s create the function that draws the display. It receives as an argument the context of the canvas (which has the methods to draw on it) and the reference to the canvas itself.</p><pre>function renderRotateLabel(ctx, canvas) {<br>  const angleText = `${state.currentAngle.toFixed(0)}°`,<br>    borderRadius = 5,<br>    rectWidth = 32,<br>    rectHeight = 19,<br>    textWidth = 6.01 * angleText.length - 2.317;<br><br>  const pos = new fabric.Point(20, 20);<br><br>  ctx.save();<br>  ctx.translate(pos.x, pos.y);<br>  ctx.beginPath();<br>  ctx.fillStyle = &quot;rgba(37,38,39,0.9)&quot;;<br>  ctx.roundRect(0, 0, rectWidth, rectHeight, borderRadius);<br>  ctx.fill();<br>  ctx.font = &quot;400 13px serif&quot;<br>  ctx.fillStyle = &quot;hsla(0,0%, 100%, 0.9)&quot;;<br>  ctx.fillText(angleText, rectWidth/2 - textWidth/2, rectHeight/2 + 4)<br>  ctx.restore();<br>}</pre><p>Let’s understand this function. First we use a template literal to create the text that will be displayed. We also define the dimensions of the rectangle that will be the background of our display, the radius of its border and finally the size of the text that will be rendered.</p><p>In the pos constant we define the position that will be rendered.</p><p>The ctx.save() function stores the drawing settings on the canvas so we can restore them later.</p><p>The ctx.translate function moves the position to the one defined in the pos constant.</p><p>Then we start our drawing with ctx.beginPath(), define the color of the fill property and draw a rectangle with rounded edges at position (0, 0) which is the position defined in pos, since we translated to this position.</p><p>The ctx.fill() paints the rectangle.</p><p>Then we define the properties of the text that will be rendered. The text position is centered on the rectangle with rectWidth/2 — textWidth/2 and rectHeight/2 + 4.</p><p>Finally, we restore the previous properties with ctx.restore().</p><p>We get the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/549/1*CaDOUS3d-1D1Cq06zkCX7g.gif" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fembed%2Fpreview%2FwvXbaMq%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DwvXbaMq&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fpen%2FwvXbaMq&amp;image=https%3A%2F%2Fshots.codepen.io%2FTheluiz-eduardo%2Fpen%2FwvXbaMq-512.jpg%3Fversion%3D1670799752&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/1f03589f5b3afc5954b156ef1f9f6d8b/href">https://medium.com/media/1f03589f5b3afc5954b156ef1f9f6d8b/href</a></iframe><p>Now let’s position this display according to the mouse position. I want it to be 40 pixels away from the mouse cursor at a 30 degrees clockwise angle.</p><p>We need to store the cursor position, for that we will create a new property in the state object. And inside rotation listener, we’ll update this property:</p><pre>const state = {<br>  // ...<br>  cursorPos: new fabric.Point() <br>}<br><br>canvas.on(&#39;object:rotating&#39;, function (e) {<br>  // ...<br>  state.cursorPos.x = e.pointer.x;<br>  state.cursorPos.y = e.pointer.y;<br>});</pre><p>Let’s create a function just to render the placement. Then we apply the same logic to our display.</p><pre>function renderVector(ctx, canvas) {<br>  const pos = state.cursorPos.add(<br>      new fabric.Point(40, 0)<br>    );<br>  ctx.save();<br>  ctx.beginPath();<br>  ctx.moveTo(state.cursorPos.x, state.cursorPos.y);<br>  ctx.strokeStyle = &quot;red&quot;;<br>  ctx.lineWidth = 2;<br>  ctx.lineTo(pos.x, pos.y);<br>  ctx.stroke();<br>  ctx.restore();<br>}</pre><p>Let’s understand what’s going on. The canvas has the top left origin, so we can represent our cartesian plan as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/487/1*LKdMGRU5qeLffOO0g4qP_Q.png" /></figure><p>Starting from the origin of our canvas, the vector stored in the state.cursorPos property points to the mouse cursor. Then we add a vector that shifts 40 pixels to the right. This piece of code that does this:</p><pre>//...<br>const pos = state.cursorPos.add(<br>      new fabric.Point(40, 0)<br>    );<br>//..</pre><p>Let’s render this function we defined:</p><pre>canvas.on(&quot;after:render&quot;, function (opt) {<br>  state.isRotating &amp;&amp; renderRotateLabel(opt.ctx, canvas);<br>  state.isRotating &amp;&amp; renderVector(opt.ctx, canvas);<br>});</pre><p>The result will be this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/483/1*4MS_lqQScsDMyR9Pa8gNCA.gif" /></figure><p>Now we need to rotate it 30 degrees clockwise. For this we do:</p><pre>// ...<br>  const pos = state.cursorPos.add(<br>    new fabric.Point(40, 0)<br>    .rotate(fabric.util.degreesToRadians(30))<br>  );<br>// ...</pre><p>The result will be this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/483/1*uiAH7P0kuxNsCKg6l2HXhA.gif" /></figure><p>Let’s now use this placement for our display. For this, in the renderRotateLabel function we change the constant pos by:</p><pre>// ...<br>  const pos = state.cursorPos.add(<br>      new fabric.Point(40, 0)<br>        .rotate(fabric.util.degreesToRadians(30))<br>    );<br>// ...</pre><p>Okay, now our display is in the desired position:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/483/1*G5glv4bD8SPOPq3rRJrCGw.gif" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fdisplay-angle-part2-9ju23t&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2F9ju23t&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2F9ju23t%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/a5bf254e2f966c1419930b8b439bbef6/href">https://medium.com/media/a5bf254e2f966c1419930b8b439bbef6/href</a></iframe><p>But it’s still not cool. When the mouse leaves the canvas, the display also disappears. Let’s make it always visible on the screen even when the mouse leaves. For that, inside the renderRotateLabel function we change the ctx.translate to be limited to the viewport.</p><pre>const { tl, br } = canvas.vptCoords;<br><br>// ...<br>  ctx.translate(<br>    Math.min(<br>      Math.max(pos.x, tl.x),<br>      br.x - rectWidth<br>    ),<br>    Math.min(<br>      Math.max(pos.y, tl.y),<br>      br.y - rectHeight<br>    )<br>  );<br>// ...</pre><p>Done, now the display does not leave the viewport:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/483/1*FhJ7a2Pu8XFqz4bntH7Q8Q.gif" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fdisplay-angle-part3-78vtnx&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2F78vtnx&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2F78vtnx%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a7893417e2f6a71050a27ff9c3c7f97/href">https://medium.com/media/8a7893417e2f6a71050a27ff9c3c7f97/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1156370e2b77" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom icon and cursor in fabric.js controls]]></title>
            <link>https://medium.com/@luizzappa/custom-icon-and-cursor-in-fabric-js-controls-4714ba0ac28f?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/4714ba0ac28f</guid>
            <category><![CDATA[canvas]]></category>
            <category><![CDATA[fabricjs]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Fri, 09 Dec 2022 21:47:13 GMT</pubDate>
            <atom:updated>2022-12-12T02:03:38.523Z</atom:updated>
            <content:encoded><![CDATA[<p>What’s cool about the <a href="http://fabricjs.com/">fabric.js</a> is the possibility of customization. In this article we’ll change both the <strong>icon </strong>that rotates the object and the <strong>mouse cursor </strong>when hovering over it that will change according to the angle of rotation.</p><p>The end result will be this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/373/1*fap0Xs3V8p30A6DsAYGqdw.gif" /></figure><h3>Initializing the canvas</h3><p>First, let’s initialize the canvas and add an object in the center of it:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fembed%2Fpreview%2FabKxpJw%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DabKxpJw&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fpen%2FabKxpJw&amp;image=https%3A%2F%2Fshots.codepen.io%2FTheluiz-eduardo%2Fpen%2FabKxpJw-512.jpg%3Fversion%3D1670614000&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/96483a2bedf2d13f8cbbb73478d5eb5f/href">https://medium.com/media/96483a2bedf2d13f8cbbb73478d5eb5f/href</a></iframe><h3>Customizing the rotation icon</h3><p>Now let’s change the object’s rotate icon to this one:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/86/1*41Fhkah5YbiCF2C-vU5rSQ.png" /><figcaption>New rotate icon</figcaption></figure><p>We could reference the icon directy from a path. But for simplicity, let’s leave it hardcoded in the rotateIcon variable. After that, we create a new image DOM element and assign out image data to the source property.</p><pre>const svgRotateIcon = encodeURIComponent(`<br>&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 18 18&quot; fill=&quot;none&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;<br>  &lt;g filter=&quot;url(#filter0_d)&quot;&gt;<br>    &lt;circle cx=&quot;9&quot; cy=&quot;9&quot; r=&quot;5&quot; fill=&quot;white&quot;/&gt;<br>    &lt;circle cx=&quot;9&quot; cy=&quot;9&quot; r=&quot;4.75&quot; stroke=&quot;black&quot; stroke-opacity=&quot;0.3&quot; stroke-width=&quot;0.5&quot;/&gt;<br>  &lt;/g&gt;<br>    &lt;path d=&quot;M10.8047 11.1242L9.49934 11.1242L9.49934 9.81885&quot; stroke=&quot;black&quot; stroke-width=&quot;0.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;/&gt;<br>    &lt;path d=&quot;M6.94856 6.72607L8.25391 6.72607L8.25391 8.03142&quot; stroke=&quot;black&quot; stroke-width=&quot;0.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;/&gt;<br>    &lt;path d=&quot;M9.69517 6.92267C10.007 7.03301 10.2858 7.22054 10.5055 7.46776C10.7252 7.71497 10.8787 8.01382 10.9517 8.33642C11.0247 8.65902 11.0148 8.99485 10.9229 9.31258C10.831 9.63031 10.6601 9.91958 10.4262 10.1534L9.49701 11.0421M8.25792 6.72607L7.30937 7.73554C7.07543 7.96936 6.90454 8.25863 6.81264 8.57636C6.72073 8.89408 6.71081 9.22992 6.78381 9.55251C6.8568 9.87511 7.01032 10.174 7.23005 10.4212C7.44978 10.6684 7.72855 10.8559 8.04036 10.9663&quot; stroke=&quot;black&quot; stroke-width=&quot;0.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;/&gt;<br>  &lt;defs&gt;<br>  &lt;filter id=&quot;filter0_d&quot; x=&quot;0&quot; y=&quot;0&quot; width=&quot;18&quot; height=&quot;18&quot; filterUnits=&quot;userSpaceOnUse&quot; color-interpolation-filters=&quot;sRGB&quot;&gt;<br>    &lt;feFlood flood-opacity=&quot;0&quot; result=&quot;BackgroundImageFix&quot;/&gt;<br>    &lt;feColorMatrix in=&quot;SourceAlpha&quot; type=&quot;matrix&quot; values=&quot;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0&quot;/&gt;<br>    &lt;feOffset/&gt;<br>    &lt;feGaussianBlur stdDeviation=&quot;2&quot;/&gt;<br>    &lt;feColorMatrix type=&quot;matrix&quot; values=&quot;0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0&quot;/&gt;<br>    &lt;feBlend mode=&quot;normal&quot; in2=&quot;BackgroundImageFix&quot; result=&quot;effect1_dropShadow&quot;/&gt;<br>    &lt;feBlend mode=&quot;normal&quot; in=&quot;SourceGraphic&quot; in2=&quot;effect1_dropShadow&quot; result=&quot;shape&quot;/&gt;<br>  &lt;/filter&gt;<br>  &lt;/defs&gt;<br>&lt;/svg&gt;<br>`)<br>const rotateIcon = `data:image/svg+xml;utf8,${svgRotateIcon}`<br>const imgIcon = document.createElement(&#39;img&#39;);<br>imgIcon.src = rotateIcon;</pre><p>Now let’s customize the rotation icon. For that we access the prototype of the object controls. The control we are instered in is the mtr :</p><pre>// Changing rotation control properties<br>fabric.Object.prototype.controls.mtr = new fabric.Control({<br>  x: 0,<br>  y: -0.5,<br>  offsetX: 0,<br>  offsetY: -40,<br>  cursorStyle: &#39;crosshair&#39;,<br>  actionHandler: fabric.controlsUtils.rotationWithSnapping,<br>  actionName: &#39;rotate&#39;,<br>  render: renderIcon,<br>  cornerSize: 38,<br>  withConnection: true<br>});<br><br>// Defining how the rendering action will be<br>function renderIcon(ctx, left, top, styleOverride, fabricObject) {<br>  var size = this.cornerSize;<br>  ctx.save();<br>  ctx.translate(left, top);<br>  ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));<br>  ctx.drawImage(imgIcon, -size / 2, -size / 2, size, size);<br>  ctx.restore();<br>};</pre><p>Let’s understand each of these properties:</p><p>x and y: define the positiong of this control in relation to the center of the object, with the “unit of measurement” being the width and height respectively. That is, if I set x = 0.5, the object will be half a width to the right (from the center). If I set y = -0.5, the object will be half height up (remember that the canvas coordinates are <strong>top left</strong>).</p><p>offsetX and offsetY : how much offset (in screen pixels) we want in the direction of the X and Y axis respectively, starting from the x and y position defined in the previous property.</p><p>To illustrate, some examples of positioning:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/982/1*AvTY4z12q0gOWeX9WEb0tw.png" /><figcaption>Control placement</figcaption></figure><p>cursorStyle: defines what the cursor will look like when hovering this control (we’ll modify this later).</p><p>actionHandler: what is the behavior of this control. We’ll use fabric.js default rotation behavior.</p><p>actionName: the name of the action that the control will execute. You can use this name to listen somewhere else in the code this action.</p><p>render: function that will be called to render the icon. We are defining this function just below.</p><p>cornerSize: icon size.</p><p>withConnection: a boolean that indicates wheter we want a line connecting to the bouding box.</p><p>Still in the code above, we define how the rendering will be. We directly access the canvas context and draw the desired image.</p><p>Ready, we customize the rotation icon:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fembed%2Fpreview%2FeYKogyg%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DeYKogyg&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fpen%2FeYKogyg&amp;image=https%3A%2F%2Fshots.codepen.io%2FTheluiz-eduardo%2Fpen%2FeYKogyg-512.jpg%3Fversion%3D1670618267&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/3ccbe5c38bed653512906e612bb624ef/href">https://medium.com/media/3ccbe5c38bed653512906e612bb624ef/href</a></iframe><h3>Customizing the cursor while rotating</h3><p>Now we want to change the cursor to indicate that we are doing a rotation. For this we’ll use an SVG, because we’ll need to transform the SVG on the fly.</p><pre>const imgCursor = encodeURIComponent(`<br>  &lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39;&gt;<br>    &lt;defs&gt;<br>      &lt;filter id=&#39;a&#39; width=&#39;266.7%&#39; height=&#39;156.2%&#39; x=&#39;-75%&#39; y=&#39;-21.9%&#39; filterUnits=&#39;objectBoundingBox&#39;&gt;<br>        &lt;feOffset dy=&#39;1&#39; in=&#39;SourceAlpha&#39; result=&#39;shadowOffsetOuter1&#39;/&gt;<br>        &lt;feGaussianBlur in=&#39;shadowOffsetOuter1&#39; result=&#39;shadowBlurOuter1&#39; stdDeviation=&#39;1&#39;/&gt;<br>        &lt;feColorMatrix in=&#39;shadowBlurOuter1&#39; result=&#39;shadowMatrixOuter1&#39; values=&#39;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0&#39;/&gt;<br>        &lt;feMerge&gt;<br>          &lt;feMergeNode in=&#39;shadowMatrixOuter1&#39;/&gt;<br>          &lt;feMergeNode in=&#39;SourceGraphic&#39;/&gt;<br>        &lt;/feMerge&gt;<br>      &lt;/filter&gt;<br>      &lt;path id=&#39;b&#39; d=&#39;M1.67 12.67a7.7 7.7 0 0 0 0-9.34L0 5V0h5L3.24 1.76a9.9 9.9 0 0 1 0 12.48L5 16H0v-5l1.67 1.67z&#39;/&gt;<br>    &lt;/defs&gt;<br>    &lt;g fill=&#39;none&#39; fill-rule=&#39;evenodd&#39;&gt;&lt;path d=&#39;M0 24V0h24v24z&#39;/&gt;<br>      &lt;g fill-rule=&#39;nonzero&#39; filter=&#39;url(#a)&#39; transform=&#39;rotate(-90 9.25 5.25)&#39;&gt;<br>        &lt;use fill=&#39;#000&#39; fill-rule=&#39;evenodd&#39; xlink:href=&#39;#b&#39;/&gt;<br>        &lt;path stroke=&#39;#FFF&#39; d=&#39;M1.6 11.9a7.21 7.21 0 0 0 0-7.8L-.5 6.2V-.5h6.7L3.9 1.8a10.4 10.4 0 0 1 0 12.4l2.3 2.3H-.5V9.8l2.1 2.1z&#39;/&gt;<br>      &lt;/g&gt;<br>    &lt;/g&gt;<br>  &lt;/svg&gt;`);</pre><p>Inside fabric.Object.prototype.controls.mtr, we change the cursorStyle property to:</p><pre> cursorStyle: `url(&quot;data:image/svg+xml;charset=utf-8,${imgCursor}&quot;) 12 12, crosshair`,</pre><p>This code is pointing to the URL of our cursor (a data-image), the point of the origin, and a fallback ( crosshair).</p><p>We customize our cursor. But it’s still not cool, notice that when we rotate the object, the cursor doesn’t rotate with it. We’ll adjust that later.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/516/1*FP4BNIk8mhylfxkDxd68zQ.gif" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fembed%2Fpreview%2FBaVERNw%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DBaVERNw&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fpen%2FBaVERNw&amp;image=https%3A%2F%2Fshots.codepen.io%2FTheluiz-eduardo%2Fpen%2FBaVERNw-512.jpg%3Fversion%3D1670622866&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/bce487a349448194e0109523a1cb2565/href">https://medium.com/media/bce487a349448194e0109523a1cb2565/href</a></iframe><p>For the dynamic behavior we’ll use a tagged template that will generate an SVG rotated according to the provided angle. For performance reasons, we’ll rotate the SVG every 15 degrees. The treatAngle function that will do this normalization.</p><pre>// Tagged template<br>function mouseRotateIcon(angle) {<br>  const relativeAngle = angle - 90;<br>  const pos = {<br>    &#39;-90&#39;: &#39;9.25 5.25&#39;,<br>    &#39;-75&#39;: &#39;9.972 3.863&#39;,<br>    &#39;-60&#39;: &#39;10.84 1.756&#39;,<br>    &#39;-45&#39;: &#39;11.972 -1.716&#39;,<br>    &#39;-30&#39;: &#39;18.83 0.17&#39;,<br>    &#39;-15&#39;: &#39;28.49 -9.49&#39;,<br>    15: &#39;-7.985 46.77&#39;,<br>    30: &#39;-0.415 27.57&#39;,<br>    45: &#39;2.32 21.713&#39;,<br>    60: &#39;3.916 18.243&#39;,<br>    75: &#39;4.762 16.135&#39;,<br>    90: &#39;5.25 14.75&#39;,<br>    105: &#39;5.84 13.617&#39;,<br>    120: &#39;6.084 12.666&#39;,<br>    135: &#39;6.317 12.01&#39;,<br>    150: &#39;6.754 11.325&#39;,<br>    165: &#39;7.06 10.653&#39;,<br>    180: &#39;7.25 10&#39;,<br>    195: &#39;7.597 9.43&#39;,<br>    210: &#39;7.825 8.672&#39;,<br>    225: &#39;7.974 7.99&#39;,<br>    240: &#39;8.383 7.332&#39;,<br>    255: &#39;8.83 6.441&#39;,<br>  }, <br>    defaultPos = &#39;7.25 10&#39;;<br>  const transform = relativeAngle === 0<br>   ? &#39;translate(9.5 3.5)&#39;<br>   : `rotate(${relativeAngle} ${pos[relativeAngle] || defaultPos})`<br>  const imgCursor = encodeURIComponent(`<br>  &lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; xmlns:xlink=&#39;http://www.w3.org/1999/xlink&#39; width=&#39;24&#39; height=&#39;24&#39;&gt;<br>    &lt;defs&gt;<br>      &lt;filter id=&#39;a&#39; width=&#39;266.7%&#39; height=&#39;156.2%&#39; x=&#39;-75%&#39; y=&#39;-21.9%&#39; filterUnits=&#39;objectBoundingBox&#39;&gt;<br>        &lt;feOffset dy=&#39;1&#39; in=&#39;SourceAlpha&#39; result=&#39;shadowOffsetOuter1&#39;/&gt;<br>        &lt;feGaussianBlur in=&#39;shadowOffsetOuter1&#39; result=&#39;shadowBlurOuter1&#39; stdDeviation=&#39;1&#39;/&gt;<br>        &lt;feColorMatrix in=&#39;shadowBlurOuter1&#39; result=&#39;shadowMatrixOuter1&#39; values=&#39;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0&#39;/&gt;<br>        &lt;feMerge&gt;<br>          &lt;feMergeNode in=&#39;shadowMatrixOuter1&#39;/&gt;<br>          &lt;feMergeNode in=&#39;SourceGraphic&#39;/&gt;<br>        &lt;/feMerge&gt;<br>      &lt;/filter&gt;<br>      &lt;path id=&#39;b&#39; d=&#39;M1.67 12.67a7.7 7.7 0 0 0 0-9.34L0 5V0h5L3.24 1.76a9.9 9.9 0 0 1 0 12.48L5 16H0v-5l1.67 1.67z&#39;/&gt;<br>    &lt;/defs&gt;<br>    &lt;g fill=&#39;none&#39; fill-rule=&#39;evenodd&#39;&gt;&lt;path d=&#39;M0 24V0h24v24z&#39;/&gt;<br>      &lt;g fill-rule=&#39;nonzero&#39; filter=&#39;url(#a)&#39; transform=&#39;${transform}&#39;&gt;<br>        &lt;use fill=&#39;#000&#39; fill-rule=&#39;evenodd&#39; xlink:href=&#39;#b&#39;/&gt;<br>        &lt;path stroke=&#39;#FFF&#39; d=&#39;M1.6 11.9a7.21 7.21 0 0 0 0-7.8L-.5 6.2V-.5h6.7L3.9 1.8a10.4 10.4 0 0 1 0 12.4l2.3 2.3H-.5V9.8l2.1 2.1z&#39;/&gt;<br>      &lt;/g&gt;<br>    &lt;/g&gt;<br>  &lt;/svg&gt;`)<br>  return `url(&quot;data:image/svg+xml;charset=utf-8,${imgCursor}&quot;) 12 12, crosshair`<br>}<br><br>function treatAngle(angle) {<br>  return angle - angle % 15<br>}</pre><p>Inside fabric.Object.prototype.controls.mtr, we delete the cursorStyle property and create the cursorStyleHandler property which expects a function that will define how our cursor will be.</p><pre>// Inside fabric.Object.prototype.controls.mtr<br>//...<br>cursorStyleHandler: rotationStyleHandler,<br>//...<br><br>// Define how the cursor will be<br>function rotationStyleHandler(eventData, control, fabricObject) {<br>  if (fabricObject.lockRotation) {<br>      return NOT_ALLOWED_CURSOR;<br>  }<br>  const angle = treatAngle(fabricObject.angle);<br>  return mouseRotateIcon(angle)<br>}</pre><p>Now we have a function that dynamically changes the cursor’s rotation. But this is still not enought, as this function is not invoked when we are rotating. Because of this we need to listend to the object’s rotating event to change our cursor. This code does this:</p><pre>canvas.on(&#39;object:rotating&#39;, function (e) {<br>  const angle = treatAngle(e.target.angle);<br>  canvas.setCursor(mouseRotateIcon(angle));<br>});</pre><p>Now our cursor rotates together with the object:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/373/1*fap0Xs3V8p30A6DsAYGqdw.gif" /></figure><p>But we can still improve the performance of our code. We are invoking the mouseRotationIcon function unnecessarily every rotation. In fact, we only need to call this function when the angle changes in the range of 15. That is, we only need to call this funtion when the angle equals to 0, 15, 30, 45 and so on..</p><p>Let’s create a state to store the last rotation angle.</p><pre>const state = {<br>  &#39;lastAngleRotation&#39;: null<br>}</pre><p>Now we’ll store the angle in this state, and we’ll only invoke the function when there is a change in the angle.</p><pre>function rotationStyleHandler(eventData, control, fabricObject) {<br>  if (fabricObject.lockRotation) {<br>      return NOT_ALLOWED_CURSOR;<br>  }<br>  const angle = treatAngle(fabricObject.angle);<br>  state.lastAngleRotation = angle;<br>  return mouseRotateIcon(angle)<br>}<br><br>canvas.on(&#39;object:rotating&#39;, function (e) {<br>  const angle = treatAngle(e.target.angle);<br>  if (state.lastAngleRotation !== angle) {<br>    canvas.setCursor(mouseRotateIcon(angle));<br>    state.lastAngleRotation = angle;<br>  };<br>});</pre><p>This is our final code:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fembed%2Fpreview%2FWNyWpjp%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DWNyWpjp&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2FTheluiz-eduardo%2Fpen%2FWNyWpjp&amp;image=https%3A%2F%2Fshots.codepen.io%2FTheluiz-eduardo%2Fpen%2FWNyWpjp-512.jpg%3Fversion%3D1670619080&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/68bdc57ee633a9765161c208ff26c2a0/href">https://medium.com/media/68bdc57ee633a9765161c208ff26c2a0/href</a></iframe><p>I plan to write more articles about fabric.js. The next one I have in mind is to enhance the current feature by indicating the object’s rotation angle while explaining some vector concepts that are important to work with canvas. <a href="https://medium.com/@luizzappa/display-angle-while-rotating-an-object-in-fabric-js-1156370e2b77"><strong>UPDATE: click here to go to the article</strong></a>.</p><p>I’d love suggestions for article topics about fabric.js in the comments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4714ba0ac28f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nuxt 2 | How to retain Scroll Position when returning to page without Navigation History]]></title>
            <link>https://levelup.gitconnected.com/nuxt-js-how-to-retain-scroll-position-when-returning-to-page-without-navigation-history-7f0250886d27?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/7f0250886d27</guid>
            <category><![CDATA[javascript-frameworks]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[vuejs]]></category>
            <category><![CDATA[nuxtjs]]></category>
            <category><![CDATA[vue-router]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Fri, 31 Dec 2021 18:10:47 GMT</pubDate>
            <atom:updated>2023-07-16T13:52:01.230Z</atom:updated>
            <content:encoded><![CDATA[<p>The Nuxt framework already provides a feature to preserve the scroll position when returning to a page <strong>if the user clicks the browser’s back button</strong> (or we use $router.go(n) which triggers the same event: <strong>popstate navigation</strong>).</p><p>I was facing a situation that needed to return to a page with $router.replace instead of $router.go(-1) (I explain the reason for this need below). This requirement gave me a problem: $router.replace <strong>doesn’t trigger the popstate event</strong>, so<strong> keeping scroll position does not work</strong>, and the page <strong>always loads at to the top</strong>.</p><p>In this article I will detail this solution. What motivated me to write this article is that I didn’t find it on google, I hope it helps someone.</p><h3><strong>The Problem</strong></h3><p>Let me detail the problem. I have a page with lots of cards and a Sign In button so the user can access the details of those cards. Suppose you are browsing and want to login. After logging in you would like to return the same position you were in. But look at the default behavior of Nuxt:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZJGkWJKBlKlXPKPe6EtKTA.gif" /><figcaption>After login, the user is redirected to the previous page at the top</figcaption></figure><p><strong>After authenticating</strong>, the user returns to the previous page, but <strong>no longer in the position he was in</strong>. This behavior disrupts the user experience.</p><p>As I said at the beginning of this article, this problem happens because in my login.vue, I use $router.replace instead of $router.go(-1) to return the source page after login. You might be wondering, why don’t you use $router.go(-1) and solve your problem? Using $router.replace is necessary so that the <strong>user cannot return to the login screen with the browser’s back button</strong>. $router.go(-1) allows this.</p><h3><strong>The Solution</strong></h3><p>We will create a <strong>router middleware</strong> to <strong>store </strong>the scroll position of the pages that have the <strong>scrollPos meta property</strong> (we will create this too). And then change Nuxt’s <strong>scrollBehavior </strong>to when the scrollPos object exists in the route’s meta property, <strong>load the page at the position stored</strong> in the object.</p><h3>1) Add meta in Nuxt Router</h3><p>On the page where we want to preserve the scroll position, we must create the <strong>meta property</strong> with the following object:</p><p><em>(in my case, it’s the page that has the different cards, which I called </em><em>search.vue)</em></p><pre>//<strong>search.vue</strong></pre><pre>export default {<br> //...<br><strong> meta: {<br>  scrollPos: {<br>    x: 0,<br>    y: 0<br>  }<br> }</strong><br>}</pre><p>The logic will be as follows, <strong>every page</strong> that has this <strong>scrollPos meta property</strong>, we will <strong>store the scroll position</strong> in this object.</p><h3>2) Creating the Router Middleware</h3><p>We will create a router middleware to check if route has the scollPos meta property, if so, the scroll position will be store on this object when leaving the page.</p><p>In your Nuxt project, create a folder named <strong>middleware</strong> and inside the file <strong>adjustScroll.js</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/353/1*o1GjkNrdvi_UeYF_zppFDg.png" /><figcaption>Middleware Folder</figcaption></figure><p>Paste this code inside <strong>adjustScroll.js</strong>:</p><pre>export default function({ from }) { <br>  const scrollPos = from?.meta[0].scrollPos<br>  if (scrollPos &amp;&amp; Object.keys(scrollPos).length &gt; 0) {<br>    scrollPos.y = window.scrollY || 0<br>    scrollPos.x = window.scrollX || 0<br>  }<br>}</pre><p>Let’s understand… This function will be executed every time we navigate between pages. It checks if the <strong>scrollPos object exist</strong>s within the <strong>Meta property’s array</strong>. If so, it stores the scroll position.</p><p>Include this middleware in your Nuxt settings (<strong>nuxt.config.js</strong>):</p><pre>//<strong>nuxt.config.js</strong></pre><pre>export default {<br>  //...<br>  <strong>router: {<br>    middleware: [&#39;adjustScroll&#39;]<br>  },</strong><br>}</pre><p>Okay, we store the scroll position, now how do we scroll when we return to the page? Let’s see in the next topic.</p><h3>3) Custom scrollBehavior after page loaded</h3><p>Nuxt allows us to customize the scrolling behavior after page loaded. That’s what we’re going to do here, when there’s the scrollPos object in the meta property, we’re going to<strong> scroll the page</strong> to the <strong>values ​​stored</strong> in that object.</p><p>In your Nuxt project, create a folder named <strong>app </strong>and inside the file <strong>router.scrollBehavior.js</strong> :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/344/1*Isetaxohj49MFLfOAS94yw.png" /><figcaption>App Folder</figcaption></figure><p>This file allows us to <strong>change </strong>the <strong>default scrolling behavior of Nuxt</strong>. As it <strong>doesn’t allow us to extend</strong>, the <a href="https://nuxtjs.org/docs/configuration-glossary/configuration-router/#scrollbehavior">documentation </a>recommendation is to change the default Nuxt code found <a href="https://github.com/nuxt/nuxt.js/blob/dev/packages/vue-app/template/router.scrollBehavior.js">here</a>.</p><p>I made some additions to this code to adapt to our need and changed the position of the declaration of the nuxt variable. Copy and paste the code below into the file (<strong>router.scrollBehavior.js</strong>):</p><blockquote>Your <strong>linter </strong>will <strong>complain </strong>about some of<strong> Nuxt’s own internal template syntax</strong>, you can ignore that.</blockquote><pre>//<strong>router.scrollBehavior.js</strong></pre><pre>&lt;% if (router.scrollBehavior) { %&gt;<br>&lt;%= isTest ? &#39;/* eslint-disable quotes, semi, indent, comma-spacing, key-spacing, object-curly-spacing, space-before-function-paren  */&#39; : &#39;&#39; %&gt;<br>export default &lt;%= serializeFunction(router.scrollBehavior) %&gt;<br>&lt;%= isTest ? &#39;/* eslint-enable quotes, semi, indent, comma-spacing, key-spacing, object-curly-spacing, space-before-function-paren  */&#39; : &#39;&#39; %&gt;<br>&lt;% } else { %&gt;import { getMatchedComponents, setScrollRestoration } from &#39;./utils&#39;</pre><pre>if (process.client) {<br>  if (&#39;scrollRestoration&#39; in window.history) {<br>    setScrollRestoration(&#39;manual&#39;)</pre><pre>// reset scrollRestoration to auto when leaving page, allowing page reload<br>    // and back-navigation from other pages to use the browser to restore the<br>    // scrolling position.<br>    window.addEventListener(&#39;beforeunload&#39;, () =&gt; {<br>      setScrollRestoration(&#39;auto&#39;)<br>    })</pre><pre>// Setting scrollRestoration to manual again when returning to this page.<br>    window.addEventListener(&#39;load&#39;, () =&gt; {<br>      setScrollRestoration(&#39;manual&#39;)<br>    })<br>  }<br>}</pre><pre>function shouldScrollToTop(route) {<br>   const Pages = getMatchedComponents(route)<br>   if (Pages.length === 1) {<br>     const { options = {} } = Pages[0]<br>     return options.scrollToTop !== false<br>   }<br>   return Pages.some(({ options }) =&gt; options &amp;&amp; options.scrollToTop)<br>}</pre><pre>export default function (to, from, savedPosition) {<br>  // If the returned position is falsy or an empty object, will retain current scroll position<br>  let position = false<br>  const isRouteChanged = to !== from</pre><pre>const nuxt = window.&lt;%= globals.nuxt %&gt;</pre><pre>// savedPosition is only available for popstate navigations (back button)<br>  const metaScrollPos = nuxt.context.route.meta[0]?.scrollPos <br>  if (savedPosition || metaScrollPos) {<br>    position = savedPosition || metaScrollPos<br>  } else if (isRouteChanged &amp;&amp; shouldScrollToTop(to)) {<br>    position = { x: 0, y: 0 }<br>  }</pre><pre>if (<br>    // Initial load (vuejs/vue-router#3199)<br>    !isRouteChanged ||<br>    // Route hash changes<br>    (to.path === from.path &amp;&amp; to.hash !== from.hash)<br>  ) {<br>    nuxt.$nextTick(() =&gt; nuxt.$emit(&#39;triggerScroll&#39;))<br>  }</pre><pre>return new Promise((resolve) =&gt; {<br>    // wait for the out transition to complete (if necessary)<br>    nuxt.$once(&#39;triggerScroll&#39;, () =&gt; {<br>      // coords will be used if no selector is provided,<br>      // or if the selector didn&#39;t match any element.<br>      if (to.hash) {<br>        let hash = to.hash<br>        // CSS.escape() is not supported with IE and Edge.<br>        if (typeof window.CSS !== &#39;undefined&#39; &amp;&amp; typeof window.CSS.escape !== &#39;undefined&#39;) {<br>          hash = &#39;#&#39; + window.CSS.escape(hash.substr(1))<br>        }<br>        try {<br>          const el = document.querySelector(hash)<br>          if (el) {<br>            // scroll to anchor by returning the selector<br>            position = { selector: hash }<br>            // Respect any scroll-margin-top set in CSS when scrolling to anchor<br>            const y = Number(getComputedStyle(el)[&#39;scroll-margin-top&#39;]?.replace(&#39;px&#39;, &#39;&#39;))<br>            if (y) {<br>              position.offset = { y }<br>            }<br>          }<br>        } catch (e) {<br>          &lt;%= isTest ? &#39;// eslint-disable-next-line no-console&#39; : &#39;&#39; %&gt;<br>          console.warn(&#39;Failed to save scroll position. Please add CSS.escape() polyfill (<a href="https://github.com/mathiasbynens/CSS.escape).&#39;">https://github.com/mathiasbynens/CSS.escape).&#39;</a>)<br>        }<br>      }<br>      resolve(position)<br>    })<br>  })<br>}<br>&lt;% } %&gt;</pre><p>Below I highlight <strong>only the part I changed</strong> from the default template. Let’s understand.</p><p>First I changed the <strong>position of the nuxt variable declaration</strong> (in the default template it occurs below this conditional). This variable stores the <strong>Nuxt context</strong> and as we need to access it in the conditional below, I changed the position of the declaration.</p><p>The second change is in the conditional. We check if <strong>there is a scrollPos object inside the route’s meta property</strong>. If so, we pass the stored values ​​to the position variable (which determines the scrolling).</p><pre>//..</pre><pre><strong>const nuxt = window.&lt;%= globals.nuxt %&gt;</strong></pre><pre>// savedPosition is only available for popstate navigations (back button)<br><strong>  const metaScrollPos = nuxt.context.route.meta[0]?.scrollPos <br>  if (savedPosition || metaScrollPos) {<br>    position = savedPosition || metaScrollPos<br>  } else if (isRouteChanged &amp;&amp; shouldScrollToTop(to)) {<br>    position = { x: 0, y: 0 }<br>  }</strong></pre><pre>//...</pre><p>Our solution is complete. <strong>Every page</strong> that has the <strong>scrollPos object in the meta property</strong> will <strong>store the scroll position</strong> for when we return. If at some point you want this page to <strong>load at to the top</strong>, just programmatically change the scrollPos object to { x: 0, y: 0}</p><h3>Finally, our middleware running</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Pu2EnGW5DF1jSBSK63RMeQ.gif" /><figcaption>Preserve scroll position when navigate between pages without navigation history</figcaption></figure><p>Now the user after logging in returns to the scroll position he was initially :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7f0250886d27" width="1" height="1" alt=""><hr><p><a href="https://levelup.gitconnected.com/nuxt-js-how-to-retain-scroll-position-when-returning-to-page-without-navigation-history-7f0250886d27">Nuxt 2 | How to retain Scroll Position when returning to page without Navigation History</a> was originally published in <a href="https://levelup.gitconnected.com">Level Up Coding</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 4: Install Database and Create…]]></title>
            <link>https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-4-install-database-and-create-88a26ce67ccc?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/88a26ce67ccc</guid>
            <category><![CDATA[oracle-database]]></category>
            <category><![CDATA[redhat-linux]]></category>
            <category><![CDATA[aws-ec2]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sun, 05 Sep 2021 23:49:20 GMT</pubDate>
            <atom:updated>2021-09-07T15:39:30.970Z</atom:updated>
            <content:encoded><![CDATA[<h3>Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 4: Install Database and Create Instance</h3><p>This is a series of articles I’ve been describing from scratch how to provision an instance on AWS EC2 with Red Hat (RHEL 7), install the Oracle Database 12c just from the command line, and finally connect through SQL Developer from a remote computer.</p><p>So to the article doesn’t get too long, I divided it into parts. I recommended reading from the beginning, as the subjects are linked and an initial configuration can impact the final result you are here:</p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-1-launching-and-connecting-with-8f842103b530">Part 1: Launch AWS EC2 Instance with RHEL 7 and connect with SSH (Windows using Putty)</a></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-2-setup-red-hat-rhel-7-30452a4c01b">Part 2: Setup Red Hat to receive the Oracle Database installation</a></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-3-download-oracle-12c-and-transfer-4689d7c54bfb">Part 3: Download Oracle 12c and transfer to AWS EC2 instance with Secure Copy Client (SCP)</a></p><p><strong>Part 4 (This article): Install Oracle Database and Create Instance</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/813/1*BO2orxI5Y9i5NPAuheFXqA.png" /><figcaption>Install Oracle Database on Red Hat AWS EC2 Instance</figcaption></figure><p>In this article we will install the oracle database. You must be in the oracle user. If not, switch to this user with this command:</p><pre>su - oracle</pre><p>Navigate to ~/database/response</p><pre>cd ~/database/response</pre><p>Let’s create a backup copy of the file <em>db_install.rsp</em></p><pre>cp db_install.rsp db_install.rsp.bak</pre><p>Edit the <em>db_install.rsp</em> with vi editor</p><pre>vi db_install.rsp</pre><p>Change the following file variables:</p><pre>oracle.install.option=INSTALL_DB_SWONLY<br>ORACLE_HOSTNAME=<strong>&lt;your-hostname&gt;</strong><br>UNIX_GROUP_NAME=oinstall<br>INVENTORY_LOCATION=/u01/app/oraInventory<br>ORACLE_HOME=/u01/app/oracle/product/12.1.0.2/db_1<br>ORACLE_BASE=/u01/app/oracle<br>oracle.install.db.InstallEdition=EE<br>oracle.install.db.DBA_GROUP=dba<br>oracle.install.db.OPER_GROUP=oper<br>oracle.install.db.BACKUPDBA_GROUP=backupdba<br>oracle.install.db.DGDBA_GROUP=dgdba<br>oracle.install.db.KMDBA_GROUP=kmdba<br>SECURITY_UPDATES_VIA_MYORACLESUPPORT=false<br>DECLINE_SECURITY_UPDATES=true</pre><blockquote>Change &lt;your-hostname&gt; by hostname defined in the first article.</blockquote><p>Save and close the file with :wq</p><p>Navigate to folder ~/database</p><pre>cd ~/database</pre><p>Then, start a new screen session with screen</p><p>This will open a screen session, create a new window, and start a shell in that window. If the connection is <strong>lost during the installation</strong>, you can <strong>reconnect and resume</strong> your screen session using the following commands:</p><p>First, find the session ID of the screen with:</p><pre>su - oracle<br>screen -ls</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/358/1*al0rquzi9uyIeut2xoq7MQ.png" /><figcaption>The highlighted number is your session id</figcaption></figure><p>Then restore screen with:</p><pre>screen -r 4065</pre><p>Returning to the installation.. Execute the installer with this command:</p><pre>./runInstaller -silent -responseFile ~/database/response/db_install.rsp -ignorePrereq -ignoreSysPrereqs -waitforcompletion -showProgress</pre><p>A warning message should appear indicating that the Oracle home directory is not empty. It does not affect the installation. Wait for installation.</p><blockquote>[INS-32016] The selected Oracle home contains directories or files.</blockquote><p>Sometimes the installation has memory errors because we are using AWS Free Tier and we have only 1GB of memory. The message is usually this:</p><pre>Error in invoking target &#39;irman ioracle&#39; of makefile<br>    &#39;/u01/app/oracle/product/12.1.0/dbhome_1/rdbms/lib/ins_rdbms.mk&#39;.<br>See &#39;/u01/app/oraInventory/logs/installActions2015(...).log&#39; for details.</pre><p>Inside the log file, the error is encountered:</p><pre>INFO: collect2: ld terminated with signal 9 [Killed]</pre><p>To solve this relink irman and ioracle with these commands:</p><pre>cd $ORACLE_HOME/rdbms/admin<br>/usr/bin/make -f $ORACLE_HOME/rdbms/lib/ins_rdbms.mk ioracle<br>/usr/bin/make -f $ORACLE_HOME/rdbms/lib/ins_rdbms.mk irman</pre><p>After installation, we need to run two scripts indicated at installation. Switch to ec2-user with logout . If you are using screen, first you need to exit screen with exit and then type logout</p><p>Run the first script:</p><pre>sudo /u01/app/oraInventory/orainstRoot.sh</pre><p>Run the second script:</p><pre>sudo /u01/app/oracle/product/12.1.0.2/db_1/root.sh</pre><p>Let’s remove the zip files to gains some space. Enter the following commands:</p><pre>cd /tmp<br>sudo rm -rf V46095-01_1of2.zip<br>sudo rm -rf V46095-01_2of2.zip</pre><p>Let’s test the connection. Switch back to oracle user:</p><pre>su - oracle</pre><p>Run the following command:</p><pre>sqlplus / as sysdba</pre><p>You should receive this message:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/548/1*VIlL7_l1UyVtkNQ7-4_dVw.png" /></figure><p>Exit sqlplus with quit</p><p>Create a database directory and flash recovery directory. Here you should initialize a new screen session (with screen) to allow you to reconnect if connection is lost during installation.</p><pre>mkdir -p /u01/app/oracle/ordata_oracle12c<br>mkdir -p /u01/app/oracle/flash_recovery_area</pre><p>Create instance:</p><pre>dbca -silent -createDatabase -templateName General_Purpose.dbc <strong>-gdbname orcl</strong> <strong>-sid orcl</strong> -responseFile NO_VALUE -characterSet AL32UTF8 -sysPassword oracle -systemPassword oracle -createAsContainerDatabase true -numberOfPDBs 1 -pdbName pdborcl -pdbAdminPassword oracle -databasetype MULTIPURPOSE -automaticMemoryManagement false -totalMemory 800 -storageType FS -datafileDestination &quot;/u01/app/oracle/ordata_oracle12c&quot; -recoveryAreaDestination &quot;/u01/app/oracle/flash_recovery_area&quot; -redoLogFileSize 50 -emConfiguration NONE -listeners LISTENER -ignorePreReqs</pre><blockquote>Change the parameters in bold if you have created a SID other than the one indicated</blockquote><p>After installation, edit the <em>/etc/oratab</em> file to indicate the database should be started when the system is booted. Switch to ec2-user with logout . If you are using screen, first you need to exit screen with exit and then type logout</p><p>Edit the file with:</p><pre>sudo vi /etc/oratab</pre><p>Paste this (remember to press<em> i</em> to enter vi Insert Mode), then save and close file (press <em>ESC </em>to exit vi Insert Mode, and type :wq)</p><pre>orcl:/u01/app/oracle/product/12.1.0.2/db_1:Y</pre><p>Switch back to oracle user with:</p><pre>su - oracle</pre><p>Let’s configure LISTENER and TNSNAMES.</p><pre>netca -silent -responseFile /u01/app/oracle/product/12.1.0.2/db_1/assistants/netca/netca.rsp</pre><p>Open <em>listener.ora</em> file:</p><pre>vi $ORACLE_HOME/network/admin/listener.ora</pre><p>Change the contents of the file for this:</p><pre>LISTENER =<br>  (DESCRIPTION_LIST =<br>    (DESCRIPTION =<br>      (ADDRESS = (PROTOCOL = TCP)(HOST = <strong>redhatsrv</strong>)(PORT = 1521))<br>      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))<br>    )<br>  )</pre><pre>SID_LIST_LISTENER =<br>  (SID_LIST =<br>    (SID_DESC =<br>      (GLOBAL_NAME = <strong>orcl</strong>)<br>      (SID_NAME = <strong>orcl</strong>)<br>      (ORACLE_HOME = /u01/app/oracle/product/12.1.0.2/db_1)<br>     )<br>  )</pre><pre>DEFAULT_SERVICE_LISTENER = <strong>orcl</strong></pre><blockquote>Change the texts in bold if you used another hostname or SID name.</blockquote><p>Save and close the file.</p><p>Reload the listener with:</p><pre>lsnrctl reload</pre><p>Open <em>tnsnames.ora</em> file:</p><pre>vi $ORACLE_HOME/network/admin/tnsnames.ora</pre><p>Change the contents of the file for this:</p><pre><strong>ORCL </strong>=<br>  (DESCRIPTION =<br>    (ADDRESS_LIST =<br>      (ADDRESS = (PROTOCOL = TCP)(HOST = <strong>oracle12c</strong>)(PORT = 1521))<br>    )<br>  )<br>  (CONNECT_DATA =<br>    (SERVER = DEDICATED)<br>    (SERVICE_NAME = <strong>orcl</strong>)<br>  )</pre><blockquote>Change the texts in bold if you used another hostname or SID name.</blockquote><p>Save and close the file.</p><p>Reload the listener with:</p><pre>lsnrctl reload</pre><p>Let’s save the state from pluggable database. Connect as sysdba:</p><pre>sqlplus / as sysdba</pre><p>Enter the follow command:</p><pre>alter pluggable database pdborcl save state;<br>exit;</pre><p>Our instance database is on, let’s connect to it through SQL Developer on our computer. First, install SQL Developer (<a href="https://www.oracle.com/tools/downloads/sqldev-downloads.html">download link</a>).</p><p>Open SQL Developer, then click <strong>View &gt; SSH</strong>. A window will appear in the left corner of your screen. Righ click on <strong>SSH Hosts</strong>, and choose <strong>New SSH Host..</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*Q2y_wy2A5kbLb3r7ijY1BQ.png" /><figcaption>Create new SSH Host on SQL Developer</figcaption></figure><p>Enter a name to this Host, in Host enter the Public IPv4 DNS (as described in <a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-1-launching-and-connecting-with-8f842103b530">first article</a>), Port is <strong>22 </strong>, Username is <strong>ec2-user</strong> , click to use key file and select the pem file downloaded when we created the EC2 instance. Click to Add a Local Port Forward, and click OK.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/432/1*ks3uJtp77Q8054jSzaDeww.png" /><figcaption>New SSH Host Configurations</figcaption></figure><p>Create a New Database Connection</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/241/1*vgS4rReIbob-vVAnExxsrw.png" /><figcaption>New Database Connection on SQL Developer</figcaption></figure><p>Enter the desired name, Username is <strong>sys</strong>, Password is <strong>oracle </strong>(the password when oracle instance database was created), Role is <strong>SYSDBA</strong>, connection type is SSH, an choose the SSH Host created in the last step. SID is <strong>orcl </strong>(if you are using the same nomenclature as mine). Click Save and close the window.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/542/1*FK5UL04jRWBEVyrmqLYcnQ.png" /><figcaption>SQL Developer — Connection Database configurations</figcaption></figure><p>It will be necessary to restart SQL Developer. After that, right click on the created database connection and choose Connect</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/365/1*4KHuV9tx2dAkNW9jootRcg.png" /></figure><p>To test the connection, in SQL Woksheet run this query:</p><pre>select * from v$instance;</pre><p>The ouput should be like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/267/1*hPzLPn3q-Y6AlXxphSQB8g.png" /></figure><p>Some people have problem with SSH tunneling on SQL Developer. If you are one of them, you can perform this tunneling directly through the Windows Prompt. Open the command and enter:</p><pre>ssh -i <strong>C:\path\your-key-pair.pem</strong> -N -L 8250:<strong>aws-ec2-public-IPv4-DNS</strong>:1521 ec2-user@<strong>aws-ec2-public-IPv4-DNS</strong></pre><blockquote>Change <strong>C:\path\your-key-pair.pem</strong> to the path to the key pair file downloaded from AWS and the two entries <strong>aws-ec2-public-IPv4-DNS </strong>to your AWS EC2 public IPv4 DNS.</blockquote><p>After you issue this command, the terminal remains open and does not return a response. Return to SQL Developer and create a new connection with the same parameters indicates a few steps back. But in connection type, use the <strong>Basic </strong>one, with hostname being <strong>localhost </strong>and port <strong>8250</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/543/1*nqMxIK_DmZUgh5er5jGs9A.png" /></figure><p>Now the connection to the database should work.</p><p>Alright! We were able to install oracle database on an AWS EC2 instance and connect externally through SQL Developer!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=88a26ce67ccc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 3: Download Oracle 12c and transfer…]]></title>
            <link>https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-3-download-oracle-12c-and-transfer-4689d7c54bfb?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/4689d7c54bfb</guid>
            <category><![CDATA[red-hat-training]]></category>
            <category><![CDATA[oracle-database]]></category>
            <category><![CDATA[scp]]></category>
            <category><![CDATA[aws-ec2]]></category>
            <category><![CDATA[putty]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sun, 05 Sep 2021 02:58:02 GMT</pubDate>
            <atom:updated>2021-09-05T23:50:41.511Z</atom:updated>
            <content:encoded><![CDATA[<h3>Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 3: Download Oracle 12c and transfer to AWS EC2 instance with SCP</h3><p>This is a series of articles I’ve been describing from scratch how to provision an instance on AWS EC2 with Red Hat (RHEL 7), install the Oracle Database 12c just from the command line, and finally connect through SQL Developer from a remote computer.</p><p>So to the article doesn’t get too long, I divided it into parts. I recommended reading from the beginning, as the subjects are linked and an initial configuration can impact the final result you are here:</p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-1-launching-and-connecting-with-8f842103b530">Part 1: Launch AWS EC2 Instance with RHEL 7 and connect with SSH (Windows using Putty)</a></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-2-setup-red-hat-rhel-7-30452a4c01b">Part 2: Setup Red Hat to receive the Oracle Database installation</a></p><p><strong>Part 3 (This article): Download Oracle 12c and transfer to AWS EC2 instance with Secure Copy Client (SCP)</strong></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-4-install-database-and-create-88a26ce67ccc">Part 4: Install Oracle Database and Create Instance</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/692/1*Ey4bd4E32Yf3VbMLCErEuw.png" /><figcaption>Transfer files to AWS EC2 instance with Secure Copy</figcaption></figure><p>First of all, we need to download the Oracle 12c. Navigate to the <a href="https://edelivery.oracle.com/">Oracle Software Delivery Cloud Website</a> and register a new account (if you don’t have one). After log on, <strong>search for Oracle Database 12c</strong> on the input:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YP10FPvVtYUzkArQaGE8qw.png" /><figcaption>Download Oracle Database 12c from Oracle Software Delivery Cloud Website</figcaption></figure><p>In the results, choose<strong> Oracle Database 12c. 12.1.0.2.0</strong>, then click on <strong>Continue </strong>at the top right of the website.</p><p>On platform, choose <strong>Linux X86–64</strong> and then click on Continue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BKQQ9Bl8TK7-SqrLI0evdw.png" /><figcaption>Oracle Database Platform</figcaption></figure><p>Accept the Oracle License Agreement, then download the two parts of Oracle Database 12.1.0.2.0 for Linux x86–64:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wp-CgwPc_9JjmXjf9cpkqg.png" /><figcaption>Download Oracle Database 12c for Linux</figcaption></figure><p>Now we need to transfer these files to our Linux instance using PuTTY Secure Copy Client (<em>pscp.exe</em>). The PSCP is a command line tool that you can use to transfer files between Windows computers and your Linux instance. Open Windows Command Line (press <em>window key + R</em> , type cmd then press OK)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/397/1*Dyqh11zK9vUYgZLcp47rxg.png" /><figcaption>Open Windows Command Line</figcaption></figure><p>To transfer a file, in the Windows Command Line, enter the following commands. Navigate to the folder where your <em>pscp.exe</em> file is stored:</p><pre>cd C:\path-to-pscp-exe-folder\</pre><p>To transfer the two downloaded zip files:</p><pre>pscp -i &quot;<strong>C:\path\my-key-pair.ppk&quot;</strong> &quot;<strong>C:\path\V46095-01_1of2.zip</strong>&quot; ec2-user@<strong>&lt;Public IPv4 DNS&gt;</strong>:/tmp/V46095-01_1of2.zip</pre><pre>pscp -i <strong>&quot;C:\path\my-key-pair.ppk&quot;</strong> &quot;<strong>C:\path\V46095-01_2of2.zip&quot;</strong> ec2-user@<strong>&lt;Public IPv4 DNS&gt;</strong>:/tmp/V46095-01_2of2.zip</pre><p>The <strong>Key Pair</strong> (<em>my-key-pair.ppk</em>) and the <strong>&lt;Public IPV4 DNS&gt;</strong> were explained in the <a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-1-launching-and-connecting-with-8f842103b530">first article</a>.</p><p>Wait for the file transfer to complete. If everything goes correctly, you should get a message similar to this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/605/1*-eIYPTOeQ6OYRdDC_6flvg.png" /><figcaption>Message of complete update</figcaption></figure><p>For unzip the files, we need to install this package. In the last article we were logged into the oracle user. Let’s to switch to ec2-user, so we can install the package. Juts enter the command logout to accomplish this (use this command only if you are in the oracle account).</p><p>Your terminal should look like this (with ec2-user):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/286/1*TD8c7tBX5ahrmlF7SDK47g.png" /></figure><p>Thus, we have the root privilege to install the package. We can run the command:</p><pre>sudo yum install unzip -y</pre><p>Switch user back to oracle:</p><pre>su - oracle</pre><p>Navigate to home oracle directory</p><pre>cd ~</pre><p>Then unzip the files with these commands:</p><pre>unzip /tmp/V46095-01_1of2.zip<br>unzip /tmp/V46095-01_2of2.zip</pre><p>In the <a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-4-install-database-and-create-88a26ce67ccc">next article</a> we will install the Oracle database and create an instance.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4689d7c54bfb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 2: Setup Red Hat (RHEL 7)]]></title>
            <link>https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-2-setup-red-hat-rhel-7-30452a4c01b?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/30452a4c01b</guid>
            <category><![CDATA[aws-ec2]]></category>
            <category><![CDATA[oracle-database]]></category>
            <category><![CDATA[red-hat-training]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sat, 04 Sep 2021 23:36:31 GMT</pubDate>
            <atom:updated>2021-09-06T18:38:23.337Z</atom:updated>
            <content:encoded><![CDATA[<h3>Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 2: Setup Red Hat (RHEL 7)</h3><p>This is a series of articles I’ve been describing from scratch how to provision an instance on AWS EC2 with Red Hat (RHEL 7), install the Oracle Database 12c just from the command line, and finally connect through SQL Developer from a remote computer.</p><p>So to the article doesn’t get too long, I divided it into parts. I recommended reading from the beginning, as the subjects are linked and an initial configuration can impact the final result you are here:</p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-1-launching-and-connecting-with-8f842103b530">Part 1: Launch AWS EC2 Instance with RHEL 7 and connect with SSH (Windows using Putty)</a></p><p><strong>Part 2 (This article): Setup Red Hat to receive the Oracle Database installation</strong></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-3-download-oracle-12c-and-transfer-4689d7c54bfb">Part 3: Download Oracle 12c and transfer to AWS EC2 instance with Secure Copy Client (SCP)</a></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-4-install-database-and-create-88a26ce67ccc">Part 4: Install Oracle Database and Create Instance</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/548/1*tybATC4mpZ7Fgt2WRR3FSA.png" /><figcaption>Setup Red Hat to receive the Oracle Database Installation</figcaption></figure><p>Once connected to our AWS EC2 Red Hat instance (<a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-1-launching-and-connecting-with-8f842103b530">explained in the first article</a>), let’s change the <strong>hostname </strong>and add an entry in the <strong>Local DNS Resolver</strong>. Enter the following commands:</p><pre>sudo hostnamectl set-hostname &lt;desired-host-name&gt;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/588/1*l4eruZ7DQyFZtjvscUDjlA.png" /></figure><p>In order for you hostname update to <strong>persist</strong>, you must verify that the <em>preserve_hostname</em> cloud-init setting is set to <strong>true</strong>. For that run the following command to add this settings:</p><pre>sudo vi /etc/cloud/cloud.cfg</pre><p>If you’re not used to the vi editor, I recommend reading <a href="https://www.dummies.com/computers/operating-systems/linux/how-to-edit-files-in-linux-using-vi/">this article</a> that gives you an introduction. In the future, I plan to write an introductory article to vi for windows users. It’s the users that i perceive have the most difficulty with “vi way”.</p><p>I’ll assume you don’t know how to use vi. Just follow the indicated keys.</p><p>Press<em> Shift+ G</em> to go to the last line of the file. Then, press <em>Shift + O</em> to add a new line above the cursor. Type it:</p><pre>preserve_hostname: true</pre><p>Then, press <em>ESC </em>and type it to save the file and exit:</p><pre>:wq</pre><blockquote><em>Esc </em>is to exit from Insert Mode, :w is for save the file and :q is for exit. You can execute the save and close commands all at once with :wq</blockquote><p>Copy your <strong>ip address</strong> with the command:</p><pre>ip a</pre><p>It’s the address after inet to the bar:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/690/1*nWsxdTsMo_EokLgTtwXodQ.png" /><figcaption>IP Address</figcaption></figure><p>Let’s edit the <strong>Local DNS Resolver</strong> with the command:</p><pre>sudo vi /etc/hosts</pre><p>Press <em>Shift +G</em> to go to the end of the file, then press <em>i </em>to enter Insert Mode. Type the <strong>ip address</strong> figured out in last step, and after that (followed by a blank space) the <strong>hostname </strong>you gave a few steps back</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/727/1*Tvl54fC8dMlRqC-8wGil0Q.png" /><figcaption>Editing Local DNS Resolver</figcaption></figure><p>Press <em>ESC </em>to exit the vi Inster Mode, then type it to save and close the file:</p><pre>:wq</pre><p>Then, we need to <strong>disable SELinux</strong>. For that, edit this file with vi:</p><pre>sudo vi /etc/selinux/config</pre><blockquote>SELinux: Security-Enhanced Linux is a security architecture for Linux systems that defines access controls for the applications, processes and files on a system.</blockquote><p>Press <em>j</em> to go down to the line containing the text <em>SELINUX=</em>, then press <em>Shift + A</em> to append new text, replace the default value with <strong>disabled</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/735/1*sdVXpx0UZhaGlb53VX8kpg.png" /><figcaption>Disable SELinux</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/328/0*Qn18ghOaLLP5xcJM.jpg" /></figure><blockquote>Bonus: vi editor uses hjkl as arrow keys, the reason is when Bill Joy created the editor, he used the ADM-3A terminal, which had the arrows on hjkl keys. Naturally he reused the same keys. <a href="https://catonmat.net/why-vim-uses-hjkl-as-arrow-keys">More information in this interesting article</a>.</blockquote><p>Press <em>ESC </em>to exit vi Insert Mode, then type :wq to save and exit the file.</p><p>Now let’s change the Kernel parameters in /etc/sysctl.conf, open this file with vi:</p><pre>sudo vi /etc/sysctl.conf</pre><p>Press <em>Shift + G</em> to go to the end of the file and press <em>O</em> to add a new blank line. Copy the lines below and past into the file. If you are using PuTTY, press <em>Shift + Insert</em> to paste.</p><pre>fs.aio-max-nr = 1048576<br>fs.file-max = 6815744<br>kernel.shmmax=536870912<br>kernel.shmmni=4096<br>kernel.shmall=262144<br>kernel.sem = 250 32000 100 128<br>net.ipv4.ip_local_port_range = 9000 65500<br>net.core.rmem_default = 262144<br>net.core.rmem_max = 4194304<br>net.core.wmem_default = 262144<br>net.core.wmem_max = 1048586</pre><p>Press ESC to exit vi Insert Mode, then type :wq to save and exit the file.</p><p>To apply parameter changes without restarting the operating system, enter the following commands:</p><pre>sudo sysctl -p<br>sudo sysctl -a</pre><p>Set the shell limit for user oracle in the file /etc/security/limit.conf . Open this file with vi editor:</p><pre>sudo vi /etc/security/limits.conf</pre><p>Press <em>Shift + G</em> to go to the end of the file and press <em>Shift + O</em> to add a new blank line above. Paste the following lines there: (If you are using PuTYY, just press <em>Shift + Insert</em> to paste)</p><pre>oracle soft nproc 2047<br>oracle hard nproc 16384<br>oracle soft nofile 1024<br>oracle hard nofile 65536<br>oracle soft stack 10240<br>oracle hard stack 32768</pre><p>Press ESC to exit vi Insert Mode, then type :wq to save and exit the file.</p><p>We need to install prerequisite packages, but before that we need to register our Red Hat to be able to install/update packages. Access the <a href="https://developers.redhat.com/">Red Hat Developer Website</a>, clique on Log in the top right corner of the screen</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/258/1*EHAWC0JNs9aTTKpzron4_Q.png" /><figcaption>Register an Red Hat Developer Account</figcaption></figure><p>In the login page, in the top right corner of the screen, click on Register</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/253/1*oz69jF5krRlLKmw0o_PxOA.png" /></figure><p>Create a new account. You will receive a confirmation email, click on the link in that email to active your account.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/604/1*h86MNkif0zpfgQyougVNWA.png" /><figcaption>Create a Red Hat Developer Account</figcaption></figure><p>Once your registration is activated, we will register your operating system through the command line. Enter the following command:</p><pre>sudo subscription-manager register --username &lt;your-username-registered-on-red-hat&gt;</pre><blockquote>Let’s assume your user registered on the Red Hat is <strong>bob</strong>, the commando would be sudo subscription-manager register --username <strong>bob</strong></blockquote><p>You will be asked for the password for the account. Registration being successful, you will receive a message similar to this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/662/1*fNBA4bhQ0S0fr88RHvynSw.png" /><figcaption>Message Red Hat successful registration</figcaption></figure><p>Attach subscriptions automatically for your server with below command:</p><pre>sudo subscription-manager attach --auto</pre><p>Enable the rhel-7-server-option-rpms repository to be able to install some of the necessary packages:</p><pre>sudo subscription-manager repos --enable=rhel-7-server-optional-rpms</pre><p>Now we can update OS packages with the command:</p><pre>sudo yum update -y</pre><p>Then, let’s install prerequisite packages using the following command:</p><pre>sudo yum install binutils compat-libstdc++-33 gcc gcc-c++ glibc glibc-devel ksh libgcc libstdc++ libstdc++-devel libaio libaio-devel libXext libXtst libX11 libXau libxcb libXi make sysstat unixODBC unixODBC-devel zlib-devel -y</pre><p>After installation, create required OS groups with the commands:</p><pre>sudo groupadd -g 1101 oinstall<br>sudo groupadd -g 1102 dba<br>sudo groupadd -g 1103 oper<br>sudo groupadd -g 1104 backupdba<br>sudo groupadd -g 1105 dgdba<br>sudo groupadd -g 1106 kmdba<br>sudo groupadd -g 1107 asmdba<br>sudo groupadd -g 1108 asmoper<br>sudo groupadd -g 1109 asmadmin</pre><p>Create the oracle user to install the database:</p><pre>sudo useradd -g 1101 -g oinstall -G dba,oper,backupdba,dgdba,kmdba,asmdba,asmoper,asmadmin oracle</pre><p>Set the oracle user password with the command:</p><blockquote>Attention: the oracle password has some stricter restrictions than the OS. I <strong>recommend </strong>using <strong>only letters and numbers</strong>. I used the @ character in the password and lost some hours of my day trying to identify the cause of an error that was this.</blockquote><pre>sudo passwd oracle</pre><p>Configure<strong> Linux Firewall</strong> to allow <strong>SQL*Net Listener</strong> to accept service requests on its default port. First, install the packaged <strong>firewalld </strong>with this command:</p><pre>sudo yum install firewalld -y</pre><p>Then, enable the firewall and start the service:</p><pre>sudo systemctl enable firewalld<br>sudo systemctl start firewalld</pre><p>Allow the port 1521/tcp:</p><pre>sudo firewall-cmd --permanent --add-port=1521/tcp</pre><p>Reload the firewall with this command:</p><pre>sudo firewall-cmd --reload</pre><p>Create directories for Oracle RDBMS Software:</p><pre>sudo mkdir -p /u01/app/oracle/product/12.1.0.2/db_1<br>sudo mkdir -p /u01/app/oracle/product/12.1.0.2/db_1/network/admin<br>sudo mkdir -p /u01/app/oraInventory</pre><p>Change the owner of these directories for the oracle user:</p><pre>sudo chown -R oracle:oinstall /u01</pre><p>Change directory permissions:</p><pre>sudo chmod -R 755 /u01</pre><p>Oracle Database needs a <strong>minimum 150MB swap file</strong>, let’s create a 2GB one.</p><pre>sudo dd if=/dev/zero of=/myswap count=2000 bs=1MiB</pre><p>Give these permissions:</p><pre>sudo chmod 600 /myswap</pre><p>Set up the swap area:</p><pre>sudo mkswap /myswap</pre><p>Enable the created swap:</p><pre>sudo swapon /myswap</pre><p>For this change to be permanent, we need to edit the <strong>File Systems Table</strong> (FSTAB):</p><pre>sudo vi /etc/fstab</pre><p>Press<em> Shift + G</em> to go to the end of the file, then press <em>O</em> to add new blank line. Paste this line there (If you’re using PuTTY, just press <em>Shift + Insert</em>)</p><pre>/myswap swap swap defaults 0 0</pre><p>Press ESC to exit the vi Insert Mode, then type :wqto save and exit the file.</p><p>As the installation process takes a long time, your connection sometimes goes down. To get around this, let’s use the screen package to keep the session active if that happens. Install the package with this command:</p><pre>sudo yum install screen -y</pre><p>Switch to oracle user that we created a few steps back:</p><pre>su - oracle</pre><p>After, entering the password, configure the enviroment variables of the oracle user with vi editor, enter this command:</p><pre>vi .bash_profile</pre><p>Press <em>Shift +G</em> to go to the end of the file, press <em>O</em> to create a new blink line and paste these codes there (If you’re using PuTTY, just press <em>Shift + Insert</em> to paste these codes)</p><pre>export TMP=/tmp<br>TMPDIR=$TMP; export TMPDIR<br>export ORACLE_HOSTNAME=redhatsrv<br>export ORACLE_UNQNAME=orcl<br>ORACLE_BASE=/u01/app/oracle; export ORACLE_BASE<br>ORACLE_HOME=$ORACLE_BASE/product/12.1.0.2/db_1; export ORACLE_HOME<br>ORACLE_SID=orcl; export ORACLE_SID<br>PATH=/usr/sbin:$ORACLE_HOME/bin:$PATH; export PATH<br>LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib:/usr/lib64; export LD_LIBRARY_PATH<br>CLASSPATH=$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib; export CLASSPATH</pre><pre>alias cdob=&#39;cd $ORACLE_BASE&#39;<br>alias cdoh=&#39;cd $ORACLE_HOME&#39;<br>alias tns=&#39;cd $ORACLE_HOME/network/admin&#39;<br>alias envo=&#39;env | grep ORACLE&#39;</pre><pre>umask 022</pre><pre>if [ $USER = &quot;oracle&quot; ]; then<br> if [ $SHELL = &quot;/bin/ksh&quot; ]; then<br>  ulimit -u 16384<br>  ulimit -n 65536<br> else<br>  ulimit -u 16384 -n 65536<br> fi<br>fi</pre><pre>envo</pre><blockquote>ORACLE_HOSTNAME must be equal to the hostname defined in the first article</blockquote><p>Press <em>ESC </em>to exit vi Insert Mode, and type :wq to save and close the file.</p><p>We finished the set up, in the <a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-3-download-oracle-12c-and-transfer-4689d7c54bfb">part 3</a> we will download Oracle Database 12c and transfer it to our AWS EC2 isntance with Secure Copy Client (SCP).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=30452a4c01b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 1: Launching and Connecting with…]]></title>
            <link>https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-1-launching-and-connecting-with-8f842103b530?source=rss-7f4b86735a16------2</link>
            <guid isPermaLink="false">https://medium.com/p/8f842103b530</guid>
            <category><![CDATA[oracle-database]]></category>
            <category><![CDATA[red-hat-training]]></category>
            <category><![CDATA[windows]]></category>
            <category><![CDATA[putty]]></category>
            <category><![CDATA[aws-ec2]]></category>
            <dc:creator><![CDATA[Luiz Eduardo Zappa]]></dc:creator>
            <pubDate>Sat, 04 Sep 2021 17:33:05 GMT</pubDate>
            <atom:updated>2021-09-06T14:08:30.585Z</atom:updated>
            <content:encoded><![CDATA[<h3>Install Oracle Database 12c on Red Hat AWS EC2 Instance — Part 1: Launching and Connecting with SSH using Putty on Windows</h3><p>I gave myself the challenge to install Oracle Database only with command line (silent mode) on Red Hat (RHEL 7) AWS EC2 Instance. I had some difficulties throughout the process, and found the solutions in a very fragmented way on the Web, so I decided to gather everything I learned and write this article in a very detailed way (from setup an AWS EC2 Instance, connecting with SSH, sending files there,… to create an instance and pluggable database). I wish I had found such a step by step like this, I hope it helps someone.</p><p>As it got too big, I divided the article into some parts. It looked like this:</p><p><strong>Part 1 (This article): Launch AWS EC2 Instance with RHEL 7 and connect with SSH (Windows using Putty)</strong></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-2-setup-red-hat-rhel-7-30452a4c01b">Part 2: Setup Red Hat to receive the Oracle Database installation</a></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-3-download-oracle-12c-and-transfer-4689d7c54bfb">Part 3: Download Oracle 12c and transfer to AWS EC2 instance with Secure Copy Client (SCP)</a></p><p><a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-4-install-database-and-create-88a26ce67ccc">Part 4: Install Oracle Database and Create Instance</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1004/1*YwTD0RUsQJAbqTl4lmcreg.png" /><figcaption>Install Oracle Database on RHEL 7 AWS EC2 Instance</figcaption></figure><p>The first thing we should do is <strong>launch an Amazon EC2 instance with the RHEL 7 (Red Hat Enterprise Linux) image</strong>. Let’s go to AWS Amazon Console, sign in, on the home screen, click on services, under compute category, click on EC2.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/728/1*6bIq_U5OjxGZB3uEh5lN9Q.png" /><figcaption>AWS Console — Accessing EC2 service</figcaption></figure><p>In the right side menu, navigate to the <strong>Instances</strong>. Then click to <strong>Launch Instances</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MGu5q-rAjR8IT9LavxIIEw.png" /><figcaption>AWS EC2 — Launch New Instance</figcaption></figure><p>Let’s look for an image of RHEL 7.9. To speed up, search directly for this code <strong>ami-0dd38e30236fe14ea </strong>, click on Community AMIs</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ov2FJHiVp3sCMUwvryQCHg.png" /><figcaption>AWS EC2 — RHEL 7 Image</figcaption></figure><p>I chose a type <strong>eligible to Free tier</strong>, but feel free to choose your configuration. Then click on <strong>step 4: Add Storage</strong>, at the top of the screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QjdPBrY_W4wYSQWmJ71dQQ.png" /><figcaption>AWS EC2 —Choose Instance Type</figcaption></figure><p>Just to install Oracle Database, we need almost 7GB, so we should increase the default storage capacity. 3<strong>0GB is enough</strong> (remembering that the <strong>Free tier holds up to 30GB</strong>). Then click on <strong>Review and Launch</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3nO9XapxCrxWNvH-3SPB7A.png" /><figcaption>AWS EC2 — Add Storage</figcaption></figure><p>Finally, click on <strong>Launch</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*29bUcNNqwnZVjGvqnOuZiQ.png" /><figcaption>AWS EC2 — Launch</figcaption></figure><p>We need to create one <strong>Key Pair </strong>to be able to access the instance with SSH. Choose a name that is intuitive for you, I chose rhelserv . Click on <strong>Download Key Pair</strong> and store this file (<strong>don’t lose it</strong>, otherwise it will be necessary to generate another one). Then click on <strong>Launch Instance</strong>s</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wJlCYDLbGObpJj0AHKxkhg.png" /><figcaption>AWS EC2 — Creating Key Pair</figcaption></figure><p>There, your instance is being <strong>provisioned</strong>. <strong>At the bottom of the page</strong>, you will find a button to view this process. <strong>Click on it</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E79poHv1uIZRtmdzEPzdeQ.png" /><figcaption>AWS EC2 — Provisioning</figcaption></figure><p>Wait a while until your <strong>Instance state</strong> is <strong>Running</strong>. Then, click on your Instance ID</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WEJXmX3Q7lyKNjAcR6aboA.png" /><figcaption>AWS EC2 — Instances Running</figcaption></figure><p>Copy your instance <strong>Public IPv4 DNS </strong>(for that, just click on the icon)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v0_coIbbXmNCnVK4o0ggew.png" /><figcaption>AWS EC2 — Instance Public IPv4</figcaption></figure><p>Now we are going to connect via SSH. As my operating system is <strong>Windows</strong>, I will use <strong>PuTTY</strong>. Other systems have this feature built in. Access <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">PuTTY Mirror </a>and download these files: <strong>putty.exe</strong> , <strong>pscp.exe</strong> and <strong>puttygen.exe</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/674/1*lgfQE-jgPbSdeh-bk5-aPw.png" /><figcaption>Download PuTTY</figcaption></figure><p>PuTTY does not support the private key format for SSH key (<em>.pem</em> file downloaded). So, we will use the tool <strong>PuTTYgen to convert the required format </strong>(<em>.ppk</em>). Open PuTTYgen (<em>puttygen.exe</em>), under Type of key to generate, choose <strong>RSA</strong>, then <strong>click on Load</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/708/1*i25LCj9aPztRGQYcY--g3Q.png" /><figcaption>PuTTYgen — Convert .pem to .ppk</figcaption></figure><p>By default, PuTTYgen display only files with the extension <em>.ppk</em> . To locate your <em>.pem</em> file, choose the option to <strong>display files of all types</strong>. Then <strong>open</strong> the <em>.pem</em> file downloaded before</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/797/1*bwHixCw3aK4fQdMLRMESGg.png" /><figcaption>PuTTYgen — Open .pem file</figcaption></figure><p>PuTTYgen displays a notice that the <em>.pem</em> file was successfully imported. Choose <strong>OK</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/330/1*UNX_BluG5_A3JHn_7XXznQ.png" /><figcaption>PuTTYgen — Successfully notice</figcaption></figure><p>Then choose <strong>Save private key</strong>. PuTTYgen displays a warning about saving the key without a passphrase. Choose <strong>Yes</strong>.</p><blockquote>A passphrase on private key is an extra layer of protection. Your private key can’t be used without the passphrase. The downside to using a passphrase is that it makes automation harder because human intervention is needed to log on an instance.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/596/1*PInsvnH5tDhiBjWZc8cAlA.png" /><figcaption>PuTTYgen — Save private key</figcaption></figure><p>Specify the name for the key and choose <strong>Save</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/784/1*rpAWC1LXRbn8T1rKE5SnWQ.png" /><figcaption>PuTTYgen — Save generated private key</figcaption></figure><p>Let’s connect to our AWS EC2 instance. Start your PuTTY (<em>putty.exe</em>), in the <strong>Category </strong>pane, choose <strong>Session </strong>and complete the following fields:</p><p><strong>Public DNS</strong>: paste your Public IPv4 DNS copied a few steps back.</p><p><strong>Port</strong>: 22</p><p><strong>Connection Type</strong>: SSH</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*MMLBJCUBvHv7gsw-s-r4Jw.png" /><figcaption>PuTTY — Session configurations</figcaption></figure><p>In the <strong>Category </strong>pane, expand <strong>Connection</strong>, expand <strong>SSH</strong>, then choose <strong>Auth</strong>. Complete the following:</p><p>Choose <strong>Browse</strong>. Then, selected the <em>.ppk</em> file that we generated and choose <strong>Open</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/503/1*zAVJNrQFwc0PaMfED69qiQ.png" /></figure><p>It’s important that we save these settings for future use. Under <strong>Category </strong>pane, choose <strong>Session</strong>, enter a name for the session in <strong>Saved Sessions</strong>, then choose <strong>Save</strong>. Then, we can click on <strong>Open</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/502/1*TLAptXSHFpL4OELG0NI1Xw.png" /><figcaption>PuTTY — Save configurations for future use</figcaption></figure><p>PuTTY displays a security alert dialog box that asks whether you trust the host to which you are connecting. Choose <strong>Accept</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/658/1*XO2cvAYKerd9jmId7nrkwg.png" /><figcaption>PuTTY — Security alert for the first connect</figcaption></figure><p>If the connection worked, your screen should look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/1*F60HW1BqVdRaaH08x79RbQ.png" /><figcaption>PuTTY — Connected Successfully</figcaption></figure><p>We were able to connect to the instance with SSH. In the <a href="https://medium.com/@luizzappa/install-oracle-database-12c-on-red-hat-aws-ec2-instance-part-2-setup-red-hat-rhel-7-30452a4c01b">next article (Part 2)</a> we will prepare Red Hat for installing Oracle Database.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f842103b530" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>