Skip to main content
Announcements
Qlik Introduces a New Era of Visualization! READ ALL ABOUT IT
Ouadie
Employee
Employee

When using Enigma.js to communicate with the Qlik Associative Engine to build visualizations for instance or simply to get access to data from Qlik Sense, you will certainly come across the concept of Generic Objects, which are structures for storing and interacting with data in an application. They are considered generic because of they are flexible structures that can represent many different app components such as sheets, bookmarks, hypercubes, lists etc..

In this blog post, we will go over Hypercubes and List Objects providing a brief explanation of the concepts and how to use them with enigma.js

Hypercubes

In Qlik Sense, a Hypercube is an interface for defining a set of data to extract. It is the definition provided to the Qlik Data Engine, which holds all information on which data is queried and how it’s calculated. If selections are applied to a Hypercube, only the selected values will be returned.

For example, this can represent data needed to display some type of visualization such as a bar chart that shows “Sum of Sales” by “Salesperson”. At its simplest form, you can think of it as a table comprised of rows and columns. In that sense, our Hypercube that would power our barchart, would consist of a table with “Salesperson” as a column, and the evaluated “Sum(Sales)” expression as the other column. The engine will fill the cells of this table and extract all the information.

In order to use a Hypercube, we need to define it through the “qHyperCubeDef” object which is passed on to the Qlik Engine API to create a query for processing the data.

A very simple definition of a Hypercube looks like this:

 

qHyperCubeDef: { 
    qDimensions: [], 
    qMeasures: [], 
    qInitialDataFetch: [{ 
        qWidth: 2, 
        qHeight: 50 
    }] 
  } 

 

But since it is a very flexible tool of extracting data, that means that there is a huge amount of settings available for it. You can view the full definition of the structure here.

Let’s go over the most important ones:

  • qDimensions: defines the array of dimensions that will be used in the HyperCube. You can have multiple dimensions depending on the results you want.
    • There is a number of settings within the dimension that you can set. More about these here.
      • For instance, qNullSuppression: which removes null values in the dimension
      • qShowAll, which if set to true, displays all dimension values, regardless of whether they are selected or not.
      • etc...
    • qMeasures: similar to how qDimensions are defined, but used to define measures, i.e: calculations performed for each distinct value of dimensions.
    • qInitialDataFetch: defines how many data cells are initially retrieved from the calculated HyperCube.
    • qSuppressZero: if set to true, removes rows that have zero values across the entrie HyperCube row
    • qMode: defines the structure of the HyperCube that will be returned. By default, the data is returned as a straight table representation. But more advanced modes are available, such as:
      • Pivot table representation (qMode -> P)
      • Stacked table representation (qMode -> K)
      • Tree representation (qMode -> T)

Below is an example of a Hypercube where we define 1 dimension (ID) and 1 measure (=Sum(Value)), Notice that we define qWidth and qHeight properties within qInitialDataFetch as 2 and 5 respectively, since we have 2 columns and would like to get 5 rows of data for a total of 10 cells.

P.S: The maximum number of cells (qWidth * qHeight) allowed in an initial data fetch is 10,000.

 

const properties = {
  qInfo: {
    qType: 'my-straight-hypercube',
  },
  qHyperCubeDef: {
    qDimensions: [
      {
        qDef: { qFieldDefs: ['ID'] },
      },
    ],
    qMeasures: [
      {
        qDef: { qDef: '=Sum(Value)' },
      },
    ],
    qInitialDataFetch: [
      {
        qHeight: 5,
        qWidth: 2,
      },
    ],
  },
};

 

So how does the implementation look like in enigma.js?

For the purposes of this example, we are creating a Session App (i.e: have an inline script and create an app on the fly).

 

const qlikScript = `
TempTable:
Load
RecNo() as ID,
Rand() as Value
AutoGenerate 100
`;

const properties = {
  qInfo: {
    qType: 'my-straight-hypercube',
  },
  qHyperCubeDef: {
    qDimensions: [
      {
        qDef: { qFieldDefs: ['ID'] },
      },
    ],
    qMeasures: [
      {
        qDef: { qDef: '=Sum(Value)' },
      },
    ],
    qInitialDataFetch: [
      {
        qHeight: 5,
        qWidth: 2,
      },
    ],
  },
};

const session = createSession();

// Open the session and create a session document:
session.open()
  .then((global) => global.getActiveDoc())
  .then((doc) => doc.setScript(qlikScript)
    .then(() => doc.doReload())
    // Create a generic object with a hypercube definition containing one dimension and one measure
    .then(() => doc.createObject(properties))
    // Get hypercube layout
    .then((object) => object.getLayout()
      .then((layout) => console.log('Hypercube data pages:', JSON.stringify(layout.qHyperCube.qDataPages, null, '  ')))
      // Select cells at position 0, 2 and 4 in the dimension.
      .then(() => object.selectHyperCubeCells('/qHyperCubeDef', [0, 2, 4], [0], false))
      // Get layout and view the selected values
      .then(() => console.log('\n### After selection (notice the `qState` values):\n'))
      .then(() => object.getLayout().then((layout) => console.log('Hypercube data pages after selection:', JSON.stringify(layout.qHyperCube.qDataPages, null, '  '))))))
  // Close the session
  .then(() => session.close())
  .catch((error) => {
    console.log('Session: Failed to open socket:', error);
    process.exit(1);
  });

 

Notice that after opening the enigma session, and calling the necessary methods for our session app, we call “createObject” to create a Generic Object with our Hypercube definition containing 1 dimension and 1 measure, then get the Hypercube Layout which returns a bunch of results including our data.

We’re mostly interested in “qDataPages” which holds our calculated data. Below is how the typical response looks like:

