Qlik Community

Qlik Design Blog

All about product and Qlik solutions: scripting, data modeling, visual design, extensions, best practices, etc.

Employee
Employee

Using Picasso.js and the Qlik Engine JSON API in React.

Introduction

On this blog, we've discussed how to get started with Qlik APIs using Enigma.js as well as Qlik's open-source visualization library, Picasso.js. This post will build on my last post, a Part 2 if you will, to show you how to move from the basic steps of communicating with a Qlik Sense server to creating a chart with Picasso.js and making selections. For this mini-tutorial, you should be familiar with React and have some familiarity with the concepts discussed in that post. We'll be using the same "Consumer Sales" application from the demo site: https://demos.qlik.com/qliksense/ConsumerGoodsSales. Let's get started.

Setting Up the Repo

First, clone the starter repo here: https://github.com/qlik-demo-team/qlik-picasso-tutorial and install the necessary modules. To avoid the hassle of setting up Webpack, this is a super basic webpack setup with the React, Enigma.js, and Picasso.js as dependencies.

 

 

 

git clone https://github.com/qlik-demo-team/qlik-picasso-tutorial.git
cd qlik-picasso-tutorial
npm i
npm run dev

 

 

 

Go to `localhost:8081` to see "Hey there". If you do, we're all set and ready to go.

Connecting to Qlik Sense

The first step to building a mash-up is making the connection to Qlik Sense. We went over this in some detail in the last post and we are going to just copy what we did there for this project. First, let's create three separate files called `qDocPromise.js`, `hypercube.js`, and `listObject.js` in the `/src` directory.

 

 

 

// qDocPromise.js
const enigma = require('enigma.js');
const schema = require('enigma.js/schemas/12.20.0.json');
const SenseUtilities = require('enigma.js/sense-utilities');

const config = {
  host: '', // <== Qlik server domain here
  secure: true,
  port: 443,
  prefix: '',
  appId: '', // <== app ID here
};
const url = SenseUtilities.buildUrl(config);

const qDocPromise = enigma.create({ schema, url }).open().then(global => global.openDoc(config.appId));

export default qDocPromise;

// hypercube.js
const hypercube = {
  qInfo: { qId: 'Consumer Sales', qType: 'data' },
  qHyperCubeDef: {
    qDimensions: [
      { qDef: { qFieldDefs: ['[Product Group Desc]']}, qFieldLabels: ['Product Group'] }
    ],
    qMeasures: [
      { qDef: { qDef: '=SUM([Sales Margin Amount])/SUM([Sales Amount])', qLabel: 'Margin' }},
    ],
    qInitialDataFetch: [{
      qTop: 0, qLeft: 0, qWidth: 10, qHeight: 1000,
    }],
    qInterColumnSortOrder: [],
    qSuppressZero: true,
    qSuppressMissing: true,
  }
}

export default hypercube;

// listObject.js
const listObject = {
  title: "A list object",
  description: "Description of the list object",
  qInfo: { qId: 'Consumer Sales List Object', qType: 'List Object'},
  qListObjectDef: {
    qStateName: "$",
    qDef: { qFieldDefs:  ['[Product Group Desc]'], qFieldLabels: ["Product Description"], qSortCriterias: [{qSortByLoadOrder: 1}]},
    qInitialDataFetch: [{
      qTop: 0, qLeft: 0, qWidth: 10, qHeight: 1000,
    }],
  },
}

export default listObject;

 

 

 

Once you've updated the qDocPromise file with your Qlik Sense domain and appID, we can import the files. We are doing this to keep our `Home` component relatively clean - it's going to get much bigger in a little bit. Import each of the files and use the `doc` to create a hypercube and list object:

 

 

 

import qDocPromise from './qDocPromise';
import hypercube from './hypercube';
import listObject from './listObject';

qDocPromise.then((doc) => {
  console.log(doc);

  doc.createSessionObject(hypercube).then((hc) => {
    hc.getLayout().then((layout) => {
      console.log(layout)
    })
  })

  doc.createSessionObject(listObject).then((lo) => {
    lo.getLayout().then((layout) => {
      console.log(layout)
    })
  })
})

 

 

 

