Vulcan: Transforms

vulcan
transforms
components

#1

Vulcan development is flying!!!

Ever since the above post, we’ve had 2 significant rebuilds and loads of features assed to the Knowledge and Research Module alone.

With that in mind, and before I get opportunity to formally construct some proper documentation, I’ll add new documentation here… in no particular order (other than relevance to me at the time of writing)

Object Transforms

“Transforms” (for want of a better word, is when one object is used to generate new Objects of a certain type, based on some script, logic or criteria.

For example, I might add a Person Object to my chart with a Twitter account. A Transform I could run is to fetch this Person’s Twitter followers and generate new Person objects with the array of results.

Or, as in this example. I start with a Company object.

I add the Duedil Shareholders AddOn to fetch the companies registered shareholders.

For now, I’ve included a button, with an action to run the Transform.

So how did I create this Transform?

A Transform is simply a function which accesses the store create action to instantiate new Objects of the given type.

We provide an interface to this inside your components.

As you may know, Components are Vue.js components. For completeness, here’s the full component example, starting with the Template.

<template>
  <div>
    <v-layout row>
      <v-flex xs12>
        <v-card>
          <v-btn
           :loading="loading"
           :disabled="loading"
           color="secondary"
           @click="loader = 'loading'"
           >
           Start Transform
         </v-btn>
        <v-list three-line>
          <template v-for="(shareholder, index) in shareholders">
                   <v-list-tile :key="index" avatar @click="">
                   <v-list-tile-avatar>
                     <v-icon>verified_user</v-icon>
                   </v-list-tile-avatar>

                  <v-list-tile-content>
                    <v-list-tile-title v-html="shareholder.sourceName"></v-list-tile-title>
                      <v-list-tile-sub-title>
                        <v-chip small light color="pink" text-color="white">
                          <v-avatar><v-icon color="white">account_balance_wallet</v-icon></v-avatar>
                          {{formatShares(shareholder.totalShareholding)}} Shares
                        </v-chip>
                        <v-chip light small color="teal" text-color="white">
                          <v-avatar><v-icon color="white">pie_chart</v-icon></v-avatar>
                          {{formatSharesPerc(shareholder.totalShareholdingPercentage)}} %
                        </v-chip>
                     </v-list-tile-sub-title>
                   </v-list-tile-content>
              
                 </v-list-tile>
                <v-divider></v-divider>
              </template>
            </v-list>
          </v-card>
        </v-flex>
      </v-layout>
    </div>
</template>

And our style for the animated button:

<style scoped>
    custom-loader {
    animation: loader 1s infinite;
    display: flex;
  }
  @-moz-keyframes loader {
    from {
      transform: rotate(0);
    }
    to {
      transform: rotate(360deg);
    }
  }
  @-webkit-keyframes loader {
    from {
      transform: rotate(0);
    }
    to {
      transform: rotate(360deg);
    }
  }
  @-o-keyframes loader {
    from {
      transform: rotate(0);
    }
    to {
      transform: rotate(360deg);
    }
  }
  @keyframes loader {
    from {
      transform: rotate(0);
    }
    to {
      transform: rotate(360deg);
    }
  }
</style>

OK, now onto the scripts.

<script>
    module.exports = {
        name: 'TestTransform',
        props: {
            countryCode: {
                type: String,
                default: "gb"
            },
            companyId: {
                type: String,
                default: "06999618"
            }
        },
        mounted: function() {
          let options = {
              headers: {
                    'Accept': 'application/json',
                    'X-AUTH-TOKEN': '<AUTH_TOKEN>'
                  }
          }
          
          let params = {
                  countryCode: this.countryCode,
                  companyId: this.companyId
              }
          
          this.$http.get('https://duedil.io/v4/company/' + params.countryCode + '/' + params.companyId + '/shareholders.json', options)
            .then(function(result){
                console.log(result);
                this.shareholders = result.body.shareholders
            }, function(error){
                console.log('Error: ' + error.message)
            })
            
        },
        data: function () {
          return {
              shareholders: [{'sourceName': 'Steve', 'totalShareholding': 9888926, 'totalSharesPercentage': 27.685}],
              loader: null,
              loading: false,
          }
        },
        methods: {
            formatShares(value){
                return value.toLocaleString('en');
            },
            formatSharesPerc(value){
                return parseFloat(value).toFixed(2);
            },
            guid() {
             const guid = function() {
              function s4() {
               return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
              }
              return (s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4());
             };  
            },
            // INSERT Transform Method
            }
        },
        watch: {
         loader () {
          const l = this.loader
          this[l] = !this[l]
          
          // CALL TRANSFORM METHOD
          this.transformGeneratePersonObjects()

          setTimeout(() => (this[l] = false), 3000)

          this.loader = null
         }
        },
        computed: {}
    }
