Compare commits

..

18 Commits

Author SHA1 Message Date
Albert Backenhof
c8afb83130 Merge pull request #59 from qlik-oss/DEB-217/CellFormatting
Fixed cell number formatting
2019-05-10 10:19:47 +02:00
Albert Backenhof
49981f6ae3 Fixed cell number formatting
-Now will always use the built in Qlik value format.

Issue: DEB-217, DEB-218, DEB-219, DEB-220
2019-05-09 10:09:33 +02:00
Albert Backenhof
b3b17e8d0c Merge pull request #58 from qlik-oss/DEB-173/MasterObject
Fixed master object bug
2019-05-03 09:28:27 +02:00
Albert Backenhof
5d45f57e00 Fixed master object bug
-Previously the smart pivot didn't render as
 a master object.

Issue: DEB-173
2019-05-03 09:09:44 +02:00
Albert Backenhof
2f59d97cf3 Merge pull request #55 from qlik-oss/DEB-204/NewString
Updated string according to Ralf Narfeldt
2019-05-03 08:26:36 +02:00
Albert Backenhof
b8d6b0a53e Merge pull request #53 from qlik-oss/QLIK-95019/Excel
Improved excel and snapshot export
2019-05-03 08:26:25 +02:00
Purwa Shrivastava
6cc82e7b38 Merge pull request #57 from qlik-oss/DEB-213/misalignedTable
DEB-213: Mis calculated the default, changed from 23 to 25 px.
2019-05-02 14:25:28 +02:00
Purwa Shrivastava
cc7e3e62ed DEB-213: Mis calculated the default, changed from 23 to 25 px. 2019-05-02 14:19:36 +02:00
Purwa Shrivastava
26de3b63ed Merge pull request #56 from qlik-oss/DEB-213/misalignedTable
DEB-213: Introdued a minimum height for the table rows, so even if th…
2019-05-02 13:04:17 +02:00
Purwa Shrivastava
8eafeffcec DEB-213: Introdued a minimum height for the table rows, so even if the content is null, the rows are displayed even. 2019-05-02 11:31:15 +02:00
Albert Backenhof
ef7926dd13 Updated string according to Ralf Narfeldt
Issue: DEB-204
2019-04-30 14:51:30 +02:00
Albert Backenhof
af307fd24b Improved excel and snapshot export
-KPI is now included in excel.
-Snapshots now work.

Issue: QLIK-95019
2019-04-30 13:04:42 +02:00
Albert Backenhof
98401db922 Merge pull request #52 from qlik-oss/saturateMatrix
Fixed bug that prevented row saturation
2019-04-23 07:01:34 +02:00
Albert Backenhof
75771e4815 Merge pull request #50 from qlik-oss/DEB-187/ColorMeasures
Color by measure indices
2019-04-23 07:01:25 +02:00
Albert Backenhof
a12205c840 Fixed bug that prevented row saturation 2019-04-18 09:44:07 +02:00
Albert Backenhof
ebfee69c7b Merge pull request #51 from qlik-oss/DEB-188/LimitSize
Fixed layout and styling of table
2019-04-18 08:28:26 +02:00
Albert Backenhof
05c25c72cb Fixed layout and styling of table
-Now using flexbox to make the layout more
 dynamic.
-Row selection of hover works as intended.
-Improved the way scrollbars are shown.

Issue: DEB-188, DEB-192
2019-04-17 11:02:23 +02:00
Albert Backenhof
d2a24dd256 Color by measure indices
-Used to specify what measure columns should
 use conditional coloring.

Issue: DEB-187
2019-04-15 14:15:12 +02:00
20 changed files with 273 additions and 361 deletions

View File