Untitled.png

Going back to our code, the full qDataPages results are as follows from our first console.log:

 

Hypercube data pages: [
  {
    "qMatrix": [
      [
        {
          "qText": "1",
          "qNum": 1,
          "qElemNumber": 0,
          "qState": "O"
        },
        {
          "qText": "0.73213545326144",
          "qNum": 0.732135453261435,
          "qElemNumber": 0,
          "qState": "L"
        }
      ],
      [
        {
          "qText": "2",
          "qNum": 2,
          "qElemNumber": 1,
          "qState": "O"
        },
        {
          "qText": "0.66564685385674",
          "qNum": 0.6656468538567424,
          "qElemNumber": 0,
          "qState": "L"
        }
      ],
      [
        {
          "qText": "3",
          "qNum": 3,
          "qElemNumber": 2,
          "qState": "O"
        },
        {
          "qText": "0.66189019801095",
          "qNum": 0.6618901980109513,
          "qElemNumber": 0,
          "qState": "L"
        }
      ],
      [
        {
          "qText": "4",
          "qNum": 4,
          "qElemNumber": 3,
          "qState": "O"
        },
        {
          "qText": "0.98009621817619",
          "qNum": 0.9800962181761861,
          "qElemNumber": 0,
          "qState": "L"
        }
      ],
      [
        {
          "qText": "5",
          "qNum": 5,
          "qElemNumber": 4,
          "qState": "O"
        },
        {
          "qText": "0.48425585823134",
          "qNum": 0.4842558582313359,
          "qElemNumber": 0,
          "qState": "L"
        }
      ]
    ],
    "qTails": [
      {
        "qUp": 0,
        "qDown": 0
      }
    ],
    "qArea": {
      "qLeft": 0,
      "qTop": 0,
      "qWidth": 2,
      "qHeight": 5
    }
  }
]

 

Notice that we have additionally called the “selectHyperCubeCells” method in order to make a selection on the cells at positions 0, 2, and 4 in the Dimension. Here is how the results of qDataPages looks like after the selection:

 

### After selection (notice the `qState` values):
Hypercube data pages after selection: [
  {
    "qMatrix": [
      [
        {
          "qText": "1",
          "qNum": 1,
          "qElemNumber": 0,
          "qState": "S"
        },
        {
          "qText": "0.73213545326144",
          "qNum": 0.732135453261435,
          "qElemNumber": 0,
          "qState": "L"
        }
      ],
      [
        {
          "qText": "3",
          "qNum": 3,
          "qElemNumber": 2,
          "qState": "S"
        },
        {
          "qText": "0.66189019801095",
          "qNum": 0.6618901980109513,
          "qElemNumber": 0,
          "qState": "L"
        }
      ],
      [
        {
          "qText": "5",
          "qNum": 5,
          "qElemNumber": 4,
          "qState": "S"
        },
        {
          "qText": "0.48425585823134",
          "qNum": 0.4842558582313359,
          "qElemNumber": 0,
          "qState": "L"
        }
      ]
    ],
    "qTails": [
      {
        "qUp": 0,
        "qDown": 0
      }
    ],
    "qArea": {
      "qLeft": 0,
      "qTop": 0,
      "qWidth": 2,
      "qHeight": 3
    }
  }
]

 

List Objects

Alongside qHyperCube, the family of Generic Objects provides us with another structure called qListObject.

Unlike a Hypercube, a List Object better serves the purposes of displaying one single dimension without any required calculation, meaning no metrics are required to be defined.

As such, it is fairly straightforward to work the list objects, and their definition is very similar to the qHyperCube object, with some added extra properties minus measures.

I usually use List Objects when I want to build a list of values to be used for selections.

The following code is an example of creating a qListObjectDef and writing the resulting list object into the console:

 

List object data: [
  [
    {
      "qText": "0.063513102941215",
      "qNum": 0.06351310294121504,
      "qElemNumber": 0,
      "qState": "O"
    }
  ],
  [
    {
      "qText": "0.15987460175529",
      "qNum": 0.15987460175529122,
      "qElemNumber": 1,
      "qState": "O"
    }
  ],
  [
    {
      "qText": "0.50091209867969",
      "qNum": 0.5009120986796916,
      "qElemNumber": 2,
      "qState": "O"
    }
  ]
]

 

After selection of the first value, the qDataPages response looks like the following:

(Notice that with a qListObject, all values are rendered, regardless of whether they have been excluded or not.If selections are applied to a list object, the selected values are displayed along with the excluded and the optional values. We do however hava access to the qState property which lets us know if the value is selected (S), alternate (A), etc..)

 

List object data: [
  [
    {
      "qText": "0.063513102941215",
      "qNum": 0.06351310294121504,
      "qElemNumber": 0,
      "qState": "S"
    }
  ],
  [
    {
      "qText": "0.15987460175529",
      "qNum": 0.15987460175529122,
      "qElemNumber": 1,
      "qState": "A"
    }
  ],
  [
    {
      "qText": "0.50091209867969",
      "qNum": 0.5009120986796916,
      "qElemNumber": 2,
      "qState": "A"
    }
  ]
]

 

Attached is a zip file containing example code that shows how a default Hypercube, Pivot Hypercube, Stacked Hypercube, and a List Object are used in enigma.js.

To run the code:

  • first run “npm install”
  • change the .env file with your tenant and API key (learn how to get an API key here).
  • then, run “node filename.js” for each file and check the console logs.

To see how Hypercube can be used as part of creating visualizations with Nebula.js, checkout my previous blog post: https://community.qlik.com/t5/Qlik-Design-Blog/Create-a-Slope-chart-with-tooltips-and-brushing-using...

2 Comments