From 9b682e62f8eb4274e17f89f95d9990ddcd409bb8 Mon Sep 17 00:00:00 2001 From: Kristoffer Lind Date: Thu, 7 Feb 2019 10:19:42 +0100 Subject: [PATCH 1/4] create new data structure --- src/initialize-transformed.js | 319 ++++++++++++++++++++++++++++++++++ src/store.js | 175 ++----------------- 2 files changed, 329 insertions(+), 165 deletions(-) create mode 100644 src/initialize-transformed.js diff --git a/src/initialize-transformed.js b/src/initialize-transformed.js new file mode 100644 index 0000000..35fcd5a --- /dev/null +++ b/src/initialize-transformed.js @@ -0,0 +1,319 @@ +import jQuery from 'jquery'; +import { distinctArray } from './utilities'; + +// TODO: rename colors +function initializeColors ({ layout }) { + console.log(layout); + return { + vColLibBlue: layout.collibblue, + vColLibBlueP: layout.collibbluep, + vColLibClean: layout.collibclean, + vColLibCleanP: layout.collibcleanp, + vColLibCustom: layout.collibcustom, + vColLibCustomP: layout.collibcustomp, + vColLibDark: layout.collibdark, + vColLibDarkP: layout.collibdarkp, + vColLibGreen: layout.collibgreen, + vColLibGreenP: layout.collibgreenp, + vColLibNight: layout.collibnight, + vColLibNightP: layout.collibnightp, + vColLibOrange: layout.colliborange, + vColLibOrangeP: layout.colliborangep, + vColLibRed: layout.collibred, + vColLibRedP: layout.collibredp, + vColLibSoft: layout.collibsoft, + vColLibSoftP: layout.collibsoftp, + vColLibViolete: layout.collibviolete, + vColLibVioleteP: layout.collibvioletep + }; +} + +function getAlignment (option) { + const alignmentOptions = { + 1: 'left', + 2: 'center', + 3: 'right' + }; + + return alignmentOptions[option] || 'left'; +} + +function getFontSizeAdjustment (option) { + const fontSizeAdjustmentOptions = { + 1: -2, + 2: 0, + 3: 2 + }; + + return fontSizeAdjustmentOptions[option] || 0; +} + +function getCellSuffix (option) { + const cellSuffixOptions = { + 1: '-s', + 3: '-l' + }; + + return cellSuffixOptions[option] || ''; +} + +function getMeasurementFormat (measurement) { + if (measurement.qNumFormat.qType === 'U' || measurement.qNumFormat.qFmt === '##############') { + return '#.##0'; + } else if (measurement.qNumFormat.qType === 'R') { + return measurement.qNumFormat.qFmt.replace(/(|)/gi, ''); + } + return measurement.qNumFormat.qFmt; +} + +function getMagnitudeLabelSuffix (magnitudeOption) { + const magnitudeLabelSuffixOptions = { + 'k': ' (k)', + 'm': ' (m)' + }; + + return magnitudeLabelSuffixOptions[magnitudeOption] || ''; +} + +function generateMeasurements (information) { + return information.map(measurement => { + const format = getMeasurementFormat(measurement); + const formatMagnitude = format.substr(format.length - 1).toLowerCase(); + const transformedMeasurement = { + format, + magnitudeLabelSuffix: getMagnitudeLabelSuffix(formatMagnitude), + name: measurement.qFallbackTitle + }; + + return transformedMeasurement; + }); +} + +function generateDimensionEntry (information, data) { + return { + displayValue: data.qText, + 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 + ), + magnitudeLabelSuffix: information.magnitudeLabelSuffix, + name: information.name, + value: data.qNum + }; +} + +let lastRow = 0; +function generateDataSet (component, dimensionsInformation, measurementsInformation) { + const dimension1 = []; + const dimension2 = []; + const measurements = generateMeasurements(measurementsInformation); + let matrix = []; + + let previousDim1Entry; + const hasSecondDimension = dimensionsInformation.length > 1; + component.backendApi.eachDataRow((rowIndex, row) => { + lastRow += 1; + const dimension1Entry = generateDimensionEntry(dimensionsInformation[0], row[0]); + dimension1.push(dimension1Entry); + let dimension2Entry; + let firstDataCell = 1; + if (hasSecondDimension) { + dimension2Entry = generateDimensionEntry(dimensionsInformation[1], row[1]); + dimension2.push(dimension2Entry); + firstDataCell = 2; + } + const matrixRow = row + .slice(firstDataCell, row.length) + .map((cell, cellIndex) => { + const measurementInformation = measurements[cellIndex]; + const generatedCell = generateMatrixCell(measurementInformation, cell); + + return generatedCell; + }); + + if (hasSecondDimension) { + // console.log(row[0]); + 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; + } + previousDim1Entry = currentDim1Entry; + } else { + matrix[matrix.length] = matrixRow; + } + }); + + // filter header dimensions to only have distinct values + + return { + dimension1: distinctArray(dimension1), + dimension2: distinctArray(dimension2), + matrix, + measurements + }; +} + +async function initializeTransformed ({ $element, layout, component }) { + const colors = initializeColors({ layout }); + const dimensionsInformation = component.backendApi.getDimensionInfos(); + const measurementsInformation = component.backendApi.getMeasureInfos(); + const dimensionCount = layout.qHyperCube.qDimensionInfo.length; + const rowCount = component.backendApi.getRowCount(); + const maxLoops = layout.maxloops; + const { + dimension1, + dimension2, + measurements, + matrix + } = generateDataSet(component, dimensionsInformation, measurementsInformation); + + const customSchemaBasic = []; + const customSchemaFull = []; + let customHeadersCount = 0; + + function readCustomSchema () { + const url = `/Extensions/qlik-smart-pivot/${layout.customfile}`; + + return jQuery.get(url).then(response => { + const allTextLines = response.split(/\r\n|\n/); + const headers = allTextLines[0].split(';'); + customHeadersCount = headers.length; + for (let lineNumber = 0; lineNumber < allTextLines.length; lineNumber += 1) { + customSchemaFull[lineNumber] = new Array(headers.length); + const data = allTextLines[lineNumber].split(';'); + + if (data.length === headers.length) { + for (let headerIndex = 0; headerIndex < headers.length; headerIndex += 1) { + customSchemaBasic[lineNumber] = data[0]; + customSchemaFull[lineNumber][headerIndex] = data[headerIndex]; + } + } + } + }); + } + + const hasCustomSchema = (layout.customfilebool && layout.customfile.length > 4); + const schemaPromise = hasCustomSchema ? readCustomSchema() : Promise.resolve(); + await schemaPromise; + + // top level properties could be reducers and then components connect to grab what they want, + // possibly with reselect for some presentational transforms (moving some of the presentational logic like formatting and such) + const transformedProperties = { + data: { + headers: { + dimension1, // column headers + dimension2, // parent row headers if exists + measurements // row headers, looped for each dimension2 if exists + }, + matrix, // 2d array of all rows/cells to render in body of datatable + meta: { + dimensionCount: dimensionsInformation.length + } + }, + general: { + allowExcelExport: layout.allowexportxls, + cellSuffix: getCellSuffix(layout.columnwidthslider), // TOOD: move to matrix cells or is it headers.measurements? + errorMessage: layout.errormessage, + maxLoops + }, + selection: { + dimensionSelectionCounts: dimensionsInformation.map(dimensionInfo => dimensionInfo.qStateCounts.qSelected) + }, + styling: { + colors, + customCSV: { + basic: customSchemaBasic, + count: customHeadersCount, + full: customSchemaFull + }, + hasCustomFileStyle: layout.customfilebool, + headerOptions: { + alignment: getAlignment(layout.HeaderAlign), + colorSchema: colors[`vColLib${layout.HeaderColorSchema}`], + fontSizeAdjustment: getFontSizeAdjustment(layout.lettersizeheader), + textColor: layout.HeaderTextColorSchema + }, + options: { + backgroundColor: colors[`vColLib${layout.ColorSchema}`], + backgroundColorOdd: colors[`vColLib${layout.ColorSchemaP}`], + color: layout.BodyTextColorSchema, + fontFamily: layout.FontFamily, + fontSizeAdjustment: getFontSizeAdjustment(layout.lettersize) + }, + semaphoreColors: { + fieldsToApplyTo: { + applyToAll: layout.allsemaphores, + specificFields: [ + layout.conceptsemaphore1, + layout.conceptsemaphore2, + layout.conceptsemaphore3, + layout.conceptsemaphore4, + layout.conceptsemaphore5, + layout.conceptsemaphore6, + layout.conceptsemaphore7, + layout.conceptsemaphore9, + layout.conceptsemaphore10 + ] + }, + status: { + critical: layout.metricstatus1, + medium: layout.metricstatus2 + }, + statusColors: { + critical: { + backgroundColor: layout.colorstatus1.color, + color: layout.colorstatus1text.color + }, + medium: { + backgroundColor: layout.colorstatus2.color, + color: layout.colorstatus2text.color + }, + normal: { + backgroundColor: layout.colorstatus3.color, + color: layout.colorstatus3text.color + } + } + }, + symbolForNulls: layout.symbolfornulls, + usePadding: layout.indentbool, + useSeparatorColumns: dimensionCount === 1 ? false : layout.separatorcols + } + }; + + if (rowCount > lastRow && rowCount <= (maxLoops * 1000)) { + const requestPage = [ + { + qHeight: Math.min(1000, rowCount - lastRow), + qLeft: 0, + qTop: matrix.length, + qWidth: 10 // should be # of columns + } + ]; + component.backendApi.getData(requestPage).then(() => { + component.paint($element, layout); + }); + } + + + return transformedProperties; +} + +export default initializeTransformed; diff --git a/src/store.js b/src/store.js index 602df3d..7f4b3f3 100644 --- a/src/store.js +++ b/src/store.js @@ -1,96 +1,30 @@ import { onlyUnique } from './utilities'; +import initializeTransformed from './initialize-transformed'; -function initialize ({ $element, layout, component }) { - const colors = { - vColLibClean: layout.collibclean, - vColLibSoft: layout.collibsoft, - vColLibDark: layout.collibdark, - vColLibNight: layout.collibnight, - vColLibRed: layout.collibred, - vColLibOrange: layout.colliborange, - vColLibBlue: layout.collibblue, - vColLibGreen: layout.collibgreen, - vColLibViolete: layout.collibviolete, - vColLibCustom: layout.collibcustom, - vColLibCleanP: layout.collibcleanp, - vColLibSoftP: layout.collibsoftp, - vColLibDarkP: layout.collibdarkp, - vColLibNightP: layout.collibnightp, - vColLibRedP: layout.collibredp, - vColLibOrangeP: layout.colliborangep, - vColLibBlueP: layout.collibbluep, - vColLibGreenP: layout.collibgreenp, - vColLibVioleteP: layout.collibvioletep, - vColLibCustomP: layout.collibcustomp - }; +async function initialize ({ $element, layout, component }) { + const transformedProperties = await initializeTransformed({ + $element, + component, + layout + }); - const nMeasAux = 0; + // TODO: remove everything from here to return statement once jquery parts in paint has been refactored const vMaxLoops = layout.maxloops; const vErrorMessage = layout.errormessage; - const vDynamicColorHeader = `vColLib${layout.HeaderColorSchema}`; - const vHeaderColorSchema = colors[vDynamicColorHeader]; - const vExportToExcel = layout.allowexportxls; - const vHeaderColorText = layout.HeaderTextColorSchema; - const vHeaderAlign = layout.HeaderAlign; - let vHeaderAlignText = ''; - switch (vHeaderAlign) { - case 1: - vHeaderAlignText = 'left'; - break; - case 2: - vHeaderAlignText = 'center'; - break; - case 3: - vHeaderAlignText = 'right'; - break; - } - let vLetterSizeHeader = 0; - switch (layout.lettersizeheader) { - case 1: - vLetterSizeHeader = -2; - break; - case 2: - vLetterSizeHeader = 0; - break; - case 3: - vLetterSizeHeader = 2; - break; - } let vDimName = ''; const ConceptMatrixFirst = new Array(); const ConceptMatrixSecond = new Array(); - let SecondHeaderLength = 0; const LabelsArray = new Array(); - const ExtraLabelsArray = new Array(); - let vExtraLabel = ''; - const vExcelButtonCode = ''; const ArrayGetSelectedCount = new Array(); let vNumDims = 0; let vNumMeasures = 0; - let vNumMeasures2 = 0; const MeasuresFormat = new Array(); - let sufixCells = ''; - switch (layout.columnwidthslider) { - case 1: - sufixCells += '-s'; - break; - case 2: - sufixCells = String(sufixCells); - break; - case 3: - sufixCells += '-l'; - break; - default: - sufixCells = String(sufixCells); - break; - } const dim_count = layout.qHyperCube.qDimensionInfo.length; const measure_count = layout.qHyperCube.qMeasureInfo.length; let vSeparatorCols = layout.separatorcols; if (dim_count == 1) { vSeparatorCols = false; } - const vFontFamily = layout.FontFamily; let lastrow = 0; const ConceptMatrix = new Array(); let ConceptMatrixRowElem = new Array(); @@ -98,18 +32,6 @@ function initialize ({ $element, layout, component }) { const ConceptMatrixColElemTable = new Array(); const ConceptMatrixPivot = new Array(); let ConceptMatrixFirstClean = new Array(); - let vLetterSize = 0; - switch (layout.lettersize) { - case 1: - vLetterSize = -2; - break; - case 2: - vLetterSize = -1; - break; - case 3: - vLetterSize = 2; - break; - } const nRows = component.backendApi.getRowCount(); const dimensionInfos = component.backendApi.getDimensionInfos(); @@ -134,20 +56,6 @@ function initialize ({ $element, layout, component }) { MeasuresFormat.push(mfor); - switch (mfor.substr(mfor.length - 1).toLowerCase()) { - case 'm': - vExtraLabel = ' (M)'; - ExtraLabelsArray.push(' (M)'); - break; - case 'k': - vExtraLabel = ' (k)'; - ExtraLabelsArray.push(' (k)'); - break; - default: - vExtraLabel = ''; - ExtraLabelsArray.push(''); - break; - } vNumMeasures++; }); @@ -203,9 +111,6 @@ function initialize ({ $element, layout, component }) { } } - SecondHeaderLength = SecondHeader.length; - vNumMeasures2 = vNumMeasures * SecondHeaderLength; - let ConceptPos = 0; let nMeas3 = 0; let vHeaderIndex = 0; @@ -224,75 +129,15 @@ function initialize ({ $element, layout, component }) { } } - if (nRows > (lastrow + 1) && nRows <= (vMaxLoops * 1000)) { - const requestPage = [ - { - qTop: lastrow + 1, - qLeft: 0, - qWidth: 10, // should be # of columns - qHeight: Math.min(1000, nRows - lastrow) - } - ]; - component.backendApi.getData(requestPage).then(() => { - component.paint($element); - }); - } - const properties = { - ArrayGetSelectedCount, - ConceptMatrix, ConceptMatrixColElem, ConceptMatrixColElemTable, - ConceptMatrixRowElem, - ConceptMatrixFirstClean, - ConceptMatrixPivot, - vHeaderColorText, - vFontFamily, - vHeaderColorSchema, - vExportToExcel, - vNumDims, - nMeasAux, - dimensionInfos, - vLetterSizeHeader, - vHeaderAlignText, - MeasuresFormat, - measure_count, - vExcelButtonCode, - sufixCells, - LabelsArray, - SecondHeader, - vSeparatorCols, - nSecond: SecondHeaderLength - 1, - nSecond2: SecondHeaderLength - 1, - vLetterSize, - ExtraLabelsArray, - dim_count, - vExtraLabel, - vNumMeasures, - vNumMeasures2, - lastrow, - measureInfos - }; - - // TODO: figure out a reasonable datastructure and use these for component - const transformedProperties = { - dimensions: [], - headers: LabelsArray, - headerOptions: { - colorSchema: vHeaderColorSchema, - textColor: vHeaderColorText - }, - options: { - fontFamily: vFontFamily - } + ConceptMatrixRowElem }; return { - colors, - dimensionInfos, - measureInfos, properties, - transformedProperties + ...transformedProperties }; } From 6233b9dbaee3682cd800267a489f54d23f38f354 Mon Sep 17 00:00:00 2001 From: Kristoffer Lind Date: Thu, 7 Feb 2019 10:20:19 +0100 Subject: [PATCH 2/4] refactor header-wrapper according to new data structure --- src/header-wrapper.jsx | 360 ------------------ src/headers-table/column-header.jsx | 40 ++ src/headers-table/export-column-header.jsx | 40 ++ src/headers-table/index.jsx | 127 ++++++ .../measurement-column-header.jsx | 77 ++++ 5 files changed, 284 insertions(+), 360 deletions(-) delete mode 100644 src/header-wrapper.jsx create mode 100644 src/headers-table/column-header.jsx create mode 100644 src/headers-table/export-column-header.jsx create mode 100644 src/headers-table/index.jsx create mode 100644 src/headers-table/measurement-column-header.jsx diff --git a/src/header-wrapper.jsx b/src/header-wrapper.jsx deleted file mode 100644 index 0b67b52..0000000 --- a/src/header-wrapper.jsx +++ /dev/null @@ -1,360 +0,0 @@ -import React, { PureComponent, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import ExportButton from './export-button.jsx'; - -/* TODO: the different render methods are very similar, split it into a few components - and try to get rid of some duplication */ -class HeaderWrapper extends PureComponent { - getBaseCSS () { - const { - vHeaderColorText, - vFontFamily, - vHeaderColorSchema, - vHeaderAlignText - } = this.props; - - const baseCSS = { - backgroundColor: vHeaderColorSchema, - color: vHeaderColorText, - fontFamily: vFontFamily, - textAlign: vHeaderAlignText - }; - return baseCSS; - } - - renderSecondDimensionTitles () { - const { - vFontFamily, - vLetterSizeHeader, - vExportToExcel, - sufixCells, - vNumDims, - measure_count, - LabelsArray, - SecondHeader, - vSeparatorCols, - nSecond, - vLetterSize, - ExtraLabelsArray - } = this.props; - - const baseCSS = this.getBaseCSS(); - - if (vNumDims === 2) { - if (measure_count > 1) { - const thStyle = { - ...baseCSS, - cursor: 'default', - fontSize: `${16 + vLetterSizeHeader} px`, - height: '80px', - verticalAlign: 'middle', - width: '230px' - }; - return ( - - - - {LabelsArray[0]} - - {SecondHeader.map((header, index) => { - const emptyStyle = { - color: 'white', - fontFamily: vFontFamily, - fontSize: `${13 + vLetterSizeHeader} px` - }; - const style = { - ...baseCSS, - fontSize: `${14 + vLetterSizeHeader} px`, - height: '45px', - verticalAlign: 'middle' - }; - return ( - - {vSeparatorCols && index > 0 && ( - - * - - )} - - {header} - - - ); - })} - - ); - } - const fDimCellsStyle = { - ...baseCSS, - cursor: 'default', - fontSize: `${16 + vLetterSizeHeader} px`, - height: '70px', - verticalAlign: 'middle', - width: '230px' - }; - - return ( - - - - {LabelsArray[0] + ExtraLabelsArray[0]} - - {SecondHeader.map((entry, entryIndex) => { - // TODO: seperator element is reused a bunch, only difference being font-size - const hasSeperator = vSeparatorCols && nSecond > 0; - const seperatorStyle = { - color: 'white', - fontFamily: vFontFamily, - fontSize: `${12 + vLetterSize} px` - }; - const seperatorElement = ( - - * - - ); - let sufixWrap = ''; - if ((entry.length > 11 && vLetterSizeHeader === 0) || (entry.length > 12 && vLetterSizeHeader === -2)) { - sufixWrap = '70'; - } else { - sufixWrap = 'Empty'; - } - const gridCells2Style = { - ...baseCSS, - fontSize: `${14 + vLetterSizeHeader} px`, - height: '70px', - verticalAlign: 'middle' - }; - const wrapStyle = { - fontFamily: vFontFamily - }; - return ( - - {hasSeperator && seperatorElement} - - - {entry} - - - - ); - })} - - ); - } - } - - renderSecondDimensionSubTitles () { - const { - vFontFamily, - vLetterSizeHeader, - sufixCells, - LabelsArray, - SecondHeader, - vSeparatorCols, - vLetterSize, - MeasuresFormat, - ExtraLabelsArray - } = this.props; - - const baseCSS = this.getBaseCSS(); - - return SecondHeader.map((header, index) => { - const emptyStyle = { - color: 'white', - fontFamily: vFontFamily, - fontSize: `${12 + vLetterSizeHeader} px` - }; - return ( - - {vSeparatorCols && index > 0 && ( - - * - - )} - {MeasuresFormat.map((measureFormat, measureFormatIndex) => { - if (measureFormat.substring(measureFormat.length - 1) === '%') { - const cells2SmallStyle = { - ...baseCSS, - cursor: 'default', - fontSize: `${13 + vLetterSizeHeader} px`, - height: '25px', - verticalAlign: 'middle' - }; - return ( - - - {LabelsArray[measureFormatIndex + 1]} - {ExtraLabelsArray[measureFormatIndex]} - - - ); - } - const cells2Style = { - ...baseCSS, - cursor: 'default', - fontSize: `${14 + vLetterSizeHeader} px`, - height: '25px', - verticalAlign: 'middle' - }; - return ( - - - {LabelsArray[measureFormatIndex + 1]} - {ExtraLabelsArray[measureFormatIndex]} - - - ); - })} - - ); - }); - } - - renderMeasureInfos () { - const { - vFontFamily, - vLetterSizeHeader, - dim_count, - vExtraLabel, - sufixCells, - measureInfos - } = this.props; - - const baseCSS = this.getBaseCSS(); - - if (dim_count === 1) { - return measureInfos.map((measureInfo, measureInfoIndex) => { - let sufixWrap = ''; - if (((measureInfo.qFallbackTitle + vExtraLabel).length > 11 && vLetterSizeHeader === 0) - || ((measureInfo.qFallbackTitle + vExtraLabel).length > 12 && vLetterSizeHeader === -2)) { - sufixWrap = '70'; - } else { - sufixWrap = 'Empty'; - } - const thStyle = { - ...baseCSS, - cursor: 'default', - fontSize: `${15 + vLetterSizeHeader} px`, - height: '70px', - verticalAlign: 'middle' - }; - return ( - - - {measureInfo.qFallbackTitle + vExtraLabel} - - - ); - }); - } - return null; - } - - renderDimensionInfos () { - const { - dimensionInfos, - vLetterSizeHeader, - vExportToExcel - } = this.props; - - const baseCSS = this.getBaseCSS(); - - return dimensionInfos.map((dimensionInfo, dimensionInfoIndex) => { - // TODO: move static properties to css file - const style = { - ...baseCSS, - cursor: 'default', - fontSize: `${17 + vLetterSizeHeader} px`, - height: '70px', - verticalAlign: 'middle', - width: '230px' - }; - - return ( - - - {dimensionInfo.qFallbackTitle} - - ); - }); - } - - render () { - const { vNumDims, measure_count } = this.props; - return ( -
- - - - {this.renderDimensionInfos()} - {this.renderMeasureInfos()} - {this.renderSecondDimensionTitles()} - - { vNumDims === 2 && measure_count > 1 && ( - - {this.renderSecondDimensionSubTitles()} - - )} - -
-
- ); - } -} - -// TODO: make any, object and array forbidden -HeaderWrapper.propTypes = { - vHeaderColorText: PropTypes.any, - vFontFamily: PropTypes.any, - vHeaderColorSchema: PropTypes.any, - vExportToExcel: PropTypes.any, - vNumDims: PropTypes.any, - dimensionInfos: PropTypes.any, - vLetterSizeHeader: PropTypes.any, - vHeaderAlignText: PropTypes.any, - MeasuresFormat: PropTypes.any, - measure_count: PropTypes.any, - sufixCells: PropTypes.any, - LabelsArray: PropTypes.any, - SecondHeader: PropTypes.any, - vSeparatorCols: PropTypes.any, - nSecond: PropTypes.any, - vLetterSize: PropTypes.any, - ExtraLabelsArray: PropTypes.any, - dim_count: PropTypes.any, - vExtraLabel: PropTypes.any, - measureInfos: PropTypes.any -}; - -export default HeaderWrapper; diff --git a/src/headers-table/column-header.jsx b/src/headers-table/column-header.jsx new file mode 100644 index 0000000..3c9f251 --- /dev/null +++ b/src/headers-table/column-header.jsx @@ -0,0 +1,40 @@ +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' + }; + + return ( + + {title} + + ); +}; + +ColumnHeader.defaultProps = { + cellSuffix: '', + colSpan: 1 +}; + +ColumnHeader.propTypes = { + baseCSS: PropTypes.shape({}).isRequired, + cellSuffix: PropTypes.string, + colSpan: PropTypes.number, + styling: PropTypes.shape({ + headerOptions: PropTypes.shape({ + fontSizeAdjustment: PropTypes.number.isRequired + }).isRequired + }).isRequired, + title: PropTypes.string.isRequired +}; + +export default ColumnHeader; diff --git a/src/headers-table/export-column-header.jsx b/src/headers-table/export-column-header.jsx new file mode 100644 index 0000000..640e89e --- /dev/null +++ b/src/headers-table/export-column-header.jsx @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ExportButton from '../export-button.jsx'; + +const ExportColumnHeader = ({ baseCSS, title, allowExcelExport, hasSecondDimension, styling }) => { + const rowSpan = hasSecondDimension ? 2 : 1; + const style = { + ...baseCSS, + cursor: 'default', + fontSize: `${16 + styling.headerOptions.fontSizeAdjustment} px`, + height: '80px', + verticalAlign: 'middle', + width: '230px' + }; + + return ( + + + {title} + + ); +}; + +ExportColumnHeader.propTypes = { + allowExcelExport: PropTypes.bool.isRequired, + baseCSS: PropTypes.shape({}).isRequired, + hasSecondDimension: PropTypes.bool.isRequired, + styling: PropTypes.shape({ + headerOptions: PropTypes.shape({ + fontSizeAdjustment: PropTypes.number.isRequired + }).isRequired + }).isRequired, + title: PropTypes.string.isRequired +}; + +export default ExportColumnHeader; diff --git a/src/headers-table/index.jsx b/src/headers-table/index.jsx new file mode 100644 index 0000000..16ace73 --- /dev/null +++ b/src/headers-table/index.jsx @@ -0,0 +1,127 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ExportColumnHeader from './export-column-header.jsx'; +import ColumnHeader from './column-header.jsx'; +import MeasurementColumnHeader from './measurement-column-header.jsx'; +import { injectSeparators } from '../utilities'; + +const HeadersTable = ({ data, general, styling }) => { + const baseCSS = { + backgroundColor: styling.headerOptions.colorSchema, + color: styling.headerOptions.textColor, + fontFamily: styling.options.fontFamily, + textAlign: styling.headerOptions.alignment + }; + + const { + dimension1, + dimension2, + measurements + } = data.headers; + + const hasSecondDimension = Boolean(dimension2); + + return ( +
+ + + + + {!hasSecondDimension && injectSeparators(measurements, styling.useSeparatorColumns) + .map(measurementEntry => ( + + )) + } + {hasSecondDimension && injectSeparators(dimension2, styling.useSeparatorColumns).map(entry => { + if (entry.isSeparator) { + const separatorStyle = { + color: 'white', + fontFamily: styling.options.fontFamily, + fontSize: `${13 + styling.headerOptions.fontSizeAdjustment}px` + }; + + return ( + + ); + } + return ( + + ); + })} + + {hasSecondDimension && ( + + {injectSeparators(dimension2, styling.useSeparatorColumns).map(dimensionEntry => { + if (dimensionEntry.isSeparator) { + const separatorStyle = { + color: 'white', + fontFamily: styling.options.fontFamily, + fontSize: `${12 + styling.headerOptions.fontSizeAdjustment}px` + }; + + return ( + + ); + } + return measurements.map(measurementEntry => ( + + )); + })} + + )} + +
+ * +
+ * +
+
+ ); +}; + +HeadersTable.propTypes = { + data: PropTypes.shape({ + headers: PropTypes.shape({ + dimension1: PropTypes.array, + dimension2: PropTypes.array, + measurements: PropTypes.array + }) + }).isRequired, + general: PropTypes.shape({}).isRequired, + styling: PropTypes.shape({ + headerOptions: PropTypes.shape({}), + options: PropTypes.shape({}) + }).isRequired +}; + +export default HeadersTable; diff --git a/src/headers-table/measurement-column-header.jsx b/src/headers-table/measurement-column-header.jsx new file mode 100644 index 0000000..4cf4718 --- /dev/null +++ b/src/headers-table/measurement-column-header.jsx @@ -0,0 +1,77 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measurement, styling }) => { + const title = `${measurement.name} ${measurement.magnitudeLabelSuffix}`; + const { fontSizeAdjustment } = styling.headerOptions; + if (hasSecondDimension) { + const isPercentageFormat = measurement.format.substring(measurement.format.length - 1) === '%'; + let baseFontSize = 14; + let cellClass = 'grid-cells2'; + if (isPercentageFormat) { + baseFontSize = 13; + cellClass = 'grid-cells2-small'; + } + const cellStyle = { + ...baseCSS, + cursor: 'default', + fontSize: `${baseFontSize + fontSizeAdjustment} px`, + height: '25px', + verticalAlign: 'middle' + }; + return ( + + + {title} + + + ); + } + 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: '70px', + verticalAlign: 'middle' + }; + return ( + + + {title} + + + ); +}; + +MeasurementColumnHeader.defaultProps = { + hasSecondDimension: false +}; + +MeasurementColumnHeader.propTypes = { + baseCSS: PropTypes.shape({}).isRequired, + general: PropTypes.shape({ + cellSuffix: PropTypes.string.isRequired + }).isRequired, + hasSecondDimension: PropTypes.bool, + measurement: PropTypes.shape({ + name: PropTypes.string.isRequired + }).isRequired, + styling: PropTypes.shape({ + headerOptions: PropTypes.shape({ + fontSizeAdjustment: PropTypes.number.isRequired + }).isRequired + }).isRequired +}; + +export default MeasurementColumnHeader; From b979a579f9d468c40722bb28a137b58dfe71212e Mon Sep 17 00:00:00 2001 From: Kristoffer Lind Date: Thu, 7 Feb 2019 10:20:37 +0100 Subject: [PATCH 3/4] refactor row-wrapper according to new data structure --- src/data-table/data-cell.jsx | 132 ++++++++++++++++++ src/data-table/header-padding.jsx | 28 ++++ src/data-table/index.jsx | 106 ++++++++++++++ src/else-dimension-measures.jsx | 222 ------------------------------ src/index.js | 2 +- src/initialize-transformed.js | 2 - src/{paint.js => paint.jsx} | 80 +++++------ src/row-list.jsx | 81 ----------- src/row-wrapper.jsx | 129 ----------------- src/single-dimension-measures.jsx | 159 --------------------- src/style-builder.js | 51 ++++--- src/utilities.js | 29 ++++ 12 files changed, 356 insertions(+), 665 deletions(-) create mode 100644 src/data-table/data-cell.jsx create mode 100644 src/data-table/header-padding.jsx create mode 100644 src/data-table/index.jsx delete mode 100644 src/else-dimension-measures.jsx rename src/{paint.js => paint.jsx} (80%) delete mode 100644 src/row-list.jsx delete mode 100644 src/row-wrapper.jsx delete mode 100644 src/single-dimension-measures.jsx diff --git a/src/data-table/data-cell.jsx b/src/data-table/data-cell.jsx new file mode 100644 index 0000000..7178553 --- /dev/null +++ b/src/data-table/data-cell.jsx @@ -0,0 +1,132 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ApplyPreMask } from '../masking'; +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) === '%'; + let formattedMeasurementValue = ''; + if (isColumnPercentageBased) { + if (isNaN(measurement.value)) { + formattedMeasurementValue = styling.symbolForNulls; + } else { + formattedMeasurementValue = ApplyPreMask('0,00%', measurement.value); + } + } 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 formattedMeasurementValue; +} + +function getSemaphoreColors (measurement, semaphoreColors) { + if (measurement < semaphoreColors.status.critical) { + return semaphoreColors.statusColors.critical; + } + if (measurement < semaphoreColors.status.medium) { + return semaphoreColors.statusColors.medium; + } + 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 = '.'; + } + + let cellStyle = { + fontFamily: styling.options.fontFamily, + ...styleBuilder.getStyle(), + paddingRight: '4px', + textAlign: 'right' + + }; + 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, + 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'; + } + + return ( + + {formattedMeasurementValue} + + ); +}; + +DataCell.propTypes = { + data: PropTypes.shape({ + headers: PropTypes.shape({ + measurements: PropTypes.array.isRequired + }).isRequired + }).isRequired, + general: PropTypes.shape({ + cellSuffix: PropTypes.string.isRequired + }).isRequired, + measurement: PropTypes.shape({ + format: PropTypes.string, + name: PropTypes.string, + value: PropTypes.any + }).isRequired, + styleBuilder: PropTypes.shape({ + hasComments: PropTypes.func.isRequired + }).isRequired, + styling: PropTypes.shape({ + symbolForNulls: PropTypes.any.isRequired + }).isRequired +}; + +export default DataCell; diff --git a/src/data-table/header-padding.jsx b/src/data-table/header-padding.jsx new file mode 100644 index 0000000..45daea5 --- /dev/null +++ b/src/data-table/header-padding.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const HeaderPadding = ({ styleBuilder, styling }) => { + if (styling.usePadding && !styleBuilder.hasCustomFileStyle()) { + const paddingStyle = { + fontFamily: styling.options.fontFamily, + marginLeft: '15px' + }; + return ( + + ); + } + return null; +}; + +HeaderPadding.propTypes = { + styleBuilder: PropTypes.shape({ + hasCustomFileStyle: PropTypes.func.isRequired + }).isRequired, + styling: PropTypes.shape({ + options: PropTypes.shape({ + fontFamily: PropTypes.string.isRequired + }).isRequired + }).isRequired +}; + +export default HeaderPadding; diff --git a/src/data-table/index.jsx b/src/data-table/index.jsx new file mode 100644 index 0000000..afe4aa5 --- /dev/null +++ b/src/data-table/index.jsx @@ -0,0 +1,106 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import StyleBuilder from '../style-builder'; +import DataCell from './data-cell.jsx'; +import HeaderPadding from './header-padding.jsx'; +import { injectSeparators } from '../utilities'; + +const DataTable = ({ data, general, styling }) => { + const { + headers: { + dimension1, + measurements + }, + matrix + } = data; + + return ( +
+ + {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 ( + + + {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` + }; + + return ( + + ); + } + return ( + + ); + })} + + ); + })} +
+ + {dimensionEntry.displayValue} + + * +
+
+ ); +}; + +DataTable.propTypes = { + data: PropTypes.shape({ + headers: PropTypes.shape({ + dimension1: PropTypes.array.isRequired + }).isRequired, + matrix: PropTypes.arrayOf(PropTypes.array.isRequired).isRequired + }).isRequired, + general: PropTypes.shape({}).isRequired, + styling: PropTypes.shape({ + hasCustomFileStyle: PropTypes.bool.isRequired + }).isRequired +}; + +export default DataTable; diff --git a/src/else-dimension-measures.jsx b/src/else-dimension-measures.jsx deleted file mode 100644 index f06c7dd..0000000 --- a/src/else-dimension-measures.jsx +++ /dev/null @@ -1,222 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { ApplyPreMask } from './masking'; -import { addSeparators } from './utilities'; - -class ElseDimensionMeasures extends React.PureComponent { - render () { - const { - vFontFamily, - vSeparatorCols, - measure_count, - sufixCells, - vSymbolForNulls, - vLetterSize, - vColorMetric1, - vColorMetric1Text, - vColorMetric2, - vColorMetric2Text, - vColorMetric3, - vColorMetric3Text, - vAllSemaphores, - ConceptMatrixPivot, - ConceptsAffectedMatrix, - vAllMetrics, - MetricsAffectedMatrix, - vCritic, - vMMedium, - vNumMeasures, - vNumMeasures2, - MeasuresFormat, - rowNumber, - columnText, - styleBuilder - } = this.props; - - // modified in here - let columnNumber, - vMaskNum, - vColorSemaphore, - vColorSemaphoreText, - vDivide; - - const measurementCells = []; - - var nMeasure7 = 0; - var nMeasure72 = -1; - var nMeasure72Semaphore = 0; - - for (var nMeasures22 = 1; nMeasures22 <= vNumMeasures2; nMeasures22++) { - nMeasure7++; - nMeasure72++; - if (columnText.substring(0, 1) === '%') { - columnNumber = ApplyPreMask('0,00%', ConceptMatrixPivot[rowNumber][nMeasures22]); - var vSpecialF = '0,00%'; - } else { - switch (MeasuresFormat[nMeasure72].substr(MeasuresFormat[nMeasure72].length - 1)) { - case 'k': - vDivide = 1000; - break; - - case 'K': - vDivide = 1000; - break; - - case 'm': - vDivide = 1000000; - break; - - case 'M': - vDivide = 1000000; - break; - - default: - vDivide = 1; - break; - } - var vSpecialF = MeasuresFormat[nMeasure72].replace(/k|K|m|M/gi, ''); - if (!isNaN(ConceptMatrixPivot[rowNumber][nMeasures22])) { - vMaskNum = ConceptMatrixPivot[rowNumber][nMeasures22]; - if (vSpecialF.substring(vSpecialF.length - 1) === '%') { - vMaskNum = vMaskNum * 100; - } - - switch (vSpecialF) { - case '#.##0': - columnNumber = addSeparators((vMaskNum / vDivide), '.', ',', 0); - break; - case '#,##0': - columnNumber = addSeparators((vMaskNum / vDivide), ',', '.', 0); - break; - default: - columnNumber = ApplyPreMask(vSpecialF, (vMaskNum / vDivide)); - break; - } - } else { - columnNumber = vSymbolForNulls; - } - } - - if (vSeparatorCols && nMeasure7 === (measure_count + 1)) { - const seperatorStyle = { - color: 'white', - fontFamily: vFontFamily, - fontSize: (12 + vLetterSize) + 'px' - }; - const seperatorElement = ( - - * - - ); - measurementCells.push(seperatorElement); - nMeasure7 = 1; - } - if (nMeasure72 === (measure_count - 1)) { - nMeasure72 = -1; - nMeasure72Semaphore = measure_count; - } else { - nMeasure72Semaphore = nMeasure72 + 1; - } - - // apply the semaphores where needed - if (styleBuilder.hasComments()) { - columnNumber = '.'; - } - - let cellElement; - if ((vAllSemaphores || ConceptsAffectedMatrix.indexOf(columnText) >= 0) && (vAllMetrics || MetricsAffectedMatrix.indexOf(nMeasure72Semaphore) >= 0) && !isNaN(ConceptMatrixPivot[rowNumber][nMeasures22]) && !styleBuilder.hasComments()) { - if (ConceptMatrixPivot[rowNumber][nMeasures22] < vCritic) { - vColorSemaphore = vColorMetric1; - vColorSemaphoreText = vColorMetric1Text; - } else { - if (ConceptMatrixPivot[rowNumber][nMeasures22] < vMMedium) { - vColorSemaphore = vColorMetric2; - vColorSemaphoreText = vColorMetric2Text; - } else { - vColorSemaphore = vColorMetric3; - vColorSemaphoreText = vColorMetric3Text; - } - } - - const cellStyle = { - fontFamily: vFontFamily, - fontSize: styleBuilder.getStyle().fontSize, - color: vColorSemaphoreText, - backgroundColor: vColorSemaphore, - textAlign: 'right', - paddingLeft: '4px' - }; - if (vSpecialF.substring(vSpecialF.length - 1) === '%' && vNumMeasures > 1) { - cellElement = ( - - {columnNumber} - - ); - } else { - cellElement = ( - - {columnNumber} - - ); - } - } else { - const cellStyle = { - fontFamily: vFontFamily, - ...styleBuilder.getStyle(), - textAlign: 'right', - paddingRight: '4px' - }; - if (vSpecialF.substring(vSpecialF.length - 1) === '%' && vNumMeasures > 1) { - cellElement = ( - - {columnNumber} - - ); - } else { - cellElement = ( - - {columnNumber} - - ); - } - } - measurementCells.push(cellElement); - } - - return ( - - {measurementCells} - - ); - } -} - -ElseDimensionMeasures.propTypes = { - vFontFamily: PropTypes.any, - vSeparatorCols: PropTypes.any, - measure_count: PropTypes.any, - sufixCells: PropTypes.any, - vSymbolForNulls: PropTypes.any, - vLetterSize: PropTypes.any, - vColorMetric1: PropTypes.any, - vColorMetric1Text: PropTypes.any, - vColorMetric2: PropTypes.any, - vColorMetric2Text: PropTypes.any, - vColorMetric3: PropTypes.any, - vColorMetric3Text: PropTypes.any, - vAllSemaphores: PropTypes.any, - ConceptMatrixPivot: PropTypes.any, - ConceptsAffectedMatrix: PropTypes.any, - vAllMetrics: PropTypes.any, - MetricsAffectedMatrix: PropTypes.any, - vCritic: PropTypes.any, - vMMedium: PropTypes.any, - vNumMeasures: PropTypes.any, - vNumMeasures2: PropTypes.any, - MeasuresFormat: PropTypes.any, - rowNumber: PropTypes.any, - columnText: PropTypes.any, - styleBuilder: PropTypes.any -}; - -export default ElseDimensionMeasures; diff --git a/src/index.js b/src/index.js index e9524b1..49b61ac 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ import '@babel/polyfill'; -import paint from './paint'; +import paint from './paint.jsx'; import definition from './definition'; import './main.less'; diff --git a/src/initialize-transformed.js b/src/initialize-transformed.js index 35fcd5a..b58f7ca 100644 --- a/src/initialize-transformed.js +++ b/src/initialize-transformed.js @@ -3,7 +3,6 @@ import { distinctArray } from './utilities'; // TODO: rename colors function initializeColors ({ layout }) { - console.log(layout); return { vColLibBlue: layout.collibblue, vColLibBlueP: layout.collibbluep, @@ -142,7 +141,6 @@ function generateDataSet (component, dimensionsInformation, measurementsInformat }); if (hasSecondDimension) { - // console.log(row[0]); const currentDim1Entry = row[0].qText; const isSameDimension1AsPrevious = currentDim1Entry === previousDim1Entry; if (isSameDimension1AsPrevious) { diff --git a/src/paint.js b/src/paint.jsx similarity index 80% rename from src/paint.js rename to src/paint.jsx index e26c71f..2ed949d 100644 --- a/src/paint.js +++ b/src/paint.jsx @@ -1,72 +1,64 @@ import $ from 'jquery'; import { enableExcelExport } from './excel-export'; -import HeaderWrapper from './header-wrapper.jsx'; -import RowWrapper, { prepareProps } from './row-wrapper.jsx'; import initializeStore from './store'; import React from 'react'; // import ReactDOM from 'react-dom'; import { renderToStaticMarkup } from 'react-dom/server'; +import HeadersTable from './headers-table/index.jsx'; +import DataTable from './data-table/index.jsx'; export default async function paint ($element, layout, component) { - const state = initializeStore({ + const state = await initializeStore({ $element, - layout, - component + component, + layout }); + const { - ArrayGetSelectedCount, - vNumDims, ConceptMatrixColElem, ConceptMatrixColElemTable, - ConceptMatrixRowElem, - vSeparatorCols + ConceptMatrixRowElem } = state.properties; - const rowWrapperProps = await prepareProps({ - state: { - layout, - colors: state.colors, - ...state.properties - } - }); + const { + data: { meta: { dimensionCount } }, + selection: { dimensionSelectionCounts }, + styling: { useSeparatorColumns } + } = state; const jsx = (
- -
- -
); // TODO: switch to render when jquery interaction stuff in renderData is gone - // ReactDOM.render(jsx, $element[0]); - const html = renderToStaticMarkup(jsx); - $element.html(html); + // ReactDOM.render(jsx, $element[0]); RenderData(); @@ -124,8 +116,8 @@ export default async function paint ($element, layout, component) { // 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 (vNumDims > 1 && indextd > 0) { - if (ArrayGetSelectedCount[1] > 0) { + 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'); @@ -136,7 +128,7 @@ export default async function paint ($element, layout, component) { $(this).toggleClass('selected'); } - if (indextd > 0 && ArrayGetSelectedCount[0] > 0) { + if (indextd > 0 && dimensionSelectionCounts[0] > 0) { const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem)); component.backendApi.selectValues(0, SelectA, true); $(this).toggleClass('selected'); @@ -155,13 +147,13 @@ export default async function paint ($element, layout, component) { let SelectCol = 0; - if (vNumDims > 1 && indextd > 0) { - if (ArrayGetSelectedCount[1] > 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 (vSeparatorCols) { + if (useSeparatorColumns) { SelectCol = ConceptMatrixColElem[(Math.round(indextd / 2) - 1)]; } else { SelectCol = ConceptMatrixColElem[(Math.round(indextd) - 1)]; @@ -180,7 +172,7 @@ export default async function paint ($element, layout, component) { let SelectRow = 0; SelectRow = ConceptMatrixRowElem[(indextr)]; - if (ArrayGetSelectedCount[0] > 0) { + if (dimensionSelectionCounts[0] > 0) { const SelectA = JSON.parse(JSON.stringify(ConceptMatrixRowElem)); component.backendApi.selectValues(0, SelectA, true); $(this).toggleClass('selected'); diff --git a/src/row-list.jsx b/src/row-list.jsx deleted file mode 100644 index 742fac1..0000000 --- a/src/row-list.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import StyleBuilder from './style-builder'; - -class RowList extends React.PureComponent { - generatePaddingTextElement (hasCustomFileStyle) { - const { vPadding, vFontFamily } = this.props; - if (vPadding && !hasCustomFileStyle) { - const paddingStyle = { - fontFamily: vFontFamily, - marginLeft: '15px' - }; - return ( - - ); - } - return null; - } - - render () { - const { - vLetterSize, - vCustomFileBool, - vFontFamily, - tableData, - MeasurementsComponent - } = this.props; - - return ( - - {tableData.map((row, rowNumber) => { - const rowHeaderText = row[0] || ''; - if (rowHeaderText === '-') { - return null; - } - const styleBuilder = new StyleBuilder(this.props); - if (vCustomFileBool) { - styleBuilder.parseCustomFileStyle(rowHeaderText); - } else { - styleBuilder.applyStandardAttributes(rowNumber); - styleBuilder.applyCustomStyle({ fontSize: (14 + vLetterSize) + 'px' }); - } - - const rowStyle = { - fontFamily: vFontFamily, - width: '230px', - ...styleBuilder.getStyle() - }; - const paddingTextElement = this.generatePaddingTextElement(styleBuilder.hasCustomFileStyle()); - const measurementsProps = { - rowHeaderText, - rowNumber, - styleBuilder - }; - return ( - - - {paddingTextElement} - {rowHeaderText} - - - - ); - })} - - ); - } -} - -RowList.propTypes = { - tableData: PropTypes.array.isRequired -}; - -export default RowList; diff --git a/src/row-wrapper.jsx b/src/row-wrapper.jsx deleted file mode 100644 index 985bd07..0000000 --- a/src/row-wrapper.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import $ from 'jquery'; -import PropTypes from 'prop-types'; -import React from 'react'; - -import ElseDimensionMeasures from './else-dimension-measures.jsx'; -import RowList from './row-list.jsx'; -import SingleDimensionMeasures from './single-dimension-measures.jsx'; - -const RowWrapper = props => { - const { - ConceptMatrix, - ConceptMatrixPivot, - vNumDims - } = props; - let MeasurementsComponent, - tableData; - if (vNumDims === 1) { - tableData = ConceptMatrix; - MeasurementsComponent = SingleDimensionMeasures; - } else { - tableData = ConceptMatrixPivot.filter(array => array.length); - MeasurementsComponent = ElseDimensionMeasures; - } - - return ( -
- - -
-
- ); -}; - -RowWrapper.propTypes = { - ConceptMatrix: PropTypes.array.isRequired, - ConceptMatrixPivot: PropTypes.array.isRequired -}; - -export default RowWrapper; - -export async function prepareProps ({ state }) { - const { colors, layout, vAllSemaphores, vDynamicColorBody, vDynamicColorBodyP } = state; - const props = { - colors, - vCustomFileBool: layout.customfilebool, - vCustomFile: layout.customfile, - vPadding: layout.indentbool, - vPaddingText: '', - vGlobalComas: 0, - vGlobalComas2: 0, - vGlobalComment: 0, - vGlobalCommentColor: '', - vGlobalFontSize: 0, - vComas: 0, - vMedium: false, - vFontSize: '', - vColorText: layout.BodyTextColorSchema, - vDivide: 1, - vSymbolForNulls: layout.symbolfornulls, - vDynamicColorBody: 'vColLib' + layout.ColorSchema, - vDynamicColorBodyP: 'vColLib' + layout.ColorSchema + 'P', - vAllMetrics: layout.allmetrics, - MetricsAffectedMatrix: JSON.parse('[' + layout.metricssemaphore + ']'), - vColorMetric1: layout.colorstatus1.color, - vColorMetric2: layout.colorstatus2.color, - vColorMetric3: layout.colorstatus3.color, - vColorMetric1Text: layout.colorstatus1text.color, - vColorMetric2Text: layout.colorstatus2text.color, - vColorMetric3Text: layout.colorstatus3text.color, - vColorSemaphore: '', - vColorSemaphoreText: '', - vCritic: layout.metricsstatus1, - vMMedium: layout.metricsstatus2, - CustomArray: new Array(), - CustomArrayBasic: new Array(), - vNumCustomHeaders: 0, - vColumnText: '', - vColumnNum: '', - vMaskNum: 0, - StyleTags: '', - vColorSchema: colors[vDynamicColorBody], - vColorSchemaP: colors[vDynamicColorBodyP], - vAllSemaphores: layout.allsemaphores, - ConceptsAffectedMatrix: new Array(10) - }; - if (vAllSemaphores == false) { - props.ConceptsAffectedMatrix[0] = layout.conceptsemaphore1; - props.ConceptsAffectedMatrix[1] = layout.conceptsemaphore2; - props.ConceptsAffectedMatrix[2] = layout.conceptsemaphore3; - props.ConceptsAffectedMatrix[3] = layout.conceptsemaphore4; - props.ConceptsAffectedMatrix[4] = layout.conceptsemaphore5; - props.ConceptsAffectedMatrix[5] = layout.conceptsemaphore6; - props.ConceptsAffectedMatrix[6] = layout.conceptsemaphore7; - props.ConceptsAffectedMatrix[7] = layout.conceptsemaphore8; - props.ConceptsAffectedMatrix[8] = layout.conceptsemaphore9; - props.ConceptsAffectedMatrix[9] = layout.conceptsemaphore10; - } - - function ReadCustomSchema () { - var Url = '/Extensions/qlik-smart-pivot/' + props.vCustomFile; - return $.get(Url).then(function (response) { - var allTextLines = response.split(/\r\n|\n/); - var headers = allTextLines[0].split(';'); - props.vNumCustomHeaders = headers.length; - - for (var i = 0; i < allTextLines.length; i++) { - props.CustomArray[i] = new Array(headers.length); - var data = allTextLines[i].split(';'); - - if (data.length == headers.length) { - for (var j = 0; j < headers.length; j++) { - props.CustomArrayBasic[i] = data[0]; - props.CustomArray[i][j] = data[j]; - } - } - } - }); - } - - const hasCustomSchema = (props.vCustomFileBool && props.vCustomFile.length > 4); - const schemaPromise = hasCustomSchema ? ReadCustomSchema() : Promise.resolve(); - await schemaPromise; - - return props; -} diff --git a/src/single-dimension-measures.jsx b/src/single-dimension-measures.jsx deleted file mode 100644 index f50eb47..0000000 --- a/src/single-dimension-measures.jsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { ApplyPreMask } from './masking'; -import { addSeparators } from './utilities'; - -class SingleDimensionMeasures extends React.PureComponent { - render () { - const { - vFontFamily, - vSymbolForNulls, - vColorMetric1, - vColorMetric1Text, - vColorMetric2, - vColorMetric2Text, - vColorMetric3, - vColorMetric3Text, - ConceptMatrix, - vAllSemaphores, - ConceptsAffectedMatrix, - vAllMetrics, - MetricsAffectedMatrix, - vCritic, - vMMedium, - vNumMeasures, - MeasuresFormat, - rowNumber, - columnText, - styleBuilder - } = this.props; - - // modified in here - let vColumnNum, - vMaskNum, - vColorSemaphore, - vColorSemaphoreText, - vDivide; - - const measurementCells = []; - - // TODO: map ConceptMatrix[rowNumber] into cells - for (var nMeasures2 = 1; nMeasures2 <= vNumMeasures; nMeasures2++) { - var vSpecialF = MeasuresFormat[nMeasures2 - 1].replace(/k|K|m|M/gi, ''); - if (columnText.substring(0, 1) == '%') { - vColumnNum = ApplyPreMask('0,00%', ConceptMatrix[rowNumber][nMeasures2]); - vSpecialF = '0,00%'; - } else { - const magnitude = MeasuresFormat[nMeasures2 - 1].substr(MeasuresFormat[nMeasures2 - 1].length - 1); - switch (magnitude.toLowerCase()) { - case 'k': - vDivide = 1000; - break; - - case 'm': - vDivide = 1000000; - break; - - default: - vDivide = 1; - break; - } - if (!isNaN(ConceptMatrix[rowNumber][nMeasures2])) { - vMaskNum = ConceptMatrix[rowNumber][nMeasures2]; - if (vSpecialF.substring(vSpecialF.length - 1) == '%') { - vMaskNum = vMaskNum * 100; - } - switch (vSpecialF) { - case '#.##0': - vColumnNum = addSeparators((vMaskNum / vDivide), '.', ',', 0); - break; - - case '#,##0': - vColumnNum = addSeparators((vMaskNum / vDivide), ',', '.', 0); - break; - - default: - vColumnNum = ApplyPreMask(vSpecialF, (vMaskNum / vDivide)); - break; - } - } else { - vColumnNum = vSymbolForNulls; - } - } - if (styleBuilder.hasComments()) { - vColumnNum = '.'; - } - // apply the semaphore styles where needed - let cellStyle; - if ((vAllSemaphores || ConceptsAffectedMatrix.indexOf(columnText) >= 0) && (vAllMetrics || MetricsAffectedMatrix.indexOf(nMeasures2) >= 0) && !isNaN(ConceptMatrix[rowNumber][nMeasures2]) && !styleBuilder.hasComments()) { - if (ConceptMatrix[rowNumber][nMeasures2] < vCritic) { - vColorSemaphore = vColorMetric1; - vColorSemaphoreText = vColorMetric1Text; - } else { - if (ConceptMatrix[rowNumber][nMeasures2] < vMMedium) { - vColorSemaphore = vColorMetric2; - vColorSemaphoreText = vColorMetric2Text; - } else { - vColorSemaphore = vColorMetric3; - vColorSemaphoreText = vColorMetric3Text; - } - } - - cellStyle = { - fontFamily: vFontFamily, - fontSize: styleBuilder.getStyle().fontSize, - color: vColorSemaphoreText, - backgroundColor: vColorSemaphore, - textAlign: 'right', - paddingLeft: '4px' - }; - } else { - cellStyle = { - fontFamily: vFontFamily, - textAlign: 'right', - paddingLeft: '4px', - ...styleBuilder.getStyle() - }; - } - - const measurementCell = ( - - {vColumnNum} - - ); - - measurementCells.push(measurementCell); - } - - return ( - - {measurementCells} - - ); - } -} - -SingleDimensionMeasures.propTypes = { - vFontFamily: PropTypes.any, - vSymbolForNulls: PropTypes.any, - vColorMetric1: PropTypes.any, - vColorMetric1Text: PropTypes.any, - vColorMetric2: PropTypes.any, - vColorMetric2Text: PropTypes.any, - vColorMetric3: PropTypes.any, - vColorMetric3Text: PropTypes.any, - ConceptMatrix: PropTypes.any, - vAllSemaphores: PropTypes.any, - ConceptsAffectedMatrix: PropTypes.any, - vAllMetrics: PropTypes.any, - MetricsAffectedMatrix: PropTypes.any, - vCritic: PropTypes.any, - vMMedium: PropTypes.any, - vNumMeasures: PropTypes.any, - MeasuresFormat: PropTypes.any, - rowNumber: PropTypes.any, - columnText: PropTypes.any, - styleBuilder: PropTypes.any -}; - -export default SingleDimensionMeasures; diff --git a/src/style-builder.js b/src/style-builder.js index 391d5d0..72ecefb 100644 --- a/src/style-builder.js +++ b/src/style-builder.js @@ -1,16 +1,11 @@ -function StyleBuilder (state) { +function StyleBuilder (styling) { const { - CustomArray, - CustomArrayBasic, - vNumCustomHeaders, - vColorSchema, - vColorText, - vColorSchemaP, - vLetterSize, - colors - } = state; + colors, + customCSV, + options + } = styling; let style = { - fontSize: `${14 + vLetterSize}px` + fontSize: `${14 + options.fontSizeAdjustment}px` }; let hasComments = false; let commentColor; @@ -18,9 +13,9 @@ function StyleBuilder (state) { function applyStandardAttributes (rowNumber) { const isEven = rowNumber % 2 === 0; - style.backgroundColor = isEven ? vColorSchema : vColorSchemaP; - style.color = vColorText; - style.fontSize = `${14 + vLetterSize}px`; + style.backgroundColor = isEven ? options.backgroundColor : options.backgroundColorOdd; + style.color = options.color; + style.fontSize = `${13 + options.fontSizeAdjustment}px`; } function applyColor (color) { @@ -28,6 +23,7 @@ function StyleBuilder (state) { commentColor = color; } + /* eslint-disable sort-keys*/ const properties = { '': () => { hasComments = true; }, // text @@ -46,17 +42,18 @@ function StyleBuilder (state) { // font color TODO: this is a color just like the others, but it applies to text instead.. any way to make it less weird? '': () => { style.color = 'white'; }, // font size - '': () => { style.fontSize = `${15 + vLetterSize}px`; }, - '': () => { style.fontSize = `${14 + vLetterSize}px`; }, - '': () => { style.fontSize = `${13 + vLetterSize}px`; }, + '': () => { style.fontSize = `${15 + options.fontSizeAdjustment}px`; }, + '': () => { style.fontSize = `${14 + options.fontSizeAdjustment}px`; }, + '': () => { style.fontSize = `${13 + options.fontSizeAdjustment}px`; }, // text alignment '
': () => { style.textAlign = 'center'; } }; + /* eslint-enable sort-keys */ // TODO: need to improve this, it has way too many false positives function isCSSColor (property) { - const isHexColor = property.substring(0, 1) == '#'; - const isRGBColor = property.substring(0, 3).toUpperCase() == 'RGB'; + const isHexColor = property.substring(0, 1) === '#'; + const isRGBColor = property.substring(0, 3).toUpperCase() === 'RGB'; return isHexColor || isRGBColor; } @@ -84,27 +81,27 @@ function StyleBuilder (state) { } function parseCustomFileStyle (columnText) { - hasCustomFileStyle = true; - for (let csvAttribute = 1; csvAttribute < vNumCustomHeaders; csvAttribute += 1) { + for (let csvAttribute = 1; csvAttribute < customCSV.count; csvAttribute += 1) { let customAttribute = ''; - if (CustomArrayBasic.indexOf(columnText) < 0) { + if (customCSV.basic.indexOf(columnText) < 0) { customAttribute = 'none'; } else { - customAttribute = CustomArray[CustomArrayBasic.indexOf(columnText)][csvAttribute]; + hasCustomFileStyle = true; + customAttribute = customCSV.full[customCSV.basic.indexOf(columnText)][csvAttribute]; } applyProperty(customAttribute); } } return { + applyCustomStyle, + applyProperty, + applyStandardAttributes, getCommentColor: () => commentColor, getStyle: () => style, + hasComments: () => hasComments, hasCustomFileStyle: () => hasCustomFileStyle, hasFontSize: () => Boolean(style.fontSize), - hasComments: () => hasComments, - applyStandardAttributes, - applyProperty, - applyCustomStyle, parseCustomFileStyle }; } diff --git a/src/utilities.js b/src/utilities.js index 0e47a6d..3ad9f54 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -2,6 +2,13 @@ export function onlyUnique (value, index, self) { return self.indexOf(value) === index; } +export function distinctArray (array) { + return array + .map(entry => JSON.stringify(entry)) + .filter(onlyUnique) + .map(entry => JSON.parse(entry)); +} + export function addSeparators (nStr, thousandsSep, decimalSep, numDecimals) { let x1; nStr = nStr.toFixed(numDecimals); @@ -21,3 +28,25 @@ export function Deferred () { this.reject = reject; }); } + +export function injectSeparators (array, shouldHaveSeparator, suppliedOptions) { + const defaultOptions = { + atEvery: 1, + separator: { isSeparator: true } + }; + const options = { + ...defaultOptions, + ...suppliedOptions + }; + + if (!shouldHaveSeparator) { + return array; + } + return array.reduce((result, entry, index) => { + result.push(entry); + if (index < array.length - 1 && (index + 1) % options.atEvery === 0) { + result.push(options.separator); + } + return result; + }, []); +} From c4a717bd77a8a5d29c4a43bca4b5a55f6f8099f7 Mon Sep 17 00:00:00 2001 From: Kristoffer Lind Date: Thu, 7 Feb 2019 11:12:49 +0100 Subject: [PATCH 4/4] fix single dimension custom style headers --- src/headers-table/index.jsx | 20 ++++++++++--------- .../measurement-column-header.jsx | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/headers-table/index.jsx b/src/headers-table/index.jsx index 16ace73..c45fa48 100644 --- a/src/headers-table/index.jsx +++ b/src/headers-table/index.jsx @@ -19,7 +19,7 @@ const HeadersTable = ({ data, general, styling }) => { measurements } = data.headers; - const hasSecondDimension = Boolean(dimension2); + const hasSecondDimension = dimension2.length > 0; return (
@@ -33,14 +33,16 @@ const HeadersTable = ({ data, general, styling }) => { styling={styling} title={dimension1[0].name} /> - {!hasSecondDimension && injectSeparators(measurements, styling.useSeparatorColumns) - .map(measurementEntry => ( - - )) - } + {!hasSecondDimension && measurements.map(measurementEntry => ( + + ))} {hasSecondDimension && injectSeparators(dimension2, styling.useSeparatorColumns).map(entry => { if (entry.isSeparator) { const separatorStyle = { diff --git a/src/headers-table/measurement-column-header.jsx b/src/headers-table/measurement-column-header.jsx index 4cf4718..3523913 100644 --- a/src/headers-table/measurement-column-header.jsx +++ b/src/headers-table/measurement-column-header.jsx @@ -45,7 +45,7 @@ const MeasurementColumnHeader = ({ baseCSS, general, hasSecondDimension, measure style={style} > {title}