Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8cd78ae5f | ||
|
|
a5bc3ecd1b | ||
|
|
bec68e7cd4 | ||
|
|
02bba4ad5a | ||
|
|
57c4b12b24 | ||
|
|
cac3fabb2f | ||
|
|
c57b0edea8 | ||
|
|
3862bd294c | ||
|
|
0c18523891 |
@@ -33,25 +33,27 @@ class DataCell extends React.PureComponent {
|
||||
styling
|
||||
} = this.props;
|
||||
|
||||
const isColumnPercentageBased = (/%/).test(measurement.format);
|
||||
let formattedMeasurementValue = formatMeasurementValue(measurement, styling);
|
||||
if (styleBuilder.hasComments()) {
|
||||
formattedMeasurementValue = '.';
|
||||
}
|
||||
let textAlignment = 'Right';
|
||||
const textAlignmentProp = styling.options.textAlignment;
|
||||
if (textAlignmentProp) {
|
||||
textAlignment = textAlignmentProp;
|
||||
}
|
||||
let textAlignment = styling.options.textAlignment || 'Right';
|
||||
|
||||
let cellStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
...styleBuilder.getStyle(),
|
||||
paddingLeft: '5px',
|
||||
textAlign: textAlignment
|
||||
|
||||
};
|
||||
|
||||
const isEmptyCell = measurement.displayValue === '';
|
||||
const isColumnPercentageBased = (/%/).test(measurement.format);
|
||||
let formattedMeasurementValue;
|
||||
if (isEmptyCell) {
|
||||
formattedMeasurementValue = '';
|
||||
cellStyle.cursor = 'default';
|
||||
} else if (styleBuilder.hasComments()) {
|
||||
formattedMeasurementValue = '.';
|
||||
} else {
|
||||
formattedMeasurementValue = formatMeasurementValue(measurement, styling);
|
||||
}
|
||||
|
||||
const { semaphoreColors, semaphoreColors: { fieldsToApplyTo } } = styling;
|
||||
const isValidSemaphoreValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
|
||||
const dimension1Row = measurement.parents.dimension1.elementNumber;
|
||||
@@ -79,7 +81,7 @@ class DataCell extends React.PureComponent {
|
||||
return (
|
||||
<td
|
||||
className={`${cellClass}${general.cellSuffix}`}
|
||||
onClick={this.handleSelect}
|
||||
onClick={isEmptyCell ? null : this.handleSelect}
|
||||
style={cellStyle}
|
||||
>
|
||||
<Tooltip
|
||||
|
||||
@@ -69,6 +69,7 @@ const DataTable = ({ data, general, qlik, renderData, styling }) => {
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
const { dimension1: dimension1Info, dimension2, measurement } = measurementData.parents;
|
||||
const id = `${dimension1Info.elementNumber}-${dimension2 && dimension2.elementNumber}-${measurement.header}`;
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ function createCube (definition, app) {
|
||||
});
|
||||
}
|
||||
|
||||
async function buildDataCube (originCubeDefinition, dimensionIndexes, app) {
|
||||
async function buildDataCube (originCubeDefinition, hasTwoDimensions, app) {
|
||||
const cubeDefinition = {
|
||||
...originCubeDefinition,
|
||||
qInitialDataFetch: [
|
||||
@@ -15,62 +15,18 @@ async function buildDataCube (originCubeDefinition, dimensionIndexes, app) {
|
||||
qWidth: 10
|
||||
}
|
||||
],
|
||||
qDimensions: [originCubeDefinition.qDimensions[dimensionIndexes.dimension1]],
|
||||
qDimensions: [originCubeDefinition.qDimensions[0]],
|
||||
qMeasures: originCubeDefinition.qMeasures
|
||||
};
|
||||
if (dimensionIndexes.dimension2) {
|
||||
cubeDefinition.qDimensions.push(originCubeDefinition.qDimensions[dimensionIndexes.dimension2]);
|
||||
if (hasTwoDimensions) {
|
||||
cubeDefinition.qDimensions.push(originCubeDefinition.qDimensions[1]);
|
||||
}
|
||||
const cube = await createCube(cubeDefinition, app);
|
||||
|
||||
return cube.qHyperCube.qDataPages[0].qMatrix;
|
||||
}
|
||||
|
||||
async function buildDesignCube (originCubeDefinition, dimensionIndexes, app) {
|
||||
if (!dimensionIndexes.design) {
|
||||
return null;
|
||||
}
|
||||
const cube = await createCube({
|
||||
qInitialDataFetch: [
|
||||
{
|
||||
qHeight: 1000,
|
||||
qWidth: 1
|
||||
}
|
||||
],
|
||||
qDimensions: [originCubeDefinition.qDimensions[dimensionIndexes.design]]
|
||||
}, app);
|
||||
|
||||
return cube.qHyperCube.qDataPages[0].qMatrix;
|
||||
}
|
||||
|
||||
const STYLE_SEPARATOR_COUNT = 7;
|
||||
function findDesignDimension (qMatrix) {
|
||||
return qMatrix[0].map(entry => (entry.qText.match(/;/g) || []).length).indexOf(STYLE_SEPARATOR_COUNT);
|
||||
}
|
||||
|
||||
function getDimensionIndexes (dimensionsInformation, designDimensionIndex) {
|
||||
const hasDesign = designDimensionIndex !== -1;
|
||||
const nonDesignDimensionCount = hasDesign ? dimensionsInformation.length - 1 : dimensionsInformation.length;
|
||||
const dimension1 = designDimensionIndex === 0 ? 1 : 0;
|
||||
let dimension2 = false;
|
||||
if (nonDesignDimensionCount === 2) {
|
||||
dimension2 = hasDesign && designDimensionIndex < 2 ? 2 : 1;
|
||||
}
|
||||
const design = hasDesign && designDimensionIndex;
|
||||
const firstMeasurementIndex = dimensionsInformation.length;
|
||||
return {
|
||||
design,
|
||||
dimension1,
|
||||
dimension2,
|
||||
firstMeasurementIndex
|
||||
};
|
||||
}
|
||||
|
||||
export async function initializeCubes ({ component, layout }) {
|
||||
export async function initializeDataCube (component, layout) {
|
||||
const app = qlik.currApp(component);
|
||||
const designDimensionIndex = findDesignDimension(layout.qHyperCube.qDataPages[0].qMatrix);
|
||||
const dimensionsInformation = layout.qHyperCube.qDimensionInfo;
|
||||
const dimensionIndexes = getDimensionIndexes(dimensionsInformation, designDimensionIndex);
|
||||
|
||||
let properties;
|
||||
if (component.backendApi.isSnapshot) {
|
||||
@@ -80,12 +36,24 @@ export async function initializeCubes ({ component, layout }) {
|
||||
properties = await component.backendApi.getProperties();
|
||||
}
|
||||
|
||||
const originCubeDefinition = properties.qHyperCubeDef;
|
||||
const designCube = await buildDesignCube(originCubeDefinition, dimensionIndexes, app);
|
||||
const dataCube = await buildDataCube(originCubeDefinition, dimensionIndexes, app);
|
||||
|
||||
return {
|
||||
design: designCube,
|
||||
data: dataCube
|
||||
};
|
||||
return buildDataCube(
|
||||
properties.qHyperCubeDef, layout.qHyperCube.qDimensionInfo.length === 2, app);
|
||||
}
|
||||
|
||||
export function initializeDesignList (component, layout) {
|
||||
if (!layout.stylingfield) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const app = qlik.currApp(component);
|
||||
const stylingField = app.field(layout.stylingfield);
|
||||
const listener = function () {
|
||||
const data = stylingField.rows.map(row => row.qText);
|
||||
stylingField.OnData.unbind(listener);
|
||||
resolve(data);
|
||||
};
|
||||
stylingField.OnData.bind(listener);
|
||||
stylingField.getData();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ const definition = {
|
||||
},
|
||||
uses: 'data'
|
||||
},
|
||||
sorting: {
|
||||
uses: 'sorting'
|
||||
},
|
||||
settings: {
|
||||
items: {
|
||||
ConceptSemaphores: conceptSemaphores,
|
||||
@@ -28,8 +31,25 @@ const definition = {
|
||||
},
|
||||
uses: 'settings'
|
||||
},
|
||||
sorting: {
|
||||
uses: 'sorting'
|
||||
about: {
|
||||
component: 'items',
|
||||
label: 'About',
|
||||
items: {
|
||||
header: {
|
||||
label: 'P&L pivot',
|
||||
style: 'header',
|
||||
component: 'text'
|
||||
},
|
||||
paragraph1: {
|
||||
label: `P&L pivot is a Qlik Sense extension which allows you to display Profit & Loss
|
||||
reporting with color and font customizations.`,
|
||||
component: 'text'
|
||||
},
|
||||
paragraph2: {
|
||||
label: 'P&L pivot is based upon an extension created by Ivan Felipe Asensio.',
|
||||
component: 'text'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
type: 'items'
|
||||
|
||||
@@ -1,12 +1,53 @@
|
||||
const qlik = window.require('qlik');
|
||||
|
||||
// fixes case for when there are 3 dimensions, missies the case with 1 design dimension and 1 data dimension
|
||||
function hasDesignDimension (data) {
|
||||
return data.qHyperCubeDef.qDimensions.length > 2;
|
||||
}
|
||||
|
||||
function getFieldList () {
|
||||
return new Promise(function (resolve) {
|
||||
const app = qlik.currApp();
|
||||
app.getList('FieldList').then(function (model) {
|
||||
// Close the model to prevent any updates.
|
||||
app.destroySessionObject(model.layout.qInfo.qId);
|
||||
|
||||
// This is a bit iffy, might be smarter to reject and handle empty lists on the props instead.
|
||||
if (!model.layout.qFieldList.qItems) {
|
||||
return resolve([]);
|
||||
}
|
||||
// Resolve an array with master objects.
|
||||
return resolve(model.layout.qFieldList.qItems.map(function (item) {
|
||||
return {
|
||||
value: item.qName,
|
||||
label: item.qName
|
||||
};
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const tableFormat = {
|
||||
type: 'items',
|
||||
label: 'Table Format',
|
||||
items: {
|
||||
StylingField: {
|
||||
ref: 'stylingfield',
|
||||
disabledRef: '',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
label: 'Style with field',
|
||||
options: function () {
|
||||
return getFieldList().then(function (items) {
|
||||
items.unshift(
|
||||
{
|
||||
value: '',
|
||||
label: 'None'
|
||||
});
|
||||
return items;
|
||||
});
|
||||
}
|
||||
},
|
||||
IndentBool: {
|
||||
ref: 'indentbool',
|
||||
type: 'boolean',
|
||||
|
||||
@@ -20,12 +20,16 @@ export default {
|
||||
},
|
||||
data: {
|
||||
dimensions: {
|
||||
max: 3,
|
||||
max: function (nMeasures) {
|
||||
return nMeasures < 9 ? 2 : 1;
|
||||
},
|
||||
min: 1,
|
||||
uses: 'dimensions'
|
||||
},
|
||||
measures: {
|
||||
max: 8,
|
||||
max: function (nDims) {
|
||||
return nDims < 2 ? 9 : 8;
|
||||
},
|
||||
min: 1,
|
||||
uses: 'measures'
|
||||
}
|
||||
|
||||
@@ -107,16 +107,16 @@ function generateMatrixCell ({ cell, dimension1Information, dimension2Informatio
|
||||
}
|
||||
|
||||
let lastRow = 0;
|
||||
function generateDataSet (component, dimensionsInformation, measurementsInformation, cubes) {
|
||||
const dimension1 = [];
|
||||
const dimension2 = [];
|
||||
function generateDataSet (
|
||||
component, dimensionsInformation, measurementsInformation, dataCube) {
|
||||
|
||||
const measurements = generateMeasurements(measurementsInformation);
|
||||
let dimension1 = [];
|
||||
let dimension2 = [];
|
||||
let matrix = [];
|
||||
|
||||
let previousDim1Entry;
|
||||
const hasDesignDimension = cubes.design;
|
||||
const hasSecondDimension = hasDesignDimension ? dimensionsInformation.length > 2 : dimensionsInformation.length > 1;
|
||||
cubes.data.forEach(row => {
|
||||
const hasSecondDimension = dimensionsInformation.length > 1;
|
||||
dataCube.forEach(row => {
|
||||
lastRow += 1;
|
||||
const dimension1Entry = generateDimensionEntry(dimensionsInformation[0], row[0]);
|
||||
dimension1.push(dimension1Entry);
|
||||
@@ -127,7 +127,7 @@ function generateDataSet (component, dimensionsInformation, measurementsInformat
|
||||
dimension2.push(dimension2Entry);
|
||||
firstDataCell = 2;
|
||||
}
|
||||
const matrixRow = row
|
||||
let matrixRow = row
|
||||
.slice(firstDataCell, row.length)
|
||||
.map((cell, cellIndex) => {
|
||||
const measurementInformation = measurements[cellIndex];
|
||||
@@ -143,36 +143,65 @@ function generateDataSet (component, dimensionsInformation, measurementsInformat
|
||||
return generatedCell;
|
||||
});
|
||||
|
||||
let appendToRowIndex = matrix.length;
|
||||
if (hasSecondDimension) {
|
||||
const currentDim1Entry = row[0].qText;
|
||||
const isSameDimension1AsPrevious = currentDim1Entry === previousDim1Entry;
|
||||
if (isSameDimension1AsPrevious) {
|
||||
const updatedRow = matrix[matrix.length - 1].concat(matrixRow);
|
||||
|
||||
matrix = [
|
||||
...matrix.slice(0, matrix.length - 1),
|
||||
updatedRow
|
||||
];
|
||||
} else {
|
||||
matrix[matrix.length] = matrixRow;
|
||||
// See if there already is a row for the current dim1
|
||||
for (let i = 0; i < matrix.length; i++) {
|
||||
if (matrix[i][0].parents.dimension1.header === matrixRow[0].parents.dimension1.header) {
|
||||
appendToRowIndex = i;
|
||||
matrixRow = matrix[i].concat(matrixRow);
|
||||
}
|
||||
}
|
||||
previousDim1Entry = currentDim1Entry;
|
||||
} else {
|
||||
matrix[matrix.length] = matrixRow;
|
||||
}
|
||||
matrix[appendToRowIndex] = matrixRow;
|
||||
});
|
||||
|
||||
// filter header dimensions to only have distinct values
|
||||
dimension1 = distinctArray(dimension1);
|
||||
dimension2 = distinctArray(dimension2);
|
||||
|
||||
// Make sure all rows are saturated, otherwise data risks being displayed in the wrong column
|
||||
matrix = matrix.map((row, rowIndex) => {
|
||||
if (row.length == dimension2.length) {
|
||||
// Row is saturated
|
||||
return row;
|
||||
}
|
||||
|
||||
// Row is not saturated, so must add empty cells to fill the gaps
|
||||
let newRow = [];
|
||||
let cellIndex = 0;
|
||||
dimension2.forEach(dim => {
|
||||
measurements.forEach(measurement => {
|
||||
if (cellIndex < row.length
|
||||
&& row[cellIndex].parents.dimension2.elementNumber === dim.elementNumber
|
||||
&& row[cellIndex].parents.measurement.header === measurement.name) {
|
||||
newRow.push(row[cellIndex]);
|
||||
cellIndex++;
|
||||
} else {
|
||||
newRow.push({
|
||||
displayValue: '',
|
||||
parents: {
|
||||
dimension1: { elementNumber: rowIndex },
|
||||
dimension2: { elementNumber: dim.elementNumber },
|
||||
measurement: { header: measurement.name }
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return newRow;
|
||||
});
|
||||
|
||||
return {
|
||||
dimension1: distinctArray(dimension1),
|
||||
dimension2: distinctArray(dimension2),
|
||||
dimension1: dimension1,
|
||||
dimension2: dimension2,
|
||||
matrix,
|
||||
measurements
|
||||
};
|
||||
}
|
||||
|
||||
function initializeTransformed ({ $element, component, cubes, layout }) {
|
||||
function initializeTransformed ({ $element, component, dataCube, designList, layout }) {
|
||||
const dimensionsInformation = component.backendApi.getDimensionInfos();
|
||||
const measurementsInformation = component.backendApi.getMeasureInfos();
|
||||
const dimensionCount = layout.qHyperCube.qDimensionInfo.length;
|
||||
@@ -183,19 +212,18 @@ function initializeTransformed ({ $element, component, cubes, layout }) {
|
||||
dimension2,
|
||||
measurements,
|
||||
matrix
|
||||
} = generateDataSet(component, dimensionsInformation, measurementsInformation, cubes);
|
||||
} = generateDataSet(component, dimensionsInformation, measurementsInformation, dataCube);
|
||||
|
||||
const customSchemaBasic = [];
|
||||
const customSchemaFull = [];
|
||||
let customHeadersCount = 0;
|
||||
|
||||
if (cubes.design) {
|
||||
const allTextLines = cubes.design.map(entry => entry[0].qText);
|
||||
const headers = allTextLines[0].split(';');
|
||||
if (designList && designList.length > 0) {
|
||||
const headers = designList[0].split(';');
|
||||
customHeadersCount = headers.length;
|
||||
for (let lineNumber = 0; lineNumber < allTextLines.length; lineNumber += 1) {
|
||||
for (let lineNumber = 0; lineNumber < designList.length; lineNumber += 1) {
|
||||
customSchemaFull[lineNumber] = new Array(headers.length);
|
||||
const data = allTextLines[lineNumber].split(';');
|
||||
const data = designList[lineNumber].split(';');
|
||||
|
||||
if (data.length === headers.length) {
|
||||
for (let headerIndex = 0; headerIndex < headers.length; headerIndex += 1) {
|
||||
@@ -239,7 +267,7 @@ function initializeTransformed ({ $element, component, cubes, layout }) {
|
||||
count: customHeadersCount,
|
||||
full: customSchemaFull
|
||||
},
|
||||
hasCustomFileStyle: Boolean(cubes.design),
|
||||
hasCustomFileStyle: Boolean(designList),
|
||||
headerOptions: {
|
||||
alignment: getAlignment(layout.HeaderAlign),
|
||||
colorSchema: layout.HeaderColorSchema.color,
|
||||
|
||||
@@ -2,25 +2,24 @@ import initializeStore from './store';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Root from './root.jsx';
|
||||
import { initializeCubes } from './dataset';
|
||||
import { initializeDataCube, initializeDesignList } from './dataset';
|
||||
|
||||
export default async function paint ($element, layout, component) {
|
||||
const cubes = await initializeCubes({
|
||||
component,
|
||||
layout
|
||||
});
|
||||
const dataCube = await initializeDataCube(component, layout);
|
||||
const designList = await initializeDesignList(component, layout);
|
||||
const state = await initializeStore({
|
||||
$element,
|
||||
component,
|
||||
cubes,
|
||||
dataCube,
|
||||
designList,
|
||||
layout
|
||||
});
|
||||
const editmodeClass = component.inAnalysisState() ? '' : 'edit-mode';
|
||||
const jsx = (
|
||||
<Root
|
||||
editmodeClass={editmodeClass}
|
||||
qlik={component}
|
||||
state={state}
|
||||
editmodeClass={editmodeClass}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import initializeTransformed from './initialize-transformed';
|
||||
|
||||
async function initialize ({ $element, layout, component, cubes }) {
|
||||
async function initialize ({ $element, layout, component, dataCube, designList }) {
|
||||
const transformedProperties = await initializeTransformed({
|
||||
$element,
|
||||
component,
|
||||
cubes,
|
||||
dataCube,
|
||||
designList,
|
||||
layout
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user