</script>

Ok, now we have the full component in place, we just need to add our transformGeneratePersonObjects() method.

transformGeneratePersonObjects(){
    // we could do something much smarter with object layout, but let's keep it simple
    const x = 1000 + (Math.floor(Math.random()*100)+100);
    const y = 1000 + (Math.floor(Math.random()*100)+100);
    
    // iterate over our Shareholders array
    for (var i = 0, l = this.shareholders.length; i < l; i++) {
        var shareholder = this.shareholders[i];
        const text = shareholder.sourceName + ' holds ' + shareholder.totalShareholding + ' shares, equating to ' + shareholder.totalShareholdingPercentage + '% of the available shares.'
        // this is the magic
         const objectPromise = this.$store.dispatch('object/create', {
            type: "AnalysisTools_PersonObject",
            position: {x, y},
            info: {
               title: shareholder.sourceName,
                settings: {
                    title: shareholder.sourceName,
                    notesLines: [{id: this.guid(), content: text, type: "Text"}]
                 }
              }
              }).then(function(createdObject){
                  console.log('Created object: ' + createdObject.id);
              })
        }
}

Project Vulcan - The History
#2

We can further improve this example through some better layout logic.

Inside our transformGeneratePersonObjects() method

Let’s get the position of our current Object to reference

const { x, y } = this.baseObject.position;

And we shall also reference the size of our current Object

const {width} = this.baseObject.size;

A few options for our layout:

let maxInLayer = 5;
let index = - maxInLayer/2;
let offset = 100;

Now we iterate over the shareholders array

this.shareholders.forEach(shareholder => {
            if (index > maxInLayer/2) {
                maxInLayer++;
                offset += 200;
                index = -maxInLayer/2;
            }
          const position = {
            x: x + width+offset,
            y: y + index * 200
          }

         // refactor of setting the notesLines content
          const { sourceName, totalShareholding, totalShareholdingPercentage } = shareholder;
          const content = `${sourceName} holds ${totalShareholding} shares, equating to ${totalShareholdingPercentage}% of the available shares.`;

          // this is the magic
          const objectPromise = this.$store.dispatch('object/create', {
            type: "AnalysisTools_PersonObject",
            position,
            info: {
              title: sourceName,
                settings: {
                    title: sourceName,
                    notesLines: [{
                      id: this.guid(),
                      type: "Text",
                      content
                    }]
                }
              }
            })
            
          // Adding connections between original and new object
          objectPromise.then(createdObject => {
            console.log('Created object: ' + createdObject.id);
            this.$store.dispatch("connection/create", {
              from: this.baseObject.id,
              to: createdObject.id
            });
          })
          
          index++;
        })

As you can see, in addition to some slight evolved layout logic, we’ve also extended the Promise handling. When the Promise is returned from the Object create, we then send a subsequent request to the store to create a connection between the original object and the new object.

Overall, the effect is much a much more controlled layout. But with this approach, we could easily add further methods that determine different layouts that users can select according to their preference.

Here’s the result…