Qlik Community

Qlik Design Blog

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

Employee
Employee

picasso.js is an awesome open source charting library from Qlik for building beautiful data visualizations quickly and easily. It’s super flexible, easy to use with hypercube data, renders in svg or canvas, and includes a lot of other great features already. But one thing it doesn’t have built in (yet) which I need when building visualizations is transitions so I developed a method for adding transitions to any picasso.js visualization (which is built into the upcoming new version of qdt-components). Let me take you through it.

Check out an example at https://webapps.qlik.com/picasso-transitions/index.html

Step 1: Create hypercube and chart

The first step is to create a hypercube, get the layout, and create a picasso.js chart.

/* Create a hypercube */
  const object = await doc.createSessionObject({
    qInfo: { qType: 'data' },
    qHyperCubeDef: {
      qDimensions: [{
        qDef: { qFieldDefs: ['[Product Group Desc]'] },
        qOtherTotalSpec: {
          qOtherMode: "OTHER_COUNTED",
          qOtherCounted: { qv: "10" },
          qSuppressOther: true,
      },
      }],
      qMeasures: [{
        qDef: { qDef: 'Sum([Sales Amount])' },
      }],
      qInitialDataFetch: [{
        qWidth: 2,
        qHeight: 10,
      }],
    },
  });
  /* Get hypercube layout */
  let layout = await object.getLayout();
  /* Create chart */
  const chart = picasso({
    renderer: {
      prio: ['canvas'],
    },
  }).chart({
    element: document.querySelector('#chart'),
    data: [
      {
        type: 'q',
        key: 'qHyperCube',
        data: layout.qHyperCube,
      },
    ],
    settings: {
      scales: {
        y: {
          data: { field: 'qMeasureInfo/0' },
          include: [0],
          invert: true,
        },
        x: { data: { extract: { field: 'qDimensionInfo/0' } } },
      },
      components: [
        {
          type: 'axis',
          dock: 'left',
          scale: 'y',
        },
        {
          type: 'axis',
          dock: 'bottom',
          scale: 'x',
        },
        {
          type: 'box',
          key: 'bars',
          data: {
            extract: {
              field: 'qDimensionInfo/0',
              props: {
                start: 0,
                end: { field: 'qMeasureInfo/0' },
              },
            },
          },
          settings: {
            minor: { scale: 'y' },
            major: { scale: 'x' },
            box: {
              fill: 'steelblue',
            },
          },
        },
      ],
    },
  });

 

Step 2:  Animate changes to layout

Now that there's a hypercube and a chart, the next step is to animate the chart when the hypercube changes. To do this, subscribe to changes in the hypercube, and in the callback get the new layout, setup a timer, and interpolate between the previous layout and the new layout and pass the tween hypercubes to the chart's update function. It looks like this.

object.on('changed', async () => {  // subscribe to changes on the object
    const nextLayout = await object.getLayout();  // get the next layout

    const duration = 1500;
    const ease = easeCubic;  // easeCubic is d3-ease easeCubic function

    const transitionTimer = timer((elapsed) => {  // create a timer using d3-timer
      // compute how far through the animation we are (0 to 1)
      const t = Math.min(1, ease(elapsed / duration));
      // calculate the tween layout at time t
      const tweenLayout = interpolate(layout, nextLayout)(t);  // interpolate function is from d3-interpolate

      // update chart
      chart.update({
        data: [{
          type: 'q',
          key: 'qHyperCube',
          data: tweenLayout.qHyperCube,
        }],
      });

      // if this animation is over
      if (t === 1) {
        // stop this timer since we are done animating.
        transitionTimer.stop();
        layout = nextLayout;
      }
    });
  });

 

Step 3: Animate size changes

Now the picasso.js chart will animate when the layout of the object changes, but it should also animate in response to CSS animations on it's parent container. A ResizeObserver can be used for that. ResizeObservers are relatively new, and should be polyfilled using the resize-observer-polyfill library. It's as simple as this.

const ro = new ResizeObserver(() => {
    chart.update();
  });
  ro.observe(document.querySelector('#chart'));

 

Don't forget to check out the example at https://webapps.qlik.com/picasso-transitions/index.html. The code for this example is attached.

This is a rudimentary example of this approach, and does not handle things such as canceling transitions if another transition is started before the previous transition is finished. If you go through this example and want to check out my work to date on this, check out the QdtPicasso component on the working branch for the next version of qdt-components at https://github.com/qlik-demo-team/qdt-components/blob/3.0/src/components/QdtPicasso/QdtPicasso.jsx.

 

Labels