@formant/ava
Version:
A framework for automated visual analytics.
246 lines (245 loc) • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.dataToAdvices = void 0;
var smart_color_1 = require("@antv/smart-color");
var utils_1 = require("../ruler/utils");
var utils_2 = require("../utils");
var inferDataType_1 = require("../utils/inferDataType");
var spec_mapping_1 = require("./spec-mapping");
/**
* Run all rules for a given chart type, get scoring result.
*
* @param chartType chart ID to be score
* @param dataProps data props of the input data
* @param ruleBase rule base
* @param options
* @returns
*/
function scoreRules(chartType, chartWIKI, dataProps, ruleBase, options) {
var purpose = options ? options.purpose : '';
var preferences = options ? options.preferences : undefined;
// for log
var log = [];
var info = { dataProps: dataProps, chartType: chartType, purpose: purpose, preferences: preferences };
var hardScore = (0, utils_1.computeScore)(chartType, chartWIKI, ruleBase, 'HARD', info, log);
// Hard-Rule pruning
if (hardScore === 0) {
var result_1 = { chartType: chartType, score: 0, log: log };
return result_1;
}
var softScore = (0, utils_1.computeScore)(chartType, chartWIKI, ruleBase, 'SOFT', info, log);
var score = hardScore * softScore;
var result = { chartType: chartType, score: score, log: log };
return result;
}
function applyDesignRules(chartType, dataProps, ruleBase, chartSpec) {
var toCheckRules = Object.values(ruleBase).filter(function (rule) { var _a; return rule.type === 'DESIGN' && rule.trigger({ dataProps: dataProps, chartType: chartType }) && !((_a = ruleBase[rule.id].option) === null || _a === void 0 ? void 0 : _a.off); });
var encodingSpec = toCheckRules.reduce(function (lastSpec, rule) {
var relatedSpec = rule.optimizer(dataProps, chartSpec);
return (0, utils_2.deepMix)(lastSpec, relatedSpec);
}, {});
return encodingSpec;
}
var DISCRETE_PALETTE_TYPES = ['monochromatic', 'analogous'];
var CATEGORICAL_PALETTE_TYPES = ['polychromatic', 'split-complementary', 'triadic', 'tetradic'];
var DEFAULT_COLOR = '#678ef2';
function applyTheme(dataProps, chartSpec, theme) {
var _a;
var specWithEncodeType = (0, inferDataType_1.getSpecWithEncodeType)(chartSpec);
var primaryColor = theme.primaryColor;
var layerEnc = specWithEncodeType.encode;
if (primaryColor && layerEnc) {
// convert primary color
var color = (0, smart_color_1.hexToColor)(primaryColor);
// if color is specified
if (layerEnc.color) {
var _b = layerEnc.color, type = _b.type, field_1 = _b.field;
var colorScheme = void 0;
if (type === 'quantitative') {
colorScheme = DISCRETE_PALETTE_TYPES[Math.floor(Math.random() * DISCRETE_PALETTE_TYPES.length)];
}
else {
colorScheme = CATEGORICAL_PALETTE_TYPES[Math.floor(Math.random() * CATEGORICAL_PALETTE_TYPES.length)];
}
var count = (_a = dataProps.find(function (d) { return d.name === field_1; })) === null || _a === void 0 ? void 0 : _a.count;
var palette = (0, smart_color_1.paletteGeneration)(colorScheme, {
color: color,
count: count,
});
return {
scale: {
color: { range: palette.colors.map(function (color) { return (0, smart_color_1.colorToHex)(color); }) },
},
};
}
return chartSpec.type === 'line'
? {
style: {
stroke: (0, smart_color_1.colorToHex)(color),
},
}
: {
style: {
fill: (0, smart_color_1.colorToHex)(color),
},
};
}
return {};
}
function applySmartColor(dataProps, chartSpec, primaryColor, colorType, simulationType) {
var _a;
var specWithEncodeType = (0, inferDataType_1.getSpecWithEncodeType)(chartSpec);
var layerEnc = specWithEncodeType.encode;
if (primaryColor && layerEnc) {
// convert primary color
var color = (0, smart_color_1.hexToColor)(primaryColor);
// if color is specified
if (layerEnc.color) {
var _b = layerEnc.color, type = _b.type, field_2 = _b.field;
var colorScheme = colorType;
if (!colorScheme) {
if (type === 'quantitative') {
colorScheme = 'monochromatic';
}
else {
colorScheme = 'polychromatic';
}
}
var count = (_a = dataProps.find(function (d) { return d.name === field_2; })) === null || _a === void 0 ? void 0 : _a.count;
var palette = (0, smart_color_1.paletteGeneration)(colorScheme, {
color: color,
count: count,
});
return {
scale: {
color: {
range: palette.colors.map(function (color) {
return (0, smart_color_1.colorToHex)(simulationType ? (0, smart_color_1.colorSimulation)(color, simulationType) : color);
}),
},
},
};
}
return chartSpec.type === 'line'
? {
style: {
stroke: (0, smart_color_1.colorToHex)(color),
},
}
: {
style: {
fill: (0, smart_color_1.colorToHex)(color),
},
};
}
return {};
}
/**
* recommending charts given data and dataProps, based on CKB and RuleBase
*
* @param data input data [ {a: xxx, b: xxx}, ... ]
* @param dataProps data props derived from data or customized by users
* @param chartWIKI ckb
* @param ruleBase rule base
* @param smartColor switch smart color on/off, optional props, default is off
* @param options options for advising such as log, preferences
* @param colorOptions color options, optional props, @see {@link SmartColorOptions}
* @returns chart list [ { type: chartTypes, spec: antv-spec, score: >0 }, ... ]
*/
function dataToAdvices(data, dataProps, chartWIKI, ruleBase, smartColor, options, colorOptions) {
/**
* `refine`: whether to apply design rules
*/
var enableRefine = (options === null || options === void 0 ? void 0 : options.refine) === undefined ? false : options.refine;
/**
* `smartColorOn`: switch SmartColor on/off
*/
var smartColorOn = smartColor;
/**
* `theme`: custom theme
*/
var theme = options === null || options === void 0 ? void 0 : options.theme;
/**
* `requireSpec`: only consider chart type with spec if true
*/
var requireSpec = (options === null || options === void 0 ? void 0 : options.requireSpec) === undefined ? true : options.requireSpec;
/**
* customized CKB input or default CKB from @antv/ckb
*/
var ChartWIKI = chartWIKI;
/**
* all charts that can be recommended
* */
var CHART_ID_OPTIONS = Object.keys(ChartWIKI);
var log = [];
// score every possible chart
var list = CHART_ID_OPTIONS.map(function (t) {
var _a;
// step 1: analyze score by rule
var resultForChartType = scoreRules(t, ChartWIKI, dataProps, ruleBase, options);
log.push(resultForChartType);
var score = resultForChartType.score;
// Zero-Score pruning
if (score <= 0) {
return { type: t, spec: null, score: score };
}
// step 2: field mapping to spec encoding
var chartTypeSpec = (0, spec_mapping_1.getChartTypeSpec)(t, data, dataProps, chartWIKI[t]);
// FIXME kpi_panel and table spec to be null temporarily
var customChartType = ['kpi_panel', 'table'];
if (!customChartType.includes(t) && !chartTypeSpec)
return { type: t, spec: null, score: score };
// step 3: apply design rules
if (chartTypeSpec && enableRefine) {
var partEncSpec = applyDesignRules(t, dataProps, ruleBase, chartTypeSpec);
(0, utils_2.deepMix)(chartTypeSpec, partEncSpec);
}
// step 4: custom theme
if (chartTypeSpec) {
if (theme && !smartColorOn) {
var partEncSpec = applyTheme(dataProps, chartTypeSpec, theme);
(0, utils_2.deepMix)(chartTypeSpec, partEncSpec);
}
else if (smartColorOn) {
/**
* `colorTheme`: theme for SmartColor
* Default color is blue
*/
var colorTheme = (_a = colorOptions === null || colorOptions === void 0 ? void 0 : colorOptions.themeColor) !== null && _a !== void 0 ? _a : DEFAULT_COLOR;
/**
* `colorType`: customize SmartColor generation type
*/
var colorType = colorOptions === null || colorOptions === void 0 ? void 0 : colorOptions.colorSchemeType;
/**
* `simulationType`: customize SmartColor for specific simulation mode
* Default color mode is normal
*/
var simulationType = colorOptions === null || colorOptions === void 0 ? void 0 : colorOptions.simulationType;
var partEncSpec = applySmartColor(dataProps, chartTypeSpec, colorTheme, colorType, simulationType);
(0, utils_2.deepMix)(chartTypeSpec, partEncSpec);
}
}
return { type: t, spec: chartTypeSpec, score: score };
});
/** compare two advice charts by their score */
function compareAdvices(chart1, chart2) {
if (chart1.score < chart2.score) {
return 1;
}
if (chart1.score > chart2.score) {
return -1;
}
return 0;
}
// filter and sorter
var isAvailableAdvice = function (advice) {
return advice.score > 0 && (requireSpec ? advice.spec : true);
};
var resultList = list.filter(isAvailableAdvice).sort(compareAdvices);
var result = {
advices: resultList,
log: log,
};
return result;
}
exports.dataToAdvices = dataToAdvices;