Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit b157848

Browse files
authored
feat: fetching and redering city labels (#41)
* feat: load cities lables * fix: labels and hovering * fix: cities level * fix: correct labels order
1 parent d05b220 commit b157848

File tree

7 files changed

+93
-28
lines changed

7 files changed

+93
-28
lines changed

asset/foreground.svg

Lines changed: 17 additions & 13 deletions
Loading

src/css/article.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
visibility: hidden;
1212
position: absolute;
1313
z-index: 30;
14-
width: 800px; /* Responsive width */
14+
width: min(800px, calc(100vw - 50px)); /* Responsive width */
1515
height: calc(100vh - 50px - 16px); /* Responsive height */
1616
right: 25px;
1717
top: 25px;

src/css/labels.css

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
text-align: center;
99
font-family: Arial, sans-serif;
1010
letter-spacing: 0.03em;
11+
pointer-events: none;
1112

1213
/* hack to make the shadow stronger */
1314
text-shadow:
@@ -42,10 +43,21 @@
4243
font-weight: bold;
4344
}
4445

46+
#labels3 {
47+
font-size: 3px;
48+
}
49+
50+
#labels3 .label {
51+
text-align: center;
52+
width: 10em;
53+
transform: translate(-50%, 10px);
54+
}
55+
4556
.label-available {
4657
transition: color 0.2s ease;
4758
color: blue;
4859
text-decoration: underline;
60+
pointer-events: auto;
4961
}
5062

5163
.label-available:hover {
@@ -60,7 +72,7 @@
6072
position: absolute;
6173
left: 0;
6274
top: 0;
63-
pointer-events: visiblePainted;
75+
pointer-events: none;
6476
}
6577

6678
@keyframes hover-animation {

src/js/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ export const CITY_5_OUTER_SIZE = 14;
1515
export const LAYER_ZOOM_THRESHOLD_0 = -Infinity;
1616
export const LAYER_ZOOM_THRESHOLD_1 = 0.8;
1717
export const LAYER_ZOOM_THRESHOLD_2 = 3.2;
18+
export const LAYER_ZOOM_THRESHOLD_3 = 9.6;
1819

1920
export const LAYER_ZOOM_RADIUS_0 = LAYER_ZOOM_THRESHOLD_0;
2021
export const LAYER_ZOOM_RADIUS_1 = LAYER_ZOOM_THRESHOLD_1;
2122
export const LAYER_ZOOM_RADIUS_2 = LAYER_ZOOM_THRESHOLD_2;
23+
export const LAYER_ZOOM_RADIUS_3 = LAYER_ZOOM_THRESHOLD_3;
2224

2325
// Labels
2426
export const IS_LABEL_ZOOM_SCALING = true;

src/js/foreground.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
LAYER_ZOOM_RADIUS_0,
88
LAYER_ZOOM_RADIUS_1,
99
LAYER_ZOOM_RADIUS_2,
10+
LAYER_ZOOM_THRESHOLD_3,
11+
LAYER_ZOOM_RADIUS_3,
1012
} from "./config";
1113

1214
function hideForegroundRects() {
@@ -70,11 +72,13 @@ export function getForegroundVisibilities(kZoom) {
7072
LAYER_ZOOM_THRESHOLD_0,
7173
LAYER_ZOOM_THRESHOLD_1,
7274
LAYER_ZOOM_THRESHOLD_2,
75+
LAYER_ZOOM_THRESHOLD_3,
7376
];
7477
const layerZoomRadiuses = [
7578
LAYER_ZOOM_RADIUS_0,
7679
LAYER_ZOOM_RADIUS_1,
7780
LAYER_ZOOM_RADIUS_2,
81+
LAYER_ZOOM_RADIUS_3,
7882
];
7983

8084
const layers = getForegroundLayers();

src/js/labels.js

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
LABEL_ZOOM_SCALE_FACTOR_MAX,
88
LABEL_ZOOM_SCALE_FACTOR_MIN,
99
} from "./config";
10+
import { data } from "./points";
1011

