npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

picasso-plugin-q

v2.2.7

Published

QIX plugin for picasso.js

Downloads

11,436

Readme

picasso-q-plugin

The q plugin registers a q dataset type that makes it a bit easier to extract data from a QIX hypercube. It also contains a brush helper that can be used to find appropriate selections in the underlying data engine.

Installation

npm install picasso-plugin-q

Register plugin

import picasso from 'picasso.js';
import picassoQ from 'picasso-plugin-q';

picasso.use(picassoQ); // register

q dataset

This dataset type understands the QIX hypercube format and its internals, making it a bit easier to traverse and extract values from an otherwise complex structure.

const ds = picasso.data('q')({
  key: 'qHyperCube', // path to the hypercube from the layout
  data: layout.qHyperCube,
});

Dimensions, measures, attribute expressions and attribute dimensions are all recognized as fields and can be found using either the path or the title of the field:

const f = ds.field('Sales');
const ff = ds.field('qDimensionInfo/1/qAttrDimInfo/2');

Extracting data values

Assuming we have a hypercube containing dimensions Year and Month, a measure Sales and an attribute expression on the first dimension containing color values:

// hypercube stub
{
  qDimensionInfo: [
    { qFallbackTitle: 'Year', qAttrDimInfo: [{ qFallbackTitle: 'color' }, /* ... */], /* ... */ },
    { qFallbackTitle: 'Month', /* ... */  }
  ],
  qMeasureInfo: [
    { qFallbackTitle: '# products', /* ... */  }
  ]
}

In a straight hypercube the qMatrix might look like this:

[
  [
    { "qText": "2011", "qNum": 2011, "qElemNumber": 0, "qState": "O", "qAttrExps": { "qValues": [{"qText": "red", "qNum": "NaN" }] } },
    { "qText": "Jan", "qNum": 1, "qElemNumber": 0, "qState": "S",  "qAttrDims": { "qValues": [{ "qText": "Jan", "qElemNo": 0 }] } },
    { "qText": "61", "qNum": 61, "qElemNumber": 0, "qState": "L" }
  ],
  [
    { "qText": "2011", "qNum": 2011, "qElemNumber": 0, "qState": "O", "qAttrExps": { "qValues": [{"qText": "blue", "qNum": "NaN" }] } },
    { "qText": "Feb", "qNum": 2, "qElemNumber": 1, "qState": "S", "qAttrDims": { "qValues": [{"qText": "Feb", "qElemNo": 1 }] } },
    { "qText": "62", "qNum": 62, "qElemNumber": 0, "qState": "L" }
  ],
  [
    { "qText": "2012","qNum": 2012, "qElemNumber": 1, "qState": "O", "qAttrExps": { "qValues": [{"qText": "red", "qNum": "NaN" }] } },
    { "qText": "Jan", "qNum": 1, "qElemNumber": 0, "qState": "S", "qAttrDims": { "qValues": [{"qText": "Jan", "qElemNo": 0}] } },
    { "qText": "88", "qNum": 88, "qElemNumber": 0, "qState": "L" }
  ],
  [
    { "qText": "2012", "qNum": 2012, "qElemNumber": 1, "qState": "O", "qAttrExps": { "qValues": [{"qText": "blue", "qNum": "NaN" }] } },
    { "qText": "Feb", "qNum": 2, "qElemNumber": 1, "qState": "S", "qAttrDims": { "qValues": [{"qText": "Feb","qElemNo": 1}] } },
    { "qText": "76", "qNum": 76, "qElemNumber": 0, "qState": "L" }
  ]

We can extract the unique Month values using:

ds.extract({
  field: 'Month',
  trackBy: (v) => v.qElemNumber,
});

// output
[
  { value: 0, label: 'Jan', source: { key: 'qHyperCube', field: 'qDimensionInfo/1' } },
  { value: 1, label: 'Feb', source: { key: 'qHyperCube', field: 'qDimensionInfo/1' } },
];

and attach aggregated properties on each item using props:

ds.extract({
  field: 'Month',
  trackBy: v => v.qElemNumber
  props: {
    years: { field: 'Year', value: v => v.qText, reduce: values => values.join(' - ') },
    color: { field: 'color', value: v => v.qText },
    products: { field: '# products', reduce: 'sum' }
  }
});

// output
[
  {
    value: 0, label: 'Jan', source: { key: 'qHyperCube', field: 'qDimensionInfo/1' },
    years: { value: '2011 - 2012', source: { key: 'qHyperCube', field: 'qDimensionInfo/0' } }
    color: { value: 'red', source: { key: 'qHyperCube', field: 'qDimensionInfo/0/qAttrExprInfo/0' } }
    products: { value: 149, source: { key: 'qHyperCube', field: 'qMeasureInfo/0' } }
  },
  {
    value: 1, label: 'Feb', source: { key: 'qHyperCube', field: 'qDimensionInfo/1' },
    years: { value: '2011 - 2012', source: { key: 'qHyperCube', field: 'qDimensionInfo/0' } }
    color: { value: 'blue', source: { key: 'qHyperCube', field: 'qDimensionInfo/0/qAttrExprInfo/0' } }
    products: { value: 138, source: { key: 'qHyperCube', field: 'qMeasureInfo/0' } }
  }
]

The default value accessor for a field depends on the field type and the qModeproperty of the hypercube:

  • For measures and attribute expressions: cell => cell.qNum or cell => cell.qValue
  • For dimensions and attribute dimensions: cell => cell.qElemNumber or cell => cell.qElemNo

The default reduce function is avg for measures and first for dimensions.

QIX selections helper

The QIX selections helper provides a mapping from brushed data points to suitable QIX selections.

By dimension value

Brushing dimension values is done by adding the value of qElemNumber to the brush, and providing the path to the relevant dimension:

const b = chart.brush('selection');
b.addValue('qHyperCube/qDimensionInfo/2', 4);
b.addValue('qHyperCube/qDimensionInfo/2', 7);

Calling picassoQ.selections with the above instance generates relevant QIX methods and parameters to apply a selection to:

const selection = picassoQ.selections(b)[0];
// {
//   method: 'selectHyperCubeValues',
//   params: [
//     '/qHyperCubeDef', // path to hypercube to apply selection to
//     2, // dimension column
//     [4, 7], // qElemNumbers
//     false
//   ]
// }

The selection can then be applied to a QIX model:

model[selection.method](...selection.params);

By measure range

Brushing measure ranges:

const b = chart.brush('selection');
b.addRange('qHyperCube/qMeasureInfo/2', { min: 13, max: 35 });

const selection = picassoQ.selections(b)[0];
// {
//   method: 'rangeSelectHyperCubeValues',
//   params: ['/qHyperCubeDef', [
//     {
//       qMeasureIx: 2,
//       qRange: { qMin: 13, qMax: 35, qMinIncEq: true, qMaxInclEq: true }
//     }
//   ]]
// }

By dimension range

Brushing dimension ranges:

const b = chart.brush('selection');
b.addRange('qHyperCube/qDimensionInfo/1', { min: 13, max: 35 });

const selection = picassoQ.selections(b)[0];
// {
//   method: 'selectHyperCubeContinuousRange',
//   params: ['/qHyperCubeDef', [
//     {
//       qDimIx: 1,
//       qRange: { qMin: 13, qMax: 35, qMinIncEq: true, qMaxInclEq: false }
//     }
//   ]]
// }

By row indices

Brushing by table row index and column:

const b = chart.brush('selection');
b.addValue('qHyperCube/qDimensionInfo/1', 10);
b.addValue('qHyperCube/qDimensionInfo/1', 13);
b.addValue('qHyperCube/qDimensionInfo/0', 11);
b.addValue('qHyperCube/qDimensionInfo/0', 17);

In the above case, rows 10 and 13 have been brushed on dimension 1, and rows 11 and 17 on dimension 0. To extract the relevant information, byCells is enabled:

const selection = picassoQ.selections(b, { byCells: true })[0];
// {
//   method: 'selectHyperCubeCells',
//   params: [
//     '/qHyperCubeDef',
//     [10, 13], // row indices in hypercube
//     [1, 0] // column indices in hypercube
//   ]
// }

Row indices are used from the first dimension that adds a value to a brush, qDimensionInfo/1, in the case above. To use values from another dimension, primarySource should be set:

const selection = picassoQ.selections(b, {
  byCells: true,
  primarySource: 'qHyperCube/qDimensionInfo/0',
})[0];
// {
//   method: 'selectHyperCubeCells',
//   params: [
//     '/qHyperCubeDef',
//     [11, 17], // row indices in hypercube
//     [1, 0] // column indices in hypercube
//   ]
// }

By attribute dimension

Brush on attribute dimension values:

const b = chart.brush('selection');
b.addValue('qHyperCube/qDimensionInfo/2/qAttrDimInfo/3', 6);
b.addValue('qHyperCube/qDimensionInfo/2/qAttrDimInfo/3', 9);

const selection = picassoQ.selections(b)[0];
// {
//   method: 'selectHyperCubeValues',
//   params: [
//     '/qHyperCubeDef/qDimensions/2/qAttributeDimensions/3', // path to hypercube to apply selection to
//     0, // dimension column in attribute dimension table
//     [6, 9], // qElemNumbers
//     false
//   ]
// }

By attribute expression range

Brush on attribute expression range:

const b = chart.brush('selection');
b.addRange('qHyperCube/qMeasureInfo/1/qAttrExprInfo/2', { min: 11, max: 21 });

QIX selections on attribute expressions are similar to selections on measure ranges. In this case however, the index of the measure is derived from the number of measures and attribute expressions that exist in the hypercube. Therefore, to calculate the index, layout containing the hypercube needs to be provided as a parameter:

const selection = picassoQ.selections(b, {}, layout)[0];
// {
//   method: 'rangeSelectHyperCubeValues',
//   params: ['/qHyperCubeDef', [
//     {
//       qMeasureIx: 7,
//       qRange: { qMin: 11, qMax: 21, qMinIncEq: true, qMaxInclEq: true }
//     }
//   ]]
// }

Assuming a layout of:

{
  qHyperCube: {
    qDimensionInfo: [
      { qAttrExprInfo: [{}] }
    ],
    qMeasureInfo: [
      { qAttrExprInfo: [{}, {}] },
      { qAttrExprInfo: [{}, {}, {/* this is the one */ }] }
    ]
  }
}

then qMeasureIx is calculated as follows:

  • number of measures: 2
  • total number of attribute expressions in all dimensions: 1
  • total number of attribute expressions in measures preceding the one specified: 2 (from first measure)
  • the actual index of the specified attribute expression: 2

which results in 2 + 1 + 2 + 2 = 7