@@ -1,7 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ApplyPreMask } from '../masking';
import { addSeparators } from '../utilities';
import Tooltip from '../tooltip/index.jsx';
class DataCell extends React.PureComponent {
@@ -45,11 +43,9 @@ class DataCell extends React.PureComponent {
const isEmptyCell = measurement.displayValue === '';
const isColumnPercentageBased = (/%/).test(measurement.format);
let formattedMeasurementValue;
if (isEmptyCell) {
if (isEmptyCell || styleBuilder.hasComments()) {
formattedMeasurementValue = '';
cellStyle.cursor = 'default';
} else if (styleBuilder.hasComments()) {
formattedMeasurementValue = '.';
} else {
formattedMeasurementValue = formatMeasurementValue(measurement, styling);
}
@@ -59,7 +55,10 @@ class DataCell extends React.PureComponent {
const isValidConditionalColoringValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
const isSpecifiedRow =
conditionalColoring.rows.indexOf(measurement.parents.dimension1.header) !== -1;
const shouldHaveConditionalColoring = conditionalColoring.colorAllRows || isSpecifiedRow;
const isSpecifiedMeasure =
conditionalColoring.measures.indexOf(measurement.parents.measurement.index) !== -1;
const shouldHaveConditionalColoring = (conditionalColoring.colorAllRows || isSpecifiedRow)
&& (conditionalColoring.colorAllMeasures || isSpecifiedMeasure);
if (isValidConditionalColoringValue && shouldHaveConditionalColoring) {
const { color, textColor } = getConditionalColor(measurement, conditionalColoring);
cellStyle.backgroundColor = color.color;
@@ -126,51 +125,11 @@ DataCell.propTypes = {
export default DataCell;
function formatMeasurementValue (measurement, styling) {
const isColumnPercentageBased = (/%/).test(measurement.format);
let formattedMeasurementValue = '';
if (isColumnPercentageBased) {
if (isNaN(measurement.value)) {
formattedMeasurementValue = styling.symbolForNulls;
} else {
formattedMeasurementValue = ApplyPreMask('0,00%', measurement.value);
}
if (isNaN(measurement.value)) {
return styling.symbolForNulls;
} else {
let magnitudeDivider;
switch (measurement.magnitude.toLowerCase()) {
case 'k':
magnitudeDivider = 1000;
break;
case 'm':
magnitudeDivider = 1000000;
break;
default:
magnitudeDivider = 1;
}
const formattingStringWithoutMagnitude = measurement.format.replace(/k|K|m|M/gi, '');
if (isNaN(measurement.value)) {
formattedMeasurementValue = styling.symbolForNulls;
} else {
let preFormatValue = measurement.value;
if (isColumnPercentageBased) {
preFormatValue *= 100;
}
switch (formattingStringWithoutMagnitude) {
case '#.##0':
formattedMeasurementValue = addSeparators((preFormatValue / magnitudeDivider), '.', ',', 0);
break;
case '#,##0':
formattedMeasurementValue = addSeparators((preFormatValue / magnitudeDivider), ',', '.', 0);
break;
default:
formattedMeasurementValue = ApplyPreMask(
formattingStringWithoutMagnitude,
(preFormatValue / magnitudeDivider)
);
break;
}
}
return measurement.displayValue;
}
return formattedMeasurementValue;
}
function getConditionalColor (measurement, conditionalColoring) {

View File

@@ -26,21 +26,26 @@ async function buildDataCube (originCubeDefinition, hasTwoDimensions, app) {
}
export async function initializeDataCube (component, layout) {
const app = qlik.currApp(component);
let properties;
if (component.backendApi.isSnapshot) {
// Fetch properties of source
properties = (await app.getObjectProperties(layout.sourceObjectId)).properties;
} else {
properties = await component.backendApi.getProperties();
return layout.snapshotData.dataCube;
}
return buildDataCube(
properties.qHyperCubeDef, layout.qHyperCube.qDimensionInfo.length === 2, app);
const app = qlik.currApp(component);
const properties = (await component.backendApi.getProperties());
// If this is a master object, fetch the hyperCubeDef of the original object
const hyperCubeDef = properties.qExtendsId
? (await app.getObjectProperties(properties.qExtendsId)).properties.qHyperCubeDef
: properties.qHyperCubeDef;
return buildDataCube(hyperCubeDef, layout.qHyperCube.qDimensionInfo.length === 2, app);
}
export function initializeDesignList (component, layout) {
if (component.backendApi.isSnapshot) {
return layout.snapshotData.designList;
}
if (!layout.stylingfield) {
return null;
}

View File

@@ -1,6 +1,6 @@
const conditionalColoring = {
type: 'items',
label: 'Color by condition',
label: 'Color by performance',
items: {
Enabled: {
ref: 'conditionalcoloring.enabled',
@@ -22,7 +22,7 @@ const conditionalColoring = {
ColorAllRows: {
ref: 'conditionalcoloring.colorall',
type: 'boolean',
label: 'Color all rows by condition',
label: 'Color all rows',
component: 'switch',
defaultValue: true,
options: [
@@ -61,9 +61,39 @@ const conditionalColoring = {
return data.conditionalcoloring.enabled && !data.conditionalcoloring.colorall;
}
},
ColorAllMeasures: {
ref: 'conditionalcoloring.colorallmeasures',
type: 'boolean',
label: 'Color all measures',
component: 'switch',
defaultValue: true,
options: [
{
value: true,
label: 'All measures'
},
{
value: false,
label: 'Specified measures'
}
],
show (data) {
return data.conditionalcoloring.enabled;
}
},
Measures: {
ref: 'conditionalcoloring.measures',
translation: 'Measures by index (ex: 0,3)',
type: 'string',
defaultValue: '',
show (data) {
return data.conditionalcoloring.enabled
&& data.conditionalcoloring.colorallmeasures === false;
}
},
ThresholdPoor: {
ref: 'conditionalcoloring.threshold_poor',
translation: 'Poor is less than',
translation: 'Poor range limit',
type: 'number',
defaultValue: -0.1,
show (data) {
@@ -72,7 +102,7 @@ const conditionalColoring = {
},
ColorPoor: {
ref: 'conditionalcoloring.color_poor',
label: 'Poor color fill',
label: 'Poor background color',
type: 'object',
component: 'color-picker',
dualOutput: true,
@@ -100,7 +130,7 @@ const conditionalColoring = {
},
ThresholdFair: {
ref: 'conditionalcoloring.threshold_fair',
translation: 'Fair is less than',
translation: 'Fair range limit',
type: 'number',
defaultValue: 0,
show (data) {
@@ -109,7 +139,7 @@ const conditionalColoring = {
},
ColorFair: {
ref: 'conditionalcoloring.color_fair',
label: 'Fair color fill',
label: 'Fair background color',
type: 'object',
component: 'color-picker',
dualOutput: true,
@@ -137,7 +167,7 @@ const conditionalColoring = {
},
ColorGood: {
ref: 'conditionalcoloring.color_good',
label: 'Good color fill',
label: 'Good background color',
type: 'object',
component: 'color-picker',
dualOutput: true,

View File

@@ -1,10 +1,10 @@
const header = {
type: 'items',
label: 'Header Format',
label: 'Header format',
items: {
Align: {
ref: 'HeaderAlign',
translation: 'Header Alignment',
translation: 'Header alignment',
type: 'number',
component: 'buttongroup',
options: [
@@ -29,14 +29,14 @@ const header = {
index: 6,
color: '#4477aa'
},
label: 'Background Header Color',
label: 'Background color',
ref: 'HeaderColorSchema',
type: 'object',
dualOutput: true
},
HeaderTextColor: {
ref: 'HeaderTextColorSchema',
label: 'Text Header Color',
label: 'Text color',
component: 'color-picker',
defaultValue: {
index: 1,
@@ -47,7 +47,7 @@ const header = {
},
HeaderFontSize: {
ref: 'lettersizeheader',
translation: 'Font Size',
translation: 'Font size',
type: 'number',
component: 'buttongroup',
options: [

View File

@@ -6,7 +6,7 @@ const pagination = {
ref: 'maxloops',
type: 'number',
component: 'dropdown',
label: 'Max Pagination Loops',
label: 'Max pagination loops',
options: [
{
value: 1,

View File

@@ -29,14 +29,14 @@ function getFieldList () {
const tableFormat = {
type: 'items',
label: 'Table Format',
label: 'Table format',
items: {
StylingField: {
ref: 'stylingfield',
disabledRef: '',
type: 'string',
component: 'dropdown',
label: 'Style with field',
label: 'Style template field',
options: function () {
return getFieldList().then(function (items) {
items.unshift(
@@ -58,7 +58,7 @@ const tableFormat = {
SeparatorColumns: {
ref: 'separatorcols',
type: 'boolean',
label: 'Separator Columns',
label: 'Column separators',
defaultValue: false
},
rowEvenBGColor: {
@@ -89,7 +89,7 @@ const tableFormat = {
ref: 'BodyTextColorSchema',
type: 'string',
component: 'dropdown',
label: 'Text Body Color',
label: 'Text body color',
options: [
{
value: 'Black',
@@ -139,7 +139,7 @@ const tableFormat = {
ref: 'FontFamily',
type: 'string',
component: 'dropdown',
label: 'FontFamily',
label: 'Font family',
options: [
{
value: 'QlikView Sans, -apple-system, sans-serif',
@@ -174,7 +174,7 @@ const tableFormat = {
},
DataFontSize: {
ref: 'lettersize',
translation: 'Font Size',
translation: 'Font size',
type: 'number',
component: 'buttongroup',
options: [
@@ -191,7 +191,7 @@ const tableFormat = {
},
textAlignment: {
ref: 'cellTextAlignment',
label: 'Cell Text alignment',
label: 'Cell text alignment',
component: 'buttongroup',
options: [
{
@@ -212,7 +212,7 @@ const tableFormat = {
ColumnWidthSlider: {
type: 'number',
component: 'slider',
label: 'Column Width',
label: 'Column width',
ref: 'columnwidthslider',
min: 1,
max: 3,
@@ -246,7 +246,7 @@ const tableFormat = {
ref: 'filteroncellclick',
type: 'boolean',
component: 'switch',
label: 'Filter data when cell clicked',
label: 'Allow selection in cells',
options: [
{
value: true,

View File

@@ -1,19 +1,32 @@
function removeAllTooltips (node) {
const tooltips = node.querySelectorAll('.tooltip');
[].forEach.call(tooltips, tooltip => {
if (tooltip.parentNode) {
tooltip.parentNode.removeChild(tooltip);
function cleanupNodes (node) {
const removables = node.querySelectorAll('.tooltip,input');
[].forEach.call(removables, removeable => {
if (removeable.parentNode) {
removeable.parentNode.removeChild(removeable);
}
});
}
function buildTableHTML (title, subtitle, footnote) {
function buildTableHTML (id, title, subtitle, footnote) {
const titleHTML = `<p style="font-size:15pt"><b>${title}</b></p>`;
const subtitleHTML = `<p style="font-size:11pt">${subtitle}</p>`;
const footnoteHTML = `<p style="font-size:11pt"><i>Note:</i>${footnote}</p>`;
const dataTableClone = document.querySelector('.data-table').cloneNode(true);
const footnoteHTML = `<p style="font-size:11pt">${footnote}</p>`;
const container = document.querySelector(`[tid="${id}"]`);
const kpiTableClone = container.querySelector('.kpi-table').cloneNode(true);
const dataTableClone = container.querySelector('.data-table').cloneNode(true);
cleanupNodes(kpiTableClone);
cleanupNodes(kpiTableClone);
removeAllTooltips(dataTableClone);
const kpiTableBodies = kpiTableClone.querySelectorAll('tbody');
const dataTableBodies = dataTableClone.querySelectorAll('tbody');
const kpiHeader = kpiTableBodies[0].querySelector('tr');
const dataTableHeaders = dataTableBodies[0].querySelectorAll('tr');
const kpiRows = kpiTableBodies[1].querySelectorAll('tr');
const dataRows = dataTableBodies[1].querySelectorAll('tr');
let combinedRows = '';
for (let i = 0; i < kpiRows.length; i++) {
combinedRows += `<tr>${kpiRows[i].innerHTML}${dataRows[i].innerHTML}</tr>`;
}
const tableHTML = `
<html
@@ -41,8 +54,23 @@ function buildTableHTML (title, subtitle, footnote) {
<body>
${titleHTML.length > 0 ? titleHTML : ''}
${subtitleHTML.length > 0 ? subtitleHTML : ''}
<div>
<table>
<tbody>
<tr>
${kpiHeader.innerHTML}
${dataTableHeaders[0].innerHTML}
</tr>
${dataTableHeaders.length > 1 ? dataTableHeaders[1].outerHTML : ''}
</tbody>
</table>
<table>
<tbody>
${combinedRows}
</tbody>
</table>
</div>
${footnoteHTML.length > 0 ? footnoteHTML : ''}
${dataTableClone.outerHTML}
</body>
</html>
`.split('>.<')
@@ -55,15 +83,15 @@ function buildTableHTML (title, subtitle, footnote) {
function downloadXLS (html) {
const filename = 'analysis.xls';
const blobObject = new Blob([html]);
// IE/Edge
if (window.navigator.msSaveOrOpenBlob) {
const blobObject = new Blob([html]);
return window.navigator.msSaveOrOpenBlob(blobObject, filename);
}
const dataURI = generateDataURI(html);
const link = window.document.createElement('a');
link.href = dataURI;
link.href = URL.createObjectURL(blobObject);
link.download = filename;
document.body.appendChild(link);
link.click();
@@ -72,15 +100,8 @@ function downloadXLS (html) {
return true;
}
function generateDataURI (html) {
const dataType = 'data:application/vnd.ms-excel;base64,';
const data = window.btoa(unescape(encodeURIComponent(html)));
return `${dataType}${data}`;
}
export function exportXLS (title, subtitle, footnote) {
export function exportXLS (id, title, subtitle, footnote) {
// original was removing icon when starting export, disable and some spinner instead, shouldn't take enough time to warrant either..?
const table = buildTableHTML(title, subtitle, footnote);
const table = buildTableHTML(id, title, subtitle, footnote);
downloadXLS(table);
}

View File

@@ -9,10 +9,10 @@ class ExportButton extends React.PureComponent {
}
handleExport () {
const { excelExport, general } = this.props;
const { id, excelExport, general } = this.props;
const { title, subtitle, footnote } = general;
if (excelExport) {
exportXLS(title, subtitle, footnote);
exportXLS(id, title, subtitle, footnote);
}
}
@@ -34,6 +34,7 @@ ExportButton.defaultProps = {
};
ExportButton.propTypes = {
id: PropTypes.string.isRequired,
excelExport: PropTypes.bool,
general: PropTypes.shape({}).isRequired
};

View File

@@ -22,7 +22,7 @@ class ColumnHeader extends React.PureComponent {
const style = {
...baseCSS,
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment}px`,
height: isMediumFontSize ? '45px' : '35px',
height: isMediumFontSize ? '43px' : '33px',
verticalAlign: 'middle'
};

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import ExportButton from '../export-button.jsx';
import { HEADER_FONT_SIZE } from '../initialize-transformed';
const ExportColumnHeader = ({ baseCSS, general, title, allowExcelExport, hasSecondDimension, styling }) => {
const ExportColumnHeader = ({ id, baseCSS, general, title, allowExcelExport, hasSecondDimension, styling }) => {
const rowSpan = hasSecondDimension ? 2 : 1;
const isMediumFontSize = styling.headerOptions.fontSizeAdjustment === HEADER_FONT_SIZE.MEDIUM;
const style = {
@@ -22,6 +22,7 @@ const ExportColumnHeader = ({ baseCSS, general, title, allowExcelExport, hasSeco
style={style}
>
<ExportButton
id={id}
excelExport={allowExcelExport}
general={general}
/>
@@ -31,6 +32,7 @@ const ExportColumnHeader = ({ baseCSS, general, title, allowExcelExport, hasSeco
};
ExportColumnHeader.propTypes = {
id: PropTypes.string.isRequired,
allowExcelExport: PropTypes.bool.isRequired,
baseCSS: PropTypes.shape({}).isRequired,
general: PropTypes.shape({}).isRequired,

View File

@@ -28,6 +28,7 @@ const HeadersTable = ({ data, general, qlik, styling, isKpi }) => {
<tr>
{isKpi ?
<ExportColumnHeader
id={qlik.options.id}
allowExcelExport={general.allowExcelExport}
baseCSS={baseCSS}
general={general}
@@ -77,7 +78,7 @@ const HeadersTable = ({ data, general, qlik, styling, isKpi }) => {
);
})}
</tr>
{hasSecondDimension && (
{!isKpi && hasSecondDimension && (
<tr>
{injectSeparators(dimension2, styling.useSeparatorColumns).map((dimensionEntry, index) => {
if (dimensionEntry.isSeparator) {

View File

@@ -18,7 +18,6 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure
}
const cellStyle = {
...baseCSS,
cursor: 'default',
fontSize: `${baseFontSize + fontSizeAdjustment}px`,
height: isMediumFontSize ? '45px' : '35px',
verticalAlign: 'middle'
@@ -40,7 +39,6 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure
const style = {
...baseCSS,
cursor: 'default',
fontSize: `${15 + fontSizeAdjustment}px`,
height: isMediumFontSize ? '90px' : '70px',
verticalAlign: 'middle'

View File

@@ -1,5 +1,9 @@
import paint from './paint.jsx';
import definition from './definition';
import { initializeDataCube, initializeDesignList } from './dataset';
import initializeStore from './store';
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './root.jsx';
import './main.less';
if (!window._babelPolyfill) { // eslint-disable-line no-underscore-dangle
@@ -53,16 +57,34 @@ export default {
exportData: true,
snapshot: true
},
paint ($element, layout) {
try {
paint($element, layout, this);
} catch (exception) {
console.error(exception); // eslint-disable-line no-console
throw exception;
}
paint: async function ($element, layout) {
const dataCube = await initializeDataCube(this, layout);
const designList = await initializeDesignList(this, layout);
const state = await initializeStore({
$element,
component: this,
dataCube,
designList,
layout
});
const editmodeClass = this.inAnalysisState() ? '' : 'edit-mode';
const jsx = (
<Root
editmodeClass={editmodeClass}
qlik={this}
state={state}
/>
);
ReactDOM.render(jsx, $element[0]);
},
snapshot: {
canTakeSnapshot: true
},
setSnapshotData: async function (snapshotLayout) {
snapshotLayout.snapshotData.dataCube = await initializeDataCube(this, snapshotLayout);
snapshotLayout.snapshotData.designList = await initializeDesignList(this, snapshotLayout);
return snapshotLayout;
},
version: 1.0
};

View File

@@ -90,7 +90,8 @@ function generateMatrixCell ({ cell, dimension1Information, dimension2Informatio
header: dimension1Information.qText
},
measurement: {
header: measurementInformation.name
header: measurementInformation.name,
index: measurementInformation.index
}
},
value: cell.qNum
@@ -130,6 +131,7 @@ function generateDataSet (
.slice(firstDataCell, row.length)
.map((cell, cellIndex) => {
const measurementInformation = measurements[cellIndex];
measurementInformation.index = cellIndex;
const dimension1Information = row[0]; // eslint-disable-line prefer-destructuring
const dimension2Information = hasSecondDimension ? row[1] : null;
const generatedCell = generateMatrixCell({
@@ -161,7 +163,7 @@ function generateDataSet (
// 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)
if ((hasSecondDimension && row.length == (dimension2.length * measurements.length))
|| (!hasSecondDimension && row.length == measurements.length)) {
// Row is saturated
return row;
@@ -201,7 +203,7 @@ function appendMissingCells (
sourceRow, destRow, sourceIndex, measurements, dim1ElementNumber, dim2ElementNumber = -1) {
let index = sourceIndex;
measurements.forEach(measurement => {
measurements.forEach((measurement, measureIndex) => {
if (index < sourceRow.length
&& (dim2ElementNumber === -1
|| sourceRow[index].parents.dimension2.elementNumber === dim2ElementNumber)
@@ -216,7 +218,10 @@ function appendMissingCells (
parents: {
dimension1: { elementNumber: dim1ElementNumber },
dimension2: { elementNumber: dim2ElementNumber },
measurement: { header: measurement.name }
measurement: {
header: measurement.name,
index: measureIndex
}
}
});
}
@@ -309,6 +314,10 @@ function initializeTransformed ({ $element, component, dataCube, designList, lay
enabled: layout.conditionalcoloring.enabled,
colorAllRows: layout.conditionalcoloring.colorall,
rows: layout.conditionalcoloring.rows.map(row => row.rowname),
colorAllMeasures: typeof layout.conditionalcoloring.colorallmeasures === 'undefined'
|| layout.conditionalcoloring.colorallmeasures,
measures: !layout.conditionalcoloring.measures
? [] : layout.conditionalcoloring.measures.split(',').map(index => Number(index)),
threshold: {
poor: layout.conditionalcoloring.threshold_poor,
fair: layout.conditionalcoloring.threshold_fair

View File

@@ -37,6 +37,10 @@
width: auto;
}
tr {
height: 25px;
}
td,
th {
padding: 5px !important; // prevent overwriting from single object
@@ -139,12 +143,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;
@@ -171,29 +170,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;
}
.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% - 92px");
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;
}
}
}
@@ -202,34 +222,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% - 92px");
width: 100%;
overflow: auto;
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;
}
// Use overlay-scrollbars for webkit-browsers
@media screen and (-webkit-min-device-pixel-ratio: 0) {
.row-wrapper {
overflow: overlay;
/* 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;

View File

@@ -1,132 +0,0 @@
import { addSeparators } from './utilities';
export function ApplyPreMask (mask, value) { // aqui
if (mask.indexOf(';') >= 0) {
if (value >= 0) {
switch (mask.substring(0, mask.indexOf(';'))) {
case '#.##0':
return (addSeparators(value, '.', ',', 0));
case '#,##0':
return (addSeparators(value, ',', '.', 0));
case '+#.##0':
return (addSeparators(value, '.', ',', 0));
case '+#,##0':
return (addSeparators(value, ',', '.', 0));
default:
return (applyMask(mask.substring(0, mask.indexOf(';')), value));
}
} else {
const vMyValue = value * -1;
let vMyMask = mask.substring(mask.indexOf(';') + 1, mask.length);
vMyMask = vMyMask.replace('(', '');
vMyMask = vMyMask.replace(')', '');
switch (vMyMask) {
case '#.##0':
return (`(${addSeparators(vMyValue, '.', ',', 0)})`);
case '#,##0':
return (`(${addSeparators(vMyValue, ',', '.', 0)})`);
case '-#.##0':
return (`(${addSeparators(vMyValue, '.', ',', 0)})`);
case '-#,##0':
return (`(${addSeparators(vMyValue, ',', '.', 0)})`);
default:
return (`(${applyMask(vMyMask, vMyValue)})`);
}
}
} else {
return (applyMask(mask, value));
}
}
function applyMask (originalMask, originalValue) {
if (!originalMask || isNaN(Number(originalValue))) {
return originalValue;
}
let isNegative;
let result;
let integer;
// find prefix/suffix
let len = originalMask.length;
const start = originalMask.search(/[0-9\-\+#]/);
const prefix = start > 0 ? originalMask.substring(0, start) : '';
// reverse string: not an ideal method if there are surrogate pairs
let str = originalMask.split('')
.reverse()
.join('');
const end = str.search(/[0-9\-\+#]/);
let offset = len - end;
const substr = originalMask.substring(offset, offset + 1);
let index = offset + ((substr === '.' || (substr === ',')) ? 1 : 0);
const suffix = end > 0 ? originalMask.substring(index, len) : '';
// mask with prefix & suffix removed
let mask = originalMask.substring(start, index);
// convert any string to number according to formation sign.
let value = mask.charAt(0) === '-' ? -originalValue : Number(originalValue);
isNegative = value < 0 ? value = -value : 0; // process only abs(), and turn on flag.
// search for separator for grp & decimal, anything not digit, not +/- sign, not #.
result = mask.match(/[^\d\-\+#]/g);
const decimal = (result && result[result.length - 1]) || '.'; // treat the right most symbol as decimal
const group = (result && result[1] && result[0]) || ','; // treat the left most symbol as group separator
// split the decimal for the format string if any.
mask = mask.split(decimal);
// Fix the decimal first, toFixed will auto fill trailing zero.
value = value.toFixed(mask[1] && mask[1].length);
value = String(Number(value)); // convert number to string to trim off *all* trailing decimal zero(es)
// fill back any trailing zero according to format
const posTrailZero = mask[1] && mask[1].lastIndexOf('0'); // look for last zero in format
const part = value.split('.');
// integer will get !part[1]
if (!part[1] || (part[1] && part[1].length <= posTrailZero)) {
value = (Number(value)).toFixed(posTrailZero + 1);
}
const szSep = mask[0].split(group); // look for separator
mask[0] = szSep.join(''); // join back without separator for counting the pos of any leading 0.
const posLeadZero = mask[0] && mask[0].indexOf('0');
if (posLeadZero > -1) {
while (part[0].length < (mask[0].length - posLeadZero)) {
part[0] = `0${part[0]}`;
}
} else if (Number(part[0]) === 0) {
part[0] = '';
}
value = value.split('.');
value[0] = part[0];
// process the first group separator from decimal (.) only, the rest ignore.
// get the length of the last slice of split result.
const posSeparator = (szSep[1] && szSep[szSep.length - 1].length);
if (posSeparator) {
integer = value[0];
str = '';
offset = integer.length % posSeparator;
len = integer.length;
for (index = 0; index < len; index++) {
str += integer.charAt(index); // ie6 only support charAt for sz.
// -posSeparator so that won't trail separator on full length
// jshint -W018
if (!((index - offset + 1) % posSeparator) && index < len - posSeparator) {
str += group;
}
}
value[0] = str;
}
value[1] = (mask[1] && value[1]) ? decimal + value[1] : '';
// remove negative sign if result is zero
result = value.join('');
if (result === '0' || result === '') {
// remove negative sign if result is zero
isNegative = false;
}
// put back any negation, combine integer and fraction, and add back prefix & suffix
return prefix + ((isNegative ? '-' : '') + result) + suffix;
}

View File

@@ -1,27 +0,0 @@
import initializeStore from './store';
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './root.jsx';
import { initializeDataCube, initializeDesignList } from './dataset';
export default async function paint ($element, layout, component) {
const dataCube = await initializeDataCube(component, layout);
const designList = await initializeDesignList(component, layout);
const state = await initializeStore({
$element,
component,
dataCube,
designList,
layout
});
const editmodeClass = component.inAnalysisState() ? '' : 'edit-mode';
const jsx = (
<Root
editmodeClass={editmodeClass}
qlik={component}
state={state}
/>
);
ReactDOM.render(jsx, $element[0]);
}

View File

@@ -5,48 +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}
isKpi
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={false}
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 = {

View File

@@ -9,21 +9,6 @@ export function distinctArray (array) {
.map(entry => JSON.parse(entry));
}
export function addSeparators (number, thousandSeparator, decimalSeparator, numberOfDecimals) {
const numberString = number.toFixed(numberOfDecimals);
const numberStringParts = numberString.split('.');
let [
wholeNumber,
decimal
] = numberStringParts;
decimal = numberStringParts.length > 1 ? decimalSeparator + decimal : '';
const regexCheckForThousand = /(\d+)(\d{3})/;
while (regexCheckForThousand.test(wholeNumber)) {
wholeNumber = wholeNumber.replace(regexCheckForThousand, `$1${thousandSeparator}$2`);
}
return wholeNumber + decimal;
}
export function Deferred () {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;

View File

@@ -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',