intia-theme 0.1.56 → 0.1.59
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/_includes/head.html +3 -2
- data/_includes/header.html +1 -1
- data/_layouts/default.html +2 -2
- data/_sass/_icons.scss +8 -0
- data/_sass/_layout.scss +292 -422
- data/_sass/_main.scss +2 -0
- data/_sass/_mobile.scss +181 -0
- data/_sass/_radar.scss +317 -0
- data/assets/img/arrow-up.svg +17 -0
- data/assets/img/icons/close-dropdown.svg +13 -0
- data/assets/img/icons/open-dropdown.svg +13 -0
- data/assets/img/logo-horizontal-short.png +0 -0
- data/assets/img/logo-short.png +0 -0
- data/assets/js/main.js +6 -1
- data/assets/js/radar.js +902 -0
- metadata +10 -3
- data/assets/img/logo-black.png +0 -0
data/assets/js/radar.js
ADDED
@@ -0,0 +1,902 @@
|
|
1
|
+
/*
|
2
|
+
all occurring angles are in radian
|
3
|
+
*/
|
4
|
+
function createRadar(config, structure, entries){
|
5
|
+
//#region variable definition #######################################################
|
6
|
+
const
|
7
|
+
radarId = config.radar.id,
|
8
|
+
diameter = config.radar.renderResolution,
|
9
|
+
radius = diameter / 2,
|
10
|
+
sectorThickness = 2 * Math.PI / structure.sectors.length,
|
11
|
+
blipMinSize = config.blip.size;
|
12
|
+
|
13
|
+
const fillingRatio = Math.PI / Math.sqrt(18); // ~0.74
|
14
|
+
|
15
|
+
|
16
|
+
let
|
17
|
+
radarData={}, // object to save all radar data
|
18
|
+
seed = 42, // seed number for reproducible random sequence
|
19
|
+
blipIdCounter = 1, // counter variable to give each blip a unique id
|
20
|
+
onlyOneSectorDisplayed = false;
|
21
|
+
|
22
|
+
//#endregion ########################################################################
|
23
|
+
|
24
|
+
window.onresize = () => {
|
25
|
+
mobileMode = (getSvgDivWidth() < diameter) ? true : false;
|
26
|
+
update();
|
27
|
+
}
|
28
|
+
|
29
|
+
//#region helper function math ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
30
|
+
/*-------------------------------------------------------------------
|
31
|
+
custom random number generator, to make random sequence reproducible
|
32
|
+
source: https://stackoverflow.com/questions/521295
|
33
|
+
-------------------------------------------------------------------*/
|
34
|
+
let random = () => {let x = Math.sin(seed++) * 10000; return x - Math.floor(x);};
|
35
|
+
let random_between = (min, max) => (min+random()*(max-min));
|
36
|
+
let normal_between = (min, max) => (min+(random()+random())*0.5*(max-min));
|
37
|
+
|
38
|
+
let pointByAngleAndRadius = (angle, radius) => ({
|
39
|
+
x: Math.cos(angle) * radius,
|
40
|
+
y: Math.sin(angle) * radius
|
41
|
+
})
|
42
|
+
|
43
|
+
let angleAndRadiusByPoint = (point) => ({
|
44
|
+
angle: angleOfPoint(point),
|
45
|
+
radius: radiusOfPoint(point),
|
46
|
+
})
|
47
|
+
|
48
|
+
let angleOfPoint = (point) => (point.y < 0)
|
49
|
+
? Math.PI*2 + Math.atan2(point.y, point.x)
|
50
|
+
: Math.atan2(point.y, point.x);
|
51
|
+
|
52
|
+
let radiusOfPoint = (point) =>
|
53
|
+
Math.sqrt(point.x * point.x + point.y * point.y);
|
54
|
+
|
55
|
+
let calcOffsetAngle = (radius) =>
|
56
|
+
Math.atan(blipRadiusWithPadding / radius);
|
57
|
+
|
58
|
+
// NEW
|
59
|
+
let occupiedSpaceByBlips = (blipCount) => {
|
60
|
+
let radius = blipMinSize/2 + config.blip.margin;
|
61
|
+
return Math.pow(radius, 2) * Math.PI * blipCount;
|
62
|
+
}
|
63
|
+
|
64
|
+
let calcAngleRatio = (angle) => angle / (2 * Math.PI);
|
65
|
+
|
66
|
+
let blipAreaInSegment = (segment) => {
|
67
|
+
let radii = Math.pow(segment.outerRadius, 2) - Math.pow(segment.innerRadius, 2)
|
68
|
+
return Math.PI * radii * calcAngleRatio(segment.angleSpan) * fillingRatio;
|
69
|
+
}
|
70
|
+
|
71
|
+
let blipMaxRadiusInArea = (blipCount, area) =>
|
72
|
+
Math.sqrt(area / (Math.PI * blipCount));
|
73
|
+
|
74
|
+
let calcSegmentOuterRadius = (innerRadius, angle, blipCount) => {
|
75
|
+
let blipSpace = occupiedSpaceByBlips(blipCount);
|
76
|
+
let angleRatio = calcAngleRatio(angle);
|
77
|
+
let squareRootTerm = blipSpace / (Math.PI * angleRatio * fillingRatio) + Math.pow(innerRadius, 2);
|
78
|
+
return Math.sqrt(squareRootTerm);
|
79
|
+
}
|
80
|
+
|
81
|
+
let calcSegmentAngleSpan = (innerRadius, outerRadius, blipCount) => {
|
82
|
+
let blipSpace = occupiedSpaceByBlips(blipCount);
|
83
|
+
let denominator = (Math.PI * (Math.pow(outerRadius, 2) - Math.pow(innerRadius, 2)) * fillingRatio);
|
84
|
+
return blipSpace / denominator * (2 * Math.PI)
|
85
|
+
}
|
86
|
+
//#endregion ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
87
|
+
|
88
|
+
//#region helper function segment borders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
89
|
+
/* Checks if a value is in interval between min and max.
|
90
|
+
-> If the value is below the interval minimum, the interval minimum is returned.
|
91
|
+
-> If the value is above the interval maximum, the interval maximum is returned.*/
|
92
|
+
let bounded_interval = (value, min, max) => {
|
93
|
+
let low = Math.min(min, max);
|
94
|
+
let high = Math.max(min, max);
|
95
|
+
return Math.min(Math.max(value, low), high);
|
96
|
+
}
|
97
|
+
|
98
|
+
let boundedRadius = (point, minRadius, maxRadius) => ({
|
99
|
+
angle: point.angle,
|
100
|
+
radius: bounded_interval(point.radius, minRadius, maxRadius)
|
101
|
+
})
|
102
|
+
|
103
|
+
let boundedAngle = (point, minAngle, maxAngle) => {
|
104
|
+
let blipPointRadius = radiusOfPoint(point);
|
105
|
+
let offsetAngle = calcOffsetAngle(blipPointRadius);
|
106
|
+
let minOffsetAngle = minAngle + offsetAngle;
|
107
|
+
let maxOffsetAngle = maxAngle - offsetAngle;
|
108
|
+
let blipPointAngle = angleOfPoint(point);
|
109
|
+
let angle = bounded_interval(blipPointAngle, minOffsetAngle, maxOffsetAngle);
|
110
|
+
//if the blip was outside the interval the blip point is recalculated
|
111
|
+
if(angle == minOffsetAngle) return pointByAngleAndRadius(minOffsetAngle, blipPointRadius);
|
112
|
+
if(angle == maxOffsetAngle) return pointByAngleAndRadius(maxOffsetAngle, blipPointRadius);
|
113
|
+
else return point;
|
114
|
+
}
|
115
|
+
|
116
|
+
let segmentFunctions = (segment) => ({
|
117
|
+
clip: (blip) => {
|
118
|
+
let pointInAngleInterval = boundedAngle(blip, segment.startAngle, segment.endAngle);
|
119
|
+
let pointInRadiusInterval = boundedRadius(
|
120
|
+
angleAndRadiusByPoint(pointInAngleInterval),
|
121
|
+
segment.blipMinRadius,
|
122
|
+
segment.blipMaxRadius
|
123
|
+
);
|
124
|
+
blip.x = pointByAngleAndRadius(pointInRadiusInterval.angle, pointInRadiusInterval.radius).x;
|
125
|
+
blip.y = pointByAngleAndRadius(pointInRadiusInterval.angle, pointInRadiusInterval.radius).y;
|
126
|
+
return { x: blip.x, y: blip.y };
|
127
|
+
},
|
128
|
+
random: () => pointByAngleAndRadius(
|
129
|
+
random_between(segment.startAngle, segment.endAngle),
|
130
|
+
normal_between(segment.blipMinRadius, segment.blipMaxRadius))
|
131
|
+
})
|
132
|
+
//#endregion ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
133
|
+
|
134
|
+
//#region helper functions svg ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
135
|
+
let getSvgDivWidth = () => {
|
136
|
+
// returns the width of the div tag where the svg is placed in excluding the padding
|
137
|
+
let radarOffsetWidth = radarDiv.select(`.radar`).node().offsetWidth;
|
138
|
+
let padding = parseInt(window.getComputedStyle(radarDiv.select(`.radar`).node()).paddingLeft) * 2;
|
139
|
+
return radarOffsetWidth - padding;
|
140
|
+
}
|
141
|
+
|
142
|
+
let translate = (x, y) => `translate(${x}, ${y})`;
|
143
|
+
|
144
|
+
let arc = (segment) => {
|
145
|
+
const startMaxPoint = pointByAngleAndRadius(segment.startAngle, segment.outerRadius);
|
146
|
+
const startMinPoint = pointByAngleAndRadius(segment.startAngle, segment.innerRadius);
|
147
|
+
const endMaxPoint = pointByAngleAndRadius(segment.endAngle, segment.outerRadius);
|
148
|
+
const endMinPoint = pointByAngleAndRadius(segment.endAngle, segment.innerRadius);
|
149
|
+
return [
|
150
|
+
'M', startMaxPoint.x, startMaxPoint.y,
|
151
|
+
'A', segment.outerRadius, segment.outerRadius, 0, 0, 1, endMaxPoint.x, endMaxPoint.y,
|
152
|
+
'L', endMinPoint.x, endMinPoint.y,
|
153
|
+
'A', segment.innerRadius, segment.innerRadius, 0, 0, 0, startMinPoint.x, startMinPoint.y,
|
154
|
+
'L', startMaxPoint.x, startMaxPoint.y,
|
155
|
+
'Z'
|
156
|
+
].join(' ');
|
157
|
+
}
|
158
|
+
|
159
|
+
let sectorNamePath = (segment) => {
|
160
|
+
const radius = segment.outerRadius;
|
161
|
+
const startPoint = pointByAngleAndRadius(segment.startAngle, radius);
|
162
|
+
const endPoint = pointByAngleAndRadius(segment.endAngle, radius);
|
163
|
+
return [
|
164
|
+
'M', startPoint.x, startPoint.y,
|
165
|
+
'A', radius, radius, 0, 0, 1, endPoint.x, endPoint.y
|
166
|
+
].join(' ');
|
167
|
+
}
|
168
|
+
|
169
|
+
let segmentNamePath = (segment) => {
|
170
|
+
const endMaxPoint = pointByAngleAndRadius(segment.endAngle, segment.outerRadius);
|
171
|
+
const endMinPoint = pointByAngleAndRadius(segment.endAngle, segment.innerRadius);
|
172
|
+
|
173
|
+
if(segment.endAngle > 1.5 * Math.PI && segment.endAngle <= 2 * Math.PI ||
|
174
|
+
segment.endAngle < 0.5 * Math.PI){
|
175
|
+
return [
|
176
|
+
'M', endMinPoint.x, endMinPoint.y,
|
177
|
+
'L', endMaxPoint.x, endMaxPoint.y
|
178
|
+
].join(' ');
|
179
|
+
}
|
180
|
+
return [
|
181
|
+
'M', endMaxPoint.x, endMaxPoint.y,
|
182
|
+
'L', endMinPoint.x, endMinPoint.y
|
183
|
+
].join(' ');
|
184
|
+
}
|
185
|
+
|
186
|
+
let getSectorColorPalette = (colorCode) => {
|
187
|
+
let colorStart, colorEnd, brighterColor;
|
188
|
+
switch (true){
|
189
|
+
case config.sector.useColor && config.segment.colorGradient:
|
190
|
+
brighterColor = d3.hsl(colorCode);
|
191
|
+
brighterColor.l *= config.segment.colorGradientLimit;
|
192
|
+
colorStart = d3.rgb(colorCode);
|
193
|
+
colorEnd = d3.rgb(brighterColor);
|
194
|
+
break;
|
195
|
+
case config.segment.colorGradient:
|
196
|
+
brighterColor = d3.hsl(config.radar.defaultColor);
|
197
|
+
brighterColor.l *= config.segment.colorGradientLimit;
|
198
|
+
colorStart = d3.rgb(config.radar.defaultColor);
|
199
|
+
colorEnd = d3.rgb(brighterColor);
|
200
|
+
break;
|
201
|
+
case config.sector.useColor:
|
202
|
+
colorStart = d3.rgb(colorCode);
|
203
|
+
colorEnd = d3.rgb(colorCode);
|
204
|
+
break;
|
205
|
+
default:
|
206
|
+
colorStart = d3.rgb(config.radar.defaultColor);
|
207
|
+
colorEnd = d3.rgb(config.radar.defaultColor);
|
208
|
+
}
|
209
|
+
return d3.scaleLinear()
|
210
|
+
.domain([0, structure.rings.list.length])
|
211
|
+
.range([colorStart, colorEnd]);
|
212
|
+
}
|
213
|
+
|
214
|
+
let getBlipColor = (blip) =>
|
215
|
+
(blip.stateID >= 0 && blip.stateID < structure.entryStates.list.length)
|
216
|
+
? structure.entryStates.list[blip.stateID].color
|
217
|
+
: config.blip.defaultColor;
|
218
|
+
|
219
|
+
let getBlipRingColor = (blip) => {
|
220
|
+
let color = (blip.stateID >= 0 && blip.stateID < structure.entryStates.list.length)
|
221
|
+
? d3.rgb(structure.entryStates.list[blip.stateID].color)
|
222
|
+
: d3.rgb(config.blip.defaultColor);
|
223
|
+
if(blip.moved != 0) color.opacity = 0.25;
|
224
|
+
return color;
|
225
|
+
}
|
226
|
+
let getBlipMovedIndicator = (blip) => {
|
227
|
+
if(blip.moved != 0){
|
228
|
+
let radius = config.blip.outerCircleRadius;
|
229
|
+
|
230
|
+
let startAngle = (blip.moved > 0)
|
231
|
+
? radarData.sectors[blip.sectorID].startAngle + Math.PI
|
232
|
+
: radarData.sectors[blip.sectorID].startAngle;
|
233
|
+
let endAngle = (blip.moved > 0)
|
234
|
+
? radarData.sectors[blip.sectorID].endAngle + Math.PI
|
235
|
+
: radarData.sectors[blip.sectorID].endAngle;
|
236
|
+
let startPoint = pointByAngleAndRadius(startAngle, radius);
|
237
|
+
let endPoint = pointByAngleAndRadius(endAngle, radius);
|
238
|
+
return [
|
239
|
+
'M', startPoint.x, startPoint.y,
|
240
|
+
'A', radius, radius, 0, 0, 1, endPoint.x, endPoint.y,
|
241
|
+
].join(' ');
|
242
|
+
}
|
243
|
+
return ``;
|
244
|
+
}
|
245
|
+
//#endregion ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
246
|
+
|
247
|
+
//#region preparing radar data ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
|
252
|
+
|
253
|
+
// create data structure ----------------------------------------------------------
|
254
|
+
radarData.rings = structure.rings.list.map((ring, index) => ({
|
255
|
+
...ring,
|
256
|
+
index: index,
|
257
|
+
}));
|
258
|
+
radarData.sectors = structure.sectors.map((sector, index) => ({
|
259
|
+
...sector,
|
260
|
+
id: index,
|
261
|
+
idText: `${radarId}_sector${index}`,
|
262
|
+
color: getSectorColorPalette(sector.color),
|
263
|
+
segments: radarData.rings,
|
264
|
+
}));
|
265
|
+
radarData.sectors.forEach(sector => {
|
266
|
+
sector.segments = sector.segments.map((segment, index) => ({
|
267
|
+
...segment,
|
268
|
+
idText: `${sector.idText}_segment${index}`,
|
269
|
+
color: sector.color(index),
|
270
|
+
blips: entries.filter(entry =>
|
271
|
+
entry.sectorID == sector.id &&
|
272
|
+
entry.ringID == index &&
|
273
|
+
entry.active)
|
274
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
275
|
+
}))
|
276
|
+
});
|
277
|
+
// --------------------------------------------------------------------------------
|
278
|
+
// adding ring radii and ring thickness -----------------------------------------
|
279
|
+
radarData.rings.forEach((ring, index) => {
|
280
|
+
ring.innerRadius = (index == 0) ? 0 : radarData.rings[index - 1].outerRadius;
|
281
|
+
ring.outerRadius = radarData.sectors.reduce((prev, curr) =>
|
282
|
+
Math.max(prev, calcSegmentOuterRadius(ring.innerRadius, sectorThickness, curr.segments[index].blips.length))
|
283
|
+
, 0);
|
284
|
+
ring.radiusThickness = ring.outerRadius - ring.innerRadius;
|
285
|
+
});
|
286
|
+
/* if the last outer radius is larger or smaller than the given radar radius, the rings must be
|
287
|
+
adjusted in such a way that any open space is evenly filled or any space that overlaps is evenly
|
288
|
+
subtracted from all ring thicknesses. */
|
289
|
+
let ringsLastOuterRadius = radarData.rings[radarData.rings.length - 1].outerRadius;
|
290
|
+
let ringThicknessCorrection = (radius - ringsLastOuterRadius) / radarData.rings.length;
|
291
|
+
radarData.rings.forEach((ring, index) => {
|
292
|
+
ring.radiusThickness = ring.radiusThickness + ringThicknessCorrection;
|
293
|
+
ring.innerRadius = (index == 0) ? 0 : radarData.rings[index - 1].outerRadius;
|
294
|
+
ring.outerRadius = ring.innerRadius + ring.radiusThickness;
|
295
|
+
});
|
296
|
+
// update segments
|
297
|
+
radarData.sectors.forEach(sector => {
|
298
|
+
sector.segments = sector.segments.map((segment, index) => ({
|
299
|
+
...segment,
|
300
|
+
radiusThickness: radarData.rings[index].radiusThickness,
|
301
|
+
innerRadius: radarData.rings[index].innerRadius,
|
302
|
+
outerRadius: radarData.rings[index].outerRadius,
|
303
|
+
}))
|
304
|
+
});
|
305
|
+
|
306
|
+
// add needed min angle span for each sector
|
307
|
+
radarData.sectors.forEach((sector) => {
|
308
|
+
sector.angleSpan = sector.segments.reduce((prev, curr) =>
|
309
|
+
Math.max(prev, calcSegmentAngleSpan(curr.innerRadius, curr.outerRadius, curr.blips.length))
|
310
|
+
, 0);
|
311
|
+
});
|
312
|
+
// add up all sector angle spans
|
313
|
+
let sectorAngleSpanSum = radarData.sectors.reduce((prev, curr) => prev + curr.angleSpan, 0);
|
314
|
+
let sectorAngleCorrection = ((Math.PI * 2) - sectorAngleSpanSum) / radarData.sectors.length;
|
315
|
+
radarData.sectors.forEach((sector, index) => {
|
316
|
+
sector.angleSpan = sector.angleSpan + sectorAngleCorrection;
|
317
|
+
sector.startAngle = (index == 0) ? 0 : radarData.sectors[index - 1].endAngle;
|
318
|
+
sector.endAngle = sector.startAngle + sector.angleSpan;
|
319
|
+
});
|
320
|
+
// update segments
|
321
|
+
radarData.sectors.forEach(sector => {
|
322
|
+
sector.segments = sector.segments.map((segment, index) => ({
|
323
|
+
...segment,
|
324
|
+
angleSpan: sector.angleSpan,
|
325
|
+
startAngle: sector.startAngle,
|
326
|
+
endAngle: sector.endAngle,
|
327
|
+
}))
|
328
|
+
})
|
329
|
+
// ------------------------------------------------------------------------------
|
330
|
+
|
331
|
+
|
332
|
+
|
333
|
+
// ------------------------------------------------------------------------------
|
334
|
+
// Blip anpassung
|
335
|
+
let sectorMinBilpRadius = radarData.sectors.reduce((prev, curr) => {
|
336
|
+
let segmentMinBilpRadius = curr.segments.reduce((prev, curr) => {
|
337
|
+
let area = blipAreaInSegment(curr);
|
338
|
+
let radius = blipMaxRadiusInArea(curr.blips.length, area);
|
339
|
+
let size = (radius - config.blip.margin) * 2;
|
340
|
+
return Math.min(size, prev)
|
341
|
+
}, Number.MAX_VALUE)
|
342
|
+
return Math.min(prev, segmentMinBilpRadius)
|
343
|
+
}, Number.MAX_VALUE);
|
344
|
+
|
345
|
+
const
|
346
|
+
blipSize = Math.max(sectorMinBilpRadius, blipMinSize),
|
347
|
+
blipRadiusWithPadding = blipSize / 2 + config.segment.padding;
|
348
|
+
|
349
|
+
config.blip.size = blipSize;
|
350
|
+
|
351
|
+
radarData.sectors.forEach(sector => {
|
352
|
+
sector.segments = sector.segments.map((segment, index) => ({
|
353
|
+
...segment,
|
354
|
+
blipMinRadius: (index == 0)
|
355
|
+
? blipRadiusWithPadding / Math.sin(sector.angleSpan / 2)
|
356
|
+
: segment.innerRadius + blipRadiusWithPadding,
|
357
|
+
blipMaxRadius: segment.outerRadius - blipRadiusWithPadding
|
358
|
+
}))
|
359
|
+
})
|
360
|
+
|
361
|
+
// ------------------------------------------------------------------------------
|
362
|
+
|
363
|
+
|
364
|
+
|
365
|
+
|
366
|
+
|
367
|
+
|
368
|
+
radarData.blips = []; // list of all blips, for a better processing later on
|
369
|
+
radarData.sectors.forEach(sector => sector.segments.forEach(segment => {
|
370
|
+
// give each blip the corresponding segment functions
|
371
|
+
segment.blips = segment.blips.map(blip => ({
|
372
|
+
...blip,
|
373
|
+
idText: `${segment.idText}_blip${blipIdCounter}`,
|
374
|
+
id: blipIdCounter++,
|
375
|
+
focused: false,
|
376
|
+
segmentFunctions: segmentFunctions(segment),
|
377
|
+
}))
|
378
|
+
// save each blip in a list, for better processing later on
|
379
|
+
segment.blips.forEach(blip => radarData.blips.push(blip))
|
380
|
+
}));
|
381
|
+
|
382
|
+
// give each blip the first random position
|
383
|
+
radarData.blips.forEach(blip => {
|
384
|
+
let point = blip.segmentFunctions.random();
|
385
|
+
blip.x = point.x;
|
386
|
+
blip.y = point.y;
|
387
|
+
});
|
388
|
+
|
389
|
+
// add data to the configuration of a blip to create blips later on
|
390
|
+
let fontSize = blipSize * 0.33,
|
391
|
+
blipRadius = blipSize * 0.5,
|
392
|
+
strokeWidth = blipRadius * 0.2,
|
393
|
+
outerCircleRadius = blipRadius ,
|
394
|
+
innerCircleRadius = outerCircleRadius - strokeWidth;
|
395
|
+
config.blip = ({
|
396
|
+
...config.blip,
|
397
|
+
fontSize: fontSize,
|
398
|
+
radius: blipRadius,
|
399
|
+
strokeWidth: strokeWidth,
|
400
|
+
outerCircleRadius: outerCircleRadius,
|
401
|
+
innerCircleRadius: innerCircleRadius
|
402
|
+
});
|
403
|
+
|
404
|
+
structure.entryStates.list = structure.entryStates.list.map((state, index)=>({
|
405
|
+
...state,
|
406
|
+
index: index
|
407
|
+
}));
|
408
|
+
|
409
|
+
|
410
|
+
//#endregion ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
411
|
+
|
412
|
+
//#region create div structure ______________________________________________________
|
413
|
+
let radarDiv = d3.select(`div#${radarId}`).classed(`radarContainer`, true);
|
414
|
+
if(config.radar.showName){
|
415
|
+
radarDiv.append(`div`)
|
416
|
+
.classed(`radarTitle`, true)
|
417
|
+
.text(config.radar.name);
|
418
|
+
}
|
419
|
+
// select sector dropdown
|
420
|
+
radarDiv.append(`div`)
|
421
|
+
.attr(`id`, `${radarId}_selectionDropdown`)
|
422
|
+
.classed(`radarSelection dropdown`, true);
|
423
|
+
radarDiv.append(`div`)
|
424
|
+
.classed(`radar`, true)
|
425
|
+
.attr(`id`, `${radarId}_radarDiv`);
|
426
|
+
/*
|
427
|
+
radarDiv.append(`div`)
|
428
|
+
.classed(`radarBlipLegend`, true);
|
429
|
+
*/
|
430
|
+
//#endregion ________________________________________________________________________
|
431
|
+
|
432
|
+
//#region create radar SVG and radar legend _________________________________________
|
433
|
+
radarDiv.select(`.radar`)
|
434
|
+
.append(`div`)
|
435
|
+
.classed(`radarContent`, true)
|
436
|
+
.append(`svg`)
|
437
|
+
.attr(`id`, `${radarId}_svg`)
|
438
|
+
.classed(`radarSVG`, true)
|
439
|
+
.attr(`preserveAspectRatio`, `xMinYMin meet`)
|
440
|
+
.attr(`viewBox`, `0 0 ${diameter} ${diameter}`);
|
441
|
+
radarDiv.select(`svg#${radarId}_svg`).append(`g`)
|
442
|
+
.attr(`id`, `${radarId}_radarContent`)
|
443
|
+
.attr(`transform`, translate(radius, radius));
|
444
|
+
// append radar legend div
|
445
|
+
radarDiv.select(`.radar`)
|
446
|
+
.append(`div`)
|
447
|
+
.attr(`id`, `${radarId}_radarLegend`)
|
448
|
+
.classed(`radarLegend dropdown`, true)
|
449
|
+
.on(`click`, ()=>
|
450
|
+
document.getElementById(`${radarId}_radarLegend`).classList.toggle(`active`))
|
451
|
+
.text(config.radar.legendDropdownText);
|
452
|
+
//#endregion ________________________________________________________________________
|
453
|
+
|
454
|
+
// can be declared only after the radar svg is appended
|
455
|
+
let mobileMode = (getSvgDivWidth() < diameter) ? true : false;
|
456
|
+
// let mobileMode = (getSvgDivWidth() < 400) ? true : false;
|
457
|
+
|
458
|
+
//#region event fuctions general ****************************************************
|
459
|
+
let update = () => {
|
460
|
+
selectionDropdownContent.select(`.selectionButton`)
|
461
|
+
.style(`display`, (mobileMode) ? `none` : `block`);
|
462
|
+
if(mobileMode && !onlyOneSectorDisplayed){
|
463
|
+
displaySector(radarData.sectors[0]);
|
464
|
+
changeSvgViewbox(radarData.sectors[0].idText);
|
465
|
+
selectionDropdownText.text(radarData.sectors[0].name);
|
466
|
+
}
|
467
|
+
else changeSvgViewbox(`${radarId}_radarContent`);
|
468
|
+
}
|
469
|
+
|
470
|
+
let changeSvgViewbox = (idText) => {
|
471
|
+
onlyOneSectorDisplayed = (idText == `${radarId}_radarContent`) ? false : true;
|
472
|
+
let box = radarDiv.select(`g#${idText}`).node().getBBox()
|
473
|
+
let size = Math.max(box.width, box.height);
|
474
|
+
let x = radius + box.x;
|
475
|
+
let y = radius + box.y;
|
476
|
+
d3.select(`svg#${radarId}_svg`).attr(`viewBox`, `${x} ${y} ${size} ${size}`);
|
477
|
+
}
|
478
|
+
//#endregion ************************************************************************
|
479
|
+
|
480
|
+
//#region event functions sector ****************************************************
|
481
|
+
let displayAllSectors = () => {
|
482
|
+
sectors.style(`display`, `block`);
|
483
|
+
blipLegendSectors.style(`display`, `block`);
|
484
|
+
}
|
485
|
+
let displaySector = (sector) => {
|
486
|
+
sectors.style(`display`, `none`);
|
487
|
+
radarDiv.select(`g#${sector.idText}`).style(`display`, `block`);
|
488
|
+
blipLegendSectors.style(`display`, `none`);
|
489
|
+
radarDiv.select(`div#${sector.idText}_legend`).style(`display`, `block`);
|
490
|
+
}
|
491
|
+
let focusAllSector = () => {
|
492
|
+
sectors.style(`opacity`, 1);
|
493
|
+
blipLegendSectors.style(`opacity`, 1);
|
494
|
+
}
|
495
|
+
let focusSector = (sector) => {
|
496
|
+
if(!onlyOneSectorDisplayed){
|
497
|
+
sectors.style(`opacity`, 0.25);
|
498
|
+
radarDiv.select(`g#${sector.idText}`).style(`opacity`, 1);
|
499
|
+
blipLegendSectors.style(`opacity`, 0.25);
|
500
|
+
radarDiv.select(`div#${sector.idText}_legend`).style(`opacity`, 1);
|
501
|
+
}
|
502
|
+
}
|
503
|
+
//#endregion ************************************************************************
|
504
|
+
|
505
|
+
//#region event functions ring ******************************************************
|
506
|
+
let focusRing = (ring) => {
|
507
|
+
segments.style(`opacity`, 0.25)
|
508
|
+
segments.filter(seg => seg.index == ring.index).style(`opacity`, 1)
|
509
|
+
}
|
510
|
+
let focusAllRings = () => segments.style(`opacity`, 1);
|
511
|
+
//#endregion ************************************************************************
|
512
|
+
|
513
|
+
//#region event functions blip ******************************************************
|
514
|
+
let blipClick = (blip) => {
|
515
|
+
let blipData = radarData.blips.find(data => data.id == blip.id);
|
516
|
+
if (blipData.focused){
|
517
|
+
window.open(blipData.link);
|
518
|
+
}
|
519
|
+
else blipData.focused = true;
|
520
|
+
}
|
521
|
+
|
522
|
+
let focusAllBlips = () => {
|
523
|
+
radarData.blips.forEach(blip => blip.focused = false);
|
524
|
+
blips.style(`opacity`, 1);
|
525
|
+
radarDiv.selectAll(`.blipLegendBlip`).classed(`active`, false);
|
526
|
+
hideBubble();
|
527
|
+
}
|
528
|
+
|
529
|
+
let focusBlip = (blip) => {
|
530
|
+
radarData.blips.find(data => data.id == blip.id).focused = true;
|
531
|
+
blips.filter(data => data.sectorID == blip.sectorID).style(`opacity`, 0.25);
|
532
|
+
radarDiv.select(`g#${blip.idText}`).style(`opacity`, 1);
|
533
|
+
blipLegendBlips.filter(data => data.id == blip.id).classed(`active`, true);
|
534
|
+
showBubble(blip);
|
535
|
+
}
|
536
|
+
|
537
|
+
let focusBlipByState = (state) => {
|
538
|
+
blips.style(`opacity`, 0.25);
|
539
|
+
blips.filter(data => data.stateID == state.index).style(`opacity`, 1);
|
540
|
+
blipLegendBlips.filter(data => data.stateID == state.index).classed(`active`, true);
|
541
|
+
}
|
542
|
+
|
543
|
+
let focusBlipByMovement = (movementValue) => {
|
544
|
+
blips.style(`opacity`, 0.25);
|
545
|
+
if(movementValue > 0) {
|
546
|
+
blips.filter(data => data.moved > 0).style(`opacity`, 1);
|
547
|
+
blipLegendBlips.filter(data => data.moved > 0).classed(`active`, true);
|
548
|
+
}
|
549
|
+
if(movementValue < 0) {
|
550
|
+
blips.filter(data => data.moved < 0).style(`opacity`, 1);
|
551
|
+
blipLegendBlips.filter(data => data.moved < 0).classed(`active`, true);
|
552
|
+
}
|
553
|
+
if(movementValue == 0) {
|
554
|
+
blips.filter(data => data.moved == 0).style(`opacity`, 1);
|
555
|
+
blipLegendBlips.filter(data => data.moved == 0).classed(`active`, true);
|
556
|
+
}
|
557
|
+
}
|
558
|
+
//#endregion ************************************************************************
|
559
|
+
|
560
|
+
//#region event functions bubble ****************************************************
|
561
|
+
let showBubble = (blip) => {
|
562
|
+
bubble.style(`display`, `block`);
|
563
|
+
let text = bubble.select(`text`).text(blip.name);
|
564
|
+
let textBox = text.node().getBBox();
|
565
|
+
bubble.attr('transform', translate(blip.x - textBox.width / 2, blip.y - 19))
|
566
|
+
bubble.select(`rect`)
|
567
|
+
.attr('x', -5)
|
568
|
+
.attr('y', -textBox.height)
|
569
|
+
.attr('width', textBox.width + 10)
|
570
|
+
.attr('height', textBox.height + 4);
|
571
|
+
bubble.select(`path`).attr('transform', translate(textBox.width / 2 - 5, 3));
|
572
|
+
}
|
573
|
+
let hideBubble = () =>
|
574
|
+
radarDiv.select(`g#${radarId}_bubble`).style(`display`, `none`);
|
575
|
+
//#endregion ************************************************************************
|
576
|
+
|
577
|
+
//#region d3-components radar -------------------------------------------------------
|
578
|
+
/* to place text on an svg-path the attribute alignment-baseline has been used,
|
579
|
+
the options of this attribute are well explained here
|
580
|
+
https://vanseodesign.com/web-design/svg-text-baseline-alignment/
|
581
|
+
*/
|
582
|
+
let makeSector = (selection) => {
|
583
|
+
selection
|
584
|
+
.attr(`id`, sector => `${sector.idText}`)
|
585
|
+
.classed(`sector`, true)
|
586
|
+
.on(`mouseover`, sector => focusSector(sector))
|
587
|
+
.on(`mouseout`, focusAllSector)
|
588
|
+
.on(`click`, sector => {
|
589
|
+
displaySector(sector);
|
590
|
+
changeSvgViewbox(sector.idText);
|
591
|
+
});
|
592
|
+
if(config.sector.showName){
|
593
|
+
let name = selection.append(`g`)
|
594
|
+
.attr(`class`, `sectorName`)
|
595
|
+
name.append(`path`)
|
596
|
+
.attr(`id`, sector => `${sector.idText}_name`)
|
597
|
+
.attr(`d`, sector => sectorNamePath(sector.segments[sector.segments.length-1]))
|
598
|
+
.attr(`fill`, `none`);
|
599
|
+
name.append(`text`).append(`textPath`)
|
600
|
+
.attr(`href`, sector => `#${sector.idText}_name`, `http://www.w3.org/1999/xlink`)
|
601
|
+
.attr(`alignment-baseline`, `after-edge`)
|
602
|
+
.attr(`startOffset`, `50%`)
|
603
|
+
.attr(`style`, `text-anchor:middle;`)
|
604
|
+
.text(sector => sector.name);
|
605
|
+
}
|
606
|
+
}
|
607
|
+
|
608
|
+
let makeSegment = (selection) => {
|
609
|
+
selection
|
610
|
+
.attr(`id`, segment => `${segment.idText}`)
|
611
|
+
.classed(`segment`, true)
|
612
|
+
.append(`path`)
|
613
|
+
.classed(`radarLines`, true)
|
614
|
+
.attr(`d`, segment => arc(segment))
|
615
|
+
.attr(`fill`, segment => segment.color);
|
616
|
+
|
617
|
+
if(config.segment.showName){
|
618
|
+
let name = selection.append(`g`)
|
619
|
+
.classed(`segmentName`, true);
|
620
|
+
name.append(`path`)
|
621
|
+
.attr(`id`, segment => `${segment.idText}_namePath`)
|
622
|
+
.attr(`d`, segment => segmentNamePath(segment))
|
623
|
+
.attr(`fill`, `none`);
|
624
|
+
name.append(`text`).append(`textPath`)
|
625
|
+
.attr(`href`, segment => `#${segment.idText}_namePath`, `http://www.w3.org/1999/xlink`)
|
626
|
+
.attr(`alignment-baseline`, segment =>
|
627
|
+
(segment.endAngle > 1.5 * Math.PI && segment.endAngle <= 2 * Math.PI ||
|
628
|
+
segment.endAngle < 0.5 * Math.PI)
|
629
|
+
? `before-edge`
|
630
|
+
: `after-edge`)
|
631
|
+
.attr(`startOffset`,`50%`)
|
632
|
+
.attr(`style`, `text-anchor:middle;`)
|
633
|
+
.text(segment => (config.segment.showNameAsId) ? segment.index : segment.name);
|
634
|
+
}
|
635
|
+
}
|
636
|
+
|
637
|
+
let makeBlip = (selection) => {
|
638
|
+
selection
|
639
|
+
.attr(`id`, data => `${data.idText}`)
|
640
|
+
.classed(`blip`, true)
|
641
|
+
.attr(`transform`, data => translate(data.x, data.y))
|
642
|
+
.on(`click`, data => blipClick(data))
|
643
|
+
.on(`mouseover`, data => focusBlip(data))
|
644
|
+
.on(`mouseout`, data => focusAllBlips(data));
|
645
|
+
// blip outer ring
|
646
|
+
selection.append(`circle`)
|
647
|
+
.attr(`r`, config.blip.outerCircleRadius)
|
648
|
+
.attr(`fill`, `rgba(0, 0, 0, 0)`)
|
649
|
+
.attr(`stroke-width`, config.blip.strokeWidth)
|
650
|
+
.attr(`stroke`, getBlipRingColor);
|
651
|
+
// blip indicater for movement
|
652
|
+
selection.append(`path`)
|
653
|
+
.attr(`d`, getBlipMovedIndicator)
|
654
|
+
.attr(`fill`, `none`)
|
655
|
+
.attr(`stroke-width`, config.blip.strokeWidth)
|
656
|
+
.attr(`stroke`, getBlipColor);
|
657
|
+
// blip innerCircle
|
658
|
+
selection.append('circle')
|
659
|
+
.attr('r', config.blip.innerCircleRadius)
|
660
|
+
.attr('fill', getBlipColor);
|
661
|
+
// blip text
|
662
|
+
selection.append('text')
|
663
|
+
.classed('blipText', true)
|
664
|
+
.attr('y', config.blip.fontSize/3)
|
665
|
+
.attr('text-anchor', 'middle')
|
666
|
+
.style(`font-size`, config.blip.fontSize)
|
667
|
+
.text(data => data.id);
|
668
|
+
}
|
669
|
+
|
670
|
+
let makeBubble = (selection) => {
|
671
|
+
selection
|
672
|
+
.classed(`radarBubble`, true)
|
673
|
+
.attr(`id`, `${radarId}_bubble`)
|
674
|
+
.style(`display`, `none`)
|
675
|
+
let fontSize = config.blip.radius;
|
676
|
+
selection.append('rect')
|
677
|
+
.attr('class', 'background')
|
678
|
+
.attr('rx', 4)
|
679
|
+
.attr('ry', 4);
|
680
|
+
selection.append('text')
|
681
|
+
.attr('class', 'bubbleText')
|
682
|
+
.attr(`y`, -fontSize/9)
|
683
|
+
.style(`font-size`, fontSize);
|
684
|
+
selection.append('path')
|
685
|
+
.attr('class', 'background')
|
686
|
+
.attr('d', 'M 0,0 10,0 5,8 z');
|
687
|
+
}
|
688
|
+
//#endregion ------------------------------------------------------------------------
|
689
|
+
|
690
|
+
//#region d3-components radar legend ------------------------------------------------
|
691
|
+
let makeLegendBlipStates = (selection) => {
|
692
|
+
selection.append(`span`)
|
693
|
+
.classed(`stateColor`, true)
|
694
|
+
.style(`background-color`, data => data.color);
|
695
|
+
selection.append(`span`)
|
696
|
+
.classed(`paddingText`, true)
|
697
|
+
.text(data => data.name);
|
698
|
+
}
|
699
|
+
|
700
|
+
let makeLegendBlipMovement = (selection) => {
|
701
|
+
selection.append(`span`)
|
702
|
+
.classed(`movementIndicator`, true)
|
703
|
+
.classed(`in`, data => data.value > 0)
|
704
|
+
.classed(`out`, data => data.value < 0);
|
705
|
+
selection.append(`span`)
|
706
|
+
.classed(`paddingText`, true)
|
707
|
+
.text(data => data.name);
|
708
|
+
}
|
709
|
+
|
710
|
+
let makeLegendRings = (selection) => {
|
711
|
+
selection.append(`span`)
|
712
|
+
.classed(`text`, true)
|
713
|
+
.text(data => `${data.index}. ${data.name}`);
|
714
|
+
}
|
715
|
+
//#endregion ------------------------------------------------------------------------
|
716
|
+
|
717
|
+
//#region d3-components radar blip legend -------------------------------------------
|
718
|
+
let makeBlipLegendSector = (selection) => {
|
719
|
+
selection
|
720
|
+
.attr(`id`, sector => `${sector.idText}_legend`)
|
721
|
+
.classed(`blipLegendSector card`, true)
|
722
|
+
.on(`click, mouseover`, sector => focusSector(sector))
|
723
|
+
.on(`mouseout`, focusAllSector)
|
724
|
+
.text(sector => sector.name);
|
725
|
+
}
|
726
|
+
|
727
|
+
let makeBlipLegendSegment = (selection) => {
|
728
|
+
selection
|
729
|
+
.attr(`id`, segment => `${segment.idText}_legend`)
|
730
|
+
.classed(`blipLegendSegment subCard`, true)
|
731
|
+
.text(segment => segment.name);
|
732
|
+
}
|
733
|
+
|
734
|
+
let makeBlipLegendBlip = (selection) => {
|
735
|
+
selection
|
736
|
+
.attr(`id`, blip => `${blip.idText}_legend`)
|
737
|
+
.classed(`blipLegendBlip cardItem`, true)
|
738
|
+
.on(`click`, blip => blipClick(blip))
|
739
|
+
.on(`mouseover`, blip => focusBlip(blip))
|
740
|
+
.on(`mouseout`, blip => focusAllBlips(blip))
|
741
|
+
.text(blip => `${blip.id} ${blip.name}`);
|
742
|
+
}
|
743
|
+
//#endregion ------------------------------------------------------------------------
|
744
|
+
|
745
|
+
//#region generate selection ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
746
|
+
let selectionDropdownText = radarDiv.select(`.radarSelection`)
|
747
|
+
.on(`click`, ()=> {
|
748
|
+
document.getElementById(`${radarId}_selectionDropdown`)
|
749
|
+
.classList.toggle(`active`);
|
750
|
+
})
|
751
|
+
.append(`span`)
|
752
|
+
.classed(`dropdownText`, true)
|
753
|
+
.text(config.radar.showAllSectorsText);
|
754
|
+
|
755
|
+
// append a dropdown content div to add the dropdown options
|
756
|
+
let selectionDropdownContent = radarDiv.select(`.radarSelection`)
|
757
|
+
.append(`div`)
|
758
|
+
.classed(`dropdownContent`, true);
|
759
|
+
|
760
|
+
// append the first dropdown option to show the whole radar
|
761
|
+
selectionDropdownContent
|
762
|
+
.append(`div`)
|
763
|
+
.classed(`selectionButton`, true)
|
764
|
+
.style(`display`, (mobileMode) ? `none` : `block`)
|
765
|
+
.text(config.radar.showAllSectorsText)
|
766
|
+
.on(`click`, () => {
|
767
|
+
displayAllSectors();
|
768
|
+
changeSvgViewbox(`${radarId}_radarContent`);
|
769
|
+
selectionDropdownText.text(config.radar.showAllSectorsText);
|
770
|
+
});
|
771
|
+
|
772
|
+
// append a dropdown option for each sector in radar
|
773
|
+
selectionDropdownContent.selectAll(null)
|
774
|
+
.data(radarData.sectors)
|
775
|
+
.enter()
|
776
|
+
.append(`div`)
|
777
|
+
.classed(`selectionButton`, true)
|
778
|
+
.text(sector => sector.name)
|
779
|
+
.on(`click`, sector => {
|
780
|
+
displaySector(sector);
|
781
|
+
changeSvgViewbox(sector.idText);
|
782
|
+
selectionDropdownText.text(sector.name);
|
783
|
+
});
|
784
|
+
//#endregion ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
785
|
+
|
786
|
+
//#region generate radar ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
787
|
+
let sectors = d3.select(`g#${radarId}_radarContent`)
|
788
|
+
.selectAll(`.sector`)
|
789
|
+
.data(radarData.sectors)
|
790
|
+
.enter()
|
791
|
+
.append(`g`)
|
792
|
+
.call(makeSector);
|
793
|
+
|
794
|
+
let segments = sectors.selectAll(`.segment`)
|
795
|
+
.data(sector => sector.segments )
|
796
|
+
.enter()
|
797
|
+
.append(`g`)
|
798
|
+
.call(makeSegment);
|
799
|
+
|
800
|
+
let blips = segments.selectAll(`.blip`)
|
801
|
+
.data(segment => segment.blips)
|
802
|
+
.enter()
|
803
|
+
.append(`g`)
|
804
|
+
.call(makeBlip);
|
805
|
+
|
806
|
+
let bubble = d3.select(`g#${radarId}_radarContent`)
|
807
|
+
.append(`g`)
|
808
|
+
.call(makeBubble);
|
809
|
+
//#endregion ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
810
|
+
|
811
|
+
//#region generate radar legend +++++++++++++++++++++++++++++++++++++++++++++++++++++
|
812
|
+
let radarLegendContainer = radarDiv.select(`.radarLegend`)
|
813
|
+
.append(`div`)
|
814
|
+
.attr(`id`, `${radarId}_radarLegendContainer`)
|
815
|
+
.classed(`dropdownContent`, true);
|
816
|
+
|
817
|
+
// generate entry states legend
|
818
|
+
let entryStatesLegend = radarLegendContainer.append(`div`)
|
819
|
+
.classed(`card`, true)
|
820
|
+
entryStatesLegend.append(`div`)
|
821
|
+
.classed(`cardTitle`, true)
|
822
|
+
.text(structure.entryStates.legendTitle);
|
823
|
+
entryStatesLegend.selectAll(null)
|
824
|
+
.data(structure.entryStates.list)
|
825
|
+
.enter()
|
826
|
+
.append(`div`)
|
827
|
+
.classed(`cardItem`, true)
|
828
|
+
.call(makeLegendBlipStates)
|
829
|
+
.on(`mouseover`, (data)=> focusBlipByState(data))
|
830
|
+
.on(`mouseout`, data => focusAllBlips(data));
|
831
|
+
|
832
|
+
// generate entry movement legend
|
833
|
+
let entryMovementLegend = radarLegendContainer.append(`div`)
|
834
|
+
.classed(`card`, true);
|
835
|
+
entryMovementLegend.append(`div`)
|
836
|
+
.classed(`cardTitle`, true)
|
837
|
+
.text(structure.entryMovement.legendTitle);
|
838
|
+
entryMovementLegend.selectAll(null)
|
839
|
+
.data(structure.entryMovement.list)
|
840
|
+
.enter()
|
841
|
+
.append(`div`)
|
842
|
+
.classed(`cardItem`, true)
|
843
|
+
.call(makeLegendBlipMovement)
|
844
|
+
.on(`mouseover`, (data)=> focusBlipByMovement(data.value))
|
845
|
+
.on(`mouseout`, data => focusAllBlips(data));
|
846
|
+
|
847
|
+
// generate ring legend
|
848
|
+
let ringLegend = radarLegendContainer.append(`div`)
|
849
|
+
.classed(`card`, true);
|
850
|
+
ringLegend.append(`div`)
|
851
|
+
.classed(`cardTitle`, true)
|
852
|
+
.text(structure.rings.legendTitle);
|
853
|
+
ringLegend.selectAll(null)
|
854
|
+
.data(radarData.rings)
|
855
|
+
.enter()
|
856
|
+
.append(`div`)
|
857
|
+
.classed(`cardItem`, true)
|
858
|
+
.call(makeLegendRings)
|
859
|
+
.on(`mouseover`, (data)=> focusRing(data))
|
860
|
+
.on(`mouseout`, ()=> focusAllRings());
|
861
|
+
//#endregion ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
862
|
+
|
863
|
+
//#region generate radar blip legend ++++++++++++++++++++++++++++++++++++++++++++++++
|
864
|
+
let blipLegendSectors = radarDiv.select(`.radarBlipLegend`).selectAll(null)
|
865
|
+
.data(radarData.sectors)
|
866
|
+
.enter()
|
867
|
+
.append(`div`)
|
868
|
+
.call(makeBlipLegendSector);
|
869
|
+
|
870
|
+
let blipLegendSegments = blipLegendSectors.selectAll(null)
|
871
|
+
.data(sector => sector.segments.filter(segment => segment.blips.length != 0))
|
872
|
+
.enter()
|
873
|
+
.append(`div`)
|
874
|
+
.call(makeBlipLegendSegment);
|
875
|
+
|
876
|
+
let blipLegendBlips = blipLegendSegments.selectAll(null)
|
877
|
+
.data(segment => segment.blips)
|
878
|
+
.enter()
|
879
|
+
.append(`div`)
|
880
|
+
.call(makeBlipLegendBlip)
|
881
|
+
//#endregion ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
882
|
+
|
883
|
+
//#region forceSimulation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
884
|
+
// make sure that blips stay inside their segment
|
885
|
+
let ticked = () => blips.attr(`transform`, (d) => translate(
|
886
|
+
d.segmentFunctions.clip(d).x,
|
887
|
+
d.segmentFunctions.clip(d).y
|
888
|
+
));
|
889
|
+
// distribute blips, while avoiding collisions
|
890
|
+
d3.forceSimulation(radarData.blips)
|
891
|
+
.force(`collision`,
|
892
|
+
d3.forceCollide()
|
893
|
+
.radius(blipSize/2 + config.blip.margin)
|
894
|
+
.strength(0.15))
|
895
|
+
.on(`tick`, ticked);
|
896
|
+
//#endregion %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
897
|
+
|
898
|
+
update();
|
899
|
+
console.log(radarData, config);
|
900
|
+
|
901
|
+
|
902
|
+
}
|