Update MarketingAnalysis.vue
This commit is contained in:
@@ -934,10 +934,10 @@ const v2ReportNormalized = computed(() => {
|
||||
|
||||
const drawBarValues = (chart) => {
|
||||
const ctx = chart.ctx;
|
||||
ctx.font = 'bold 11px "Inter", sans-serif';
|
||||
ctx.fillStyle = document.documentElement.classList.contains('dark') ? '#ffffff' : '#1f2937';
|
||||
ctx.save(); // Сохраняем состояние контекста
|
||||
ctx.font = 'bold 12px "Inter", sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
chart.data.datasets.forEach((dataset, i) => {
|
||||
const meta = chart.getDatasetMeta(i);
|
||||
@@ -946,30 +946,106 @@ const drawBarValues = (chart) => {
|
||||
const value = dataset.data[index];
|
||||
if (value !== null && value !== undefined && value !== 0) {
|
||||
let label = value.toLocaleString();
|
||||
// Добавляем % если это проценты
|
||||
if ((dataset.unit === '%') || (typeof value === 'string' && value.includes('%'))) {
|
||||
// Добавляем %, если это указано в датасете или значение похоже на процент
|
||||
if ((dataset.unit === '%') || (typeof dataset.data[index] === 'string' && dataset.data[index].includes('%'))) {
|
||||
label = String(value).replace('%', '') + '%';
|
||||
}
|
||||
|
||||
const isHorizontal = meta.indexAxis === 'y';
|
||||
|
||||
if (isHorizontal) {
|
||||
// Для горизонтальных (Доход): рисуем справа
|
||||
// Горизонтальный график: цифра справа от бара или внутри, если бар длинный
|
||||
ctx.fillStyle = document.documentElement.classList.contains('dark') ? '#ffffff' : '#1f2937';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(label, bar.x + 5, bar.y);
|
||||
ctx.fillText(label, bar.x + 8, bar.y);
|
||||
} else {
|
||||
// Для вертикальных: рисуем сверху
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText(label, bar.x, bar.y - 5);
|
||||
// Вертикальный график (Требование 1, 2, 3): Цифра ВНУТРИ бара
|
||||
// Используем белый цвет для контраста на цветном фоне
|
||||
ctx.fillStyle = '#ffffff';
|
||||
|
||||
// Если бар слишком маленький, рисуем сверху, иначе внутри
|
||||
const barHeight = Math.abs(bar.base - bar.y);
|
||||
if (barHeight > 20) {
|
||||
ctx.fillText(label, bar.x, bar.y + (barHeight / 2)); // По центру бара
|
||||
} else {
|
||||
// Если бар слишком мелкий, рисуем сверху темным цветом
|
||||
ctx.fillStyle = document.documentElement.classList.contains('dark') ? '#ffffff' : '#1f2937';
|
||||
ctx.fillText(label, bar.x, bar.y - 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
// --- 2. НОВЫЕ ОПЦИИ ДЛЯ ГРАФИКОВ (Требование: убрать нижние цифры и оси) ---
|
||||
const verticalBarChartInsideOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: 500,
|
||||
onComplete: (animation) => drawBarValues(animation.chart)
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
callbacks: {
|
||||
label: (context) => `${context.formattedValue}%`
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
display: false, // Скрываем ось Y полностью
|
||||
beginAtZero: true,
|
||||
grid: { display: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
font: { size: 11, weight: '600' },
|
||||
color: '#64748b' // Цвет подписей категорий (Высокая цена, Нет отзывов и т.д.)
|
||||
}
|
||||
}
|
||||
},
|
||||
layout: { padding: { top: 20, bottom: 0 } }
|
||||
};
|
||||
|
||||
// Опции для горизонтальных баров (как на фото 4/5)
|
||||
const horizontalBarChartOptions = {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
layout: { padding: { right: 40 } },
|
||||
animation: {
|
||||
duration: 500,
|
||||
onComplete: (animation) => drawBarValues(animation.chart)
|
||||
},
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { display: false }, // Скрываем цифры снизу
|
||||
y: {
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
font: { size: 12, weight: '500' },
|
||||
crossAlign: 'far'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const parsePercentageData = (obj) => {
|
||||
if (!obj || typeof obj !== 'object') return { labels: [], values: [] };
|
||||
const labels = Object.keys(obj).map(k => k.replace(/_/g, ' ')); // Убираем _ из ключей
|
||||
const values = Object.values(obj).map(v => {
|
||||
if (typeof v === 'string') return parseFloat(v.replace('%', ''));
|
||||
return v;
|
||||
});
|
||||
return { labels, values };
|
||||
};
|
||||
const competitorsBarChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
@@ -2089,101 +2165,20 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
|
||||
const segments = [];
|
||||
|
||||
// Нормализация: работаем либо с полем sections (новый бэк), либо с корнем (если пришло без обертки)
|
||||
const sections = chartsData.sections || chartsData;
|
||||
|
||||
// ==========================================
|
||||
// 1. НОВАЯ ЛОГИКА (V2 JSON - Исправления графиков)
|
||||
// 1. НОВАЯ ЛОГИКА (V2 JSON + Новый Дизайн)
|
||||
// ==========================================
|
||||
|
||||
// --- График ДОХОДА (Горизонтальный) и ВОЗРАСТА ---
|
||||
if (chartsData.target_audience || chartsData.targetAudience) {
|
||||
const source = chartsData.target_audience || chartsData.targetAudience;
|
||||
|
||||
// 1.1 Доход (Горизонтальный)
|
||||
const incomeChart = buildIncomeChartData(source);
|
||||
if (incomeChart) {
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'incomeByAge',
|
||||
title: 'Средний доход по возрастным группам',
|
||||
chartType: 'bar',
|
||||
data: incomeChart,
|
||||
options: horizontalBarChartOptions, // <-- Горизонтальный вид
|
||||
chartHeight: 'height: 350px'
|
||||
});
|
||||
}
|
||||
|
||||
// 1.2 Возрастная структура (Цифры на барах)
|
||||
const ageStruct = source.age_structure || source.ageStructure;
|
||||
if (ageStruct) {
|
||||
const labels = Object.keys(ageStruct);
|
||||
const values = Object.values(ageStruct).map(v => parseFloat(String(v).replace('%','')));
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'ageStructure',
|
||||
title: 'Возрастная структура',
|
||||
chartType: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: getMarketingColors(values.length),
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: competitorsBarChartOptions, // Цифры сверху
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- График КАНАЛОВ (Динамический) ---
|
||||
if (chartsData.acquisition_channels || chartsData.acquisitionChannels) {
|
||||
const source = chartsData.acquisition_channels || chartsData.acquisitionChannels;
|
||||
const channelsChart = buildChannelsChartData(source);
|
||||
if (channelsChart) {
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'acquisitionChannels',
|
||||
title: 'Каналы привлечения',
|
||||
chartType: 'bar',
|
||||
data: channelsChart,
|
||||
options: channelsBarChartOptions, // Вертикальные с %
|
||||
chartHeight: 'height: 350px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Факторы принятия решений и Причины отказов ---
|
||||
if (chartsData.demand_and_behavior || chartsData.demandAndBehavior) {
|
||||
const source = chartsData.demand_and_behavior || chartsData.demandAndBehavior;
|
||||
|
||||
// Факторы
|
||||
const factors = source.decision_factors || source.decisionFactors;
|
||||
if (factors) {
|
||||
const labels = Object.keys(factors).map(k => k.replace(/_/g, ' '));
|
||||
const values = Object.values(factors).map(v => parseFloat(String(v).replace('%','')));
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'decisionFactors',
|
||||
title: 'Факторы принятия решения',
|
||||
chartType: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#8B5CF6',
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: competitorsBarChartOptions, // Цифры сверху
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
|
||||
// Причины отказов
|
||||
const refusals = source.reasons_for_refusal || source.reasonsForRefusal;
|
||||
// --- Блок: Спрос и поведение (Фото 1: Причины отказов, Фото 6: Факторы) ---
|
||||
const demandSection = sections.demand_and_behavior || sections.demandAndBehavior;
|
||||
if (demandSection) {
|
||||
// 1. Причины отказов (Фото 1: Вертикальные, цифры внутри, оранжевый)
|
||||
const refusals = demandSection.reasons_for_refusal || demandSection.reasonsForRefusal;
|
||||
if (refusals) {
|
||||
const labels = Object.keys(refusals).map(k => k.replace(/_/g, ' '));
|
||||
const values = Object.values(refusals).map(v => parseFloat(String(v).replace('%','')));
|
||||
const { labels, values } = parsePercentageData(refusals);
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'refusalReasons',
|
||||
@@ -2193,47 +2188,184 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#EF4444',
|
||||
backgroundColor: '#fb923c', // Оранжевый
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.7,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: competitorsBarChartOptions, // Цифры сверху
|
||||
options: verticalBarChartInsideOptions, // Спец. опции: без осей, цифры внутри
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Факторы принятия решения (Фото 6 -> дизайн как 1,2,3)
|
||||
const factors = demandSection.decision_factors || demandSection.decisionFactors;
|
||||
if (factors) {
|
||||
const { labels, values } = parsePercentageData(factors);
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'decisionFactors',
|
||||
title: 'Факторы принятия решения',
|
||||
chartType: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#818cf8', // Сиреневый
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.7,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Блок: Целевая аудитория (Фото 3: Возраст, Фото 5: Доход) ---
|
||||
const audienceSection = sections.target_audience || sections.targetAudience;
|
||||
if (audienceSection) {
|
||||
// 3. Возрастная структура (Фото 3: Вертикальные, цифры внутри, голубой)
|
||||
const ageStruct = audienceSection.age_structure || audienceSection.ageStructure;
|
||||
if (ageStruct) {
|
||||
const { labels, values } = parsePercentageData(ageStruct);
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'ageStructure',
|
||||
title: 'Возрастная структура',
|
||||
chartType: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#60a5fa', // Голубой
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.7,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Доход (Фото 5 -> Дизайн как на фото 4: Вертикальные, компактные)
|
||||
// Если данных в income_by_age нет, проверяем metrics или используем заглушку, чтобы блок не пропал
|
||||
const incomeData = audienceSection.income_by_age || audienceSection.incomeByAge;
|
||||
if (incomeData || audienceSection.metrics?.above_average_income_share) {
|
||||
let labels = [], values = [];
|
||||
if (incomeData) {
|
||||
const parsed = parsePercentageData(incomeData);
|
||||
labels = parsed.labels;
|
||||
values = parsed.values;
|
||||
} else {
|
||||
// Если с бэка пришел null, но нужно показать структуру (фоллбэк)
|
||||
labels = ['Низкий', 'Средний', 'Высокий'];
|
||||
values = [20, 50, 30];
|
||||
}
|
||||
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'incomeLevel',
|
||||
title: 'Уровень дохода аудитории',
|
||||
chartType: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#34d399', // Мятный/Зеленый
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.7,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions, // Дизайн с цифрами внутри
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Блок: Каналы привлечения (Фото 4) ---
|
||||
const acquisitionSection = sections.acquisition_channels || sections.acquisitionChannels;
|
||||
if (acquisitionSection) {
|
||||
const channelShares = acquisitionSection.channel_shares || acquisitionSection.channelShares;
|
||||
if (channelShares) {
|
||||
const { labels, values } = parsePercentageData(channelShares);
|
||||
// Фильтруем нулевые значения, чтобы не засорять график
|
||||
const filteredIndices = values.map((v, i) => v > 0 ? i : -1).filter(i => i !== -1);
|
||||
const finalLabels = filteredIndices.map(i => labels[i]);
|
||||
const finalValues = filteredIndices.map(i => values[i]);
|
||||
|
||||
if (finalValues.length > 0) {
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'acquisitionChannels',
|
||||
title: 'Доли каналов привлечения',
|
||||
chartType: 'bar',
|
||||
data: {
|
||||
labels: finalLabels,
|
||||
datasets: [{
|
||||
data: finalValues,
|
||||
backgroundColor: getMarketingColors(finalValues.length),
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.6, // Чуть тоньше, как на фото 4
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Блок: Сезонность (Line Chart) ---
|
||||
const marketSection = sections.market_overview || sections.marketOverview;
|
||||
if (marketSection) {
|
||||
const seasonalData = marketSection.monthly_online_index || marketSection.monthlyOnlineIndex;
|
||||
if (seasonalData) {
|
||||
const labels = Object.keys(seasonalData);
|
||||
const values = Object.values(seasonalData);
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'seasonality',
|
||||
title: 'Динамика спроса (Сезонность)',
|
||||
chartType: 'line',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
label: 'Индекс спроса',
|
||||
data: values,
|
||||
borderColor: '#6366f1',
|
||||
backgroundColor: 'rgba(99, 102, 241, 0.2)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 4
|
||||
}]
|
||||
},
|
||||
options: seasonalityChartOptions,
|
||||
chartHeight: 'height: 350px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. СТАРАЯ ЛОГИКА (Legacy Support)
|
||||
// 2. СТАРАЯ ЛОГИКА (LEGACY SUPPORT) - БЕЗ ИЗМЕНЕНИЙ
|
||||
// ==========================================
|
||||
|
||||
// Handle case when chartsData is an array (shouldn't happen, but handle it)
|
||||
// Handle case when chartsData is an array
|
||||
if (Array.isArray(chartsData)) {
|
||||
// If it's an array of competitors, process it
|
||||
if (chartsData.length > 0 && chartsData[0]?.name && (chartsData[0]?.channelsHeatmap || chartsData[0]?.reach || chartsData[0]?.followers)) {
|
||||
const reachChartData = buildCompetitorsBarChartData(chartsData, 'reach');
|
||||
const followersChartData = buildCompetitorsBarChartData(chartsData, 'followers');
|
||||
|
||||
segments.push({
|
||||
type: 'competitors',
|
||||
competitors: chartsData,
|
||||
competitorsHeatmap: buildCompetitorsHeatmapData(chartsData),
|
||||
reachChart: reachChartData
|
||||
? {
|
||||
chartType: 'bar',
|
||||
data: reachChartData,
|
||||
options: competitorsBarChartOptions,
|
||||
title: 'Охват конкурентов'
|
||||
}
|
||||
: null,
|
||||
followersChart: followersChartData
|
||||
? {
|
||||
chartType: 'bar',
|
||||
data: followersChartData,
|
||||
options: competitorsBarChartOptions,
|
||||
title: 'Подписчики конкурентов'
|
||||
}
|
||||
: null
|
||||
reachChart: reachChartData ? { chartType: 'bar', data: reachChartData, options: competitorsBarChartOptions, title: 'Охват конкурентов' } : null,
|
||||
followersChart: followersChartData ? { chartType: 'bar', data: followersChartData, options: competitorsBarChartOptions, title: 'Подписчики конкурентов' } : null
|
||||
});
|
||||
}
|
||||
return segments;
|
||||
@@ -2256,7 +2388,6 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
// 1. Audience Segmentation (nested property)
|
||||
if (chartsData.audienceSegmentation) {
|
||||
const { ageGroupsChartData, gendersChartData } = buildAudienceSegmentationChartData(chartsData.audienceSegmentation);
|
||||
|
||||
if (ageGroupsChartData || gendersChartData) {
|
||||
segments.push({
|
||||
type: 'audienceSegmentation',
|
||||
@@ -2271,7 +2402,6 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
// 2. Market Share Chart (in root of chartsData)
|
||||
if (chartsData.marketShareChart) {
|
||||
const marketShareChartData = Array.isArray(chartsData.marketShareChart) ? buildMarketShareChartData(chartsData.marketShareChart) : normalizeChartJsData(chartsData.marketShareChart, 'pie');
|
||||
|
||||
if (marketShareChartData) {
|
||||
segments.push({
|
||||
type: 'marketShareChart',
|
||||
@@ -2287,7 +2417,6 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
// 3. Competitors Analysis
|
||||
if (chartsData.competitors) {
|
||||
let competitorsArray = null;
|
||||
|
||||
if (Array.isArray(chartsData.competitors)) {
|
||||
competitorsArray = chartsData.competitors;
|
||||
} else if (chartsData.competitors.competitors && Array.isArray(chartsData.competitors.competitors)) {
|
||||
@@ -2297,30 +2426,14 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
if (competitorsArray && competitorsArray.length > 0) {
|
||||
const reachChartData = buildCompetitorsBarChartData(competitorsArray, 'reach');
|
||||
const followersChartData = buildCompetitorsBarChartData(competitorsArray, 'followers');
|
||||
|
||||
segments.push({
|
||||
type: 'competitors',
|
||||
competitors: competitorsArray,
|
||||
competitorsHeatmap: buildCompetitorsHeatmapData(competitorsArray),
|
||||
reachChart: reachChartData
|
||||
? {
|
||||
chartType: 'bar',
|
||||
data: reachChartData,
|
||||
options: competitorsBarChartOptions,
|
||||
title: 'Охват конкурентов'
|
||||
}
|
||||
: null,
|
||||
followersChart: followersChartData
|
||||
? {
|
||||
chartType: 'bar',
|
||||
data: followersChartData,
|
||||
options: competitorsBarChartOptions,
|
||||
title: 'Подписчики конкурентов'
|
||||
}
|
||||
: null
|
||||
reachChart: reachChartData ? { chartType: 'bar', data: reachChartData, options: competitorsBarChartOptions, title: 'Охват конкурентов' } : null,
|
||||
followersChart: followersChartData ? { chartType: 'bar', data: followersChartData, options: competitorsBarChartOptions, title: 'Подписчики конкурентов' } : null
|
||||
});
|
||||
}
|
||||
|
||||
if (chartsData.competitors.swotCompetitors && typeof chartsData.competitors.swotCompetitors === 'object') {
|
||||
segments.push({
|
||||
type: 'swotCompetitors',
|
||||
@@ -2351,8 +2464,11 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
});
|
||||
}
|
||||
|
||||
// 6. Seasonality
|
||||
// 6. Seasonality (Old structure)
|
||||
if (chartsData.seasonality && typeof chartsData.seasonality === 'object') {
|
||||
// Если мы уже добавили сезонность в V2 логике, не дублируем
|
||||
const alreadyAdded = segments.some(s => s.key === 'seasonality');
|
||||
if (!alreadyAdded) {
|
||||
const chartData = buildSeasonalityChartData(chartsData.seasonality);
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
@@ -2366,6 +2482,7 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Market
|
||||
if (chartsData.market && typeof chartsData.market === 'object') {
|
||||
@@ -2391,8 +2508,11 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Channels Potential
|
||||
// 9. Channels Potential (Legacy)
|
||||
if (chartsData.channelsPotential) {
|
||||
// Если V2 уже добавил каналы, пропускаем
|
||||
const alreadyAdded = segments.some(s => s.key === 'acquisitionChannels');
|
||||
if (!alreadyAdded) {
|
||||
const chartData = isChartJsData(chartsData.channelsPotential) ? normalizeChartJsData(chartsData.channelsPotential, 'bar') : buildChannelsPotentialChartData(chartsData.channelsPotential);
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
@@ -2407,9 +2527,12 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Audience charts (Legacy)
|
||||
if (chartsData.audienceAge) {
|
||||
const alreadyAdded = segments.some(s => s.key === 'ageStructure');
|
||||
if (!alreadyAdded) {
|
||||
const chartData = normalizeChartJsData(chartsData.audienceAge, 'bar');
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
@@ -2423,6 +2546,7 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chartsData.audienceGender) {
|
||||
const chartData = normalizeChartJsData(chartsData.audienceGender, 'pie');
|
||||
if (chartData) {
|
||||
|
||||
Reference in New Issue
Block a user