Improve chart Y-axis scaling for better data visualization
- Add getAxisDomain helper function for calculating nice axis bounds - Update Overview charts: Price Trend, Unit Availability, Avg Price by Plan - Update Analytics charts: Monthly Trends, Seasonality, Price per Sqft - Axes now auto-scale with 10% padding and round to nice intervals
This commit is contained in:
47
src/App.jsx
47
src/App.jsx
@ -157,6 +157,39 @@ const ApartmentDashboard = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Calculate nice axis bounds that round to reasonable values
|
||||||
|
const getAxisDomain = (data, dataKey, padding = 0.1) => {
|
||||||
|
if (!data || data.length === 0) return [0, 100];
|
||||||
|
|
||||||
|
const values = data.map(d => d[dataKey]).filter(v => v != null && !isNaN(v));
|
||||||
|
if (values.length === 0) return [0, 100];
|
||||||
|
|
||||||
|
const min = Math.min(...values);
|
||||||
|
const max = Math.max(...values);
|
||||||
|
const range = max - min;
|
||||||
|
|
||||||
|
// Add padding to the range
|
||||||
|
const paddedMin = min - range * padding;
|
||||||
|
const paddedMax = max + range * padding;
|
||||||
|
|
||||||
|
// Determine a nice interval based on the range
|
||||||
|
const getNiceInterval = (range) => {
|
||||||
|
if (range <= 10) return 1;
|
||||||
|
if (range <= 50) return 5;
|
||||||
|
if (range <= 100) return 10;
|
||||||
|
if (range <= 500) return 50;
|
||||||
|
if (range <= 1000) return 100;
|
||||||
|
if (range <= 5000) return 500;
|
||||||
|
return 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
const interval = getNiceInterval(range);
|
||||||
|
const niceMin = Math.floor(paddedMin / interval) * interval;
|
||||||
|
const niceMax = Math.ceil(paddedMax / interval) * interval;
|
||||||
|
|
||||||
|
return [Math.max(0, niceMin), niceMax];
|
||||||
|
};
|
||||||
|
|
||||||
// Process price history based on time range
|
// Process price history based on time range
|
||||||
const processPriceHistory = (priceHistory, timeRange) => {
|
const processPriceHistory = (priceHistory, timeRange) => {
|
||||||
if (!priceHistory || priceHistory.length === 0) return [];
|
if (!priceHistory || priceHistory.length === 0) return [];
|
||||||
@ -515,7 +548,7 @@ const ApartmentDashboard = () => {
|
|||||||
dataKey="date"
|
dataKey="date"
|
||||||
tickFormatter={(date) => formatDate(date)}
|
tickFormatter={(date) => formatDate(date)}
|
||||||
/>
|
/>
|
||||||
<YAxis tickFormatter={(value) => formatPrice(value)} />
|
<YAxis tickFormatter={(value) => formatPrice(value)} domain={getAxisDomain(priceHistory.filter(d => d.avgPrice !== null), 'avgPrice')} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [formatPrice(value), 'Avg Price']}
|
formatter={(value, name) => [formatPrice(value), 'Avg Price']}
|
||||||
labelFormatter={(date) => formatDate(date)}
|
labelFormatter={(date) => formatDate(date)}
|
||||||
@ -535,7 +568,7 @@ const ApartmentDashboard = () => {
|
|||||||
dataKey="date"
|
dataKey="date"
|
||||||
tickFormatter={(date) => formatDate(date)}
|
tickFormatter={(date) => formatDate(date)}
|
||||||
/>
|
/>
|
||||||
<YAxis />
|
<YAxis domain={getAxisDomain(priceHistory, 'unitCount')} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [value, 'Available Units']}
|
formatter={(value, name) => [value, 'Available Units']}
|
||||||
labelFormatter={(date) => formatDate(date)}
|
labelFormatter={(date) => formatDate(date)}
|
||||||
@ -578,7 +611,7 @@ const ApartmentDashboard = () => {
|
|||||||
<BarChart data={planStats}>
|
<BarChart data={planStats}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
|
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
|
||||||
<YAxis tickFormatter={(value) => formatPrice(value)} />
|
<YAxis tickFormatter={(value) => formatPrice(value)} domain={getAxisDomain(planStats, 'avgPrice')} />
|
||||||
<Tooltip formatter={(value, name) => [formatPrice(value), 'Avg Price']} />
|
<Tooltip formatter={(value, name) => [formatPrice(value), 'Avg Price']} />
|
||||||
<Bar dataKey="avgPrice" fill="#8B0000" />
|
<Bar dataKey="avgPrice" fill="#8B0000" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
@ -792,8 +825,8 @@ const ApartmentDashboard = () => {
|
|||||||
<ComposedChart data={analyticsData.monthlyTrends}>
|
<ComposedChart data={analyticsData.monthlyTrends}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="month" tickFormatter={(m) => m ? m.substring(2) : ''} />
|
<XAxis dataKey="month" tickFormatter={(m) => m ? m.substring(2) : ''} />
|
||||||
<YAxis yAxisId="left" tickFormatter={(v) => `$${(v/1000).toFixed(1)}k`} />
|
<YAxis yAxisId="left" tickFormatter={(v) => `$${(v/1000).toFixed(1)}k`} domain={getAxisDomain(analyticsData.monthlyTrends, 'avgPrice')} />
|
||||||
<YAxis yAxisId="right" orientation="right" />
|
<YAxis yAxisId="right" orientation="right" domain={getAxisDomain(analyticsData.monthlyTrends, 'unitCount')} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'avgPrice') return [formatPrice(value), 'Avg Price'];
|
if (name === 'avgPrice') return [formatPrice(value), 'Avg Price'];
|
||||||
@ -821,7 +854,7 @@ const ApartmentDashboard = () => {
|
|||||||
}))}>
|
}))}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="monthName" />
|
<XAxis dataKey="monthName" />
|
||||||
<YAxis tickFormatter={(v) => `$${(v/1000).toFixed(1)}k`} domain={['dataMin - 100', 'dataMax + 100']} />
|
<YAxis tickFormatter={(v) => `$${(v/1000).toFixed(1)}k`} domain={getAxisDomain(analyticsData.seasonality, 'avgPrice')} />
|
||||||
<Tooltip formatter={(value) => [formatPrice(value), 'Avg Price']} />
|
<Tooltip formatter={(value) => [formatPrice(value), 'Avg Price']} />
|
||||||
<Bar dataKey="avgPrice" fill="#8B0000" />
|
<Bar dataKey="avgPrice" fill="#8B0000" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
@ -839,7 +872,7 @@ const ApartmentDashboard = () => {
|
|||||||
<BarChart data={analyticsData.pricePerSqftByBed}>
|
<BarChart data={analyticsData.pricePerSqftByBed}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="bedrooms" tickFormatter={(v) => `${v} BR`} />
|
<XAxis dataKey="bedrooms" tickFormatter={(v) => `${v} BR`} />
|
||||||
<YAxis tickFormatter={(v) => `$${v.toFixed(2)}`} />
|
<YAxis tickFormatter={(v) => `$${v.toFixed(2)}`} domain={getAxisDomain(analyticsData.pricePerSqftByBed, 'avgPricePerSqft')} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'avgPricePerSqft') return [`$${value.toFixed(2)}/sqft`, 'Price/Sqft'];
|
if (name === 'avgPricePerSqft') return [`$${value.toFixed(2)}/sqft`, 'Price/Sqft'];
|
||||||
|
|||||||
Reference in New Issue
Block a user