Skip to content
This repository has been archived by the owner on Jul 19, 2022. It is now read-only.

Guild progress chart redesign #73

Merged
merged 1 commit into from
Apr 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion main/src/api/guilds/interfaces/IAPIClub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface IClubResponse extends IBaseClubResponseV2 {
owner: ISummonerResponse;
}

interface IClubRatingResponse {
export interface IClubRatingResponse {
id: number;
club: IClubResponseV1;
rank_reward: string;
Expand Down
12 changes: 10 additions & 2 deletions main/src/api/guilds/interfaces/IInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,19 @@ export interface IInternalGuildPathPoint {
rank?: number;
description?: string;
}
interface IInternalGuildCurrentPosition extends IInternalGuildPathPoint {
export interface IInternalGuildPathSegment {
start: IInternalGuildPathPoint;
end: IInternalGuildPathPoint;
isCurrent: boolean;
progress: number;
points: IInternalGuildPathPoint[];
isTop?: boolean;
}
export interface IInternalGuildCurrentPosition extends IInternalGuildPathPoint {
rank_reward: string;
games: number;
}
export interface IInternalGuildPath {
current_position: IInternalGuildCurrentPosition;
points: IInternalGuildPathPoint[];
segments: IInternalGuildPathSegment[];
}
95 changes: 63 additions & 32 deletions main/src/handlers/guild/guild-path.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
import { GuildsClient } from "@guilds-main/api/guilds";
import { IInternalGuildPath, IInternalGuildPathPoint } from "@guilds-main/api/guilds/interfaces/IInternal";
import { IClubRatingResponse } from "@guilds-main/api/guilds/interfaces/IAPIClub";
import { IInternalGuildCurrentPosition, IInternalGuildPath, IInternalGuildPathPoint, IInternalGuildPathSegment } from "@guilds-main/api/guilds/interfaces/IInternal";
import { calculateRelativeProgress } from "@guilds-shared/helpers/points";


function absolutePointsToRelative(guildPoints: number, absolutePoints: IInternalGuildPathPoint[]): IInternalGuildPathPoint[] {
if (guildPoints < 3000) {
return absolutePoints;
const guildRatingToPoint = (club: IClubRatingResponse): IInternalGuildPathPoint => ({ rank: club.rank, points: club.points });
const sortByPoints = (first: IInternalGuildPathPoint, second: IInternalGuildPathPoint): number => first.points - second.points;

function constructSegment(guildPoint: IInternalGuildPathPoint, start: IInternalGuildPathPoint, end: IInternalGuildPathPoint): IInternalGuildPathSegment {
const progress = calculateRelativeProgress(guildPoint.points, start.points, end.points);
const isCurrent = start.points < guildPoint.points && guildPoint.points < end.points;

return {
start,
end,
isCurrent,
progress,
points: []
};
}

function constructSegments(guildPoint: IInternalGuildPathPoint, points: IInternalGuildPathPoint[], topPoints: IInternalGuildPathPoint[] = []): IInternalGuildPathSegment[] {
const segments: IInternalGuildPathSegment[] = [];

for (let i = 0, len = points.length - 1; i < len; i++) {
const start: IInternalGuildPathPoint = points[i];
const end: IInternalGuildPathPoint = points[i + 1];

segments.push(constructSegment(guildPoint, start, end));
}

const relativeStart = guildPoints - 3000;
const relativePoints = absolutePoints.filter((point) => point.points > relativeStart);
if (topPoints.length) {
segments.push(constructSegment(guildPoint, points[points.length - 1], topPoints[0]));

const topSegment = constructSegment(guildPoint, topPoints[0], topPoints[topPoints.length - 1]);
topSegment.points = topSegment.points.concat(topPoints.slice(1, topPoints.length - 1));
topSegment.isTop = true;

segments.push(topSegment);
}

return [{ points: relativeStart }, ...relativePoints];
return segments;
}

export async function getGuildSeasonPath(guildsClient: GuildsClient, season_id: number): Promise<IInternalGuildPath> {
const absolutePoints: IInternalGuildPathPoint[] = [{ points: 0 }];
let absolutePoints: IInternalGuildPathPoint[] = [
{ points: 0 }, { description: "Старт", points: 1000 }
];

const season_data = await guildsClient.api.getSeasonRatingForMyClub(season_id);
let { points } = season_data;
Expand All @@ -30,47 +62,46 @@ export async function getGuildSeasonPath(guildsClient: GuildsClient, season_id:
}
}

absolutePoints.push({
description: "Старт",
points: 1000
});
const currentPosition: IInternalGuildCurrentPosition = { games, points, rank, rank_reward };

const noticiablePlaces = [500, 250, 100, 50];
const clubOnNoticiablePlaces = await Promise.all(noticiablePlaces.map(place => guildsClient.getClubOnSeasonTopN(season_id, place)));
const clubsInTop10 = await guildsClient.api.getTopClubsForSeasonWithId(season_id, { page: 1, per_page: 10 });

const placesToGet = [500, 250, 100, 50, 10, 3, 2, 1].filter(place => rank === 0 ? place : place < rank).slice(0, 2);
const clubsOnPlaces = await Promise.all(placesToGet.map(place => guildsClient.getClubOnSeasonTopN(season_id, place)));
const clubsPathPoints = clubsOnPlaces.map<IInternalGuildPathPoint>(club => ({ rank: club.rank, points: club.points }));
absolutePoints = absolutePoints.concat(clubOnNoticiablePlaces.map<IInternalGuildPathPoint>(guildRatingToPoint)).sort(sortByPoints);
const topPoints = clubsInTop10.map<IInternalGuildPathPoint>(guildRatingToPoint).sort(sortByPoints);

return {
current_position: { games, points, rank, rank_reward },
points: absolutePointsToRelative(points, [...absolutePoints, ...clubsPathPoints])
current_position: currentPosition,
segments: constructSegments(currentPosition, absolutePoints, topPoints)
};
}

export async function getGuildStagePath(guildsClient: GuildsClient, season_id: number, stage_id: number): Promise<IInternalGuildPath> {
const absolutePoints: IInternalGuildPathPoint[] = [{
points: 0
}];
let absolutePoints: IInternalGuildPathPoint[] = [
{ points: 0 }, { description: "Старт", points: 1000 }
];

const { games, points, rank, rank_reward } = await guildsClient.api.getStageRatingForMyClub(stage_id, season_id);

absolutePoints.push({
description: "Старт",
points: 1000
});
const currentPosition: IInternalGuildCurrentPosition = { games, points, rank, rank_reward };

if (points < 1000) {
return {
current_position: { games, points, rank, rank_reward },
points: absolutePoints
current_position: currentPosition,
segments: constructSegments(currentPosition, absolutePoints)
};
}

const placesToGet = [15, 5, 1].filter(place => rank === 0 ? place : place < rank).slice(0, 2);
const clubsOnPlaces = await Promise.all(placesToGet.map(place => guildsClient.getClubOnStageTopN(stage_id, season_id, place)));
const clubsPathPoints = clubsOnPlaces.map<IInternalGuildPathPoint>(club => ({ rank: club.rank, points: club.points }));
const [clubOnTop15, clubsInTop5] = await Promise.all([
guildsClient.getClubOnStageTopN(stage_id, season_id, 15),
guildsClient.api.getTopClubsForStageWithId(stage_id, season_id, { page: 1, per_page: 5 })
]);

absolutePoints = [...absolutePoints, guildRatingToPoint(clubOnTop15)].sort(sortByPoints);
const topPoints = clubsInTop5.map<IInternalGuildPathPoint>(guildRatingToPoint).sort(sortByPoints);

return {
current_position: { games, points, rank, rank_reward },
points: absolutePointsToRelative(points, [...absolutePoints, ...clubsPathPoints])
current_position: currentPosition,
segments: constructSegments(currentPosition, absolutePoints, topPoints)
};
}
129 changes: 64 additions & 65 deletions renderer/src/blocks/GuildPlaceGraph.svelte
Original file line number Diff line number Diff line change
@@ -1,90 +1,89 @@
<script>
import GuildPlaceGraphAxis from "./GuildPlaceGraphAxis";
import GuildPlaceGraphTrack from "./GuildPlaceGraphTrack";

export let current = { points: 0, rank: 0 };
export let points = [];
export let segments = [];

$: start_point = points.length ? points[0].points : 0;
$: full_width = points.length
? points[points.length - 1].points - start_point + 1000
: 0;
const currentSegment = segments.find(segment => segment.isCurrent);
let selectedSegmentIndex = segments.indexOf(currentSegment);

const calculate_position = point =>
points.length ? ((point.points - start_point) / full_width) * 100 : 0;
$: selectedSegment = segments[selectedSegmentIndex];

$: points_position = points.map(point => ({
...point,
position: calculate_position(point)
}));
$: current_position = calculate_position(current);
const prevSegment = () => (selectedSegmentIndex = selectedSegmentIndex - 1);
const nextSegment = () => (selectedSegmentIndex = selectedSegmentIndex + 1);
</script>

<style>
.guild-graph {
height: 80px;
}
.guild-graph__figure {
width: calc(100% - 4px);
height: 40px;
display: flex;
align-items: center;
}
.guild-graph__figure line {
stroke: var(--main-secondary);
stroke-width: 2px;

.guild-graph__nav {
border-radius: 50%;
flex-shrink: 0;
flex-grow: 0;
width: 24px;
height: 24px;
margin-top: -2px;
}
.guild-graph__bg,
.guild-graph__fg {
height: 20px;
stroke: var(--main-secondary);
stroke-width: 2px;
.guild-graph__nav--prev {
margin-right: 8px;
}
.guild-graph__bg {
fill: var(--main-background-transparent);
.guild-graph__nav--next {
margin-left: 8px;
}
.guild-graph__fg {
fill: var(--main-medium);
.guild-graph__nav__img {
max-width: 80%;
}

.guild-graph__axis {
.guild-graph__figure {
flex: 1 auto;
position: relative;
}

.guild-graph__point {
position: absolute;
transform: translateX(-50%);
text-align: center;
}
.guild-graph__point:first-child {
transform: none;
}
.guild-graph__point:last-child {
transform: translateX(-100%);
.guild-graph__track {
width: 100%;
height: 130px;
}
.guild-graph__point__points {
font-size: 0.75rem;

.guild-graph__axis {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>

<div class="guild-graph">
<svg class="guild-graph__figure" text-rendering="optimizeLegibility">
<g>
<rect x="0" y="0" width="100%" class="guild-graph__bg" />
<rect x="0" y="0" width="{current_position}%" class="guild-graph__fg" />
</g>
<g class="lines">
{#each points_position as point}
<line x1="{point.position}%" x2="{point.position}%" y1="0" y2="40px" />
{/each}
<line x1="{current_position}%" x2="{current_position}%" y1="0" y2="30px" />
</g>
</svg>
<div class="guild-graph__axis">
{#each points_position as point}
<div class="guild-graph__point" style={`left: ${point.position}%`}>
{#if point.description}
{point.description}
{:else if point.rank}Топ-{point.rank}{/if}
{#if point.points !== undefined}
<div class="guild-graph__point__points">{point.points}pt</div>
{/if}
</div>
{/each}
<button
class="guild-graph__nav guild-graph__nav--prev flex-center"
type="button"
disabled={selectedSegmentIndex === 0}
on:click={prevSegment}>
<img
src="./images/icons/arrow-left.svg"
alt="Назад"
class="guild-graph__nav__img" />
</button>
<div class="guild-graph__figure">
<div class="guild-graph__track">
<GuildPlaceGraphTrack {...selectedSegment} currentPoint={current} />
</div>
<div class="guild-graph__axis">
<GuildPlaceGraphAxis {...selectedSegment} currentPoint={current} />
</div>
</div>
<button
class="guild-graph__nav guild-graph__nav--next flex-center"
type="button"
disabled={selectedSegmentIndex === segments.length - 1}
on:click={nextSegment}>
<img
src="./images/icons/arrow-right.svg"
alt="Вперед"
class="guild-graph__nav__img" />
</button>
</div>
40 changes: 40 additions & 0 deletions renderer/src/blocks/GuildPlaceGraphAxis.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script>
import GuildPlaceGraphPoint from "./GuildPlaceGraphPoint.svelte";
import { calculateRelativeProgress } from "@guilds-shared/helpers/points";

export let currentPoint;

export let start;
export let end;
export let progress = 0;
export let points = [];
export let isCurrent = false;
export let isTop = false;

const calculatePosition = (point, i) =>
isTop
? (100 / (points.length + 1)) * (i + 1)
: calculateRelativeProgress(point.points, start.points, end.points) * 100;
$: currentProgress = isCurrent
? calculatePosition(
currentPoint,
isTop ? points.length - currentPoint.rank + 1 : 0
)
: progress * 100;
</script>

<GuildPlaceGraphPoint {...start} isStart={true} />
<GuildPlaceGraphPoint {...end} isEnd={true} />

{#if isCurrent}
<GuildPlaceGraphPoint
{...currentPoint}
isCurrent
position={currentProgress} />
{/if}

{#if points.length}
{#each points as point, i (point.rank)}
<GuildPlaceGraphPoint {...point} position={calculatePosition(point, i)} />
{/each}
{/if}
Loading