@antv/g2
Version:
the Grammar of Graphics in Javascript
314 lines • 14.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractSingleAxisScaleInfo = extractSingleAxisScaleInfo;
exports.extractMultiAxisScaleInfo = extractMultiAxisScaleInfo;
exports.filterMarkDataByDomain = filterMarkDataByDomain;
exports.processSingleAxisFiltering = processSingleAxisFiltering;
exports.processMultiAxisViewFiltering = processMultiAxisViewFiltering;
exports.processMultiAxisMarkFiltering = processMultiAxisMarkFiltering;
exports.updateChannelDomains = updateChannelDomains;
const scale_1 = require("../utils/scale");
const utils_1 = require("./utils");
/**
* Extracts scale information for single-axis adaptive filtering.
*
* @param shouldFilterXAxis - Whether to adapt X-axis (true) or Y-axis (false)
* @param scaleX - X-axis scale instance
* @param scaleY - Y-axis scale instance
* @returns Scale information required for single-axis filtering
*/
function extractSingleAxisScaleInfo(shouldFilterXAxis, scaleX, scaleY) {
const currentScale = shouldFilterXAxis ? scaleY : scaleX;
const targetScale = shouldFilterXAxis ? scaleX : scaleY;
const isSourceDiscrete = (0, scale_1.isOrdinalScale)(currentScale);
const isTargetDiscrete = (0, scale_1.isOrdinalScale)(targetScale);
const targetOriginalDomain = targetScale.getOptions().domain;
const shouldPreserveZeroBaseline = !isTargetDiscrete &&
targetOriginalDomain &&
targetOriginalDomain.length >= 2 &&
targetOriginalDomain[0] === 0;
return {
currentScale,
targetScale,
isSourceDiscrete,
isTargetDiscrete,
shouldPreserveZeroBaseline,
};
}
/**
* Extracts scale information for multi-axis adaptive filtering.
* Handles scenarios where multiple independent scales exist (x1, x2, y1, y2, etc.).
*
* @param shouldFilterXAxis - Whether to adapt X-axis (true) or Y-axis (false)
* @param scale - Record of all available scale instances
* @param scaleX - Primary X-axis scale instance
* @param scaleY - Primary Y-axis scale instance
* @param channelDomain - Channel domain configuration
* @returns Scale information required for multi-axis filtering
*/
function extractMultiAxisScaleInfo(shouldFilterXAxis, scale, scaleX, scaleY, channelDomain) {
const currentScale = shouldFilterXAxis ? scaleY : scaleX;
/**
* Retrieves scale domains for a specific axis type (x or y).
* Supports both primary axes (x, y) and numbered variants (x1, x2, y1, y2).
*/
const getAxisScaleDomains = (axisType) => {
const axisScales = {};
Object.keys(channelDomain).forEach((key) => {
if (key === axisType || key.match(new RegExp(`^${axisType}\\d+$`))) {
axisScales[key] = channelDomain[key];
}
});
return axisScales;
};
const targetScaleDomain = shouldFilterXAxis
? getAxisScaleDomains('x')
: getAxisScaleDomains('y');
const targetScaleKeys = Object.keys(targetScaleDomain);
const targetScales = targetScaleKeys.map((item) => scale[item]);
const isSourceDiscrete = (0, scale_1.isOrdinalScale)(currentScale);
const isTargetDiscrete = targetScales.map((targetScale) => (0, scale_1.isOrdinalScale)(targetScale));
const shouldPreserveZeroBaseline = targetScales.map((targetScale, index) => {
const targetOriginalDomain = targetScale.getOptions().domain;
const isDiscrete = isTargetDiscrete[index];
return (!isDiscrete &&
targetOriginalDomain &&
targetOriginalDomain.length >= 2 &&
targetOriginalDomain[0] === 0);
});
return {
currentScale,
targetScales,
isSourceDiscrete,
isTargetDiscrete,
shouldPreserveZeroBaseline,
targetScaleKeys,
};
}
/**
* Removes duplicate values from an array and returns sorted result.
*
* @param values - Array of numeric values
* @returns Sorted array with unique values
*/
function getUniqueSortedValues(values) {
const uniqueValues = Array.from(new Set(values));
return uniqueValues.sort((a, b) => a - b);
}
/**
* Calculates the filtered domain based on target scale type and constraints.
*
* @param options - Configuration for domain calculation
* @returns Calculated domain array
*/
function calculateFilteredDomain({ isTargetDiscrete, filteredValues, shouldPreserveZeroBaseline, }) {
if (isTargetDiscrete) {
return getUniqueSortedValues(filteredValues);
}
else {
const min = Math.min(...filteredValues);
const max = Math.max(...filteredValues);
return shouldPreserveZeroBaseline ? [0, max] : [min, max];
}
}
/**
* Converts various value types to numeric for comparison.
* Handles Date objects, strings, and numbers.
*/
function convertToNumeric(value) {
if (value instanceof Date) {
return value.getTime();
}
if (typeof value === 'string') {
return parseFloat(value);
}
return Number(value);
}
/**
* Filters mark data based on domain constraints with bidirectional support.
* Supports both discrete and continuous scales with proper domain calculation.
*
* @param markDataPairs - Array of mark data with channel information
* @param domain - Domain values for filtering
* @param isSourceDiscrete - Whether the source scale is discrete
* @param isTargetDiscrete - Whether the target scale is discrete
* @param shouldPreserveZeroBaseline - Whether to preserve zero baseline for continuous scales
* @param adaptiveMode - Adaptive filtering mode configuration
* @param shouldFilterXAxis - Whether to adapt X-axis (true) or Y-axis (false)
* @returns Filtered domain array
*/
function filterMarkDataByDomain(markDataPairs, domain, isSourceDiscrete, isTargetDiscrete, shouldPreserveZeroBaseline, adaptiveMode = 'filter', shouldFilterXAxis = false) {
if ((0, utils_1.isFalsyValue)(adaptiveMode)) {
return [];
}
const sourceChannel = shouldFilterXAxis ? 'y' : 'x';
const targetChannel = shouldFilterXAxis ? 'x' : 'y';
const allFilteredTargetValues = [];
for (const markData of markDataPairs) {
const { channelData } = markData;
const sourceValues = channelData[sourceChannel] || [];
const targetValues = channelData[targetChannel] || [];
// Handle different data structures based on channel type:
// X channel: one-dimensional array [x1, x2, x3, ...]
// Y channel: two-dimensional array [[y1, y2, y3, ...]]
// Normalize source values to one-dimensional array
const normalizedSourceValues = Array.isArray(sourceValues[0])
? sourceValues[0] // If it's 2D array (Y channel), take first sub-array
: sourceValues; // If it's 1D array (X channel), use as is
// Handle target values based on their structure
const isTargetArray2D = Array.isArray(targetValues[0]);
if (normalizedSourceValues.length === 0)
continue;
const dataLength = normalizedSourceValues.length;
for (let i = 0; i < dataLength; i++) {
const sourceValue = normalizedSourceValues[i];
let shouldInclude = false;
if (isSourceDiscrete) {
shouldInclude = domain.includes(sourceValue);
}
else {
// Handle both numeric and Date domains
if (domain.length >= 2) {
const sourceTime = convertToNumeric(sourceValue);
const domainStartTime = convertToNumeric(domain[0]);
const domainEndTime = convertToNumeric(domain[domain.length - 1]);
if (!isNaN(sourceTime) &&
!isNaN(domainStartTime) &&
!isNaN(domainEndTime)) {
shouldInclude =
sourceTime >= domainStartTime && sourceTime <= domainEndTime;
}
}
}
if (adaptiveMode === 'filter' && shouldInclude) {
// Collect target channel values for this data point
if (isTargetArray2D) {
// Target is 2D array (Y channel)
const numChannels = targetValues.length;
for (let channelIdx = 0; channelIdx < numChannels; channelIdx++) {
const channelData = targetValues[channelIdx];
if (Array.isArray(channelData) && i < channelData.length) {
const targetValue = channelData[i];
const numericValue = convertToNumeric(targetValue);
if (!isNaN(numericValue)) {
allFilteredTargetValues.push(numericValue);
}
}
}
}
else {
// Target is 1D array (X channel)
if (i < targetValues.length) {
const targetValue = targetValues[i];
const numericValue = convertToNumeric(targetValue);
if (!isNaN(numericValue)) {
allFilteredTargetValues.push(numericValue);
}
}
}
}
}
}
if (allFilteredTargetValues.length > 0) {
return calculateFilteredDomain({
isTargetDiscrete,
filteredValues: allFilteredTargetValues,
shouldPreserveZeroBaseline,
});
}
return [];
}
/**
* Processes adaptive filtering for single-axis scenarios.
*
* @param params - Single-axis filtering parameters
* @returns Filtered domain array
*/
function processSingleAxisFiltering({ markDataPairs, domain, scaleInfo, adaptiveMode, shouldFilterXAxis = false, }) {
const { isSourceDiscrete, isTargetDiscrete, shouldPreserveZeroBaseline } = scaleInfo;
return filterMarkDataByDomain(markDataPairs, domain, isSourceDiscrete, isTargetDiscrete, shouldPreserveZeroBaseline, adaptiveMode, shouldFilterXAxis);
}
/**
* Processes multi-axis filtering for view-level sliders.
* Handles scenarios with independent scales across multiple marks.
*
* @param params - Multi-axis filtering parameters
* @returns Map of scale keys to filtered domain arrays
*/
function processMultiAxisViewFiltering({ markDataPairs, domain, scaleInfo, markToScaleMap, adaptiveMode, shouldFilterXAxis = false, }) {
const filteredDomain = new Map();
const { isSourceDiscrete, isTargetDiscrete, shouldPreserveZeroBaseline, targetScaleKeys, } = scaleInfo;
markDataPairs.forEach((markData) => {
const scaleKey = markToScaleMap.get(markData.markKey);
if (!scaleKey)
return;
const scaleIndex = targetScaleKeys.indexOf(scaleKey);
if (scaleIndex === -1)
return;
const currentIsTargetDiscrete = isTargetDiscrete[scaleIndex];
const currentShouldPreserveZeroBaseline = shouldPreserveZeroBaseline[scaleIndex];
const markFilteredDomain = filterMarkDataByDomain([markData], domain, isSourceDiscrete, currentIsTargetDiscrete, currentShouldPreserveZeroBaseline, adaptiveMode, shouldFilterXAxis);
filteredDomain.set(scaleKey, markFilteredDomain);
});
return filteredDomain;
}
/**
* Processes multi-axis filtering for mark-level sliders.
* Uses all marks that share the same target scale key instead of just the target mark.
*
* @param markDataPairs - Array of mark data pairs
* @param domain - Domain values for filtering
* @param scaleInfo - Single-axis scale information
* @param targetMarkKey - Key of the target mark to filter
* @param targetScaleKey - Key of the target scale to update
* @param adaptiveMode - Adaptive filtering mode
* @param shouldFilterXAxis - Whether to adapt X-axis (true) or Y-axis (false)
* @param markToScaleMap - Map from mark keys to scale keys to identify shared axes
* @returns Map of scale keys to filtered domain arrays
*/
function processMultiAxisMarkFiltering(markDataPairs, domain, scaleInfo, targetMarkKey, targetScaleKey, adaptiveMode, shouldFilterXAxis = false, markToScaleMap) {
const filteredDomain = new Map();
// Early return if no data to process
if (markDataPairs.length === 0 || domain.length === 0) {
return filteredDomain;
}
const { isSourceDiscrete, isTargetDiscrete, shouldPreserveZeroBaseline } = scaleInfo;
// Find all marks that share the same target scale key
const relevantMarkData = markToScaleMap
? markDataPairs.filter((markData) => {
const markScaleKey = markToScaleMap.get(markData.markKey);
return markScaleKey === targetScaleKey;
})
: markDataPairs.filter((markData) => markData.markKey === targetMarkKey);
// Early return if no relevant marks found
if (relevantMarkData.length === 0) {
return filteredDomain;
}
const markFilteredDomain = filterMarkDataByDomain(relevantMarkData, domain, isSourceDiscrete, isTargetDiscrete, shouldPreserveZeroBaseline, adaptiveMode, shouldFilterXAxis);
filteredDomain.set(targetScaleKey, markFilteredDomain);
return filteredDomain;
}
/**
* Updates channel domains with filtered results.
* Supports both single-axis and multi-axis scenarios.
*
* @param channelDomain - Current channel domain configuration
* @param filteredDomain - Filtered domain values (array for single-axis, Map for multi-axis)
* @param shouldFilterXAxis - Whether X-axis is being filtered
* @param isMultiAxis - Whether this is a multi-axis scenario
*/
function updateChannelDomains(channelDomain, filteredDomain, shouldFilterXAxis, isMultiAxis) {
if (isMultiAxis && filteredDomain instanceof Map) {
filteredDomain.forEach((domain, scaleKey) => {
if (domain && Array.isArray(domain) && domain.length > 0) {
channelDomain[scaleKey] = domain;
}
});
}
else if (!isMultiAxis && Array.isArray(filteredDomain)) {
if (filteredDomain.length > 0) {
channelDomain[shouldFilterXAxis ? 'x' : 'y'] = filteredDomain;
}
}
}
//# sourceMappingURL=adaptiveFilter.js.map