fix(tradein/v2): аналитика — 2 графика в ряд, чтобы Цена×срок влезала на экран (#2480)
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 44s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 1m55s
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 44s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 1m55s
This commit is contained in:
parent
4f205bd7da
commit
a88e755403
1 changed files with 335 additions and 323 deletions
|
|
@ -327,204 +327,196 @@ export default function AnalyticsView({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* история цен */}
|
{/* история цен + цена × срок продажи — side by side so both fit the
|
||||||
<div style={{ position: "relative", ...cardStyle }}>
|
fixed-height v2 artboard panel without scrolling (reclaims ~194px
|
||||||
<div
|
of vertical space vs. stacking; chart heights unchanged). */}
|
||||||
style={{
|
<div
|
||||||
display: "flex",
|
style={{
|
||||||
alignItems: "flex-end",
|
display: "grid",
|
||||||
justifyContent: "space-between",
|
gridTemplateColumns: "1fr 1fr",
|
||||||
}}
|
gap: 16,
|
||||||
>
|
alignItems: "start",
|
||||||
<div>
|
}}
|
||||||
<div style={{ fontSize: 12, fontWeight: 600, color: tokens.ink2 }}>
|
>
|
||||||
История цен в этом доме
|
{/* история цен */}
|
||||||
</div>
|
<div style={{ position: "relative", ...cardStyle }}>
|
||||||
<div style={{ fontSize: 10, color: tokens.muted2, marginTop: 4 }}>
|
|
||||||
{historyNote}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", gap: 16, fontSize: 10 }}>
|
|
||||||
{avitoPts.length > 0 ? (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 6,
|
|
||||||
color: tokens.muted,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{ width: 14, height: 2, background: tokens.accent }}
|
|
||||||
/>
|
|
||||||
Avito
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
{yandexPts.length > 0 ? (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 6,
|
|
||||||
color: tokens.muted,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{ width: 14, height: 2, background: tokens.gold }}
|
|
||||||
/>
|
|
||||||
Яндекс
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 900 220"
|
|
||||||
style={{ width: "100%", height: 200, marginTop: 12 }}
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
role="img"
|
|
||||||
aria-label="График истории цен в этом доме: медиана ₽/м² по годам, серии Avito и Яндекс"
|
|
||||||
>
|
|
||||||
<title>История цен в этом доме — медиана ₽/м² по годам</title>
|
|
||||||
<g stroke={tokens.lineSoft2} strokeWidth={1}>
|
|
||||||
{PH_GRID_Y.map((y) => (
|
|
||||||
<line key={y} x1={40} y1={y} x2={900} y2={y} />
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<g fontFamily={tokens.font.mono} fontSize={11} fill={tokens.muted2}>
|
|
||||||
{data.priceHistory.yTicks.map((t) => (
|
|
||||||
<text key={t.label} x={0} y={t.y + 4}>
|
|
||||||
{t.label}
|
|
||||||
</text>
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fontFamily={tokens.font.mono}
|
|
||||||
fontSize={11}
|
|
||||||
fill={tokens.muted2}
|
|
||||||
textAnchor="middle"
|
|
||||||
>
|
|
||||||
{data.priceHistory.years.map((yr, i) => (
|
|
||||||
<text
|
|
||||||
key={yr}
|
|
||||||
x={histYearX(i, data.priceHistory.years.length)}
|
|
||||||
y={214}
|
|
||||||
>
|
|
||||||
{yr}
|
|
||||||
</text>
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<polyline
|
|
||||||
points={data.priceHistory.avito}
|
|
||||||
fill="none"
|
|
||||||
stroke={tokens.accent}
|
|
||||||
strokeWidth={2.5}
|
|
||||||
/>
|
|
||||||
<polyline
|
|
||||||
points={data.priceHistory.yandex}
|
|
||||||
fill="none"
|
|
||||||
stroke={tokens.gold}
|
|
||||||
strokeWidth={2.5}
|
|
||||||
/>
|
|
||||||
{/* white dot fill on an accent ring — documented one-off, no token (design l.568) */}
|
|
||||||
<g
|
|
||||||
fill="#fff"
|
|
||||||
stroke={tokens.accent}
|
|
||||||
strokeWidth={2}
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
<title>Архивные объявления Avito · нажмите, чтобы открыть</title>
|
|
||||||
{avitoPts.map((p, i) => (
|
|
||||||
<circle key={i} cx={p.x} cy={p.y} r={3.5} />
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill={tokens.gold}
|
|
||||||
stroke={tokens.gold}
|
|
||||||
strokeWidth={2}
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
{yandexDots.map((p, i) => (
|
|
||||||
<circle key={i} cx={p.x} cy={p.y} r={3.5} />
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="transparent"
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
onMouseEnter={() => setHoverHist(true)}
|
|
||||||
onMouseLeave={() => setHoverHist(false)}
|
|
||||||
onClick={() => onNavigate?.(2)}
|
|
||||||
>
|
|
||||||
{hoverPts.map((p, i) => (
|
|
||||||
<circle key={i} cx={p.x} cy={p.y} r={13} />
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
{hoverHist ? (
|
|
||||||
// Dark navy tooltip surface + white text — documented one-off literals
|
|
||||||
// matching the design (l.572); no token equivalent.
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
display: "flex",
|
||||||
left: 64,
|
alignItems: "flex-end",
|
||||||
top: 54,
|
justifyContent: "space-between",
|
||||||
background: "rgba(23,38,58,.96)",
|
|
||||||
color: "#fff",
|
|
||||||
borderRadius: 7,
|
|
||||||
padding: "9px 13px",
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
zIndex: 6,
|
|
||||||
pointerEvents: "none",
|
|
||||||
boxShadow: "0 10px 26px rgba(20,40,70,.28)",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontWeight: 600 }}>
|
<div>
|
||||||
Архивные объявления этого дома
|
<div style={{ fontSize: 12, fontWeight: 600, color: tokens.ink2 }}>
|
||||||
|
История цен в этом доме
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 10, color: tokens.muted2, marginTop: 4 }}>
|
||||||
|
{historyNote}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: tokens.barMid, fontSize: 10, marginTop: 2 }}>
|
<div style={{ display: "flex", gap: 16, fontSize: 10 }}>
|
||||||
Нажмите, чтобы открыть список →
|
{avitoPts.length > 0 ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 6,
|
||||||
|
color: tokens.muted,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{ width: 14, height: 2, background: tokens.accent }}
|
||||||
|
/>
|
||||||
|
Avito
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{yandexPts.length > 0 ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 6,
|
||||||
|
color: tokens.muted,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{ width: 14, height: 2, background: tokens.gold }}
|
||||||
|
/>
|
||||||
|
Яндекс
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
<svg
|
||||||
</div>
|
viewBox="0 0 900 220"
|
||||||
|
style={{ width: "100%", height: 200, marginTop: 12 }}
|
||||||
{/* цена × срок продажи (детально) */}
|
preserveAspectRatio="none"
|
||||||
<div style={cardStyle}>
|
role="img"
|
||||||
<div
|
aria-label="График истории цен в этом доме: медиана ₽/м² по годам, серии Avito и Яндекс"
|
||||||
style={{
|
>
|
||||||
display: "flex",
|
<title>История цен в этом доме — медиана ₽/м² по годам</title>
|
||||||
alignItems: "flex-end",
|
<g stroke={tokens.lineSoft2} strokeWidth={1}>
|
||||||
justifyContent: "space-between",
|
{PH_GRID_Y.map((y) => (
|
||||||
}}
|
<line key={y} x1={40} y1={y} x2={900} y2={y} />
|
||||||
>
|
))}
|
||||||
<div>
|
</g>
|
||||||
<div style={{ fontSize: 12, fontWeight: 600, color: tokens.ink2 }}>
|
<g fontFamily={tokens.font.mono} fontSize={11} fill={tokens.muted2}>
|
||||||
Цена × срок продажи
|
{data.priceHistory.yTicks.map((t) => (
|
||||||
</div>
|
<text key={t.label} x={0} y={t.y + 4}>
|
||||||
<div style={{ fontSize: 10, color: tokens.muted2, marginTop: 4 }}>
|
{t.label}
|
||||||
Зависимость цены от срока экспозиции · {data.scatterDetail.note}
|
</text>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</g>
|
||||||
<div style={{ display: "flex", gap: 14, fontSize: 10 }}>
|
<g
|
||||||
<span
|
fontFamily={tokens.font.mono}
|
||||||
|
fontSize={11}
|
||||||
|
fill={tokens.muted2}
|
||||||
|
textAnchor="middle"
|
||||||
|
>
|
||||||
|
{data.priceHistory.years.map((yr, i) => (
|
||||||
|
<text
|
||||||
|
key={yr}
|
||||||
|
x={histYearX(i, data.priceHistory.years.length)}
|
||||||
|
y={214}
|
||||||
|
>
|
||||||
|
{yr}
|
||||||
|
</text>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
<polyline
|
||||||
|
points={data.priceHistory.avito}
|
||||||
|
fill="none"
|
||||||
|
stroke={tokens.accent}
|
||||||
|
strokeWidth={2.5}
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
points={data.priceHistory.yandex}
|
||||||
|
fill="none"
|
||||||
|
stroke={tokens.gold}
|
||||||
|
strokeWidth={2.5}
|
||||||
|
/>
|
||||||
|
{/* white dot fill on an accent ring — documented one-off, no token (design l.568) */}
|
||||||
|
<g
|
||||||
|
fill="#fff"
|
||||||
|
stroke={tokens.accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<title>Архивные объявления Avito · нажмите, чтобы открыть</title>
|
||||||
|
{avitoPts.map((p, i) => (
|
||||||
|
<circle key={i} cx={p.x} cy={p.y} r={3.5} />
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
fill={tokens.gold}
|
||||||
|
stroke={tokens.gold}
|
||||||
|
strokeWidth={2}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
{yandexDots.map((p, i) => (
|
||||||
|
<circle key={i} cx={p.x} cy={p.y} r={3.5} />
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
fill="transparent"
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onMouseEnter={() => setHoverHist(true)}
|
||||||
|
onMouseLeave={() => setHoverHist(false)}
|
||||||
|
onClick={() => onNavigate?.(2)}
|
||||||
|
>
|
||||||
|
{hoverPts.map((p, i) => (
|
||||||
|
<circle key={i} cx={p.x} cy={p.y} r={13} />
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
{hoverHist ? (
|
||||||
|
// Dark navy tooltip surface + white text — documented one-off literals
|
||||||
|
// matching the design (l.572); no token equivalent.
|
||||||
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
position: "absolute",
|
||||||
alignItems: "center",
|
left: 64,
|
||||||
gap: 6,
|
top: 54,
|
||||||
color: tokens.muted,
|
background: "rgba(23,38,58,.96)",
|
||||||
|
color: "#fff",
|
||||||
|
borderRadius: 7,
|
||||||
|
padding: "9px 13px",
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
zIndex: 6,
|
||||||
|
pointerEvents: "none",
|
||||||
|
boxShadow: "0 10px 26px rgba(20,40,70,.28)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<div style={{ fontWeight: 600 }}>
|
||||||
style={{
|
Архивные объявления этого дома
|
||||||
width: 8,
|
</div>
|
||||||
height: 8,
|
<div style={{ color: tokens.barMid, fontSize: 10, marginTop: 2 }}>
|
||||||
borderRadius: "50%",
|
Нажмите, чтобы открыть список →
|
||||||
background: tokens.accent,
|
</div>
|
||||||
}}
|
</div>
|
||||||
/>
|
) : null}
|
||||||
аналоги
|
</div>
|
||||||
</span>
|
|
||||||
{data.scatterDetail.deals.length > 0 ? (
|
{/* цена × срок продажи (детально) */}
|
||||||
|
<div style={cardStyle}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 600, color: tokens.ink2 }}>
|
||||||
|
Цена × срок продажи
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 10, color: tokens.muted2, marginTop: 4 }}>
|
||||||
|
Зависимость цены от срока экспозиции · {data.scatterDetail.note}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", gap: 14, fontSize: 10 }}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|
@ -538,149 +530,169 @@ export default function AnalyticsView({
|
||||||
width: 8,
|
width: 8,
|
||||||
height: 8,
|
height: 8,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
background: tokens.scatterDeal,
|
background: tokens.accent,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
сделки
|
аналоги
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
{data.scatterDetail.deals.length > 0 ? (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 6,
|
gap: 6,
|
||||||
color: tokens.muted,
|
color: tokens.muted,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: "50%",
|
||||||
|
background: tokens.scatterDeal,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
сделки
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
width: 8,
|
display: "flex",
|
||||||
height: 8,
|
alignItems: "center",
|
||||||
borderRadius: "50%",
|
gap: 6,
|
||||||
background: tokens.ink,
|
color: tokens.muted,
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
объект
|
<span
|
||||||
</span>
|
style={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: "50%",
|
||||||
|
background: tokens.ink,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
объект
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 900 300"
|
||||||
|
style={{ width: "100%", height: 280, marginTop: 12 }}
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
role="img"
|
||||||
|
aria-label="Точечный график зависимости цены продажи от срока экспозиции: аналоги, сделки и ваш объект"
|
||||||
|
>
|
||||||
|
<title>Цена × срок продажи</title>
|
||||||
|
<g stroke={tokens.lineSoft2} strokeWidth={1}>
|
||||||
|
<line x1={60} y1={20} x2={880} y2={20} />
|
||||||
|
<line x1={60} y1={77} x2={880} y2={77} />
|
||||||
|
<line x1={60} y1={134} x2={880} y2={134} />
|
||||||
|
<line x1={60} y1={191} x2={880} y2={191} />
|
||||||
|
<line x1={60} y1={250} x2={880} y2={250} />
|
||||||
|
<line x1={265} y1={20} x2={265} y2={250} />
|
||||||
|
<line x1={470} y1={20} x2={470} y2={250} />
|
||||||
|
<line x1={675} y1={20} x2={675} y2={250} />
|
||||||
|
</g>
|
||||||
|
<line
|
||||||
|
x1={60}
|
||||||
|
y1={20}
|
||||||
|
x2={60}
|
||||||
|
y2={250}
|
||||||
|
stroke={tokens.line3}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1={60}
|
||||||
|
y1={250}
|
||||||
|
x2={880}
|
||||||
|
y2={250}
|
||||||
|
stroke={tokens.line3}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
<g fill={tokens.scatterDeal}>
|
||||||
|
{data.scatterDetail.deals.map((p, i) => (
|
||||||
|
<circle key={i} cx={p.x} cy={p.y} r={p.r} />
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
<g fill={tokens.accent}>
|
||||||
|
{data.scatterDetail.analogs.map((p, i) => (
|
||||||
|
<circle key={i} cx={p.x} cy={p.y} r={p.r} />
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
<g opacity={data.scatterDetail.subjectApprox ? 0.55 : 1}>
|
||||||
|
{/* V4: subjectApprox marks a fallback x (est_days_on_market unknown) —
|
||||||
|
dashed ring + title flag it as an estimate, not a measurement. */}
|
||||||
|
{data.scatterDetail.subjectApprox ? (
|
||||||
|
<title>
|
||||||
|
Срок продажи — оценка (~90 дн.), точных данных нет
|
||||||
|
</title>
|
||||||
|
) : null}
|
||||||
|
<circle
|
||||||
|
cx={data.scatterDetail.subject.x}
|
||||||
|
cy={data.scatterDetail.subject.y}
|
||||||
|
r={data.scatterDetail.subject.r}
|
||||||
|
fill={tokens.ink}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={data.scatterDetail.subject.x}
|
||||||
|
cy={data.scatterDetail.subject.y}
|
||||||
|
r={11}
|
||||||
|
fill="none"
|
||||||
|
stroke={tokens.ink}
|
||||||
|
strokeWidth={1.2}
|
||||||
|
strokeDasharray={
|
||||||
|
data.scatterDetail.subjectApprox ? "3 3" : undefined
|
||||||
|
}
|
||||||
|
opacity={0.4}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
fontFamily={tokens.font.mono}
|
||||||
|
fontSize={11}
|
||||||
|
fill={tokens.muted2}
|
||||||
|
textAnchor="end"
|
||||||
|
>
|
||||||
|
{data.scatterDetail.yTicks.map((t) => (
|
||||||
|
<text key={t.label} x={52} y={t.y}>
|
||||||
|
{t.label}
|
||||||
|
</text>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
fontFamily={tokens.font.mono}
|
||||||
|
fontSize={11}
|
||||||
|
fill={tokens.muted2}
|
||||||
|
textAnchor="middle"
|
||||||
|
>
|
||||||
|
{data.scatterDetail.xTicks.map((t) => (
|
||||||
|
<text key={t.label} x={t.x} y={268}>
|
||||||
|
{t.label}
|
||||||
|
</text>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x={470}
|
||||||
|
y={288}
|
||||||
|
fontSize={10}
|
||||||
|
letterSpacing={1}
|
||||||
|
fill={tokens.muted2}
|
||||||
|
textAnchor="middle"
|
||||||
|
>
|
||||||
|
СРОК ПРОДАЖИ, ДН
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x={18}
|
||||||
|
y={135}
|
||||||
|
fontSize={10}
|
||||||
|
letterSpacing={1}
|
||||||
|
fill={tokens.muted2}
|
||||||
|
textAnchor="middle"
|
||||||
|
transform="rotate(-90 18 135)"
|
||||||
|
>
|
||||||
|
ЦЕНА ПРОДАЖИ, ₽
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<svg
|
|
||||||
viewBox="0 0 900 300"
|
|
||||||
style={{ width: "100%", height: 280, marginTop: 12 }}
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
role="img"
|
|
||||||
aria-label="Точечный график зависимости цены продажи от срока экспозиции: аналоги, сделки и ваш объект"
|
|
||||||
>
|
|
||||||
<title>Цена × срок продажи</title>
|
|
||||||
<g stroke={tokens.lineSoft2} strokeWidth={1}>
|
|
||||||
<line x1={60} y1={20} x2={880} y2={20} />
|
|
||||||
<line x1={60} y1={77} x2={880} y2={77} />
|
|
||||||
<line x1={60} y1={134} x2={880} y2={134} />
|
|
||||||
<line x1={60} y1={191} x2={880} y2={191} />
|
|
||||||
<line x1={60} y1={250} x2={880} y2={250} />
|
|
||||||
<line x1={265} y1={20} x2={265} y2={250} />
|
|
||||||
<line x1={470} y1={20} x2={470} y2={250} />
|
|
||||||
<line x1={675} y1={20} x2={675} y2={250} />
|
|
||||||
</g>
|
|
||||||
<line
|
|
||||||
x1={60}
|
|
||||||
y1={20}
|
|
||||||
x2={60}
|
|
||||||
y2={250}
|
|
||||||
stroke={tokens.line3}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1={60}
|
|
||||||
y1={250}
|
|
||||||
x2={880}
|
|
||||||
y2={250}
|
|
||||||
stroke={tokens.line3}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
/>
|
|
||||||
<g fill={tokens.scatterDeal}>
|
|
||||||
{data.scatterDetail.deals.map((p, i) => (
|
|
||||||
<circle key={i} cx={p.x} cy={p.y} r={p.r} />
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<g fill={tokens.accent}>
|
|
||||||
{data.scatterDetail.analogs.map((p, i) => (
|
|
||||||
<circle key={i} cx={p.x} cy={p.y} r={p.r} />
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<g opacity={data.scatterDetail.subjectApprox ? 0.55 : 1}>
|
|
||||||
{/* V4: subjectApprox marks a fallback x (est_days_on_market unknown) —
|
|
||||||
dashed ring + title flag it as an estimate, not a measurement. */}
|
|
||||||
{data.scatterDetail.subjectApprox ? (
|
|
||||||
<title>
|
|
||||||
Срок продажи — оценка (~90 дн.), точных данных нет
|
|
||||||
</title>
|
|
||||||
) : null}
|
|
||||||
<circle
|
|
||||||
cx={data.scatterDetail.subject.x}
|
|
||||||
cy={data.scatterDetail.subject.y}
|
|
||||||
r={data.scatterDetail.subject.r}
|
|
||||||
fill={tokens.ink}
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx={data.scatterDetail.subject.x}
|
|
||||||
cy={data.scatterDetail.subject.y}
|
|
||||||
r={11}
|
|
||||||
fill="none"
|
|
||||||
stroke={tokens.ink}
|
|
||||||
strokeWidth={1.2}
|
|
||||||
strokeDasharray={
|
|
||||||
data.scatterDetail.subjectApprox ? "3 3" : undefined
|
|
||||||
}
|
|
||||||
opacity={0.4}
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fontFamily={tokens.font.mono}
|
|
||||||
fontSize={11}
|
|
||||||
fill={tokens.muted2}
|
|
||||||
textAnchor="end"
|
|
||||||
>
|
|
||||||
{data.scatterDetail.yTicks.map((t) => (
|
|
||||||
<text key={t.label} x={52} y={t.y}>
|
|
||||||
{t.label}
|
|
||||||
</text>
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fontFamily={tokens.font.mono}
|
|
||||||
fontSize={11}
|
|
||||||
fill={tokens.muted2}
|
|
||||||
textAnchor="middle"
|
|
||||||
>
|
|
||||||
{data.scatterDetail.xTicks.map((t) => (
|
|
||||||
<text key={t.label} x={t.x} y={268}>
|
|
||||||
{t.label}
|
|
||||||
</text>
|
|
||||||
))}
|
|
||||||
</g>
|
|
||||||
<text
|
|
||||||
x={470}
|
|
||||||
y={288}
|
|
||||||
fontSize={10}
|
|
||||||
letterSpacing={1}
|
|
||||||
fill={tokens.muted2}
|
|
||||||
textAnchor="middle"
|
|
||||||
>
|
|
||||||
СРОК ПРОДАЖИ, ДН
|
|
||||||
</text>
|
|
||||||
<text
|
|
||||||
x={18}
|
|
||||||
y={135}
|
|
||||||
fontSize={10}
|
|
||||||
letterSpacing={1}
|
|
||||||
fill={tokens.muted2}
|
|
||||||
textAnchor="middle"
|
|
||||||
transform="rotate(-90 18 135)"
|
|
||||||
>
|
|
||||||
ЦЕНА ПРОДАЖИ, ₽
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue