UNPKG

@neo4j-ndl/react-charts

Version:

React implementation of charts from Neo4j Design System

231 lines 9.14 kB
"use strict"; /** * * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.generateThresholdAriaDescription = generateThresholdAriaDescription; const defaults_1 = require("./defaults"); const threshold_1 = require("./threshold"); /** * Extract numeric y-values from inline series data. * Handles formats: [x, y], [x, y, ...], or single number. */ function extractInlineValues(data, encode) { if (!Array.isArray(data)) { return []; } const yIndex = (encode === null || encode === void 0 ? void 0 : encode.y) !== undefined && typeof encode.y === 'number' ? encode.y : 1; const values = []; for (const point of data) { if (point === null || point === undefined) { continue; } if (Array.isArray(point)) { const val = point[yIndex]; if (typeof val === 'number' && !isNaN(val)) { values.push(val); } } else if (typeof point === 'number' && !isNaN(point)) { values.push(point); } else if (typeof point === 'object' && 'value' in point) { const val = point.value; if (typeof val === 'number' && !isNaN(val)) { values.push(val); } else if (Array.isArray(val)) { const v = val[yIndex]; if (typeof v === 'number' && !isNaN(v)) { values.push(v); } } } } return values; } /** * Extract numeric y-values from a dataset source for a given series. */ function extractDatasetValues(dataset, series) { if (!dataset) { return []; } const datasets = Array.isArray(dataset) ? dataset : [dataset]; const datasetIndex = 'datasetIndex' in series && typeof series.datasetIndex === 'number' ? series.datasetIndex : 0; const currentDataset = datasets[datasetIndex]; if (!currentDataset || !Array.isArray(currentDataset.source)) { return []; } const source = currentDataset.source; if (source.length < 1) { return []; } // Determine column names and data start index. // When `dimensions` is provided, column names come from there and all // rows in `source` are data. Otherwise the first row is a header. const dimensions = currentDataset.dimensions; let columnNames; let dataStartIndex; if (Array.isArray(dimensions) && dimensions.length > 0) { columnNames = dimensions.map((d) => (typeof d === 'string' ? d : d.name)); dataStartIndex = 0; } else { const headerRow = source[0]; if (!Array.isArray(headerRow) || source.length < 2) { return []; } columnNames = headerRow; dataStartIndex = 1; } // Determine which column contains the y-values const encode = 'encode' in series ? series.encode : undefined; let yColumnIndex = -1; if ((encode === null || encode === void 0 ? void 0 : encode.y) !== undefined) { if (typeof encode.y === 'number') { yColumnIndex = encode.y; } else if (typeof encode.y === 'string') { yColumnIndex = columnNames.indexOf(encode.y); } } if (yColumnIndex === -1) { // Avoid guessing a data column for aria descriptions. // If we cannot resolve a value column explicitly, return no values. return []; } const values = []; for (let i = dataStartIndex; i < source.length; i++) { const row = source[i]; if (!Array.isArray(row)) { continue; } const val = row[yColumnIndex]; if (typeof val === 'number' && !isNaN(val)) { values.push(val); } } return values; } function pluralize(count, singular, plural) { return count === 1 ? singular : (plural !== null && plural !== void 0 ? plural : `${singular}s`); } /** * Resolves y-values for a series from either inline data or a dataset. */ function resolveSeriesValues(series, dataset) { if ('data' in series && series.data) { const encode = 'encode' in series ? series.encode : undefined; return extractInlineValues(series.data, encode); } return extractDatasetValues(dataset, series); } /** * Generates a human-readable aria-label description for the chart * that includes meaningful threshold context and data relationships. */ function generateThresholdAriaDescription(propsSeries, dataset) { var _a, _b, _c; const seriesArray = Array.isArray(propsSeries) ? propsSeries : [propsSeries]; // Separate threshold lines from data series const thresholds = []; const dataSeries = []; for (const series of seriesArray) { if (series.type === 'thresholdLine') { const threshold = series; thresholds.push({ condition: (_a = threshold.condition) !== null && _a !== void 0 ? _a : defaults_1.defaultThresholdLineSeriesOption.condition, customConditionText: threshold.customConditionText, hasCustomCondition: typeof threshold.customCondition === 'function', notificationType: threshold.notificationType, yAxisValue: threshold.yAxis, }); } else { const echartsSeriesOption = series; const name = typeof echartsSeriesOption.name === 'string' ? echartsSeriesOption.name : `Series ${dataSeries.length + 1}`; const values = resolveSeriesValues(echartsSeriesOption, dataset); dataSeries.push({ name, type: (_b = echartsSeriesOption.type) !== null && _b !== void 0 ? _b : 'line', values, }); } } if (thresholds.length === 0) { return undefined; } const parts = []; // 1. Overview const dataSeriesCountText = dataSeries.length === 1 ? '1 data series' : `${dataSeries.length} data series`; const thresholdCountText = thresholds.length === 1 ? '1 threshold line' : `${thresholds.length} threshold lines`; parts.push(`This chart has ${dataSeriesCountText} and ${thresholdCountText}.`); // 2. Describe each data series for (const series of dataSeries) { const { name, type, values } = series; if (values.length > 0) { const min = Math.min(...values); const max = Math.max(...values); parts.push(`${name} is a ${type} chart with ${values.length} ${pluralize(values.length, 'data point')} ranging from ${min} to ${max}.`); } else { parts.push(`${name} is a ${type} chart with no resolved data.`); } } // 3. Describe threshold lines for (const threshold of thresholds) { const conditionText = threshold.hasCustomCondition ? ((_c = threshold.customConditionText) !== null && _c !== void 0 ? _c : 'custom condition') : (0, threshold_1.thresholdConditionToText)(threshold.condition); parts.push(`${threshold.notificationType === 'warning' ? 'Warning' : 'Danger'} threshold at ${threshold.yAxisValue}: triggers when values are ${conditionText} ${threshold.yAxisValue}.`); } // 4. Report threshold violations per data series for (const series of dataSeries) { const { name, values } = series; if (values.length === 0) { continue; } for (const threshold of thresholds) { // Custom conditions use runtime callbacks that we cannot evaluate // at description-generation time, so we skip violation reporting. if (threshold.hasCustomCondition) { continue; } const violatingValues = values.filter((v) => (0, threshold_1.evaluateThresholdCondition)(v, threshold.condition, threshold.yAxisValue).isConditionMet); if (violatingValues.length > 0) { const count = violatingValues.length; parts.push(`${name} has ${count} ${pluralize(count, 'value')} ${(0, threshold_1.thresholdConditionToText)(threshold.condition)} the ${threshold.notificationType} threshold of ${threshold.yAxisValue}.`); } } } return parts.join(' '); } //# sourceMappingURL=aria-description.js.map