Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44b33b4c92 | ||
|
|
61b339b146 | ||
|
|
fc363d7739 | ||
|
|
35d4dde118 | ||
|
|
e70e76a401 | ||
|
|
34477d7ef1 | ||
|
|
b65d1c51fc | ||
|
|
9111ec762b | ||
|
|
b86806d4cd | ||
|
|
c3651a37da | ||
|
|
8b843e028a | ||
|
|
2bdd98aaca | ||
|
|
d723451656 | ||
|
|
f2f201c6e2 |
@@ -18,6 +18,14 @@ jobs:
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm install
|
||||
- run:
|
||||
name: BlackDuck scan
|
||||
command: curl -s https://blackducksoftware.github.io/hub-detect/hub-detect.sh | bash -s -- \
|
||||
--blackduck.url="https://qliktech.blackducksoftware.com" \
|
||||
--blackduck.trust.cert=true \
|
||||
--blackduck.username="svc-blackduck" \
|
||||
--blackduck.password=${svc_blackduck} \
|
||||
--detect.project.name="viz-bundle-qlik-smart-pivot"
|
||||
- run:
|
||||
name: Run tests
|
||||
command: npm run test-once
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Smart pivot",
|
||||
"description": "Formatted table for P&L reports.",
|
||||
"name": "P&L pivot",
|
||||
"description": "Profit & Loss reporting with color and font customizations.",
|
||||
"type": "visualization",
|
||||
"version": "X.Y.Z",
|
||||
"icon": "table",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { addSeparators } from '../utilities';
|
||||
|
||||
function formatMeasurementValue (measurement, styling) {
|
||||
// TODO: measurement.name is a horrible propertyname, it's actually the column header
|
||||
const isColumnPercentageBased = measurement.name.substring(0, 1) === '%';
|
||||
const isColumnPercentageBased = measurement.parents.measurement.header.substring(0, 1) === '%';
|
||||
let formattedMeasurementValue = '';
|
||||
if (isColumnPercentageBased) {
|
||||
if (isNaN(measurement.value)) {
|
||||
@@ -62,50 +62,74 @@ function getSemaphoreColors (measurement, semaphoreColors) {
|
||||
return semaphoreColors.statusColors.normal;
|
||||
}
|
||||
|
||||
const DataCell = ({ data, general, measurement, styleBuilder, styling }) => {
|
||||
const isColumnPercentageBased = measurement.name.substring(0, 1) === '%';
|
||||
let formattedMeasurementValue = formatMeasurementValue(measurement, styling);
|
||||
if (styleBuilder.hasComments()) {
|
||||
formattedMeasurementValue = '.';
|
||||
class DataCell extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
let cellStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
...styleBuilder.getStyle(),
|
||||
paddingRight: '4px',
|
||||
textAlign: 'right'
|
||||
handleSelect () {
|
||||
const { data: { meta: { dimensionCount } }, general: { allowFilteringByClick }, measurement, qlik } = this.props;
|
||||
const hasSecondDimension = dimensionCount > 1;
|
||||
if (!allowFilteringByClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
const { semaphoreColors } = styling;
|
||||
const isValidSemaphoreValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
|
||||
const shouldHaveSemaphoreColors = semaphoreColors.fieldsToApplyTo.applyToAll || semaphoreColors.fieldsToApplyTo.specificFields.indexOf(measurement.name) !== -1;
|
||||
if (isValidSemaphoreValue && shouldHaveSemaphoreColors) {
|
||||
const { backgroundColor, color } = getSemaphoreColors(measurement, semaphoreColors);
|
||||
cellStyle = {
|
||||
backgroundColor,
|
||||
color,
|
||||
qlik.backendApi.selectValues(0, [measurement.parents.dimension1.elementNumber], true);
|
||||
|
||||
if (hasSecondDimension) {
|
||||
qlik.backendApi.selectValues(1, [measurement.parents.dimension2.elementNumber], true);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, general, measurement, styleBuilder, styling } = this.props;
|
||||
const isColumnPercentageBased = measurement.name.substring(0, 1) === '%';
|
||||
let formattedMeasurementValue = formatMeasurementValue(measurement, styling);
|
||||
if (styleBuilder.hasComments()) {
|
||||
formattedMeasurementValue = '.';
|
||||
}
|
||||
|
||||
let cellStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
fontSize: styleBuilder.getStyle().fontSize,
|
||||
...styleBuilder.getStyle(),
|
||||
paddingLeft: '4px',
|
||||
textAlign: 'right'
|
||||
|
||||
};
|
||||
}
|
||||
const { semaphoreColors } = styling;
|
||||
const isValidSemaphoreValue = !styleBuilder.hasComments() && !isNaN(measurement.value);
|
||||
const shouldHaveSemaphoreColors = semaphoreColors.fieldsToApplyTo.applyToAll || semaphoreColors.fieldsToApplyTo.specificFields.indexOf(measurement.parents.dimension1.header) !== -1;
|
||||
if (isValidSemaphoreValue && shouldHaveSemaphoreColors) {
|
||||
const { backgroundColor, color } = getSemaphoreColors(measurement, semaphoreColors);
|
||||
cellStyle = {
|
||||
backgroundColor,
|
||||
color,
|
||||
fontFamily: styling.options.fontFamily,
|
||||
fontSize: styleBuilder.getStyle().fontSize,
|
||||
paddingLeft: '4px',
|
||||
textAlign: 'right'
|
||||
};
|
||||
}
|
||||
|
||||
let cellClass = 'grid-cells';
|
||||
const shouldUseSmallCells = isColumnPercentageBased && data.headers.measurements.length > 1;
|
||||
if (shouldUseSmallCells) {
|
||||
cellClass = 'grid-cells-small';
|
||||
}
|
||||
let cellClass = 'grid-cells';
|
||||
const shouldUseSmallCells = isColumnPercentageBased && data.headers.measurements.length > 1;
|
||||
if (shouldUseSmallCells) {
|
||||
cellClass = 'grid-cells-small';
|
||||
}
|
||||
|
||||
return (
|
||||
<td
|
||||
className={`${cellClass}${general.cellSuffix}`}
|
||||
style={cellStyle}
|
||||
>
|
||||
{formattedMeasurementValue}
|
||||
</td>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<td
|
||||
className={`${cellClass}${general.cellSuffix}`}
|
||||
onClick={this.handleSelect}
|
||||
style={cellStyle}
|
||||
>
|
||||
{formattedMeasurementValue}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DataCell.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
@@ -121,6 +145,11 @@ DataCell.propTypes = {
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.any
|
||||
}).isRequired,
|
||||
qlik: PropTypes.shape({
|
||||
backendApi: PropTypes.shape({
|
||||
selectValues: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
styleBuilder: PropTypes.shape({
|
||||
hasComments: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
|
||||
@@ -3,9 +3,10 @@ import PropTypes from 'prop-types';
|
||||
import StyleBuilder from '../style-builder';
|
||||
import DataCell from './data-cell.jsx';
|
||||
import HeaderPadding from './header-padding.jsx';
|
||||
import RowHeader from './row-header.jsx';
|
||||
import { injectSeparators } from '../utilities';
|
||||
|
||||
const DataTable = ({ data, general, styling }) => {
|
||||
const DataTable = ({ data, general, qlik, renderData, styling }) => {
|
||||
const {
|
||||
headers: {
|
||||
dimension1,
|
||||
@@ -17,79 +18,85 @@ const DataTable = ({ data, general, styling }) => {
|
||||
return (
|
||||
<div className="row-wrapper">
|
||||
<table>
|
||||
{dimension1.map((dimensionEntry, dimensionIndex) => {
|
||||
const rowHeaderText = dimensionEntry.displayValue || '';
|
||||
if (rowHeaderText === '-') {
|
||||
return null;
|
||||
}
|
||||
const styleBuilder = new StyleBuilder(styling);
|
||||
if (styling.hasCustomFileStyle) {
|
||||
styleBuilder.parseCustomFileStyle(rowHeaderText);
|
||||
} else {
|
||||
styleBuilder.applyStandardAttributes(dimensionIndex);
|
||||
styleBuilder.applyCustomStyle({
|
||||
fontSize: `${14 + styling.options.fontSizeAdjustment}px`
|
||||
});
|
||||
}
|
||||
const rowStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
width: '230px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
<tbody>
|
||||
{dimension1.map((dimensionEntry, dimensionIndex) => {
|
||||
const rowHeaderText = dimensionEntry.displayValue || '';
|
||||
if (rowHeaderText === '-') {
|
||||
return null;
|
||||
}
|
||||
const styleBuilder = new StyleBuilder(styling);
|
||||
if (styling.hasCustomFileStyle) {
|
||||
styleBuilder.parseCustomFileStyle(rowHeaderText);
|
||||
} else {
|
||||
styleBuilder.applyStandardAttributes(dimensionIndex);
|
||||
styleBuilder.applyCustomStyle({
|
||||
fontSize: `${14 + styling.options.fontSizeAdjustment}px`
|
||||
});
|
||||
}
|
||||
const rowStyle = {
|
||||
fontFamily: styling.options.fontFamily,
|
||||
width: '230px',
|
||||
...styleBuilder.getStyle()
|
||||
};
|
||||
|
||||
return (
|
||||
<tr key={dimensionEntry.displayValue}>
|
||||
<td
|
||||
className="fdim-cells"
|
||||
style={rowStyle}
|
||||
>
|
||||
<HeaderPadding
|
||||
return (
|
||||
<tr key={dimensionEntry.displayValue}>
|
||||
<RowHeader
|
||||
entry={dimensionEntry}
|
||||
qlik={qlik}
|
||||
rowStyle={rowStyle}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
{dimensionEntry.displayValue}
|
||||
</td>
|
||||
{injectSeparators(
|
||||
matrix[dimensionIndex],
|
||||
styling.useSeparatorColumns,
|
||||
{ atEvery: measurements.length }
|
||||
).map(measurementData => {
|
||||
if (measurementData.isSeparator) {
|
||||
const separatorStyle = {
|
||||
color: 'white',
|
||||
fontFamily: styling.options.fontFamily,
|
||||
fontSize: `${12 + styling.options.fontSizeAdjustment}px`
|
||||
};
|
||||
{renderData && injectSeparators(
|
||||
matrix[dimensionIndex],
|
||||
styling.useSeparatorColumns,
|
||||
{ atEvery: measurements.length }
|
||||
).map((measurementData, index) => {
|
||||
if (measurementData.isSeparator) {
|
||||
const separatorStyle = {
|
||||
color: 'white',
|
||||
fontFamily: styling.options.fontFamily,
|
||||
fontSize: `${12 + styling.options.fontSizeAdjustment}px`
|
||||
};
|
||||
|
||||
return (
|
||||
<td
|
||||
className="empty"
|
||||
key={`${dimensionEntry.displayValue}-${index}-separator`}
|
||||
style={separatorStyle}
|
||||
>
|
||||
*
|
||||
</td>
|
||||
);
|
||||
}
|
||||
const { dimension1: dimension1Info, dimension2, measurement } = measurementData.parents;
|
||||
const id = `${dimension1Info.elementNumber}-${dimension2 && dimension2.elementNumber}-${measurement.header}`;
|
||||
return (
|
||||
<td
|
||||
className="empty"
|
||||
key={`${dimensionEntry.displayValue}-${measurementData.name}-separator`}
|
||||
style={separatorStyle}
|
||||
>
|
||||
*
|
||||
</td>
|
||||
<DataCell
|
||||
data={data}
|
||||
general={general}
|
||||
key={`${dimensionEntry.displayValue}-${id}`}
|
||||
measurement={measurementData}
|
||||
qlik={qlik}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<DataCell
|
||||
data={data}
|
||||
general={general}
|
||||
key={`${dimensionEntry.displayValue}-${measurementData.name}`}
|
||||
measurement={measurementData}
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DataTable.defaultProps = {
|
||||
renderData: true
|
||||
};
|
||||
|
||||
DataTable.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
headers: PropTypes.shape({
|
||||
@@ -98,6 +105,8 @@ DataTable.propTypes = {
|
||||
matrix: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired
|
||||
}).isRequired,
|
||||
general: PropTypes.shape({}).isRequired,
|
||||
qlik: PropTypes.shape({}).isRequired,
|
||||
renderData: PropTypes.bool,
|
||||
styling: PropTypes.shape({
|
||||
hasCustomFileStyle: PropTypes.bool.isRequired
|
||||
}).isRequired
|
||||
|
||||
49
src/data-table/row-header.jsx
Normal file
49
src/data-table/row-header.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import HeaderPadding from './header-padding.jsx';
|
||||
|
||||
class RowHeader extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
handleSelect () {
|
||||
const { entry, qlik } = this.props;
|
||||
qlik.backendApi.selectValues(0, [entry.elementNumber], true);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { entry, rowStyle, styleBuilder, styling } = this.props;
|
||||
return (
|
||||
<td
|
||||
className="fdim-cells"
|
||||
onClick={this.handleSelect}
|
||||
style={rowStyle}
|
||||
>
|
||||
<HeaderPadding
|
||||
styleBuilder={styleBuilder}
|
||||
styling={styling}
|
||||
/>
|
||||
{entry.displayValue}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RowHeader.propTypes = {
|
||||
entry: PropTypes.shape({
|
||||
displayValue: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
qlik: PropTypes.shape({
|
||||
backendApi: PropTypes.shape({
|
||||
selectValues: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
rowStyle: PropTypes.shape({}).isRequired,
|
||||
styleBuilder: PropTypes.shape({}).isRequired,
|
||||
styling: PropTypes.shape({}).isRequired
|
||||
};
|
||||
|
||||
export default RowHeader;
|
||||
@@ -139,6 +139,10 @@ const formatted = {
|
||||
component: 'dropdown',
|
||||
label: 'FontFamily',
|
||||
options: [
|
||||
{
|
||||
value: 'QlikView Sans',
|
||||
label: 'QlikView Sans'
|
||||
},
|
||||
{
|
||||
value: 'Arial',
|
||||
label: 'Arial'
|
||||
@@ -164,7 +168,7 @@ const formatted = {
|
||||
label: 'Verdana'
|
||||
}
|
||||
],
|
||||
defaultValue: 'Calibri'
|
||||
defaultValue: 'QlikView Sans'
|
||||
},
|
||||
DataFontSize: {
|
||||
ref: 'lettersize',
|
||||
@@ -181,7 +185,7 @@ const formatted = {
|
||||
label: 'Medium'
|
||||
}
|
||||
],
|
||||
defaultValue: 2
|
||||
defaultValue: 1
|
||||
},
|
||||
ColumnWidthSlider: {
|
||||
type: 'number',
|
||||
|
||||
@@ -1,73 +1,86 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const isIE = /* @cc_on!@*/false || Boolean(document.documentMode);
|
||||
const isChrome = Boolean(window.chrome) && Boolean(window.chrome.webstore);
|
||||
const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
|
||||
const isFirefox = typeof InstallTrigger !== 'undefined';
|
||||
|
||||
export function enableExcelExport (layout, f) {
|
||||
let myTitle = '';
|
||||
let mySubTitle = '';
|
||||
let myFootNote = '';
|
||||
if (layout.title.length > 0) {
|
||||
myTitle += '<p style="font-size:15pt"><b>';
|
||||
myTitle += layout.title;
|
||||
myTitle += '</b></p>';
|
||||
}
|
||||
if (layout.subtitle.length > 0) {
|
||||
mySubTitle += '<p style="font-size:11pt">';
|
||||
mySubTitle += layout.subtitle;
|
||||
mySubTitle += '</p>';
|
||||
}
|
||||
if (layout.footnote.length > 0) {
|
||||
myFootNote += '<p style="font-size:11pt"><i>Note:</i>';
|
||||
myFootNote += layout.footnote;
|
||||
myFootNote += '</p>';
|
||||
}
|
||||
|
||||
$('.icon-xls').on('click', () => {
|
||||
$('.header-wrapper th').children('.tooltip')
|
||||
.remove(); // remove some popup effects when exporting
|
||||
$('.header-wrapper th').children('.icon-xls')
|
||||
.remove(); // remove the xls icon when exporting
|
||||
if (isChrome || isSafari) {
|
||||
const $clonedDiv = $('.data-table').clone(true); // .kpi-table a secas exporta la 1ªcol
|
||||
let vEncodeHead = '<html><head><meta charset="UTF-8"></head>';
|
||||
vEncodeHead += myTitle + mySubTitle + myFootNote;
|
||||
const vEncode = encodeURIComponent($clonedDiv.html());
|
||||
let vDecode = `${vEncodeHead + vEncode}</html>`;
|
||||
|
||||
$clonedDiv.find('tr.header');
|
||||
vDecode = vDecode.split('%3E.%3C').join('%3E%3C');
|
||||
window.open(`data:application/vnd.ms-excel,${vDecode}`);
|
||||
$.preventDefault();
|
||||
}
|
||||
if (isIE) {
|
||||
let a = '<html><head><meta charset="UTF-8"></head>';
|
||||
a += myTitle + mySubTitle + myFootNote;
|
||||
a += f;
|
||||
a = a.split('>.<').join('><');
|
||||
a += '</html>';
|
||||
|
||||
const w = window.open();
|
||||
w.document.open();
|
||||
w.document.write(a);
|
||||
w.document.close();
|
||||
w.document.execCommand('SaveAs', true, 'Analysis.xls' || 'c:\TMP');
|
||||
w.close();
|
||||
}
|
||||
|
||||
if (isFirefox) {
|
||||
const $clonedDiv = $('.data-table').clone(true);// .kpi-table a secas exporta la 1ªcol
|
||||
let vEncodeHead = '<html><head><meta charset="UTF-8"></head>';
|
||||
vEncodeHead += myTitle + mySubTitle + myFootNote;
|
||||
const vEncode = encodeURIComponent($clonedDiv.html());
|
||||
let vDecode = `${vEncodeHead + vEncode}</html>`;
|
||||
|
||||
$clonedDiv.find('tr.header');
|
||||
vDecode = vDecode.split('>.<').join('><');
|
||||
window.open(`data:application/vnd.ms-excel,${vDecode}`);
|
||||
$.preventDefault();
|
||||
function removeAllTooltips (node) {
|
||||
const tooltips = node.querySelectorAll('.tooltip');
|
||||
[].forEach.call(tooltips, tooltip => {
|
||||
if (tooltip.parentNode) {
|
||||
tooltip.parentNode.removeChild(tooltip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildTableHTML (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);
|
||||
|
||||
removeAllTooltips(dataTableClone);
|
||||
|
||||
const tableHTML = `
|
||||
<html
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns="http://www.w3.org/TR/REC-html40"
|
||||
>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<x:ExcelWorkbook>
|
||||
<x:ExcelWorksheets>
|
||||
<x:ExcelWorksheet>
|
||||
<x:Name>${title || 'Analyze'}</x:Name>
|
||||
<x:WorksheetOptions>
|
||||
<x:DisplayGridlines/>
|
||||
</x:WorksheetOptions>
|
||||
</x:ExcelWorksheet>
|
||||
</x:ExcelWorksheets>
|
||||
</x:ExcelWorkbook>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
${titleHTML.length > 0 ? titleHTML : ''}
|
||||
${subtitleHTML.length > 0 ? subtitleHTML : ''}
|
||||
${footnoteHTML.length > 0 ? footnoteHTML : ''}
|
||||
${dataTableClone.outerHTML}
|
||||
</body>
|
||||
</html>
|
||||
`.split('>.<')
|
||||
.join('><')
|
||||
.split('>*<')
|
||||
.join('><');
|
||||
|
||||
return tableHTML;
|
||||
}
|
||||
|
||||
function downloadXLS (html) {
|
||||
const filename = 'analysis.xls';
|
||||
// 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.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
downloadXLS(table);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { exportXLS } from './excel-export';
|
||||
|
||||
// TODO: move interaction logic in here from excel-export.js
|
||||
class ExportButton extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.handleExport = this.handleExport.bind(this);
|
||||
}
|
||||
|
||||
handleExport () {
|
||||
const { excelExport, general } = this.props;
|
||||
const { title, subtitle, footnote } = general;
|
||||
if (excelExport) {
|
||||
exportXLS(title, subtitle, footnote);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { excelExport } = this.props;
|
||||
return excelExport === true && (
|
||||
<input
|
||||
className="icon-xls"
|
||||
onClick={this.handleExport}
|
||||
src="/Extensions/qlik-smart-pivot/Excel.png"
|
||||
type="image"
|
||||
/>
|
||||
@@ -20,7 +34,8 @@ ExportButton.defaultProps = {
|
||||
};
|
||||
|
||||
ExportButton.propTypes = {
|
||||
excelExport: PropTypes.bool
|
||||
excelExport: PropTypes.bool,
|
||||
general: PropTypes.shape({}).isRequired
|
||||
};
|
||||
|
||||
export default ExportButton;
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ColumnHeader = ({ baseCSS, cellSuffix, colSpan, styling, title }) => {
|
||||
const style = {
|
||||
...baseCSS,
|
||||
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment} px`,
|
||||
height: '45px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
class ColumnHeader extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
return (
|
||||
<th
|
||||
className={`grid-cells2${cellSuffix}`}
|
||||
colSpan={colSpan}
|
||||
style={style}
|
||||
>
|
||||
{title}
|
||||
</th>
|
||||
);
|
||||
};
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
handleSelect () {
|
||||
const { entry, qlik } = this.props;
|
||||
qlik.backendApi.selectValues(1, [entry.elementNumber], true);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { baseCSS, cellSuffix, colSpan, entry, styling } = this.props;
|
||||
const style = {
|
||||
...baseCSS,
|
||||
fontSize: `${14 + styling.headerOptions.fontSizeAdjustment} px`,
|
||||
height: '45px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
|
||||
return (
|
||||
<th
|
||||
className={`grid-cells2${cellSuffix}`}
|
||||
colSpan={colSpan}
|
||||
onClick={this.handleSelect}
|
||||
style={style}
|
||||
>
|
||||
{entry.displayValue}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ColumnHeader.defaultProps = {
|
||||
cellSuffix: '',
|
||||
@@ -29,12 +44,20 @@ ColumnHeader.propTypes = {
|
||||
baseCSS: PropTypes.shape({}).isRequired,
|
||||
cellSuffix: PropTypes.string,
|
||||
colSpan: PropTypes.number,
|
||||
entry: PropTypes.shape({
|
||||
elementNumber: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
qlik: PropTypes.shape({
|
||||
backendApi: PropTypes.shape({
|
||||
selectValues: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({
|
||||
fontSizeAdjustment: PropTypes.number.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default ColumnHeader;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ExportButton from '../export-button.jsx';
|
||||
|
||||
const ExportColumnHeader = ({ baseCSS, title, allowExcelExport, hasSecondDimension, styling }) => {
|
||||
const ExportColumnHeader = ({ baseCSS, general, title, allowExcelExport, hasSecondDimension, styling }) => {
|
||||
const rowSpan = hasSecondDimension ? 2 : 1;
|
||||
const style = {
|
||||
...baseCSS,
|
||||
@@ -19,7 +19,10 @@ const ExportColumnHeader = ({ baseCSS, title, allowExcelExport, hasSecondDimensi
|
||||
rowSpan={rowSpan}
|
||||
style={style}
|
||||
>
|
||||
<ExportButton excelExport={allowExcelExport} />
|
||||
<ExportButton
|
||||
excelExport={allowExcelExport}
|
||||
general={general}
|
||||
/>
|
||||
{title}
|
||||
</th>
|
||||
);
|
||||
@@ -28,6 +31,7 @@ const ExportColumnHeader = ({ baseCSS, title, allowExcelExport, hasSecondDimensi
|
||||
ExportColumnHeader.propTypes = {
|
||||
allowExcelExport: PropTypes.bool.isRequired,
|
||||
baseCSS: PropTypes.shape({}).isRequired,
|
||||
general: PropTypes.shape({}).isRequired,
|
||||
hasSecondDimension: PropTypes.bool.isRequired,
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({
|
||||
|
||||
@@ -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, styling }) => {
|
||||
const HeadersTable = ({ data, general, qlik, styling }) => {
|
||||
const baseCSS = {
|
||||
backgroundColor: styling.headerOptions.colorSchema,
|
||||
color: styling.headerOptions.textColor,
|
||||
@@ -29,6 +29,7 @@ const HeadersTable = ({ data, general, styling }) => {
|
||||
<ExportColumnHeader
|
||||
allowExcelExport={general.allowExcelExport}
|
||||
baseCSS={baseCSS}
|
||||
general={general}
|
||||
hasSecondDimension={hasSecondDimension}
|
||||
styling={styling}
|
||||
title={dimension1[0].name}
|
||||
@@ -43,7 +44,7 @@ const HeadersTable = ({ data, general, styling }) => {
|
||||
styling={styling}
|
||||
/>
|
||||
))}
|
||||
{hasSecondDimension && injectSeparators(dimension2, styling.useSeparatorColumns).map(entry => {
|
||||
{hasSecondDimension && injectSeparators(dimension2, styling.useSeparatorColumns).map((entry, index) => {
|
||||
if (entry.isSeparator) {
|
||||
const separatorStyle = {
|
||||
color: 'white',
|
||||
@@ -54,6 +55,7 @@ const HeadersTable = ({ data, general, styling }) => {
|
||||
return (
|
||||
<th
|
||||
className="empty"
|
||||
key={index}
|
||||
style={separatorStyle}
|
||||
>
|
||||
*
|
||||
@@ -65,16 +67,17 @@ const HeadersTable = ({ data, general, styling }) => {
|
||||
baseCSS={baseCSS}
|
||||
cellSuffix={general.cellSuffix}
|
||||
colSpan={measurements.length}
|
||||
entry={entry}
|
||||
key={entry.displayValue}
|
||||
qlik={qlik}
|
||||
styling={styling}
|
||||
title={entry.displayValue}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
{hasSecondDimension && (
|
||||
<tr>
|
||||
{injectSeparators(dimension2, styling.useSeparatorColumns).map(dimensionEntry => {
|
||||
{injectSeparators(dimension2, styling.useSeparatorColumns).map((dimensionEntry, index) => {
|
||||
if (dimensionEntry.isSeparator) {
|
||||
const separatorStyle = {
|
||||
color: 'white',
|
||||
@@ -85,6 +88,7 @@ const HeadersTable = ({ data, general, styling }) => {
|
||||
return (
|
||||
<th
|
||||
className="empty"
|
||||
key={index}
|
||||
style={separatorStyle}
|
||||
>
|
||||
*
|
||||
@@ -120,6 +124,11 @@ HeadersTable.propTypes = {
|
||||
})
|
||||
}).isRequired,
|
||||
general: PropTypes.shape({}).isRequired,
|
||||
qlik: PropTypes.shape({
|
||||
backendApi: PropTypes.shape({
|
||||
selectValues: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
styling: PropTypes.shape({
|
||||
headerOptions: PropTypes.shape({}),
|
||||
options: PropTypes.shape({})
|
||||
|
||||
@@ -25,6 +25,11 @@ export default {
|
||||
qMeasures: []
|
||||
}
|
||||
},
|
||||
support: {
|
||||
export: true,
|
||||
exportData: true,
|
||||
snapshot: true
|
||||
},
|
||||
paint ($element, layout) {
|
||||
try {
|
||||
paint($element, layout, this);
|
||||
|
||||
@@ -39,8 +39,8 @@ function getAlignment (option) {
|
||||
|
||||
function getFontSizeAdjustment (option) {
|
||||
const fontSizeAdjustmentOptions = {
|
||||
1: -2,
|
||||
2: 0,
|
||||
1: -1,
|
||||
2: 1,
|
||||
3: 2
|
||||
};
|
||||
|
||||
@@ -91,24 +91,41 @@ function generateMeasurements (information) {
|
||||
function generateDimensionEntry (information, data) {
|
||||
return {
|
||||
displayValue: data.qText,
|
||||
elementNumber: data.qElemNumber,
|
||||
name: information.qFallbackTitle,
|
||||
value: data.qNum
|
||||
};
|
||||
}
|
||||
|
||||
function generateMatrixCell (information, data) {
|
||||
return {
|
||||
displayValue: data.qText,
|
||||
elementNumber: data.qElemNumber,
|
||||
format: information.format,
|
||||
magnitude: information.magnitudeLabelSuffix.substring(
|
||||
information.magnitudeLabelSuffix.length - 2,
|
||||
information.magnitudeLabelSuffix.length - 1
|
||||
function generateMatrixCell ({ cell, dimension1Information, dimension2Information, measurementInformation }) {
|
||||
const matrixCell = {
|
||||
displayValue: cell.qText,
|
||||
format: measurementInformation.format,
|
||||
magnitude: measurementInformation.magnitudeLabelSuffix.substring(
|
||||
measurementInformation.magnitudeLabelSuffix.length - 2,
|
||||
measurementInformation.magnitudeLabelSuffix.length - 1
|
||||
),
|
||||
magnitudeLabelSuffix: information.magnitudeLabelSuffix,
|
||||
name: information.name,
|
||||
value: data.qNum
|
||||
magnitudeLabelSuffix: measurementInformation.magnitudeLabelSuffix,
|
||||
name: measurementInformation.name,
|
||||
parents: {
|
||||
dimension1: {
|
||||
elementNumber: dimension1Information.qElemNumber,
|
||||
header: dimension1Information.qText
|
||||
},
|
||||
measurement: {
|
||||
header: measurementInformation.name
|
||||
}
|
||||
},
|
||||
value: cell.qNum
|
||||
};
|
||||
|
||||
if (dimension2Information) {
|
||||
matrixCell.parents.dimension2 = {
|
||||
elementNumber: dimension2Information.qElemNumber
|
||||
};
|
||||
}
|
||||
|
||||
return matrixCell;
|
||||
}
|
||||
|
||||
let lastRow = 0;
|
||||
@@ -135,7 +152,14 @@ function generateDataSet (component, dimensionsInformation, measurementsInformat
|
||||
.slice(firstDataCell, row.length)
|
||||
.map((cell, cellIndex) => {
|
||||
const measurementInformation = measurements[cellIndex];
|
||||
const generatedCell = generateMatrixCell(measurementInformation, cell);
|
||||
const dimension1Information = row[0]; // eslint-disable-line prefer-destructuring
|
||||
const dimension2Information = hasSecondDimension ? row[1] : null;
|
||||
const generatedCell = generateMatrixCell({
|
||||
cell,
|
||||
dimension1Information,
|
||||
dimension2Information,
|
||||
measurementInformation
|
||||
});
|
||||
|
||||
return generatedCell;
|
||||
});
|
||||
@@ -228,9 +252,13 @@ async function initializeTransformed ({ $element, layout, component }) {
|
||||
},
|
||||
general: {
|
||||
allowExcelExport: layout.allowexportxls,
|
||||
allowFilteringByClick: layout.filteroncellclick,
|
||||
cellSuffix: getCellSuffix(layout.columnwidthslider), // TOOD: move to matrix cells or is it headers.measurements?
|
||||
errorMessage: layout.errormessage,
|
||||
maxLoops
|
||||
footnote: layout.footnote,
|
||||
maxLoops,
|
||||
subtitle: layout.subtitle,
|
||||
title: layout.title
|
||||
},
|
||||
selection: {
|
||||
dimensionSelectionCounts: dimensionsInformation.map(dimensionInfo => dimensionInfo.qStateCounts.qSelected)
|
||||
|
||||
192
src/paint.jsx
192
src/paint.jsx
@@ -1,9 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
import { enableExcelExport } from './excel-export';
|
||||
import initializeStore from './store';
|
||||
import React from 'react';
|
||||
// import ReactDOM from 'react-dom';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import ReactDOM from 'react-dom';
|
||||
import HeadersTable from './headers-table/index.jsx';
|
||||
import DataTable from './data-table/index.jsx';
|
||||
|
||||
@@ -14,29 +12,20 @@ export default async function paint ($element, layout, component) {
|
||||
layout
|
||||
});
|
||||
|
||||
const {
|
||||
ConceptMatrixColElem,
|
||||
ConceptMatrixColElemTable,
|
||||
ConceptMatrixRowElem
|
||||
} = state.properties;
|
||||
|
||||
const {
|
||||
data: { meta: { dimensionCount } },
|
||||
selection: { dimensionSelectionCounts },
|
||||
styling: { useSeparatorColumns }
|
||||
} = state;
|
||||
|
||||
const jsx = (
|
||||
<React.Fragment>
|
||||
<div className="kpi-table">
|
||||
<HeadersTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={component}
|
||||
styling={state.styling}
|
||||
/>
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={component}
|
||||
renderData={false}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</div>
|
||||
@@ -44,159 +33,52 @@ export default async function paint ($element, layout, component) {
|
||||
<HeadersTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={component}
|
||||
styling={state.styling}
|
||||
/>
|
||||
<DataTable
|
||||
data={state.data}
|
||||
general={state.general}
|
||||
qlik={component}
|
||||
styling={state.styling}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
// TODO: switch to render when jquery interaction stuff in renderData is gone
|
||||
const html = renderToStaticMarkup(jsx);
|
||||
$element.html(html);
|
||||
// ReactDOM.render(jsx, $element[0]);
|
||||
ReactDOM.render(jsx, $element[0]);
|
||||
|
||||
RenderData();
|
||||
// TODO: skipped the following as they weren't blockers for letting react handle rendering,
|
||||
// they are however the only reason we still depend on jQuery and should be removed as part of unnecessary dependencies issue
|
||||
$(`[tid="${layout.qInfo.qId}"] .data-table .row-wrapper`).on('scroll', function () {
|
||||
$(`[tid="${layout.qInfo.qId}"] .kpi-table .row-wrapper`).scrollTop($(this).scrollTop());
|
||||
});
|
||||
|
||||
// TODO: move jquery interactions into their respective components
|
||||
// Hook up interactions and some html mangling
|
||||
function RenderData () {
|
||||
$('.data-table .row-wrapper').on('scroll', function () {
|
||||
$(`[tid="${layout.qInfo.qId}"] .kpi-table .row-wrapper`).scrollTop($(this).scrollTop());
|
||||
// freeze first column
|
||||
$(`[tid="${layout.qInfo.qId}"] .qv-object-content-container`).on('scroll', (t) => {
|
||||
$(`[tid="${layout.qInfo.qId}"] .kpi-table`).css('left', `${Math.round(t.target.scrollLeft)}px`);
|
||||
});
|
||||
|
||||
// TODO: fixing tooltips has a seperate issue, make sure to remove this as part of that issue
|
||||
$(`[tid="${layout.qInfo.qId}"] .header-wrapper th`).hover(function () {
|
||||
$(`[tid="${layout.qInfo.qId}"] .tooltip`).delay(500)
|
||||
.show(0);
|
||||
$(`[tid="${layout.qInfo.qId}"] .header-wrapper th`).children(`[tid="${layout.qInfo.qId}"] .tooltip`)
|
||||
.remove();
|
||||
|
||||
const element = $(this);
|
||||
const offset = element.offset();
|
||||
const toolTip = $('<div class="tooltip"></div>');
|
||||
|
||||
toolTip.css({
|
||||
left: offset.left,
|
||||
top: offset.top
|
||||
});
|
||||
|
||||
// on hover popup with cell value, only in headers
|
||||
$('.header-wrapper th').hover(function () {
|
||||
$('.tooltip').delay(500)
|
||||
.show(0);
|
||||
$('.header-wrapper th').children('.tooltip')
|
||||
.remove();
|
||||
|
||||
const element = $(this);
|
||||
const offset = element.offset();
|
||||
const toolTip = $("<div class='tooltip'></div>");
|
||||
|
||||
toolTip.css({
|
||||
top: offset.top,
|
||||
left: offset.left
|
||||
});
|
||||
|
||||
toolTip.text(element.text());
|
||||
$('.header-wrapper th').append(toolTip);
|
||||
}, () => {
|
||||
$('.tooltip').delay(0)
|
||||
.hide(0);
|
||||
});
|
||||
|
||||
// allow making selections inside the table
|
||||
$('.data-table td').on('click', function () {
|
||||
if (layout.filteroncellclick == false) {
|
||||
return;
|
||||
}
|
||||
const indextr = $(this).parent()
|
||||
.parent()
|
||||
.children()
|
||||
.index($(this).parent()); // identifica la row
|
||||
const indextd = $(this).parent()
|
||||
.children()
|
||||
.index($(this)); // identifica la col
|
||||
|
||||
let SelectRow = 0;
|
||||
let SelectCol = 0;
|
||||
|
||||
SelectRow = ConceptMatrixRowElem[(indextr)];
|
||||
|
||||
// este if verifica primero si hay selecciones hechas en la dimensión, si las hay
|
||||
// las reselecciona para poder borrar antes de poder seleccionar lo que quiero
|
||||
// no es viable pedirle que seleccione a la vez elementos de 2 selecciones, se queda
|
||||
// colgado el menú de confirm, por eso uso este sistema, que sí funciona.
|
||||
// it can cause issues like error messages and wrong selections if there are null values
|
||||
// and the check allow null values is active
|
||||
if (dimensionCount > 1 && indextd > 0) {
|
||||
if (dimensionSelectionCounts[1] > 0) {
|
||||
const SelectB = JSON.parse(JSON.stringify(ConceptMatrixColElemTable));
|
||||
component.backendApi.selectValues(1, SelectB, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
SelectCol = ConceptMatrixColElemTable[(indextd)];
|
||||
|
||||
component.backendApi.selectValues(1, [SelectCol], true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
|
||||
if (indextd > 0 && dimensionSelectionCounts[0] > 0) {
|
||||
const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem));
|
||||
component.backendApi.selectValues(0, SelectA, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
|
||||
if (indextd > 0) {
|
||||
component.backendApi.selectValues(0, [SelectRow], true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
});
|
||||
// allow selections through the header of the second dimension
|
||||
$('.header-wrapper th').on('click', function () {
|
||||
const indextd = $(this).parent()
|
||||
.children()
|
||||
.index($(this)); // identifica la col
|
||||
|
||||
let SelectCol = 0;
|
||||
|
||||
if (dimensionCount > 1 && indextd > 0) {
|
||||
if (dimensionSelectionCounts[1] > 0) {
|
||||
const SelectB = JSON.parse(JSON.stringify(ConceptMatrixColElem));
|
||||
component.backendApi.selectValues(1, SelectB, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
if (useSeparatorColumns) {
|
||||
SelectCol = ConceptMatrixColElem[(Math.round(indextd / 2) - 1)];
|
||||
} else {
|
||||
SelectCol = ConceptMatrixColElem[(Math.round(indextd) - 1)];
|
||||
}
|
||||
|
||||
component.backendApi.selectValues(1, [SelectCol], true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
});
|
||||
// allow selections in desc dimension cells
|
||||
$('.kpi-table td').on('click', function () {
|
||||
const indextr = $(this).parent()
|
||||
.parent()
|
||||
.children()
|
||||
.index($(this).parent()); // identifica la row
|
||||
let SelectRow = 0;
|
||||
SelectRow = ConceptMatrixRowElem[(indextr)];
|
||||
|
||||
if (dimensionSelectionCounts[0] > 0) {
|
||||
const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem));
|
||||
component.backendApi.selectValues(0, SelectA, true);
|
||||
$(this).toggleClass('selected');
|
||||
}
|
||||
|
||||
component.backendApi.selectValues(0, [SelectRow], true);
|
||||
$(this).toggleClass('selected');
|
||||
});
|
||||
|
||||
enableExcelExport(layout, html);
|
||||
|
||||
// freeze first column
|
||||
$('.qv-object-content-container').on('scroll', (t) => {
|
||||
$('.kpi-table').css('left', `${Math.round(t.target.scrollLeft)}px`);
|
||||
});
|
||||
$('.kpi-table .row-wrapper tr').each(function () {
|
||||
$(this).find('th:not(.fdim-cells)')
|
||||
.remove();
|
||||
$(this).find('td:not(.fdim-cells)')
|
||||
.remove();
|
||||
});
|
||||
$('.kpi-table .header-wrapper tr').each(function () {
|
||||
$(this).find('th:not(.fdim-cells)')
|
||||
.remove();
|
||||
});
|
||||
}
|
||||
toolTip.text(element.text());
|
||||
$(`[tid="${layout.qInfo.qId}"] .header-wrapper th`).append(toolTip);
|
||||
}, () => {
|
||||
$(`[tid="${layout.qInfo.qId}"] .tooltip`).delay(0)
|
||||
.hide(0);
|
||||
});
|
||||
}
|
||||
|
||||
133
src/store.js
133
src/store.js
@@ -1,4 +1,3 @@
|
||||
import { onlyUnique } from './utilities';
|
||||
import initializeTransformed from './initialize-transformed';
|
||||
|
||||
async function initialize ({ $element, layout, component }) {
|
||||
@@ -8,137 +7,7 @@ async function initialize ({ $element, layout, component }) {
|
||||
layout
|
||||
});
|
||||
|
||||
// TODO: remove everything from here to return statement once jquery parts in paint has been refactored
|
||||
const vMaxLoops = layout.maxloops;
|
||||
const vErrorMessage = layout.errormessage;
|
||||
let vDimName = '';
|
||||
const ConceptMatrixFirst = new Array();
|
||||
const ConceptMatrixSecond = new Array();
|
||||
const LabelsArray = new Array();
|
||||
const ArrayGetSelectedCount = new Array();
|
||||
let vNumDims = 0;
|
||||
let vNumMeasures = 0;
|
||||
const MeasuresFormat = new Array();
|
||||
const dim_count = layout.qHyperCube.qDimensionInfo.length;
|
||||
const measure_count = layout.qHyperCube.qMeasureInfo.length;
|
||||
let vSeparatorCols = layout.separatorcols;
|
||||
if (dim_count == 1) {
|
||||
vSeparatorCols = false;
|
||||
}
|
||||
let lastrow = 0;
|
||||
const ConceptMatrix = new Array();
|
||||
let ConceptMatrixRowElem = new Array();
|
||||
let ConceptMatrixColElem = new Array();
|
||||
const ConceptMatrixColElemTable = new Array();
|
||||
const ConceptMatrixPivot = new Array();
|
||||
let ConceptMatrixFirstClean = new Array();
|
||||
const nRows = component.backendApi.getRowCount();
|
||||
|
||||
const dimensionInfos = component.backendApi.getDimensionInfos();
|
||||
LabelsArray.push(dimensionInfos[0].qFallbackTitle);
|
||||
ArrayGetSelectedCount.concat(dimensionInfos.map(dimensionInfo => dimensionInfo.qStateCounts.qSelected));
|
||||
vNumDims += dimensionInfos.length;
|
||||
|
||||
const measureInfos = component.backendApi.getMeasureInfos();
|
||||
measureInfos.forEach(measureInfo => {
|
||||
vDimName = measureInfo.qFallbackTitle;
|
||||
LabelsArray.push(vDimName);
|
||||
let mfor = '';
|
||||
|
||||
if (measureInfo.qNumFormat.qType == 'U' || measureInfo.qNumFormat.qFmt == '##############') {
|
||||
mfor = '#.##0'; // in case of undefined
|
||||
} else if (measureInfo.qNumFormat.qType == 'R') {
|
||||
mfor = measureInfo.qNumFormat.qFmt;
|
||||
mfor = mfor.replace(/(|)/gi, '');
|
||||
} else {
|
||||
mfor = measureInfo.qNumFormat.qFmt;
|
||||
}
|
||||
|
||||
MeasuresFormat.push(mfor);
|
||||
|
||||
vNumMeasures++;
|
||||
});
|
||||
|
||||
component.backendApi.eachDataRow((t, a) => {
|
||||
lastrow = t;
|
||||
|
||||
const vNumMeasuresPlus = vNumMeasures + 1;
|
||||
|
||||
ConceptMatrix[t] = new Array();
|
||||
ConceptMatrix[t][0] = a[0].qText;
|
||||
|
||||
ConceptMatrixFirst[t] = a[0].qText;
|
||||
ConceptMatrixRowElem[t] = a[0].qElemNumber;
|
||||
let nMeasures = 0;
|
||||
if (vNumDims == 1) {
|
||||
for (nMeasures = 1; nMeasures <= vNumMeasures; nMeasures++) {
|
||||
ConceptMatrix[t][nMeasures] = a[nMeasures].qNum;
|
||||
}
|
||||
} else {
|
||||
ConceptMatrix[t][1] = a[1].qText;
|
||||
ConceptMatrixColElem[t] = a[1].qElemNumber;
|
||||
ConceptMatrixSecond[t] = a[1].qText;
|
||||
// set the hipercube in a plain array without pivoting
|
||||
for (nMeasures = 2; nMeasures <= vNumMeasuresPlus; nMeasures++) {
|
||||
ConceptMatrix[t][nMeasures] = a[nMeasures].qNum;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ConceptMatrixFirstClean = ConceptMatrixFirst.filter(onlyUnique);
|
||||
|
||||
if (nRows >= (vMaxLoops * 1000)) {
|
||||
alert(vErrorMessage);
|
||||
}
|
||||
|
||||
if (vNumDims == 2) {
|
||||
// new array with unique values for 2nd dim
|
||||
var SecondHeader = ConceptMatrixSecond.filter(onlyUnique);// second dimension concepts
|
||||
ConceptMatrixRowElem = ConceptMatrixRowElem.filter(onlyUnique);// first dimension concepts
|
||||
ConceptMatrixColElem = ConceptMatrixColElem.filter(onlyUnique);// dimension code for further selections
|
||||
const eo = ConceptMatrixColElem.length;
|
||||
let vLoopColsMeasures = 1;
|
||||
ConceptMatrixColElemTable[0] = ConceptMatrixColElem[0];
|
||||
for (let xx = 0; xx < eo; xx++) {
|
||||
if (vSeparatorCols && xx > 0) {
|
||||
ConceptMatrixColElemTable[vLoopColsMeasures] = ConceptMatrixColElem[xx];
|
||||
vLoopColsMeasures++;
|
||||
}
|
||||
|
||||
for (let xxx = 0; xxx < vNumMeasures; xxx++) {
|
||||
ConceptMatrixColElemTable[vLoopColsMeasures] = ConceptMatrixColElem[xx];
|
||||
vLoopColsMeasures++;
|
||||
}
|
||||
}
|
||||
|
||||
let ConceptPos = 0;
|
||||
let nMeas3 = 0;
|
||||
let vHeaderIndex = 0;
|
||||
let MeasurePos = 0;
|
||||
for (let nPivotElems = 0; nPivotElems <= lastrow; nPivotElems++) {
|
||||
ConceptMatrixPivot[nPivotElems] = new Array();
|
||||
ConceptPos = ConceptMatrixFirstClean.indexOf(ConceptMatrix[nPivotElems][0]);
|
||||
ConceptMatrixPivot[ConceptPos][0] = ConceptMatrix[nPivotElems][0];
|
||||
|
||||
for (let nMeas2 = 1; nMeas2 <= measure_count; nMeas2++) {
|
||||
nMeas3 = nMeas2 + 1;
|
||||
vHeaderIndex = (SecondHeader.indexOf(ConceptMatrix[nPivotElems][1]) + 1);
|
||||
MeasurePos = (vHeaderIndex * measure_count) + (nMeas2 - measure_count);
|
||||
ConceptMatrixPivot[ConceptPos][MeasurePos] = ConceptMatrix[nPivotElems][nMeas3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const properties = {
|
||||
ConceptMatrixColElem,
|
||||
ConceptMatrixColElemTable,
|
||||
ConceptMatrixRowElem
|
||||
};
|
||||
|
||||
return {
|
||||
properties,
|
||||
...transformedProperties
|
||||
};
|
||||
return transformedProperties;
|
||||
}
|
||||
|
||||
export default initialize;
|
||||
|
||||
Reference in New Issue
Block a user