diff --git a/src/client/components/facet_results/ApexCharts.js b/src/client/components/facet_results/ApexCharts.js index 77d8fe0f6c0bfcb0f3e6e9121bcf8557fdde3311..a1d388146ce4f79a52e7e53a449a5cb67cbf0362 100644 --- a/src/client/components/facet_results/ApexCharts.js +++ b/src/client/components/facet_results/ApexCharts.js @@ -33,16 +33,25 @@ class ApexChart extends React.Component { constructor (props) { super(props) this.chartRef = React.createRef() + const { resultClassConfig, apexChartsConfig } = this.props + let resultClass = this.props.resultClass + if (resultClassConfig.dropdownForResultClasses) { + resultClass = resultClassConfig.defaultResultClass + } this.state = { - resultClass: props.resultClass, - createChartData: props.createChartData, - chartType: props.dropdownForChartTypes ? props.chartTypes[0].id : null, + resultClass, + createChartData: resultClassConfig.createChartData + ? apexChartsConfig[resultClassConfig.createChartData] + : apexChartsConfig[resultClassConfig.chartTypes[0].createChartData], + chartType: resultClassConfig.dropdownForChartTypes ? resultClassConfig.chartTypes[0].id : null, dialogData: null } } componentDidMount = () => { - if (this.props.rawData && this.props.rawData.length > 0 && !this.props.doNotRenderOnMount) { + const { results } = this.props + const { doNotRenderOnMount } = this.props.resultClassConfig + if (results && results.length > 0 && !doNotRenderOnMount) { this.renderChart() } this.props.fetchData({ @@ -55,12 +64,11 @@ class ApexChart extends React.Component { } componentDidUpdate = (prevProps, prevState) => { - // Render the chart again if the raw data has changed - if (prevProps.rawDataUpdateID !== this.props.rawDataUpdateID) { + if (prevProps.resultUpdateID !== this.props.resultUpdateID) { this.renderChart() } - // check if filters have changed - if (this.props.pageType === 'facetResults' && prevProps.facetUpdateID !== this.props.facetUpdateID) { + const { pageType = 'facetResults' } = this.props + if (pageType === 'facetResults' && prevProps.facetUpdateID !== this.props.facetUpdateID) { this.props.fetchData({ perspectiveID: this.props.perspectiveConfig.id, resultClass: this.state.resultClass, @@ -102,24 +110,7 @@ class ApexChart extends React.Component { } this.chart = new ApexCharts( this.chartRef.current, - this.state.createChartData({ - rawData: this.props.rawData, - title: this.props.title, - xaxisTitle: this.props.xaxisTitle || intl.get(`apexCharts.${this.state.resultClass}Xaxis`), - yaxisTitle: this.props.yaxisTitle || '', - seriesTitle: this.props.seriesTitle || '', - xaxisType: this.props.xaxisType || null, - xaxisTickAmount: this.props.xaxisTickAmount || null, - xaxisLabels: this.props.xaxisLabels || null, - stroke: this.props.stroke || null, - fill: this.props.fill || null, - tooltip: this.props.tooltip || null, - fetchInstanceAnalysis: this.props.fetchInstanceAnalysis, - resultClass: this.props.resultClass, - facetID: this.props.facetID, - facetClass: this.props.facetClass, - screenSize: this.props.screenSize - }) + this.state.createChartData({ ...this.props }) ) this.chart.render() } @@ -128,10 +119,10 @@ class ApexChart extends React.Component { handleChartTypeOnChanhge = event => { const chartType = event.target.value - const chartTypeObj = this.props.chartTypes.find(chartTypeObj => chartTypeObj.id === chartType) + const chartTypeObj = this.props.resultClassConfig.chartTypes.find(chartTypeObj => chartTypeObj.id === chartType) this.setState({ chartType, - createChartData: chartTypeObj.createChartData + createChartData: this.props.apexChartsConfig[chartTypeObj.createChartData] }) } @@ -146,12 +137,12 @@ class ApexChart extends React.Component { if (this.isSmallScreen()) { return 'auto' } - const rootHeightReduction = this.props.layoutConfig.tabHeight + 2 * defaultPadding + 1 + const rootHeightReduction = this.props.portalConfig.layoutConfig.tabHeight + 2 * defaultPadding + 1 return `calc(100% - ${rootHeightReduction}px)` } getHeightForChartContainer = () => { - const { dropdownForResultClasses, dropdownForChartTypes } = this.props + const { dropdownForResultClasses, dropdownForChartTypes } = this.props.resultClassConfig if (this.isSmallScreen()) { return 600 } @@ -166,14 +157,8 @@ class ApexChart extends React.Component { } render () { - const { - fetching, pageType, classes, dropdownForResultClasses, - dropdownForChartTypes, facetResultsType - } = this.props - let facetResultsTypeCapitalized = '' - if (facetResultsType) { - facetResultsTypeCapitalized = facetResultsType[0].toUpperCase() + facetResultsType.substring(1).toLowerCase() - } + const { classes, fetching, resultClassConfig } = this.props + const { pageType = 'facetResults', dropdownForResultClasses, resultClasses, dropdownForChartTypes, chartTypes } = resultClassConfig let rootStyle = { width: '100%', height: '100%' @@ -199,9 +184,7 @@ class ApexChart extends React.Component { width: '100%', height: this.getHeightForChartContainer() } - let dropdownText = intl.get('apexCharts.by') === '' - ? intl.get('apexCharts.grouping') - : `${facetResultsTypeCapitalized} ${intl.get('apexCharts.by')}` + let dropdownText = intl.get('apexCharts.grouping') if (this.props.xaxisType === 'numeric') { dropdownText = intl.get('apexCharts.property') } @@ -216,7 +199,7 @@ class ApexChart extends React.Component { value={this.state.resultClass} onChange={this.handleResultClassOnChanhge} > - {this.props.resultClasses.map(resultClass => + {Object.keys(resultClasses).map(resultClass => <MenuItem key={resultClass} value={resultClass}>{intl.get(`apexCharts.resultClasses.${resultClass}`)}</MenuItem> )} </Select> @@ -231,7 +214,7 @@ class ApexChart extends React.Component { value={this.state.chartType} onChange={this.handleChartTypeOnChanhge} > - {this.props.chartTypes.map(chartType => + {chartTypes.map(chartType => <MenuItem key={chartType.id} value={chartType.id}>{intl.get(`apexCharts.${chartType.id}`)}</MenuItem> )} </Select> @@ -261,23 +244,9 @@ class ApexChart extends React.Component { } ApexChart.propTypes = { - pageType: PropTypes.string.isRequired, - createChartData: PropTypes.func, - rawData: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.object - ]), - rawDataUpdateID: PropTypes.number, fetchData: PropTypes.func.isRequired, - fetching: PropTypes.bool.isRequired, resultClass: PropTypes.string, - facetClass: PropTypes.string, - facetID: PropTypes.string, - uri: PropTypes.string, - dropdownForResultClasses: PropTypes.bool, - facetResultsType: PropTypes.string, - resultClasses: PropTypes.array, - layoutConfig: PropTypes.object.isRequired + facetClass: PropTypes.string } export const ApexChartComponent = ApexChart diff --git a/src/client/components/facet_results/ResultClassRoute.js b/src/client/components/facet_results/ResultClassRoute.js index 5189d9a9f1cd8985eecaf4e4b77b5a408dc567da..8b15782a7a74962ef1d40b2cef26894b62c440b4 100644 --- a/src/client/components/facet_results/ResultClassRoute.js +++ b/src/client/components/facet_results/ResultClassRoute.js @@ -247,65 +247,21 @@ const ResultClassRoute = props => { break } case 'ApexCharts': { - const { - pageType = 'facetResults', - title, - xaxisTitle, - xaxisType, - xaxisTickAmount, - yaxisTitle, - seriesTitle, - stroke, - fill, - createChartData, - doNotRenderOnMount = false, - dropdownForResultClasses = false, - dropdownForChartTypes = false - } = resultClassConfig const apexProps = { portalConfig, perspectiveConfig: perspective, - pageType, + resultClassConfig, + apexChartsConfig: props.apexChartsConfig, + screenSize, resultClass, facetClass, - rawData: perspectiveState.results, - rawDataUpdateID: perspectiveState.resultUpdateID, + perspectiveState, + results: perspectiveState.results, fetching: perspectiveState.fetching, - fetchData: props.fetchResults, - createChartData: props.apexChartsConfig[createChartData], - title, - xaxisTitle, - xaxisType, - xaxisTickAmount, - yaxisTitle, - seriesTitle, - stroke, - fill, - layoutConfig: props.layoutConfig, - doNotRenderOnMount, - dropdownForResultClasses - } - if (pageType === 'facetResults') { - apexProps.facetUpdateID = facetState.facetUpdateID - } - if (pageType === 'instancePage') { - apexProps.uri = perspectiveState.instanceTableData.id - } - if (dropdownForResultClasses && has(resultClassConfig, 'resultClasses')) { - apexProps.resultClass = resultClassConfig.resultClasses[0] - apexProps.resultClasses = resultClassConfig.resultClasses - apexProps.dropdownForResultClasses = true - } - if (dropdownForChartTypes && has(resultClassConfig, 'chartTypes')) { - const { chartTypes } = resultClassConfig - const newChartTypes = chartTypes.map(chartType => { - return { - id: chartType.id, - createChartData: props.apexChartsConfig[chartType.createChartData] - } - }) - apexProps.chartTypes = newChartTypes - apexProps.dropdownForChartTypes = true + resultUpdateID: perspectiveState.resultUpdateID, + instanceAnalysisDataUpdateID: perspectiveState.instanceAnalysisDataUpdateID, + facetUpdateID: facetState.facetUpdateID, + fetchData: props.fetchResults } routeComponent = ( <Route diff --git a/src/client/helpers/helpers.js b/src/client/helpers/helpers.js index 555a30b7931d258f8c13467d5eb3257b88171156..e3f127d9ee5e6719b029b5b02eb552d8ccf54e49 100644 --- a/src/client/helpers/helpers.js +++ b/src/client/helpers/helpers.js @@ -161,9 +161,9 @@ export const arrayToObject = ({ array, keyField }) => return obj }, {}) -export const generateLabelForMissingValue = ({ facetClass, facetID }) => { +export const generateLabelForMissingValue = ({ perspective, property }) => { // Check if there is a translated label for missing value, or use defaults - return intl.get(`perspectives.${facetClass}.properties.${facetID}.missingValueLabel`) || + return intl.get(`perspectives.${perspective}.properties.${property}.missingValueLabel`) || intl.get('facetBar.defaultMissingValueLabel') || 'Unknown' } diff --git a/src/client/library_configs/ApexCharts/ApexChartsConfig.js b/src/client/library_configs/ApexCharts/ApexChartsConfig.js index 4577e011b2fe71ebbef56ff2b56f59a2de3bf7f5..7e01a689b21624c23c53dc338fbd6b0868728a8b 100644 --- a/src/client/library_configs/ApexCharts/ApexChartsConfig.js +++ b/src/client/library_configs/ApexCharts/ApexChartsConfig.js @@ -2,6 +2,15 @@ import intl from 'react-intl-universal' import { generateLabelForMissingValue } from '../../helpers/helpers' +// list of colors generated with http://phrogz.net/css/distinct-colors.html +const pieChartColors = ['#a12a3c', '#0f00b5', '#81c7a4', '#ffdea6', '#ff0033', '#424cff', '#1b6935', '#ff9d00', '#5c3c43', + '#5f74b8', '#18b532', '#3b3226', '#fa216d', '#153ca1', '#00ff09', '#703a00', '#b31772', '#a4c9fc', '#273623', + '#f57200', '#360e2c', '#001c3d', '#ccffa6', '#a18068', '#ba79b6', '#004e75', '#547500', '#c2774c', '#f321fa', '#1793b3', + '#929c65', '#b53218', '#563c5c', '#1ac2c4', '#c4c734', '#4c150a', '#912eb3', '#2a5252', '#524b00', '#bf7d7c', '#24005e', + '#20f2ba', '#b5882f'] + +const defaultSliceVisibilityThreshold = 0.01 + export const createSingleLineChartData = ({ rawData, title, @@ -106,26 +115,37 @@ export const createMultipleLineChartData = ({ return apexChartOptionsWithData } -export const createApexPieChartData = ({ rawData, screenSize, facetClass, facetID }) => { +export const createApexPieChartData = ({ + resultClass, + facetClass, + perspectiveState, + results, + resultClassConfig, + screenSize +}) => { const labels = [] const series = [] let otherCount = 0 - const totalLength = rawData.length - const threshold = 0.15 - rawData.forEach(item => { - const portion = parseInt(item.instanceCount) / totalLength - if (portion < threshold) { - otherCount += parseInt(item.instanceCount) + const arraySum = results.reduce((sum, current) => sum + current.instanceCount, 0) + let actualResultClassConfig = resultClassConfig + if (resultClassConfig.dropdownForResultClasses) { + actualResultClassConfig = resultClassConfig.resultClasses[perspectiveState.resultClass] + } + const { sliceVisibilityThreshold = defaultSliceVisibilityThreshold, propertyID } = actualResultClassConfig + results.forEach(item => { + const sliceFraction = item.instanceCount / arraySum + if (sliceFraction <= sliceVisibilityThreshold) { + otherCount += item.instanceCount } else { - if (item.id === 'http://ldf.fi/MISSING_VALUE') { - item.prefLabel = generateLabelForMissingValue({ facetClass, facetID }) + if (item.id === 'http://ldf.fi/MISSING_VALUE' || item.category === 'http://ldf.fi/MISSING_VALUE') { + item.prefLabel = generateLabelForMissingValue({ perspective: facetClass, property: propertyID }) } labels.push(item.prefLabel) - series.push(parseInt(item.instanceCount)) + series.push(item.instanceCount) } }) if (otherCount !== 0) { - labels.push('Other') + labels.push(intl.get('apexCharts.other') || 'Other') series.push(otherCount) } let chartColors = [] @@ -157,13 +177,6 @@ export const createApexPieChartData = ({ rawData, screenSize, facetClass, facetI return apexChartOptionsWithData } -// list of colors generated with http://phrogz.net/css/distinct-colors.html -const pieChartColors = ['#a12a3c', '#0f00b5', '#81c7a4', '#ffdea6', '#ff0033', '#424cff', '#1b6935', '#ff9d00', '#5c3c43', - '#5f74b8', '#18b532', '#3b3226', '#fa216d', '#153ca1', '#00ff09', '#703a00', '#b31772', '#a4c9fc', '#273623', - '#f57200', '#360e2c', '#001c3d', '#ccffa6', '#a18068', '#ba79b6', '#004e75', '#547500', '#c2774c', '#f321fa', '#1793b3', - '#929c65', '#b53218', '#563c5c', '#1ac2c4', '#c4c734', '#4c150a', '#912eb3', '#2a5252', '#524b00', '#bf7d7c', '#24005e', - '#20f2ba', '#b5882f'] - const apexPieChartOptions = { // see https://apexcharts.com/docs --> Options chart: { @@ -214,30 +227,45 @@ const apexPieChartOptions = { } export const createApexBarChartData = ({ - rawData, - title, - xaxisTitle, - yaxisTitle, - seriesTitle + resultClass, + facetClass, + perspectiveState, + results, + resultClassConfig, + screenSize }) => { + const { + title, + seriesTitle, + xaxisTitle, + yaxisTitle + } = resultClassConfig const categories = [] const colors = [] const data = [] let otherCount = 0 - const totalLength = rawData.length - const threshold = 0.3 - rawData.forEach(item => { - const portion = parseInt(item.instanceCount) / totalLength - if (portion < threshold) { - otherCount += parseInt(item.instanceCount) + const arraySum = results.reduce((sum, current) => sum + current.instanceCount, 0) + let actualResultClassConfig = resultClassConfig + if (resultClassConfig.dropdownForResultClasses) { + actualResultClassConfig = resultClassConfig.resultClasses[perspectiveState.resultClass] + } + const { sliceVisibilityThreshold = defaultSliceVisibilityThreshold, propertyID } = actualResultClassConfig + + results.forEach(item => { + const sliceFraction = item.instanceCount / arraySum + if (sliceFraction <= sliceVisibilityThreshold) { + otherCount += item.instanceCount } else { + if (item.id === 'http://ldf.fi/MISSING_VALUE' || item.category === 'http://ldf.fi/MISSING_VALUE') { + item.prefLabel = generateLabelForMissingValue({ perspective: facetClass, property: propertyID }) + } categories.push(item.prefLabel) colors.push('#000000') - data.push(parseInt(item.instanceCount)) + data.push(item.instanceCount) } }) if (otherCount !== 0) { - categories.push('Other') + categories.push(intl.get('apexCharts.other') || 'Other') colors.push('#000000') data.push(otherCount) } diff --git a/src/client/reducers/index.js b/src/client/reducers/index.js index 4092b58d09c09240383b536f55c371342534a284..5bef21769074814dd969d3fe7b1316d822d7a63e 100644 --- a/src/client/reducers/index.js +++ b/src/client/reducers/index.js @@ -83,9 +83,18 @@ for (const perspective of perspectiveConfig) { ...facetsInitialState, facets } + let extraResultClasses = {} + for (const resultClass in resultClasses) { + if (resultClasses[resultClass].resultClasses) { + extraResultClasses = { + ...extraResultClasses, + ...resultClasses[resultClass].resultClasses + } + } + } const resultsReducer = createResultsReducer( resultsInitialStateFull, - new Set(Object.keys({ ...resultClasses, ...instancePageResultClasses }))) + new Set(Object.keys({ ...resultClasses, ...instancePageResultClasses, ...extraResultClasses }))) const facetsReducer = createFacetsReducer(facetsInitialStateFull, perspectiveID) const facetsConstrainSelfReducer = createFacetsConstrainSelfReducer(facetsInitialStateFull, perspectiveID) reducers[perspectiveID] = resultsReducer diff --git a/src/server/sparql/Mappers.js b/src/server/sparql/Mappers.js index 04dc9b6ea851f2843b2ddb166c2686443d735ad0..e6b2c746a7cf4d6ecf4928b18221e67409a8632c 100644 --- a/src/server/sparql/Mappers.js +++ b/src/server/sparql/Mappers.js @@ -35,6 +35,39 @@ export const mapCoordinates = sparqlBindings => { return results } +export const mapBirthYearCount = sparqlBindings => { + // console.log(sparqlBindings); + const results = sparqlBindings.map(b => { + return { + counted: b.counted.value, + count: b.count.value + } + }) + return results +} + +export const mapAgeCount = sparqlBindings => { + // console.log(sparqlBindings); + const results = sparqlBindings.map(b => { + return { + counted: b.counted.value, + count: b.count.value + } + }) + return results +} + +export const mapCountGroups = sparqlBindings => { + // console.log(sparqlBindings); + const results = sparqlBindings.map(b => { + return { + counted: b.counted.value, + count: b.count.value + } + }) + return results +} + export const mapCount = sparqlBindings => { return sparqlBindings[0].count.value } @@ -190,7 +223,7 @@ export const mapPieChart = sparqlBindings => { return { category: b.category.value, prefLabel: b.prefLabel.value, - instanceCount: b.instanceCount.value + instanceCount: parseInt(b.instanceCount.value) } }) return results diff --git a/src/server/sparql/Utils.js b/src/server/sparql/Utils.js index e26f041dce2440215da8bbd5affb98aeea8c5c00..203b93a5207391cd3c4cd8d65125fdbde47724b2 100644 --- a/src/server/sparql/Utils.js +++ b/src/server/sparql/Utils.js @@ -49,10 +49,24 @@ export const createBackendSearchConfig = async () => { } } // handle other resultClasses + let extraResultClasses = {} for (const resultClass in perspectiveConfig.resultClasses) { if (resultClass === perspectiveID) { continue } const resultClassConfig = perspectiveConfig.resultClasses[resultClass] processResultClassConfig(resultClassConfig, sparqlQueries, resultMappers) + if (resultClassConfig.resultClasses) { + for (const extraResultClass in resultClassConfig.resultClasses) { + processResultClassConfig(resultClassConfig.resultClasses[extraResultClass], sparqlQueries, resultMappers) + } + extraResultClasses = { + ...extraResultClasses, + ...resultClassConfig.resultClasses + } + } + } + perspectiveConfig.resultClasses = { + ...perspectiveConfig.resultClasses, + ...extraResultClasses } // merge facet results and instance page result classes if (hasInstancePageResultClasses) {