1112
class Label {
1213
constructor(html, x, y) {
@@ -18,19 +19,20 @@ class Label {
1819

1920
export function initLabels(xScale, yScale, kZoom) {
2021
article.fetchAvailableArticlesList().then(() => {
21-
initLabelsAfterFetchingArticlesList(xScale, yScale, kZoom);
22+
buildLabelsDiv();
23+
initLabelsAfterFetchingArticlesList();
24+
updateLabels(xScale, yScale, kZoom);
2225
});
2326
}
2427

25-
function initLabelsAfterFetchingArticlesList(xScale, yScale, kZoom) {
26-
buildLabelsDiv();
27-
28-
getForegroundLayers().forEach((layer, layer_no) => {
28+
function initLabelsAfterFetchingArticlesList() {
29+
[...getForegroundLayers()].reverse().forEach((layer, i) => {
30+
const layer_no = getForegroundLayers().length - 1 - i;
2931
buildLabelsDivLayer(layer_no);
3032
const LabelsDivLayer = getLabelsDivLayer(layer_no);
33+
const orgFontSize = getFontSizeInPx(LabelsDivLayer);
3134

3235
getLabelsFromSvgGroup(layer).forEach((label) => {
33-
const orgFontSize = getFontSizeInPx(LabelsDivLayer);
3436
LabelsDivLayer.append("div")
3537
.attr("x", label.x)
3638
.attr("y", label.y)
@@ -40,9 +42,22 @@ function initLabelsAfterFetchingArticlesList(xScale, yScale, kZoom) {
4042
.classed("label-unavailable", !article.isArticleAvailable(label.html))
4143
.text(label.html);
4244
});
43-
});
4445

45-
updateLabels(xScale, yScale, kZoom);
46+
// Add city labels to the last layer
47+
if (layer_no === getForegroundLayers().length - 1) {
48+
data.forEach((dataPoint) => {
49+
if (dataPoint.cityLabel) {
50+
LabelsDivLayer.append("div")
51+
.attr("x", dataPoint.x)
52+
.attr("y", dataPoint.y)
53+
.attr("org-font-size", orgFontSize)
54+
.classed("label", true)
55+
.classed("city-label", true)
56+
.text(dataPoint.cityLabel);
57+
}
58+
});
59+
}
60+
});
4661
}
4762

4863
function calcLabelFontSize(orgFontSizeInPx, kZoom) {
@@ -70,17 +85,23 @@ export function updateLabels(xScale, yScale, kZoom) {
7085
const label = d3.select(labels[index]);
7186
const x = label.attr("x");
7287
const y = label.attr("y");
88+
const isCityLabel = label.classed("city-label");
7389
const xMoved = xScale(x);
74-
const yMoved = yScale(-y);
90+
const yMoved = yScale(isCityLabel ? y : -y);
7591
const orgFontSize = label.attr("org-font-size");
76-
const isAvailable = article.isArticleAvailable(label.text());
92+
const isAvailable =
93+
!isCityLabel && article.isArticleAvailable(label.text());
94+
7795
label
7896
.style("left", xMoved + "px")
7997
.style("top", yMoved + "px")
8098
.style("opacity", visibilities[layer_no])
8199
.style("display", visibilities[layer_no] == 0 ? "none" : "block")
82-
.style("font-size", calcLabelFontSize(orgFontSize, kZoom))
83-
.on("click", () => handleClickLabel(isAvailable, labels[index]));
100+
.style("font-size", calcLabelFontSize(orgFontSize, kZoom));
101+
102+
if (!isCityLabel) {
103+
label.on("click", () => handleClickLabel(isAvailable, labels[index]));
104+
}
84105
});
85106
});
86107
}

src/js/points.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import * as chart from "./chart";
22

33
export let data = [];
44
let concepts = {};
5+
let cityLabels = [];
6+
export let cityLabelsByClusterId = {};
57

68
function parseKeyConceptsRaw(keyConceptsRaw) {
79
return keyConceptsRaw.split(",");
@@ -20,6 +22,7 @@ function parseDataPointItem(item) {
2022
growthRating: Number(item["growth_rating"]),
2123
clusterCategoryId: Number(item["cluster_category"]),
2224
keyConcepts: parseKeyConceptsRaw(item["key_concepts"]),
25+
cityLabel: cityLabelsByClusterId[Number(item["cluster_id"])] || null,
2326
};
2427
}
2528

@@ -43,7 +46,12 @@ function findClosestDataPoint(dataPoints, x, y, radius) {
4346
export function buildDataPointDetails(dataPoint) {
4447
let html = "";
4548

46-
html += "<strong>#" + dataPoint.clusterId + "</strong>" + "<br />";
49+
if (dataPoint.cityLabel) {
50+
html += "<strong>" + dataPoint.cityLabel + "</strong>";
51+
} else {
52+
html += "<strong>#" + dataPoint.clusterId + "</strong>";
53+
}
54+
html += "<br />";
4755

4856
if (dataPoint.numRecentArticles <= 100) {
4957
html += "<span class='few-articles'>";
@@ -112,13 +120,27 @@ function loadData(url, parseItem, dataTarget, onLoaded) {
112120
runLoaderWorker(loaderWorker, url);
113121
}
114122

123+
function parseCityLabelItem(item) {
124+
cityLabelsByClusterId[Number(item["cluster_id"])] = item["label"];
125+
}
126+
127+
function loadCityLabels() {
128+
loadData(
129+
new URL("../../asset/labels.tsv", import.meta.url),
130+
parseCityLabelItem,
131+
cityLabels, // Store city labels in the cityLabels array
132+
() => {},
133+
);
134+
}
135+
115136
export function loadDataPoints() {
116137
loadData(
117138
new URL("../../asset/data.tsv", import.meta.url),
118139
parseDataPointItem,
119140
data, // Separate array for data points
120141
handleDataPointsLoaded,
121142
);
143+
loadCityLabels(); // Load city labels after loading data points
122144
}
123145

124146
export function loadConcepts() {

0 commit comments

Comments
 (0)