Compare commits
33 Commits
DEB-130/Ve
...
saturateMa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a12205c840 | ||
|
|
ebfee69c7b | ||
|
|
05c25c72cb | ||
|
|
a6a6ef24b5 | ||
|
|
8374ecce85 | ||
|
|
0135d4fc64 | ||
|
|
47d7f33cb9 | ||
|
|
4c2e483592 | ||
|
|
801c7c862e | ||
|
|
f3bda74202 | ||
|
|
3ec5d13ae6 | ||
|
|
2133bb44ef | ||
|
|
e447666982 | ||
|
|
7038140088 | ||
|
|
b5d9633496 | ||
|
|
a518432db4 | ||
|
|
f1875702be | ||
|
|
b1a9130663 | ||
|
|
dfb30285ab | ||
|
|
c8cd78ae5f | ||
|
|
a5bc3ecd1b | ||
|
|
bec68e7cd4 | ||
|
|
02bba4ad5a | ||
|
|
57c4b12b24 | ||
|
|
ca33540fd6 | ||
|
|
cac3fabb2f | ||
|
|
c57b0edea8 | ||
|
|
3862bd294c | ||
|
|
0c18523891 | ||
|
|
629821bd6b | ||
|
|
08c5cf8104 | ||
|
|
d3c39bea75 | ||
|
|
34527c3d6d |
@@ -15,7 +15,7 @@ gulp.task('qext', function () {
|
||||
type: 'visualization',
|
||||
description: pkg.description + '\nVersion: ' + VERSION,
|
||||
version: VERSION,
|
||||
icon: 'table',
|
||||
icon: 'pivot-table',
|
||||
preview: 'qlik-smart-pivot.png',
|
||||
keywords: 'qlik-sense, visualization',
|
||||
author: pkg.author,
|
||||
|
||||
@@ -33,40 +33,38 @@ 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 { semaphoreColors, semaphoreColors: { fieldsToApplyTo } } = styling;
|
||||
const isValidSemaphoreValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
|
||||
const dimension1Row = measurement.parents.dimension1.elementNumber;
|
||||
const isSpecifiedMetricField = fieldsToApplyTo.metricsSpecificFields.indexOf(dimension1Row) !== -1;
|
||||
const shouldHaveSemaphoreColors = (fieldsToApplyTo.applyToMetric || isSpecifiedMetricField);
|
||||
if (isValidSemaphoreValue && shouldHaveSemaphoreColors) {
|
||||
const { backgroundColor, color } = getSemaphoreColors(measurement, semaphoreColors);
|
||||
cellStyle = {
|
||||
...styleBuilder.getStyle(),
|
||||
backgroundColor,
|
||||
color,
|
||||
fontFamily: styling.options.fontFamily,
|
||||
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 { conditionalColoring } = styling;
|
||||
if (conditionalColoring.enabled) {
|
||||
const isValidConditionalColoringValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
|
||||
const isSpecifiedRow =
|
||||
conditionalColoring.rows.indexOf(measurement.parents.dimension1.header) !== -1;
|
||||
const shouldHaveConditionalColoring = conditionalColoring.colorAllRows || isSpecifiedRow;
|
||||
if (isValidConditionalColoringValue && shouldHaveConditionalColoring) {
|
||||
const { color, textColor } = getConditionalColor(measurement, conditionalColoring);
|
||||
cellStyle.backgroundColor = color.color;
|
||||
cellStyle.color = textColor.color;
|
||||
}
|
||||
}
|
||||
|
||||
let cellClass = 'grid-cells';
|
||||
@@ -79,10 +77,11 @@ class DataCell extends React.PureComponent {
|
||||
return (
|
||||
<td
|
||||
className={`${cellClass}${general.cellSuffix}`}
|
||||
onClick={this.handleSelect}
|
||||
onClick={isEmptyCell ? null : this.handleSelect}
|
||||
style={cellStyle}
|
||||
>
|
||||
<Tooltip
|
||||
styling={styling}
|
||||
tooltipText={formattedMeasurementValue}
|
||||
>
|
||||
{formattedMeasurementValue}
|
||||
@@ -174,12 +173,12 @@ function formatMeasurementValue (measurement, styling) {
|
||||
return formattedMeasurementValue;
|
||||
}
|
||||
|
||||
function getSemaphoreColors (measurement, semaphoreColors) {
|
||||
if (measurement.value < semaphoreColors.status.critical) {
|
||||
return semaphoreColors.statusColors.critical;
|
||||
function getConditionalColor (measurement, conditionalColoring) {
|
||||
if (measurement.value < conditionalColoring.threshold.poor) {
|
||||
return conditionalColoring.colors.poor;
|
||||
}
|
||||
if (measurement.value < semaphoreColors.status.medium) {
|
||||
return semaphoreColors.statusColors.medium;
|
||||
if (measurement.value < conditionalColoring.threshold.fair) {
|
||||
return conditionalColoring.colors.fair;
|
||||
}
|
||||
return semaphoreColors.statusColors.normal;
|
||||
return conditionalColoring.colors.good;
|
||||
}
|
||||
|
||||
@@ -40,13 +40,15 @@ const DataTable = ({ data, general, qlik, renderData, styling }) => {
|
||||
|
||||
return (
|
||||
<tr key={dimensionEntry.displayValue}>
|
||||
<RowHeader
|
||||
entry={dimensionEntry}
|
||||
qlik={qlik}
|
||||
rowStyle={rowStyle}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
{!renderData ?
|
||||
<RowHeader
|
||||
entry={dimensionEntry}
|
||||
qlik={qlik}
|
||||
rowStyle={rowStyle}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/> : null
|
||||
}
|
||||
{renderData && injectSeparators(
|
||||
matrix[dimensionIndex],
|
||||
styling.useSeparatorColumns,
|
||||
@@ -69,6 +71,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 (
|
||||
|
||||
@@ -27,6 +27,7 @@ class RowHeader extends React.PureComponent {
|
||||
>
|
||||
<Tooltip
|
||||
isTooltipActive={!inEditState}
|
||||
styling={styling}
|
||||
tooltipText={entry.displayValue}
|
||||
>
|
||||
<HeaderPadding
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
const conceptSemaphores = {
|
||||
items: {
|
||||
AllConcepts: {
|
||||
component: 'switch',
|
||||
defaultValue: true,
|
||||
label: 'All concepts affected',
|
||||
options: [
|
||||
{
|
||||
label: 'On',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: 'Off',
|
||||
value: false
|
||||
}
|
||||
],
|
||||
ref: 'allsemaphores',
|
||||
type: 'boolean'
|
||||
},
|
||||
ConceptsAffected1: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore1',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 1',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected2: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore2',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 2',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected3: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore3',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 3',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected4: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore4',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 4',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected5: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore5',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 5',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected6: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore6',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 6',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected7: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore7',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 7',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected8: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore8',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 8',
|
||||
type: 'string'
|
||||
},
|
||||
ConceptsAffected9: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore9',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 9',
|
||||
type: 'string'
|
||||
},
|
||||
// eslint-disable-next-line sort-keys
|
||||
ConceptsAffected10: {
|
||||
defaultValue: '',
|
||||
ref: 'conceptsemaphore10',
|
||||
show: data => !data.allsemaphores,
|
||||
translation: 'Concept 10',
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
label: 'Concept Semaphores',
|
||||
type: 'items'
|
||||
};
|
||||
|
||||
export default conceptSemaphores;
|
||||
169
src/definition/conditional-coloring.js
Normal file
169
src/definition/conditional-coloring.js
Normal file
@@ -0,0 +1,169 @@
|
||||
const conditionalColoring = {
|
||||
type: 'items',
|
||||
label: 'Color by condition',
|
||||
items: {
|
||||
Enabled: {
|
||||
ref: 'conditionalcoloring.enabled',
|
||||
type: 'boolean',
|
||||
label: 'Enabled',
|
||||
component: 'switch',
|
||||
defaultValue: false,
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
]
|
||||
},
|
||||
ColorAllRows: {
|
||||
ref: 'conditionalcoloring.colorall',
|
||||
type: 'boolean',
|
||||
label: 'Color all rows by condition',
|
||||
component: 'switch',
|
||||
defaultValue: true,
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'All rows'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Specified rows'
|
||||
}
|
||||
],
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
Rows: {
|
||||
type: 'array',
|
||||
ref: 'conditionalcoloring.rows',
|
||||
label: 'Rows to color',
|
||||
itemTitleRef: function (data) {
|
||||
return data.rowname;
|
||||
},
|
||||
allowAdd: true,
|
||||
allowRemove: true,
|
||||
addTranslation: 'Add row to color',
|
||||
items: {
|
||||
Row: {
|
||||
ref: 'rowname',
|
||||
label: 'Name of row',
|
||||
type: 'string',
|
||||
defaultValue: ''
|
||||
}
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled && !data.conditionalcoloring.colorall;
|
||||
}
|
||||
},
|
||||
ThresholdPoor: {
|
||||
ref: 'conditionalcoloring.threshold_poor',
|
||||
translation: 'Poor is less than',
|
||||
type: 'number',
|
||||
defaultValue: -0.1,
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ColorPoor: {
|
||||
ref: 'conditionalcoloring.color_poor',
|
||||
label: 'Poor color fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 10,
|
||||
color: '#f93f17'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
TextColorPoor: {
|
||||
ref: 'conditionalcoloring.textcolor_poor',
|
||||
label: 'Poor text color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 1,
|
||||
color: '#ffffff'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ThresholdFair: {
|
||||
ref: 'conditionalcoloring.threshold_fair',
|
||||
translation: 'Fair is less than',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ColorFair: {
|
||||
ref: 'conditionalcoloring.color_fair',
|
||||
label: 'Fair color fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 8,
|
||||
color: '#ffcf02'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
TextColorFair: {
|
||||
ref: 'conditionalcoloring.textcolor_fair',
|
||||
label: 'Fair text color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 15,
|
||||
color: '#000000'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
ColorGood: {
|
||||
ref: 'conditionalcoloring.color_good',
|
||||
label: 'Good color fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 3,
|
||||
color: '#276e27'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
},
|
||||
TextColorGood: {
|
||||
ref: 'conditionalcoloring.textcolor_good',
|
||||
label: 'Good text color',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
dualOutput: true,
|
||||
defaultValue: {
|
||||
index: 1,
|
||||
color: '#ffffff'
|
||||
},
|
||||
show (data) {
|
||||
return data.conditionalcoloring.enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default conditionalColoring;
|
||||
@@ -1,8 +1,7 @@
|
||||
import pagination from './pagination';
|
||||
import header from './header';
|
||||
import tableFormat from './table-format';
|
||||
import conceptSemaphores from './concept-semaphores';
|
||||
import metricSemaphores from './metric-semaphores';
|
||||
import conditionalColoring from './conditional-coloring';
|
||||
|
||||
const definition = {
|
||||
component: 'accordion',
|
||||
@@ -18,18 +17,37 @@ const definition = {
|
||||
},
|
||||
uses: 'data'
|
||||
},
|
||||
sorting: {
|
||||
uses: 'sorting'
|
||||
},
|
||||
settings: {
|
||||
items: {
|
||||
ConceptSemaphores: conceptSemaphores,
|
||||
Formatted: tableFormat,
|
||||
Header: header,
|
||||
MetricSemaphores: metricSemaphores,
|
||||
ConditionalColoring: conditionalColoring,
|
||||
Pagination: pagination
|
||||
},
|
||||
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,106 +0,0 @@
|
||||
const metricSemaphores = {
|
||||
type: 'items',
|
||||
label: 'Metric Semaphores',
|
||||
items: {
|
||||
AllMetrics: {
|
||||
ref: 'allmetrics',
|
||||
type: 'boolean',
|
||||
component: 'switch',
|
||||
label: 'All metrics affected',
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
label: 'On'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'Off'
|
||||
}
|
||||
],
|
||||
defaultValue: false
|
||||
},
|
||||
MetricsAffected: {
|
||||
ref: 'metricssemaphore',
|
||||
translation: 'Metrics affected (1,2,4,...)',
|
||||
type: 'string',
|
||||
defaultValue: '-',
|
||||
show (data) {
|
||||
return !data.allmetrics;
|
||||
}
|
||||
},
|
||||
MetricStatus1: {
|
||||
ref: 'metricsstatus1',
|
||||
translation: 'Critic is less than',
|
||||
type: 'number',
|
||||
defaultValue: -0.1
|
||||
},
|
||||
ColorStatus1: {
|
||||
ref: 'colorstatus1',
|
||||
label: 'Critic Color Fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 8,
|
||||
color: '#f93f17'
|
||||
}
|
||||
},
|
||||
ColorStatus1Text: {
|
||||
ref: 'colorstatus1text',
|
||||
label: 'Critic Color Text',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 11,
|
||||
color: '#ffffff'
|
||||
}
|
||||
},
|
||||
MetricStatus2: {
|
||||
ref: 'metricsstatus2',
|
||||
translation: 'Medium is less than',
|
||||
type: 'number',
|
||||
defaultValue: 0
|
||||
},
|
||||
ColorStatus2: {
|
||||
ref: 'colorstatus2',
|
||||
label: 'Medium Color Fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 9,
|
||||
color: '#ffcf02'
|
||||
}
|
||||
},
|
||||
ColorStatus2Text: {
|
||||
ref: 'colorstatus2text',
|
||||
label: 'Medium Color Text',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 12,
|
||||
color: '#000000'
|
||||
}
|
||||
},
|
||||
ColorStatus3: {
|
||||
ref: 'colorstatus3',
|
||||
label: 'Success Color Fill',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 10,
|
||||
color: '#276e27'
|
||||
}
|
||||
},
|
||||
ColorStatus3Text: {
|
||||
ref: 'colorstatus3text',
|
||||
label: 'Success Color Text',
|
||||
type: 'object',
|
||||
component: 'color-picker',
|
||||
defaultValue: {
|
||||
index: 11,
|
||||
color: '#ffffff'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default metricSemaphores;
|
||||
@@ -55,7 +55,7 @@ const pagination = {
|
||||
ref: 'errormessage',
|
||||
label: 'Default error message',
|
||||
type: 'string',
|
||||
defaultValue: 'Ups! It seems you asked for too many data. Please filter more to see the whole picture.'
|
||||
defaultValue: 'Unable to display all the data. Apply more filters to limit the amount of displayed data.'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { HEADER_FONT_SIZE } from '../initialize-transformed';
|
||||
import Tooltip from '../tooltip/index.jsx';
|
||||
|
||||
class ColumnHeader extends React.PureComponent {
|
||||
@@ -16,11 +17,12 @@ class ColumnHeader extends React.PureComponent {
|
||||
render () {
|
||||
const { baseCSS, cellSuffix, colSpan, entry, styling, qlik } = this.props;
|
||||
const inEditState = qlik.inEditState();
|
||||
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
|
||||
|
||||
const style = {
|
||||
...baseCSS,
|
||||
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment}px`,
|
||||
height: '45px',
|
||||
height: isMediumFontSize ? '43px' : '33px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
|
||||
@@ -33,6 +35,7 @@ class ColumnHeader extends React.PureComponent {
|
||||
>
|
||||
<Tooltip
|
||||
isTooltipActive={!inEditState}
|
||||
styling={styling}
|
||||
tooltipText={entry.displayValue}
|
||||
>
|
||||
{entry.displayValue}
|
||||
|
||||
@@ -10,7 +10,7 @@ const ExportColumnHeader = ({ baseCSS, general, title, allowExcelExport, hasSeco
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${16 + styling.headerOptions.fontSizeAdjustment}px`,
|
||||
height: isMediumFontSize ? '100px' : '80px',
|
||||
height: isMediumFontSize ? '90px' : '70px',
|
||||
verticalAlign: 'middle',
|
||||
width: '230px'
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import ColumnHeader from './column-header.jsx';
|
||||
import MeasurementColumnHeader from './measurement-column-header.jsx';
|
||||
import { injectSeparators } from '../utilities';
|
||||
|
||||
const HeadersTable = ({ data, general, qlik, styling }) => {
|
||||
const HeadersTable = ({ data, general, qlik, styling, isKpi }) => {
|
||||
const baseCSS = {
|
||||
backgroundColor: styling.headerOptions.colorSchema,
|
||||
color: styling.headerOptions.textColor,
|
||||
@@ -26,15 +26,17 @@ const HeadersTable = ({ data, general, qlik, styling }) => {
|
||||
<table className="header">
|
||||
<tbody>
|
||||
<tr>
|
||||
<ExportColumnHeader
|
||||
allowExcelExport={general.allowExcelExport}
|
||||
baseCSS={baseCSS}
|
||||
general={general}
|
||||
hasSecondDimension={hasSecondDimension}
|
||||
styling={styling}
|
||||
title={dimension1[0].name}
|
||||
/>
|
||||
{!hasSecondDimension && measurements.map(measurementEntry => (
|
||||
{isKpi ?
|
||||
<ExportColumnHeader
|
||||
allowExcelExport={general.allowExcelExport}
|
||||
baseCSS={baseCSS}
|
||||
general={general}
|
||||
hasSecondDimension={hasSecondDimension}
|
||||
styling={styling}
|
||||
title={dimension1[0].name}
|
||||
/> : null
|
||||
}
|
||||
{!isKpi && !hasSecondDimension && measurements.map(measurementEntry => (
|
||||
<MeasurementColumnHeader
|
||||
baseCSS={baseCSS}
|
||||
general={general}
|
||||
@@ -44,7 +46,7 @@ const HeadersTable = ({ data, general, qlik, styling }) => {
|
||||
styling={styling}
|
||||
/>
|
||||
))}
|
||||
{hasSecondDimension && injectSeparators(dimension2, styling.useSeparatorColumns).map((entry, index) => {
|
||||
{!isKpi && hasSecondDimension && injectSeparators(dimension2, styling.useSeparatorColumns).map((entry, index) => {
|
||||
if (entry.isSeparator) {
|
||||
const separatorStyle = {
|
||||
color: 'white',
|
||||
@@ -75,7 +77,7 @@ const HeadersTable = ({ data, general, qlik, styling }) => {
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
{hasSecondDimension && (
|
||||
{!isKpi && hasSecondDimension && (
|
||||
<tr>
|
||||
{injectSeparators(dimension2, styling.useSeparatorColumns).map((dimensionEntry, index) => {
|
||||
if (dimensionEntry.isSeparator) {
|
||||
@@ -137,7 +139,8 @@ HeadersTable.propTypes = {
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({}),
|
||||
options: PropTypes.shape({})
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
isKpi: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default HeadersTable;
|
||||
|
||||
@@ -6,7 +6,7 @@ import Tooltip from '../tooltip/index.jsx';
|
||||
const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measurement, styling }) => {
|
||||
const title = `${measurement.name} ${measurement.magnitudeLabelSuffix}`;
|
||||
const { fontSizeAdjustment } = styling.headerOptions;
|
||||
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
|
||||
const isMediumFontSize = fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
|
||||
|
||||
if (hasSecondDimension) {
|
||||
const isPercentageFormat = measurement.format.substring(measurement.format.length - 1) === '%';
|
||||
@@ -18,9 +18,8 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure
|
||||
}
|
||||
const cellStyle = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${baseFontSize + fontSizeAdjustment}px`,
|
||||
height: isMediumFontSize ? '50px' : '25px',
|
||||
height: isMediumFontSize ? '45px' : '35px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
return (
|
||||
@@ -28,37 +27,33 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure
|
||||
className={`${cellClass}${general.cellSuffix}`}
|
||||
style={cellStyle}
|
||||
>
|
||||
<span className="wrapclass25">
|
||||
<Tooltip
|
||||
tooltipText={title}
|
||||
>
|
||||
{title}
|
||||
</Tooltip>
|
||||
</span>
|
||||
<Tooltip
|
||||
tooltipText={title}
|
||||
styling={styling}
|
||||
>
|
||||
{title}
|
||||
</Tooltip>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
const isLong = (title.length > 11 && fontSizeAdjustment === 0) || (title.length > 12 && fontSizeAdjustment === -2);
|
||||
const suffixWrap = isLong ? '70' : 'empty';
|
||||
const style = {
|
||||
...baseCSS,
|
||||
cursor: 'default',
|
||||
fontSize: `${15 + fontSizeAdjustment}px`,
|
||||
height: isMediumFontSize ? '100px' : '80px',
|
||||
height: isMediumFontSize ? '90px' : '70px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
return (
|
||||
<th
|
||||
className={`grid-cells2 ${general.cellSuffix}`}
|
||||
className={`grid-cells2${general.cellSuffix}`}
|
||||
style={style}
|
||||
>
|
||||
<span
|
||||
className={`wrapclass${suffixWrap}`}
|
||||
style={{ fontFamily: styling.headerOptions.fontFamily }}
|
||||
<Tooltip
|
||||
tooltipText={title}
|
||||
styling={styling}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@ function getAlignment (option) {
|
||||
function getFontSizeAdjustment (option) {
|
||||
const fontSizeAdjustmentOptions = {
|
||||
1: HEADER_FONT_SIZE.SMALL,
|
||||
2: HEADER_FONT_SIZE.MEDIUM,
|
||||
3: 2
|
||||
2: HEADER_FONT_SIZE.MEDIUM
|
||||
};
|
||||
|
||||
return fontSizeAdjustmentOptions[option] || 0;
|
||||
@@ -68,7 +67,7 @@ function generateMeasurements (information) {
|
||||
|
||||
function generateDimensionEntry (information, data) {
|
||||
return {
|
||||
displayValue: data.qText,
|
||||
displayValue: data.qText || data.qNum,
|
||||
elementNumber: data.qElemNumber,
|
||||
name: information.qFallbackTitle,
|
||||
value: data.qNum
|
||||
@@ -107,16 +106,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 +126,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 +142,89 @@ 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 ((hasSecondDimension && row.length == (dimension2.length * measurements.length))
|
||||
|| (!hasSecondDimension && row.length == measurements.length)) {
|
||||
// Row is saturated
|
||||
return row;
|
||||
}
|
||||
|
||||
// Row is not saturated, so must add empty cells to fill the gaps
|
||||
let newRow = [];
|
||||
if (hasSecondDimension) {
|
||||
// Got a second dimension, so need to add measurements for all values of the second dimension
|
||||
let rowDataIndex = 0;
|
||||
dimension2.forEach(dim => {
|
||||
rowDataIndex = appendMissingCells(
|
||||
row, newRow, rowDataIndex, measurements, rowIndex, dim.elementNumber);
|
||||
});
|
||||
} else {
|
||||
appendMissingCells(row, newRow, 0, measurements, rowIndex);
|
||||
}
|
||||
|
||||
return newRow;
|
||||
});
|
||||
|
||||
return {
|
||||
dimension1: distinctArray(dimension1),
|
||||
dimension2: distinctArray(dimension2),
|
||||
dimension1: dimension1,
|
||||
dimension2: dimension2,
|
||||
matrix,
|
||||
measurements
|
||||
};
|
||||
}
|
||||
|
||||
function initializeTransformed ({ $element, component, cubes, layout }) {
|
||||
/*
|
||||
* Appends the cells of the source row, as well as those missing, to the destination row, starting
|
||||
* from the given source index. Returns the source index of the next source cell after this has
|
||||
* completed. If there is a second dimension the dim2ElementNumber should be set to the current
|
||||
* index of the dimension2 value being processed.
|
||||
*/
|
||||
function appendMissingCells (
|
||||
sourceRow, destRow, sourceIndex, measurements, dim1ElementNumber, dim2ElementNumber = -1) {
|
||||
|
||||
let index = sourceIndex;
|
||||
measurements.forEach(measurement => {
|
||||
if (index < sourceRow.length
|
||||
&& (dim2ElementNumber === -1
|
||||
|| sourceRow[index].parents.dimension2.elementNumber === dim2ElementNumber)
|
||||
&& sourceRow[index].parents.measurement.header === measurement.name) {
|
||||
// Source contains the expected cell
|
||||
destRow.push(sourceRow[index]);
|
||||
index++;
|
||||
} else {
|
||||
// Source doesn't contain the expected cell, so add empty
|
||||
destRow.push({
|
||||
displayValue: '',
|
||||
parents: {
|
||||
dimension1: { elementNumber: dim1ElementNumber },
|
||||
dimension2: { elementNumber: dim2ElementNumber },
|
||||
measurement: { header: measurement.name }
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return index;
|
||||
}
|
||||
|
||||
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 +235,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 +290,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,
|
||||
@@ -254,39 +305,26 @@ function initializeTransformed ({ $element, component, cubes, layout }) {
|
||||
fontSizeAdjustment: getFontSizeAdjustment(layout.lettersize),
|
||||
textAlignment: layout.cellTextAlignment
|
||||
},
|
||||
semaphoreColors: {
|
||||
fieldsToApplyTo: {
|
||||
applyToAll: layout.allsemaphores,
|
||||
applyToMetric: layout.allmetrics,
|
||||
specificFields: [
|
||||
layout.conceptsemaphore1,
|
||||
layout.conceptsemaphore2,
|
||||
layout.conceptsemaphore3,
|
||||
layout.conceptsemaphore4,
|
||||
layout.conceptsemaphore5,
|
||||
layout.conceptsemaphore6,
|
||||
layout.conceptsemaphore7,
|
||||
layout.conceptsemaphore9,
|
||||
layout.conceptsemaphore10
|
||||
],
|
||||
metricsSpecificFields: layout.metricssemaphore.split(',').map(entry => Number(entry))
|
||||
conditionalColoring: {
|
||||
enabled: layout.conditionalcoloring.enabled,
|
||||
colorAllRows: layout.conditionalcoloring.colorall,
|
||||
rows: layout.conditionalcoloring.rows.map(row => row.rowname),
|
||||
threshold: {
|
||||
poor: layout.conditionalcoloring.threshold_poor,
|
||||
fair: layout.conditionalcoloring.threshold_fair
|
||||
},
|
||||
status: {
|
||||
critical: layout.metricsstatus1,
|
||||
medium: layout.metricsstatus2
|
||||
},
|
||||
statusColors: {
|
||||
critical: {
|
||||
backgroundColor: layout.colorstatus1.color,
|
||||
color: layout.colorstatus1text.color
|
||||
colors: {
|
||||
poor: {
|
||||
color: layout.conditionalcoloring.color_poor,
|
||||
textColor: layout.conditionalcoloring.textcolor_poor
|
||||
},
|
||||
medium: {
|
||||
backgroundColor: layout.colorstatus2.color,
|
||||
color: layout.colorstatus2text.color
|
||||
fair: {
|
||||
color: layout.conditionalcoloring.color_fair,
|
||||
textColor: layout.conditionalcoloring.textcolor_fair
|
||||
},
|
||||
normal: {
|
||||
backgroundColor: layout.colorstatus3.color,
|
||||
color: layout.colorstatus3text.color
|
||||
good: {
|
||||
color: layout.conditionalcoloring.color_good,
|
||||
textColor: layout.conditionalcoloring.textcolor_good
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
134
src/main.less
134
src/main.less
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable */
|
||||
.qv-object-qlik-smart-pivot {
|
||||
@TableBorder: 1px solid #d3d3d3;
|
||||
@KpiTableWidth: 230px;
|
||||
|
||||
*,
|
||||
@@ -33,18 +32,13 @@
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border-collapse: separate;
|
||||
border-spacing: 1px;
|
||||
width: auto;
|
||||
border-left: @TableBorder;
|
||||
border-right: @TableBorder;
|
||||
border-top: @TableBorder;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #fff;
|
||||
border-collapse: collapse;
|
||||
padding: 5px !important; // prevent overwriting from single object
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -70,45 +64,12 @@
|
||||
th.main-kpi {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border-bottom: @TableBorder;
|
||||
}
|
||||
|
||||
.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// This is for wrap text in headers
|
||||
.wrapclass25 {
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.wrapclass45 {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wrapclass70 {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.wrapclassEmpty {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// *****************
|
||||
// Medium column size
|
||||
// *****************
|
||||
@@ -178,12 +139,7 @@
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.fdim-cells:hover {
|
||||
background-color: #808080 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
tbody tr:hover td {
|
||||
cursor: default;
|
||||
background-color: #808080 !important;
|
||||
color: #fff;
|
||||
@@ -210,33 +166,50 @@
|
||||
border: groove;
|
||||
}
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kpi-table .fdim-cells,
|
||||
.data-table td {
|
||||
line-height: 1em !important;
|
||||
}
|
||||
|
||||
.data-table .fdim-cells {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kpi-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
flex: none;
|
||||
width: @KpiTableWidth !important;
|
||||
overflow: hidden !important;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-right: 1px solid #fff;
|
||||
box-shadow: 4px 2px 8px #e1e1e1;
|
||||
|
||||
.header-wrapper {
|
||||
flex: none;
|
||||
box-shadow: 4px 2px 8px #e1e1e1;
|
||||
}
|
||||
|
||||
.row-wrapper {
|
||||
height: calc(~"100% - 97px");
|
||||
overflow: scroll;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
margin-bottom: 8px;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
box-shadow: 4px 2px 8px #e1e1e1;
|
||||
min-height: 0; /* This is to make flex size-filling work */
|
||||
|
||||
/* Adapt for Edge */
|
||||
@supports (-ms-ime-align: auto) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Adapt for IE11 */
|
||||
@media screen and (-ms-high-contrast: none) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,27 +218,50 @@
|
||||
}
|
||||
|
||||
.data-table {
|
||||
height: 100%;
|
||||
width: calc(100% - 243px);
|
||||
position: absolute;
|
||||
margin-left: @KpiTableWidth + 13px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
margin-left: 13px;
|
||||
min-width: 0; /* This is to make flex size-filling work */
|
||||
|
||||
.header-wrapper {
|
||||
flex: none;
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.row-wrapper {
|
||||
height: calc(~"100% - 97px");
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
min-height: 0; /* This is to make flex size-filling work */
|
||||
|
||||
/* Style scrollbar for FF */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #d3d3d3 transparent;
|
||||
}
|
||||
|
||||
/* Adapt for Edge */
|
||||
@supports (-ms-ime-align: auto) {
|
||||
.header-wrapper {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adapt for IE11 */
|
||||
@media screen and (-ms-high-contrast: none) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.header-wrapper {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hide scrollbars
|
||||
.kpi-table .header-wrapper,
|
||||
.kpi-table .row-wrapper,
|
||||
.data-table .header-wrapper {
|
||||
// stylelint-disable-next-line property-no-unknown
|
||||
scrollbar-width: none;
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
72
src/root.jsx
72
src/root.jsx
@@ -5,46 +5,50 @@ import DataTable from './data-table/index.jsx';
|
||||
import { LinkedScrollWrapper, LinkedScrollSection } from './linked-scroll';
|
||||
|
||||
const Root = ({ state, qlik, editmodeClass }) => (
|
||||
<LinkedScrollWrapper>
|
||||
<div className={`kpi-table ${editmodeClass}`}>
|
||||
<HeadersTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={qlik}
|
||||
styling={state.styling}
|
||||
/>
|
||||
<LinkedScrollSection linkVertical>
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={qlik}
|
||||
renderData={false}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
</div>
|
||||
<div className={`data-table ${editmodeClass}`}>
|
||||
<LinkedScrollSection linkHorizontal>
|
||||
<div className="root">
|
||||
<LinkedScrollWrapper>
|
||||
<div className={`kpi-table ${editmodeClass}`}>
|
||||
<HeadersTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
isKpi
|
||||
qlik={qlik}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
<LinkedScrollSection
|
||||
linkHorizontal
|
||||
linkVertical
|
||||
>
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={qlik}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
</div>
|
||||
</LinkedScrollWrapper>
|
||||
<LinkedScrollSection linkVertical>
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={qlik}
|
||||
renderData={false}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
</div>
|
||||
<div className={`data-table ${editmodeClass}`}>
|
||||
<LinkedScrollSection linkHorizontal>
|
||||
<HeadersTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
isKpi={false}
|
||||
qlik={qlik}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
<LinkedScrollSection
|
||||
linkHorizontal
|
||||
linkVertical
|
||||
>
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={qlik}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</LinkedScrollSection>
|
||||
</div>
|
||||
</LinkedScrollWrapper>
|
||||
</div>
|
||||
);
|
||||
|
||||
Root.propTypes = {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -25,23 +25,21 @@ class Tooltip extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, tooltipText } = this.props;
|
||||
const { children, styling, tooltipText } = this.props;
|
||||
const { showTooltip } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseMove={handleCalculateTooltipPosition}
|
||||
onMouseOut={this.handleRenderTooltip}
|
||||
onMouseOver={this.handleRenderTooltip}
|
||||
style={{ fontFamily: styling.options.fontFamily }}
|
||||
>
|
||||
{children}
|
||||
|
||||
{showTooltip
|
||||
? (
|
||||
<div
|
||||
className="tooltip-wrapper"
|
||||
>
|
||||
<p>
|
||||
<div className="tooltip-wrapper">
|
||||
<p style={{ fontFamily: styling.options.fontFamily }}>
|
||||
{tooltipText}
|
||||
</p>
|
||||
</div>
|
||||
@@ -56,6 +54,11 @@ Tooltip.propTypes = {
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node
|
||||
]).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
fontFamily: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
tooltipText: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = {
|
||||
'indentation': 2,
|
||||
'length-zero-no-unit': true,
|
||||
'max-empty-lines': 1,
|
||||
'max-nesting-depth': 3,
|
||||
'max-nesting-depth': 5,
|
||||
'media-feature-colon-space-after': 'always',
|
||||
'media-feature-colon-space-before': 'never',
|
||||
'media-feature-name-case': 'lower',
|
||||
|
||||
Reference in New Issue
Block a user