Hopefully you are still following along. If this is a bit complex and feels like too much of a jump, take a look at Part 1 where we go through this in a bit more detail. The code above simply creates the connection to Qlik Sense and then using the doc returned from the qDocPromise creates the hypercube object and list object. It is with the hypercube object that we can get the data and with the list object that we can create our dropdown to make selections.

Get the Data and Build the Chart

Now we're going to update the `Home` so that we can use Picasso.js. Again, Picasso makes it easy for us to quickly build out charts and is uniquely built to handle objects from the Qlik Engine JSON API. Now we just need to import Picasso, set-up the element that is going to hold the chart, and create a settings file:

 

 

 

// Home.js
import React, { useRef } from 'react'; // NEW CODE

import qDocPromise from './qDocPromise';
import hypercube from './hypercube';
import listObject from './listObject';

// >>>>> NEW CODE
import picasso from 'picasso.js';
import picassoQ from 'picasso-plugin-q';
picasso.use(picassoQ);
import settings from './settings'; // new file
// <<<<<<<<<<
const Home = () => {
  const elementNode = useRef(null); // NEW CODE

  qDocPromise.then((doc) => {
    console.log(doc);
  
    doc.createSessionObject(hypercube).then((hc) => {
      hc.getLayout().then((layout) => {
        console.log(layout)
      })
    })
  
    doc.createSessionObject(listObject).then((lo) => {
      lo.getLayout().then((layout) => {
        console.log(layout)
      })
    })
  })
  
  // NEW CODE
  return (
    <div className="parent">
      <div style={{ padding: '20px'}} >
        <div ref={elementNode} style={{ height: '400px', width: '600px', minWidth: 'min(600px, 90vw)', position: 'relative'}}></div>
      </div>
    </div>
  )
}

export default Home;

// settings.js (new file)
const settings = {
  scales: {
    y: {
      data: { field: 'Margin' },
      invert: true,
      include: [0]
    },
    c: {
      data: { field: 'Margin' },
      type: 'color'
    },
    t: { data: { extract: { field: '[Product Group Desc]' } }, padding: 0.3 },
  },
  components: [{
    type: 'axis',
    dock: 'left',
    scale: 'y'
  },{
    type: 'axis',
    dock: 'bottom',
    scale: 't'
  },{
    key: 'bars',
    type: 'box',
    data: {
      extract: {
        field: '[Product Group Desc]',
        props: {
          start: 0,
          end: { field: 'Margin' }
        }
      }
    },
    settings: {
      major: { scale: 't' },
      minor: { scale: 'y' },
      box: {
        fill: { scale: 'c', ref: 'end' }
      }
    }
  }]
}

export default settings;

 

 

 

Now that we have setup Picasso, let's get the data. As you can see below, we don't even need to clean-up the data. We simply use the `getLayout` method and then pass it to Picasso:

 

 

 

doc.createSessionObject(hypercube).then((hc) => {
  hc.getLayout().then((layout) => {
    let _pic = picasso.chart({
      element: elementNode.current,
      data: [{
        type: 'q',
        key: 'qHyperCube',
        data: layout.qHyperCube
      }],
      settings
    })
  })
})

 

 

 

Creating a Picasso chart is pretty easy and needs only a few things. First, it needs the element that will hold the chart. Next, it needs the data, which in this case, is from the Qlik Object. And lastly, the settings object we created to specify the scales. That chart looks something like this:
chart_bp02.png

Build the Dropdown

Now that we have a working chart, let's build a dropdown using our list object. There is a lot of new code to add so I'll show you the code and then explain it. First, create a new file called Dropdown.js:

 

 

 

// Dropdown.js
import React from 'react';

const Dropdown = ({ options, makeSelection }) => {
  console.log(options);

  const handleChange = (val) => {
    const currentSelected = options.filter(o => o.qState === 'S').map(o => Number(o.qElemNumber))
    currentSelected.push(Number(val));
    makeSelection(currentSelected);
  }
  return (
    <select onChange={(e) => handleChange(e.target.value)}>
      {options.map((opt, i) => <option key={i} value={opt.qElemNumber}>{opt.qText} - {opt.qState}</option>)}
    </select>
  )
}

export default Dropdown;

 

 

 

This is a rudimentary dropdown that will make selections as you change the option in the dropdown. It is a plain-old HTML element which, for front-end developers, means that it's not particularly attractive...which is perfect for this tutorial as it removes all the complicated code that a more attractive dropdown would have. The dropdown takes two parameters: the array of options from our list object and then a function to tell Qlik what our selections are. Now update the `Home.js` file with this. There are a lot of changes but again, we'll go over them after:

 

 

 

// Home.js
import React, { useRef, useState, useEffect } from 'react';
import Dropdown from './Dropdown';

import qDocPromise from './qDocPromise';
import hypercube from './hypercube';
import listObject from './listObject';

import picasso from 'picasso.js';
import picassoQ from 'picasso-plugin-q';
picasso.use(picassoQ);
import settings from './settings';

const Home = () => {
  const elementNode = useRef(null);
  const [qListObject, setQListObject] = useState(null);
  const [options, setOptions] = useState([])

  const updateOptions = (layout) => {
    const lOptions = layout.qListObject.qDataPages[0].qMatrix.map((arr) => {
      return arr[0];
    })
    setOptions(lOptions);
  }

  useEffect(() => {
    qDocPromise.then((doc) => {
      console.log(doc);
    
      doc.createSessionObject(hypercube).then((hc) => {
        hc.getLayout().then((layout) => {
          let _pic = picasso.chart({
            element: elementNode.current,
            data: [{
              type: 'q',
              key: 'qHyperCube',
              data: layout.qHyperCube
            }],
            settings
          })
        })
      })
    
      // List Object
      doc.createSessionObject(listObject).then((lo) => {
        setQListObject(lo);
        lo.getLayout().then((layout) => {
          updateOptions(layout);
        })
  
        lo.on('changed', () => {
          lo.getLayout().then((layout) => {
            updateOptions(layout);
          })
        })
      })
    })
  }, [])

  const makeSelection = (arr) => {
    qListObject.selectListObjectValues('/qListObjectDef', arr, false, false)
  }

  return (
    <div className="parent">
      <Dropdown options={options} makeSelection={makeSelection} />
      <div style={{ padding: '20px'}} >
        <div ref={elementNode} style={{ height: '400px', width: '600px', minWidth: 'min(600px, 90vw)', position: 'relative'}}></div>
      </div>
    </div>
  )
}

export default Home;

 

 

 

We now have a chart and dropdown for us to make selections. Use the dropdown to make a selection...what
happens? If you notice, the dropdown is changing: the "O" is turning into an "S" for some while other options have an "X" next to them. But why isn't the chart updating? We clearly still have a little more to do. The chart isn't updating because we only just built the chart once - we aren't watching for changes. So after we get the hypercube data, we never update the chart again - it just shows the old data. To fix this, we have to do for the hypercube what we did for the list object: add a listener that tells us when there are changes to the hypercube. When the hypercube changes, we update the chart:

 

 

 

qDocPromise.then((doc) => {
  let _pic;
  doc.createSessionObject(hypercube).then((hc) => {
    hc.getLayout().then((layout) => {
      _pic = picasso.chart({
        element: elementNode.current,
        data: [{
          type: 'q',
          key: 'qHyperCube',
          data: layout.qHyperCube
        }],
        settings
      })
    })

    hc.on('changed', () => {
      hc.getLayout().then((layout) => {
        _pic.update({
          data: [{
            type: 'q',
            key: 'qHyperCube',
            data: layout.qHyperCube
          }],
        })
      })
    })
  })

  // more code

})

 

 

 

Now, every time we change the dropdown, the chart should update.

Conclusion

I'm sure you're wondering about next steps like how do I clear selections, why does the dropdown have to be so ugly, seems like a lot of work for just one chart, right? All of those things are true. This little tutorial was designed to highlight some of the considerations that go into the creation of a mashup and the work required to bring not only an attractive chart to a website but also make it interactive like Qlik Sense. Fortunately, we already have a library to solve these very problems: Qdt-Components. We've covered a number of different features that Qdt-Components has on this blog but we'll soon be unveiling the newest version, 3.0. To see what we're working on BEFORE it goes live, take a look at the 3.0 branch of GitHub (https://github.com/qlik-demo-team/qdt-components/tree/3.0).