261 lines
9.7 KiB
JavaScript
261 lines
9.7 KiB
JavaScript
// Данные по городам
|
||
const CITIES = {
|
||
"Алматы": { price: 650, year1: 7.2, year2: 18.4, count: 12450 },
|
||
"Астана": { price: 580, year1: 5.8, year2: 15.1, count: 8920 },
|
||
"Шымкент": { price: 390, year1: 6.1, year2: 14.7, count: 4560 },
|
||
"Караганда": { price: 320, year1: 4.2, year2: 12.3, count: 3210 },
|
||
"Актобе": { price: 340, year1: 5.1, year2: 13.8, count: 2180 },
|
||
"Тараз": { price: 290, year1: 3.9, year2: 11.2, count: 1540 },
|
||
"Павлодар": { price: 310, year1: 4.5, year2: 12.9, count: 1890 },
|
||
"Усть-Каменогорск": { price: 360, year1: 5.3, year2: 14.1, count: 1670 },
|
||
"Семей": { price: 280, year1: 3.7, year2: 10.8, count: 1230 },
|
||
"Атырау": { price: 420, year1: 6.8, year2: 16.2, count: 1980 },
|
||
"Костанай": { price: 300, year1: 4.1, year2: 11.5, count: 1450 },
|
||
"Кызылорда": { price: 330, year1: 4.8, year2: 13.1, count: 1120 }
|
||
};
|
||
|
||
// История цен по Алматы (24 месяца)
|
||
const ALMATY_HISTORY = [
|
||
{ month: "Янв 2024", price: 550 }, { month: "Фев 2024", price: 555 },
|
||
{ month: "Мар 2024", price: 560 }, { month: "Апр 2024", price: 565 },
|
||
{ month: "Май 2024", price: 572 }, { month: "Июн 2024", price: 578 },
|
||
{ month: "Июл 2024", price: 585 }, { month: "Авг 2024", price: 590 },
|
||
{ month: "Сен 2024", price: 598 }, { month: "Окт 2024", price: 605 },
|
||
{ month: "Ноя 2024", price: 612 }, { month: "Дек 2024", price: 620 },
|
||
{ month: "Янв 2025", price: 625 }, { month: "Фев 2025", price: 630 },
|
||
{ month: "Мар 2025", price: 635 }, { month: "Апр 2025", price: 640 },
|
||
{ month: "Май 2025", price: 645 }, { month: "Июн 2025", price: 648 },
|
||
{ month: "Июл 2025", price: 650 }, { month: "Авг 2025", price: 652 },
|
||
{ month: "Сен 2025", price: 655 }, { month: "Окт 2025", price: 658 },
|
||
{ month: "Ноя 2025", price: 660 }, { month: "Дек 2025", price: 665 }
|
||
];
|
||
|
||
// Генерация истории для других городов (на основе Алматы)
|
||
function generateHistory(basePrice, growthRate) {
|
||
const base = ALMATY_HISTORY.map((h, i) => ({
|
||
month: h.month,
|
||
price: Math.round(basePrice * (h.price / 650) * (1 + growthRate / 100))
|
||
}));
|
||
return base;
|
||
}
|
||
|
||
// Инициализация при загрузке
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
renderCitiesTable();
|
||
renderCitiesCards();
|
||
initMainChart();
|
||
initAnalyticsCharts();
|
||
});
|
||
|
||
// Рендер таблицы городов
|
||
function renderCitiesTable() {
|
||
const tbody = document.getElementById('cities-table');
|
||
const sorted = Object.entries(CITIES).sort((a, b) => b[1].price - a[1].price);
|
||
|
||
tbody.innerHTML = sorted.map(([city, data]) => {
|
||
const trendClass = data.year1 > 5 ? 'trend-up' : data.year1 < 3 ? 'trend-down' : '';
|
||
return `
|
||
<tr class="border-b last:border-0 hover:bg-gray-50">
|
||
<td class="py-3 font-medium">${city}</td>
|
||
<td class="py-3">${data.price.toLocaleString('ru-RU')} тыс. ₸</td>
|
||
<td class="py-3 ${trendClass}">${data.year1 > 0 ? '+' : ''}${data.year1}%</td>
|
||
<td class="py-3 ${trendClass}">${data.year2 > 0 ? '+' : ''}${data.year2}%</td>
|
||
<td class="py-3 text-gray-600">${data.count.toLocaleString('ru-RU')}</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
// Рендер карточек городов
|
||
function renderCitiesCards() {
|
||
const container = document.getElementById('cities-cards');
|
||
const sorted = Object.entries(CITIES).sort((a, b) => b[1].price - a[1].price);
|
||
|
||
container.innerHTML = sorted.map(([city, data]) => `
|
||
<div class="card hover:shadow-lg transition-shadow cursor-pointer" onclick="alert('Детальная статистика по ${city} будет в следующей версии')">
|
||
<h3 class="text-lg font-semibold mb-2">${city}</h3>
|
||
<div class="text-2xl font-bold text-[var(--accent)] mb-1">${data.price.toLocaleString('ru-RU')} тыс. ₸</div>
|
||
<div class="text-sm text-gray-600">за м²</div>
|
||
<div class="flex gap-2 mt-3 text-sm">
|
||
<span class="trend-up">+${data.year1}% за год</span>
|
||
<span class="text-gray-400">|</span>
|
||
<span class="trend-up">+${data.year2}% за 2 года</span>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// Показ аналитики
|
||
function showAnalytics() {
|
||
const city = document.getElementById('filter-city').value;
|
||
const rooms = parseInt(document.getElementById('filter-rooms').value);
|
||
const areaFrom = parseInt(document.getElementById('filter-area-from').value) || 40;
|
||
const areaTo = parseInt(document.getElementById('filter-area-to').value) || 80;
|
||
const avgArea = (areaFrom + areaTo) / 2;
|
||
|
||
const cityData = CITIES[city];
|
||
const avgPriceM2 = cityData.price;
|
||
const avgFlatPrice = Math.round(avgPriceM2 * avgArea / 1000);
|
||
|
||
document.getElementById('stat-avg-price').textContent = `${avgFlatPrice.toLocaleString('ru-RU')} млн ₸`;
|
||
document.getElementById('stat-price-m2').textContent = `${avgPriceM2.toLocaleString('ru-RU')} тыс. ₸`;
|
||
document.getElementById('stat-year2').textContent = `+${cityData.year2}%`;
|
||
document.getElementById('stat-year1').textContent = `+${cityData.year1}%`;
|
||
|
||
document.getElementById('results').classList.remove('hidden');
|
||
document.getElementById('results').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
|
||
updateMainChart(city);
|
||
}
|
||
|
||
// Обновление главного графика
|
||
let mainChart = null;
|
||
function initMainChart() {
|
||
const ctx = document.getElementById('priceChart').getContext('2d');
|
||
mainChart = new Chart(ctx, {
|
||
type: 'line',
|
||
data: {
|
||
labels: ALMATY_HISTORY.map(h => h.month),
|
||
datasets: [{
|
||
label: 'Цена за м², тыс. ₸',
|
||
data: ALMATY_HISTORY.map(h => h.price),
|
||
borderColor: 'var(--accent)',
|
||
backgroundColor: 'rgba(0, 184, 204, 0.1)',
|
||
tension: 0.4,
|
||
fill: true,
|
||
pointRadius: 2,
|
||
pointHoverRadius: 4
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: { display: false },
|
||
tooltip: {
|
||
callbacks: {
|
||
label: (ctx) => `${ctx.raw.toLocaleString('ru-RU')} тыс. ₸`
|
||
}
|
||
}
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: false,
|
||
ticks: { callback: (v) => v + ' тыс.' }
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function updateMainChart(city) {
|
||
const cityData = CITIES[city];
|
||
const history = generateHistory(cityData.price, cityData.year2);
|
||
|
||
mainChart.data.labels = history.map(h => h.month);
|
||
mainChart.data.datasets[0].data = history.map(h => h.price);
|
||
mainChart.update();
|
||
}
|
||
|
||
// Калькулятор сравнения
|
||
function calculateComparison() {
|
||
const city = document.getElementById('calc-city').value;
|
||
const price = parseFloat(document.getElementById('calc-price').value);
|
||
const area = parseFloat(document.getElementById('calc-area').value);
|
||
const rooms = parseInt(document.getElementById('calc-rooms').value);
|
||
|
||
if (!price || !area) {
|
||
alert('Введите цену и площадь');
|
||
return;
|
||
}
|
||
|
||
const cityData = CITIES[city];
|
||
const priceM2Flat = (price * 1000000) / area;
|
||
const priceM2Market = cityData.price * 1000;
|
||
const diff = ((priceM2Flat / priceM2Market) - 1) * 100;
|
||
|
||
const resultEl = document.getElementById('calc-result');
|
||
const diffEl = document.getElementById('calc-diff');
|
||
const msgEl = document.getElementById('calc-message');
|
||
|
||
resultEl.classList.remove('hidden');
|
||
diffEl.textContent = `${diff > 0 ? '+' : ''}${diff.toFixed(1)}%`;
|
||
|
||
if (diff > 5) {
|
||
diffEl.className = 'text-4xl font-bold mb-2 trend-down';
|
||
msgEl.textContent = 'дороже средней цены рынка';
|
||
} else if (diff < -5) {
|
||
diffEl.className = 'text-4xl font-bold mb-2 trend-up';
|
||
msgEl.textContent = 'дешевле средней цены рынка 🟢';
|
||
} else {
|
||
diffEl.className = 'text-4xl font-bold mb-2';
|
||
msgEl.textContent = 'соответствует рыночной цене';
|
||
}
|
||
}
|
||
|
||
// Графики аналитики
|
||
let almatyChart = null;
|
||
let citiesChart = null;
|
||
|
||
function initAnalyticsCharts() {
|
||
// График Алматы
|
||
const almatyCtx = document.getElementById('almatyChart').getContext('2d');
|
||
almatyChart = new Chart(almatyCtx, {
|
||
type: 'line',
|
||
data: {
|
||
labels: ALMATY_HISTORY.map(h => h.month),
|
||
datasets: [{
|
||
label: 'Алматы',
|
||
data: ALMATY_HISTORY.map(h => h.price),
|
||
borderColor: 'var(--accent)',
|
||
tension: 0.4,
|
||
pointRadius: 0,
|
||
pointHoverRadius: 4
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: { legend: { display: false } },
|
||
scales: {
|
||
y: { ticks: { callback: (v) => v + ' тыс.' } }
|
||
}
|
||
}
|
||
});
|
||
|
||
// Сравнение городов
|
||
const citiesCtx = document.getElementById('citiesChart').getContext('2d');
|
||
const topCities = Object.entries(CITIES)
|
||
.sort((a, b) => b[1].price - a[1].price)
|
||
.slice(0, 5);
|
||
|
||
citiesChart = new Chart(citiesCtx, {
|
||
type: 'bar',
|
||
data: {
|
||
labels: topCities.map(c => c[0]),
|
||
datasets: [{
|
||
label: 'Цена за м², тыс. ₸',
|
||
data: topCities.map(c => c[1].price),
|
||
backgroundColor: 'var(--accent)',
|
||
borderRadius: 4
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: { legend: { display: false } },
|
||
scales: {
|
||
y: { beginAtZero: true }
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Навигация
|
||
document.querySelectorAll('.nav-link').forEach(link => {
|
||
link.addEventListener('click', (e) => {
|
||
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
||
e.target.classList.add('active');
|
||
});
|
||
});
|