import { each, get, omit, set } from '@antv/util'; import { annotation as baseAnnotation, interaction, theme, tooltip } from '../../adaptor/common'; import { interval, point, violin } from '../../adaptor/geometries'; import { AXIS_META_CONFIG_KEYS } from '../../constant'; import { Params } from '../../core/adaptor'; import { deepAssign, findViewById, flow, pick } from '../../utils'; import { addViewAnimation } from '../../utils/view'; import { MEDIAN_FIELD, MEDIAN_VIEW_ID, MIN_MAX_FIELD, MIN_MAX_VIEW_ID, QUANTILE_FIELD, QUANTILE_VIEW_ID, VIOLIN_SIZE_FIELD, VIOLIN_VIEW_ID, VIOLIN_Y_FIELD, X_FIELD, } from './constant'; import { ViolinOptions } from './types'; import { transformViolinData } from './utils'; const TOOLTIP_FIELDS = ['low', 'high', 'q1', 'q3', 'median']; const adjustCfg = [ { type: 'dodge', marginRatio: 1 / 32, } as const, ]; /** 处理数据 */ function data(params: Params): Params { const { chart, options } = params; chart.data(transformViolinData(options)); return params; } /** 小提琴轮廓 */ function violinView(params: Params): Params { const { chart, options } = params; const { seriesField, color, shape = 'violin', violinStyle, tooltip, state } = options; const view = chart.createView({ id: VIOLIN_VIEW_ID }); violin({ chart: view, options: { xField: X_FIELD, yField: VIOLIN_Y_FIELD, seriesField: seriesField ? seriesField : X_FIELD, sizeField: VIOLIN_SIZE_FIELD, tooltip: { fields: TOOLTIP_FIELDS, ...tooltip, }, violin: { style: violinStyle, color, shape, }, state, }, }); view.geometries[0].adjust(adjustCfg); return params; } /** 箱线 */ function boxView(params: Params): Params { const { chart, options } = params; const { seriesField, color, tooltip, box } = options; // 如果配置 `box` 为 false ,不渲染内部箱线图 if (box === false) return params; // 边缘线 const minMaxView = chart.createView({ id: MIN_MAX_VIEW_ID }); interval({ chart: minMaxView, options: { xField: X_FIELD, yField: MIN_MAX_FIELD, seriesField: seriesField ? seriesField : X_FIELD, tooltip: { fields: TOOLTIP_FIELDS, ...tooltip, }, state: typeof box === 'object' ? box.state : {}, interval: { color, size: 1, style: { lineWidth: 0, }, }, }, }); minMaxView.geometries[0].adjust(adjustCfg); // 四分点位 const quantileView = chart.createView({ id: QUANTILE_VIEW_ID }); interval({ chart: quantileView, options: { xField: X_FIELD, yField: QUANTILE_FIELD, seriesField: seriesField ? seriesField : X_FIELD, tooltip: { fields: TOOLTIP_FIELDS, ...tooltip, }, state: typeof box === 'object' ? box.state : {}, interval: { color, size: 8, style: { fillOpacity: 1, }, }, }, }); quantileView.geometries[0].adjust(adjustCfg); // 中位值 const medianView = chart.createView({ id: MEDIAN_VIEW_ID }); point({ chart: medianView, options: { xField: X_FIELD, yField: MEDIAN_FIELD, seriesField: seriesField ? seriesField : X_FIELD, tooltip: { fields: TOOLTIP_FIELDS, ...tooltip, }, state: typeof box === 'object' ? box.state : {}, point: { color, size: 1, style: { fill: 'white', lineWidth: 0, }, }, }, }); medianView.geometries[0].adjust(adjustCfg); // 关闭辅助 view 的轴 quantileView.axis(false); minMaxView.axis(false); medianView.axis(false); // 关闭辅助 view 的图例 medianView.legend(false); minMaxView.legend(false); quantileView.legend(false); return params; } /** * meta 配置 */ function meta(params: Params): Params { const { chart, options } = params; const { meta, xAxis, yAxis } = options; const baseMeta = {}; const scales = deepAssign(baseMeta, meta, { [X_FIELD]: { sync: true, ...pick(xAxis, AXIS_META_CONFIG_KEYS), // fix: dodge is not support linear attribute, please use category attribute! // 强制 x 轴类型为分类类型 type: 'cat', }, [VIOLIN_Y_FIELD]: { sync: true, ...pick(yAxis, AXIS_META_CONFIG_KEYS), }, [MIN_MAX_FIELD]: { sync: VIOLIN_Y_FIELD, ...pick(yAxis, AXIS_META_CONFIG_KEYS), }, [QUANTILE_FIELD]: { sync: VIOLIN_Y_FIELD, ...pick(yAxis, AXIS_META_CONFIG_KEYS), }, [MEDIAN_FIELD]: { sync: VIOLIN_Y_FIELD, ...pick(yAxis, AXIS_META_CONFIG_KEYS), }, }); chart.scale(scales); return params; } /** * axis 配置 */ function axis(params: Params): Params { const { chart, options } = params; const { xAxis, yAxis } = options; const view = findViewById(chart, VIOLIN_VIEW_ID); // 为 false 则是不显示轴 if (xAxis === false) { view.axis(X_FIELD, false); } else { view.axis(X_FIELD, xAxis); } if (yAxis === false) { view.axis(VIOLIN_Y_FIELD, false); } else { view.axis(VIOLIN_Y_FIELD, yAxis); } chart.axis(false); return params; } /** * * @param params * @returns */ function legend(params: Params): Params { const { chart, options } = params; const { legend, seriesField, shape } = options; if (legend === false) { chart.legend(false); } else { const legendField = seriesField ? seriesField : X_FIELD; // fixme 暂不明为啥有描边 const legendOptions = omit(legend as any, ['selected']); if (!shape || !shape.startsWith('hollow')) { if (!get(legendOptions, ['marker', 'style', 'lineWidth'])) { set(legendOptions, ['marker', 'style', 'lineWidth'], 0); } } chart.legend(legendField, legendOptions); // 特殊的处理 fixme G2 层得解决这个问题 if (get(legend, 'selected')) { each(chart.views, (view) => view.legend(legendField, legend)); } } return params; } /** * annotation, apply to violin view. * @param params * @returns */ function annotation(params: Params): Params { const { chart } = params; const violinView = findViewById(chart, VIOLIN_VIEW_ID); baseAnnotation()({ ...params, chart: violinView }); return params; } /** * 动画 * @param params */ export function animation(params: Params): Params { const { chart, options } = params; const { animation } = options; // 所有的 Geometry 都使用同一动画(各个图形如有区别,自行覆盖) each(chart.views, (view) => { addViewAnimation(view, animation); }); return params; } /** * 小提琴图适配器 * @param params */ export function adaptor(params: Params) { return flow( theme, data, violinView, boxView, meta, tooltip, axis, legend, interaction, annotation, animation )(params); }