@icij/murmur-next 4.0.1 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/AccordionStep.vue +53 -42
- package/lib/components/AccordionWrapper.vue +25 -24
- package/lib/components/ActiveTextTruncate.vue +44 -22
- package/lib/components/AdvancedLinkForm.vue +96 -46
- package/lib/components/Brand.vue +30 -23
- package/lib/components/BrandExpansion.vue +12 -3
- package/lib/components/ConfirmButton.vue +30 -26
- package/lib/components/ContentPlaceholder.vue +11 -7
- package/lib/components/CustomPagination.vue +50 -32
- package/lib/components/DigitsInput.vue +64 -60
- package/lib/components/DonateForm.vue +112 -83
- package/lib/components/EmbedForm.vue +37 -21
- package/lib/components/EmbeddableFooter.vue +14 -10
- package/lib/components/FollowUsPopover.vue +42 -40
- package/lib/components/GenericFooter.vue +98 -23
- package/lib/components/GenericHeader.vue +66 -29
- package/lib/components/HapticCopy.vue +41 -29
- package/lib/components/ImddbHeader.vue +113 -92
- package/lib/components/OrdinalLegend.vue +43 -20
- package/lib/components/RangePicker.vue +63 -42
- package/lib/components/ResponsiveIframe.vue +9 -2
- package/lib/components/ScaleLegend.vue +56 -18
- package/lib/components/SecretInput.vue +7 -8
- package/lib/components/SelectableDropdown.vue +120 -74
- package/lib/components/SharingOptions.vue +93 -36
- package/lib/components/SharingOptionsLink.vue +11 -5
- package/lib/components/SignUpForm.vue +44 -23
- package/lib/components/SlideUpDown.vue +7 -2
- package/lib/components/TexturedDeck.vue +24 -14
- package/lib/components/TinyPagination.vue +35 -22
- package/lib/composables/chart.ts +174 -157
- package/lib/composables/resizeObserver.ts +29 -29
- package/lib/composables/sendEmail.ts +53 -42
- package/lib/config.default.ts +17 -10
- package/lib/config.ts +34 -27
- package/lib/datavisualisations/BarChart.vue +48 -42
- package/lib/datavisualisations/ColumnChart.vue +133 -89
- package/lib/datavisualisations/LineChart.vue +79 -57
- package/lib/datavisualisations/StackedBarChart.vue +116 -68
- package/lib/datavisualisations/StackedColumnChart.vue +196 -115
- package/lib/enums.ts +25 -15
- package/lib/i18n.ts +3 -3
- package/lib/keys.ts +2 -2
- package/lib/main.ts +14 -10
- package/lib/maps/ChoroplethMap.vue +299 -160
- package/lib/maps/ChoroplethMapAnnotation.vue +29 -18
- package/lib/maps/SymbolMap.vue +194 -123
- package/lib/shims-bootstrap-vue.d.ts +1 -1
- package/lib/shims-vue.d.ts +3 -3
- package/lib/styles/functions.scss +10 -6
- package/lib/styles/lib.scss +2 -4
- package/lib/styles/mixins.scss +8 -8
- package/lib/styles/utilities.scss +1 -1
- package/lib/styles/variables.scss +24 -18
- package/lib/types.ts +26 -10
- package/lib/utils/animation.ts +4 -4
- package/lib/utils/assets.ts +31 -28
- package/lib/utils/clipboard.ts +16 -10
- package/lib/utils/iframe-resizer.ts +18 -13
- package/lib/utils/placeholder.ts +54 -23
- package/lib/utils/placeholderTypes.ts +3 -3
- package/package.json +7 -2
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
clamp,
|
|
4
|
+
debounce,
|
|
5
|
+
get,
|
|
6
|
+
kebabCase,
|
|
7
|
+
keys,
|
|
8
|
+
max,
|
|
9
|
+
min,
|
|
10
|
+
pickBy,
|
|
11
|
+
values
|
|
12
|
+
} from 'lodash'
|
|
3
13
|
|
|
4
14
|
import * as d3 from 'd3'
|
|
5
|
-
import {geoRobinson} from 'd3-geo-projection'
|
|
6
|
-
import type {GeoProjection} from 'd3-geo'
|
|
7
|
-
import {geoGraticule} from 'd3-geo'
|
|
8
|
-
import {feature} from 'topojson'
|
|
9
|
-
import {GeometryCollection} from
|
|
15
|
+
import { geoRobinson } from 'd3-geo-projection'
|
|
16
|
+
import type { GeoProjection } from 'd3-geo'
|
|
17
|
+
import { geoGraticule } from 'd3-geo'
|
|
18
|
+
import { feature } from 'topojson'
|
|
19
|
+
import { GeometryCollection } from 'topojson-specification'
|
|
10
20
|
|
|
11
21
|
import {
|
|
12
22
|
ComponentPublicInstance,
|
|
@@ -18,10 +28,15 @@ import {
|
|
|
18
28
|
watch
|
|
19
29
|
} from 'vue'
|
|
20
30
|
|
|
21
|
-
import {ParentKey} from
|
|
22
|
-
import {MapTransform, ParentMap} from
|
|
31
|
+
import { ParentKey } from '@/keys'
|
|
32
|
+
import { MapTransform, ParentMap } from '@/types'
|
|
23
33
|
import config from '../config'
|
|
24
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
chartEmits,
|
|
36
|
+
chartProps,
|
|
37
|
+
getChartProps,
|
|
38
|
+
useChart
|
|
39
|
+
} from '@/composables/chart'
|
|
25
40
|
import ScaleLegend from '@/components/ScaleLegend.vue'
|
|
26
41
|
|
|
27
42
|
export default defineComponent({
|
|
@@ -196,9 +211,8 @@ export default defineComponent({
|
|
|
196
211
|
},
|
|
197
212
|
...chartProps()
|
|
198
213
|
},
|
|
199
|
-
emits: [
|
|
200
|
-
setup(props, {emit}) {
|
|
201
|
-
|
|
214
|
+
emits: ['click', 'reset', 'zoomed', ...chartEmits],
|
|
215
|
+
setup(props, { emit }) {
|
|
202
216
|
const resizable = ref<ComponentPublicInstance<HTMLElement> | null>(null)
|
|
203
217
|
const topojson = ref<any>(null)
|
|
204
218
|
const topojsonPromise = ref<any | null>(null)
|
|
@@ -206,12 +220,25 @@ export default defineComponent({
|
|
|
206
220
|
const featureCursor = ref<{ [cursor: string]: string } | null>(null)
|
|
207
221
|
const featureZoom = ref<string | null>(null)
|
|
208
222
|
const isLoaded = ref<boolean>(false)
|
|
209
|
-
const mapTransform = ref<MapTransform>({
|
|
223
|
+
const mapTransform = ref<MapTransform>({
|
|
224
|
+
k: 1,
|
|
225
|
+
x: 0,
|
|
226
|
+
y: 0,
|
|
227
|
+
rotateX: 0,
|
|
228
|
+
rotateY: 0
|
|
229
|
+
})
|
|
210
230
|
const debouncedDraw = debounce(function () {
|
|
211
231
|
draw()
|
|
212
232
|
}, 10)
|
|
213
233
|
|
|
214
|
-
const {loadedData} = useChart(
|
|
234
|
+
const { loadedData } = useChart(
|
|
235
|
+
resizable,
|
|
236
|
+
getChartProps(props),
|
|
237
|
+
{ emit },
|
|
238
|
+
isLoaded,
|
|
239
|
+
debouncedDraw,
|
|
240
|
+
afterLoaded
|
|
241
|
+
)
|
|
215
242
|
|
|
216
243
|
async function afterLoaded() {
|
|
217
244
|
return new Promise<void>(async (resolve) => {
|
|
@@ -231,8 +258,8 @@ export default defineComponent({
|
|
|
231
258
|
return [lng, lat]
|
|
232
259
|
})
|
|
233
260
|
const featureColorScaleEnd = computed(() => {
|
|
234
|
-
const defaultColor = '#852308'
|
|
235
|
-
const node = map.value?.node()
|
|
261
|
+
const defaultColor = '#852308'
|
|
262
|
+
const node = map.value?.node()
|
|
236
263
|
if (isLoaded.value && node) {
|
|
237
264
|
const computedStyle = window.getComputedStyle(node)
|
|
238
265
|
return computedStyle.getPropertyValue('--primary') || defaultColor
|
|
@@ -242,8 +269,8 @@ export default defineComponent({
|
|
|
242
269
|
const featureColorScaleStart = computed(() => {
|
|
243
270
|
// `socialMode` is always different from null but accessing it will make
|
|
244
271
|
// this computed property reactive.
|
|
245
|
-
const defaultColor = '#fff'
|
|
246
|
-
const node = map.value?.node()
|
|
272
|
+
const defaultColor = '#fff'
|
|
273
|
+
const node = map.value?.node()
|
|
247
274
|
if (isLoaded.value && props.socialMode !== null && node) {
|
|
248
275
|
const computedStyle = window.getComputedStyle(node)
|
|
249
276
|
return computedStyle.getPropertyValue('color') || defaultColor
|
|
@@ -253,8 +280,10 @@ export default defineComponent({
|
|
|
253
280
|
const featureColor = computed(() => {
|
|
254
281
|
return (d: number) => {
|
|
255
282
|
const id = get(d, props.topojsonObjectsPath)
|
|
256
|
-
const hasIdProp = loadedData.value && id in loadedData.value
|
|
257
|
-
return hasIdProp
|
|
283
|
+
const hasIdProp = loadedData.value && id in loadedData.value
|
|
284
|
+
return hasIdProp
|
|
285
|
+
? featureColorScaleFunction.value(loadedData.value[id])
|
|
286
|
+
: undefined
|
|
258
287
|
}
|
|
259
288
|
})
|
|
260
289
|
const featureColorScaleFunction = computed(() => {
|
|
@@ -270,9 +299,9 @@ export default defineComponent({
|
|
|
270
299
|
|
|
271
300
|
const defaultFeatureColorScale = computed(() => {
|
|
272
301
|
return d3
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
302
|
+
.scaleSequential()
|
|
303
|
+
.domain([Math.max(1, minValue.value), maxValue.value])
|
|
304
|
+
.range([featureColorScaleStart.value, featureColorScaleEnd.value])
|
|
276
305
|
})
|
|
277
306
|
const initialFeaturePath = computed(() => {
|
|
278
307
|
return featurePath.value.projection(initialMapProjection.value)
|
|
@@ -283,9 +312,10 @@ export default defineComponent({
|
|
|
283
312
|
|
|
284
313
|
const initialMapProjection = computed(() => {
|
|
285
314
|
if (props.spherical) {
|
|
286
|
-
return mapProjection.value
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
return mapProjection.value
|
|
316
|
+
.rotate(sphericalCenter.value)
|
|
317
|
+
.fitHeight(mapHeight.value, geojson.value)
|
|
318
|
+
.translate([mapWidth.value / 2, mapHeight.value / 2])
|
|
289
319
|
}
|
|
290
320
|
return mapProjection.value.center(planarCenter.value)
|
|
291
321
|
})
|
|
@@ -299,9 +329,12 @@ export default defineComponent({
|
|
|
299
329
|
return !!featureZoom.value
|
|
300
330
|
})
|
|
301
331
|
|
|
302
|
-
|
|
303
332
|
const geojson = computed(() => {
|
|
304
|
-
const object = get(
|
|
333
|
+
const object = get(
|
|
334
|
+
topojson.value,
|
|
335
|
+
['objects', props.topojsonObjects],
|
|
336
|
+
null
|
|
337
|
+
)
|
|
305
338
|
return feature(topojson.value, object as GeometryCollection)
|
|
306
339
|
})
|
|
307
340
|
|
|
@@ -314,24 +347,27 @@ export default defineComponent({
|
|
|
314
347
|
})
|
|
315
348
|
const mapProjection = computed(() => {
|
|
316
349
|
if (!props.projection) {
|
|
317
|
-
throw new Error(
|
|
350
|
+
throw new Error('props.projection is ' + props.projection)
|
|
318
351
|
}
|
|
319
|
-
return props
|
|
352
|
+
return props
|
|
353
|
+
.projection()
|
|
354
|
+
.fitSize(
|
|
355
|
+
[mapWidth.value, mapHeight.value],
|
|
356
|
+
geojson.value
|
|
357
|
+
) as GeoProjection
|
|
320
358
|
})
|
|
321
359
|
const rotatingMapProjection = computed(() => {
|
|
322
|
-
const {rotateX = null, rotateY = null} = mapTransform.value
|
|
360
|
+
const { rotateX = null, rotateY = null } = mapTransform.value
|
|
323
361
|
let proj
|
|
324
362
|
let text
|
|
325
363
|
if (rotateX !== null && rotateY !== null) {
|
|
326
|
-
text=
|
|
327
|
-
proj= mapProjection.value.rotate([rotateX, rotateY]) ?? null
|
|
328
|
-
}else {
|
|
329
|
-
text=
|
|
330
|
-
proj= mapProjection.value
|
|
331
|
-
|
|
364
|
+
text = 'rotate'
|
|
365
|
+
proj = mapProjection.value.rotate([rotateX, rotateY]) ?? null
|
|
366
|
+
} else {
|
|
367
|
+
text = 'normal'
|
|
368
|
+
proj = mapProjection.value
|
|
332
369
|
}
|
|
333
370
|
return proj
|
|
334
|
-
|
|
335
371
|
})
|
|
336
372
|
|
|
337
373
|
const mapCenter = computed(() => {
|
|
@@ -339,17 +375,20 @@ export default defineComponent({
|
|
|
339
375
|
})
|
|
340
376
|
const mapZoom = computed(() => {
|
|
341
377
|
return d3
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
378
|
+
.zoom()
|
|
379
|
+
.scaleExtent([props.zoomMin, props.zoomMax])
|
|
380
|
+
.translateExtent([
|
|
381
|
+
[0, 0],
|
|
382
|
+
[mapWidth.value, mapHeight.value]
|
|
383
|
+
])
|
|
384
|
+
.on('zoom', mapZoomed)
|
|
349
385
|
})
|
|
350
386
|
|
|
351
387
|
const mapSphericalZoom = computed(() => {
|
|
352
|
-
return d3
|
|
388
|
+
return d3
|
|
389
|
+
.zoom(map.value)
|
|
390
|
+
.scaleExtent([props.zoomMin, props.zoomMax])
|
|
391
|
+
.on('zoom', mapSphericalZoomed)
|
|
353
392
|
})
|
|
354
393
|
const mapRotate = computed(() => {
|
|
355
394
|
return d3.drag(map.value).on('drag', mapRotated)
|
|
@@ -363,7 +402,13 @@ export default defineComponent({
|
|
|
363
402
|
})
|
|
364
403
|
|
|
365
404
|
const mapStyle = computed(() => {
|
|
366
|
-
const {
|
|
405
|
+
const {
|
|
406
|
+
k = 0,
|
|
407
|
+
x = 0,
|
|
408
|
+
y = 0,
|
|
409
|
+
rotateX = 0,
|
|
410
|
+
rotateY = 0
|
|
411
|
+
} = mapTransform.value
|
|
367
412
|
return {
|
|
368
413
|
'--map-height': props.height,
|
|
369
414
|
'--map-color': props.color,
|
|
@@ -376,13 +421,15 @@ export default defineComponent({
|
|
|
376
421
|
}
|
|
377
422
|
})
|
|
378
423
|
|
|
379
|
-
const map = computed(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
424
|
+
const map = computed(
|
|
425
|
+
(): d3.Selection<SVGElement, unknown, null, undefined> | null => {
|
|
426
|
+
const selection = d3.select(resizable.value).select<SVGElement>('svg')
|
|
427
|
+
if (!selection) {
|
|
428
|
+
throw new Error('Empty SVG selection')
|
|
429
|
+
}
|
|
430
|
+
return selection
|
|
383
431
|
}
|
|
384
|
-
|
|
385
|
-
})
|
|
432
|
+
)
|
|
386
433
|
const maxValue = computed(() => {
|
|
387
434
|
if (props.max !== null) {
|
|
388
435
|
return props.max
|
|
@@ -399,11 +446,11 @@ export default defineComponent({
|
|
|
399
446
|
return props.spherical ? '50% 50%' : '0 0'
|
|
400
447
|
})
|
|
401
448
|
|
|
402
|
-
function setMapNodeSize({width, height}) {
|
|
403
|
-
const node = map.value?.node()
|
|
449
|
+
function setMapNodeSize({ width, height }) {
|
|
450
|
+
const node = map.value?.node()
|
|
404
451
|
if (node) {
|
|
405
|
-
node[
|
|
406
|
-
node[
|
|
452
|
+
node['width'] = width
|
|
453
|
+
node['height'] = height
|
|
407
454
|
}
|
|
408
455
|
}
|
|
409
456
|
|
|
@@ -416,7 +463,7 @@ export default defineComponent({
|
|
|
416
463
|
|
|
417
464
|
function prepare() {
|
|
418
465
|
if (!map.value) {
|
|
419
|
-
throw new Error(
|
|
466
|
+
throw new Error('Map is null')
|
|
420
467
|
}
|
|
421
468
|
// Set the map sizes
|
|
422
469
|
mapRect.value = map.value.node()?.getBoundingClientRect() as DOMRect
|
|
@@ -454,35 +501,38 @@ export default defineComponent({
|
|
|
454
501
|
}
|
|
455
502
|
|
|
456
503
|
function drawOutline() {
|
|
457
|
-
map.value
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
504
|
+
map.value
|
|
505
|
+
?.select('.choropleth-map__main__outline')
|
|
506
|
+
.append('path')
|
|
507
|
+
.attr('d', initialFeaturePath.value({ type: 'Sphere' }))
|
|
508
|
+
.attr('stroke', props.outlineColor)
|
|
461
509
|
}
|
|
462
510
|
|
|
463
511
|
function drawGraticule() {
|
|
464
|
-
map.value
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
512
|
+
map.value
|
|
513
|
+
?.select('.choropleth-map__main__graticule')
|
|
514
|
+
.append('path')
|
|
515
|
+
.attr('d', initialGraticulePath.value)
|
|
516
|
+
.attr('stroke', props.graticuleColor)
|
|
468
517
|
}
|
|
469
518
|
|
|
470
519
|
function drawFeatures() {
|
|
471
|
-
const features = map.value
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
520
|
+
const features = map.value
|
|
521
|
+
?.select('.choropleth-map__main__features')
|
|
522
|
+
.selectAll('.choropleth-map__main__features__item')
|
|
523
|
+
.data(geojson.value.features)
|
|
524
|
+
.enter()
|
|
525
|
+
.append('path')
|
|
476
526
|
if (!features) {
|
|
477
|
-
throw new Error(
|
|
527
|
+
throw new Error('features is undefined')
|
|
478
528
|
}
|
|
479
529
|
features
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
530
|
+
.attr('class', featureClass)
|
|
531
|
+
.attr('d', initialFeaturePath.value)
|
|
532
|
+
.on('mouseover', featureMouseOver)
|
|
533
|
+
.on('mouseleave', featureMouseLeave)
|
|
534
|
+
.on('click', mapClicked)
|
|
535
|
+
.style('color', featureColor.value)
|
|
486
536
|
}
|
|
487
537
|
|
|
488
538
|
function update() {
|
|
@@ -490,10 +540,11 @@ export default defineComponent({
|
|
|
490
540
|
if (!map.value) {
|
|
491
541
|
return
|
|
492
542
|
}
|
|
493
|
-
map.value
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
543
|
+
map.value
|
|
544
|
+
.selectAll('.choropleth-map__main__features__item')
|
|
545
|
+
.data(geojson.value.features)
|
|
546
|
+
.attr('class', featureClass)
|
|
547
|
+
.style('color', featureColor.value)
|
|
497
548
|
}
|
|
498
549
|
|
|
499
550
|
function featureClass(d: string) {
|
|
@@ -529,7 +580,7 @@ export default defineComponent({
|
|
|
529
580
|
async function loadTopojson() {
|
|
530
581
|
if (!topojsonPromise.value) {
|
|
531
582
|
if (!props.topojsonUrl?.length) {
|
|
532
|
-
throw new Error(
|
|
583
|
+
throw new Error('Empty topojsonUrl')
|
|
533
584
|
}
|
|
534
585
|
topojsonPromise.value = d3.json(props.topojsonUrl)
|
|
535
586
|
topojson.value = await topojsonPromise.value
|
|
@@ -537,7 +588,6 @@ export default defineComponent({
|
|
|
537
588
|
return topojsonPromise.value
|
|
538
589
|
}
|
|
539
590
|
|
|
540
|
-
|
|
541
591
|
async function mapClicked(event: MouseEvent, d: number) {
|
|
542
592
|
/**
|
|
543
593
|
* A click on a feature
|
|
@@ -562,19 +612,23 @@ export default defineComponent({
|
|
|
562
612
|
emit('zoomed', d)
|
|
563
613
|
}
|
|
564
614
|
|
|
565
|
-
function mapSphericalZoomed({
|
|
615
|
+
function mapSphericalZoomed({
|
|
616
|
+
transform: { k }
|
|
617
|
+
}: {
|
|
618
|
+
transform: MapTransform
|
|
619
|
+
}) {
|
|
566
620
|
const transform = `scale(${k})`
|
|
567
|
-
mapTransform.value = {...mapTransform.value, k}
|
|
621
|
+
mapTransform.value = { ...mapTransform.value, k }
|
|
568
622
|
applyTransformToTrackedElements(transform)
|
|
569
623
|
}
|
|
570
624
|
|
|
571
|
-
function mapZoomed({transform}: { transform: MapTransform }) {
|
|
625
|
+
function mapZoomed({ transform }: { transform: MapTransform }) {
|
|
572
626
|
mapTransform.value = transform
|
|
573
627
|
applyTransformToTrackedElements(transform)
|
|
574
628
|
}
|
|
575
629
|
|
|
576
630
|
function mapRotated(event: Event) {
|
|
577
|
-
const {yaw, pitch} = calculateRotation(event)
|
|
631
|
+
const { yaw, pitch } = calculateRotation(event)
|
|
578
632
|
applyRotation(yaw, pitch)
|
|
579
633
|
}
|
|
580
634
|
|
|
@@ -584,40 +638,54 @@ export default defineComponent({
|
|
|
584
638
|
const [rotateX, rotateY] = mapProjection.value.rotate()
|
|
585
639
|
const yaw = rotateX + event.dx * k
|
|
586
640
|
const pitch = rotateY - event.dy * k
|
|
587
|
-
return {yaw, pitch}
|
|
641
|
+
return { yaw, pitch }
|
|
588
642
|
}
|
|
589
643
|
|
|
590
644
|
function applyTransformToTrackedElements(transform) {
|
|
591
|
-
map.value
|
|
645
|
+
map.value
|
|
646
|
+
?.selectAll('.choropleth-map__main__tracked')
|
|
647
|
+
.attr('transform', transform)
|
|
592
648
|
}
|
|
593
649
|
|
|
594
650
|
function applyRotation(rotateX: number, rotateY: number) {
|
|
595
|
-
mapTransform.value = {...mapTransform.value, rotateX, rotateY}
|
|
596
|
-
const featuresPaths = initialFeaturePath.value.projection(
|
|
651
|
+
mapTransform.value = { ...mapTransform.value, rotateX, rotateY }
|
|
652
|
+
const featuresPaths = initialFeaturePath.value.projection(
|
|
653
|
+
rotatingMapProjection.value
|
|
654
|
+
)
|
|
597
655
|
const graticulePaths = featuresPaths(graticuleLines.value)
|
|
598
|
-
map.value
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
656
|
+
map.value
|
|
657
|
+
?.selectAll('g.choropleth-map__main__features path')
|
|
658
|
+
.attr('d', featuresPaths)
|
|
659
|
+
map.value
|
|
660
|
+
?.selectAll('g.choropleth-map__main__graticule path')
|
|
661
|
+
.attr('d', graticulePaths)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function applyZoomIdentity(
|
|
665
|
+
zoomIdentity,
|
|
666
|
+
pointer: number[] | null = null,
|
|
667
|
+
transitionDuration = props.transitionDuration
|
|
668
|
+
) {
|
|
669
|
+
return map.value
|
|
670
|
+
?.transition()
|
|
671
|
+
.duration(transitionDuration)
|
|
672
|
+
.call(mapZoom.value.transform, zoomIdentity, pointer)
|
|
673
|
+
.end()
|
|
607
674
|
}
|
|
608
675
|
|
|
609
676
|
function reapplyZoom() {
|
|
610
|
-
mapTransform.value = {k: 1, x: 0, y: 0, rotateX: 0, rotateY: 0}
|
|
677
|
+
mapTransform.value = { k: 1, x: 0, y: 0, rotateX: 0, rotateY: 0 }
|
|
611
678
|
applyZoomIdentity(d3.zoomIdentity)
|
|
612
679
|
featureZoom.value = null
|
|
613
680
|
emitResetEvent()
|
|
614
681
|
}
|
|
615
682
|
|
|
616
683
|
function resetZoom(_event: MouseEvent, _d: number) {
|
|
617
|
-
map.value
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
684
|
+
map.value
|
|
685
|
+
?.style('--map-scale', 1)
|
|
686
|
+
.transition()
|
|
687
|
+
.duration(props.transitionDuration)
|
|
688
|
+
.call(mapZoom.value?.transform, d3.zoomIdentity)
|
|
621
689
|
featureZoom.value = null
|
|
622
690
|
emitResetEvent()
|
|
623
691
|
}
|
|
@@ -628,48 +696,64 @@ export default defineComponent({
|
|
|
628
696
|
* @event reset
|
|
629
697
|
*/
|
|
630
698
|
emit('reset')
|
|
631
|
-
|
|
632
699
|
}
|
|
633
700
|
|
|
634
701
|
function setFeaturesClasses() {
|
|
635
|
-
map.value
|
|
702
|
+
map.value
|
|
703
|
+
?.selectAll('.choropleth-map__main__features__item')
|
|
704
|
+
.attr('class', featureClass)
|
|
636
705
|
}
|
|
637
706
|
|
|
638
707
|
function setFeatureZoom(d: any, pointer = [0, 0]) {
|
|
639
|
-
|
|
640
708
|
featureZoom.value = get(d, props.topojsonObjectsPath)
|
|
641
709
|
const [[x0, y0], [x1, y1]] = featurePath.value.bounds(d)
|
|
642
|
-
const scale = Math.min(
|
|
710
|
+
const scale = Math.min(
|
|
711
|
+
8,
|
|
712
|
+
0.9 / Math.max((x1 - x0) / mapWidth.value, (y1 - y0) / mapHeight.value)
|
|
713
|
+
)
|
|
643
714
|
const zoomIdentity = d3.zoomIdentity
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
return map.value
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
715
|
+
.translate(mapWidth.value / 2, mapHeight.value / 2)
|
|
716
|
+
.scale(scale)
|
|
717
|
+
.translate(-(x0 + x1) / 2, -(y0 + y1) / 2)
|
|
718
|
+
return map.value
|
|
719
|
+
?.style('--map-scale', scale)
|
|
720
|
+
.transition()
|
|
721
|
+
.duration(props.transitionDuration)
|
|
722
|
+
.call(mapZoom.value?.transform, zoomIdentity, pointer)
|
|
723
|
+
.end()
|
|
652
724
|
}
|
|
653
725
|
|
|
654
726
|
function calculateFeatureZoomIdentity(d: any) {
|
|
655
727
|
const [[x0, y0], [x1, y1]] = featurePath.value.bounds(d)
|
|
656
|
-
const scale = Math.min(
|
|
728
|
+
const scale = Math.min(
|
|
729
|
+
8,
|
|
730
|
+
0.9 / Math.max((x1 - x0) / mapWidth.value, (y1 - y0) / mapHeight.value)
|
|
731
|
+
)
|
|
657
732
|
const translateX = -(x0 + x1) / 2
|
|
658
733
|
const translateY = -(y0 + y1) / 2
|
|
659
734
|
return d3.zoomIdentity
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
735
|
+
.translate(mapWidth.value / 2, mapHeight.value / 2)
|
|
736
|
+
.scale(scale)
|
|
737
|
+
.translate(translateX, translateY)
|
|
663
738
|
}
|
|
664
739
|
|
|
665
740
|
function applyFeatureZoom(d: any, pointer = [0, 0]) {
|
|
666
741
|
const zoomIdentity = calculateFeatureZoomIdentity(d)
|
|
667
742
|
featureZoom.value = get(d, props.topojsonObjectsPath)
|
|
668
|
-
mapTransform.value = {
|
|
743
|
+
mapTransform.value = {
|
|
744
|
+
k: zoomIdentity.k,
|
|
745
|
+
x: zoomIdentity.x,
|
|
746
|
+
y: zoomIdentity.y,
|
|
747
|
+
rotateX: 0,
|
|
748
|
+
rotateY: 0
|
|
749
|
+
}
|
|
669
750
|
return applyZoomIdentity(zoomIdentity, pointer)
|
|
670
751
|
}
|
|
671
752
|
|
|
672
|
-
function applyZoom(
|
|
753
|
+
function applyZoom(
|
|
754
|
+
zoom: number,
|
|
755
|
+
transitionDuration = props.transitionDuration
|
|
756
|
+
) {
|
|
673
757
|
const zoomScale = clamp(zoom, props.zoomMin, props.zoomMax)
|
|
674
758
|
if (props.spherical) {
|
|
675
759
|
return setSphericalZoom(zoomScale, transitionDuration)
|
|
@@ -680,34 +764,58 @@ export default defineComponent({
|
|
|
680
764
|
|
|
681
765
|
function setSphericalZoom(zoomScale: number, transitionDuration: number) {
|
|
682
766
|
const zoomIdentity = d3.zoomIdentity.scale(zoomScale)
|
|
683
|
-
mapTransform.value = {...mapTransform.value, k: zoomScale}
|
|
767
|
+
mapTransform.value = { ...mapTransform.value, k: zoomScale }
|
|
684
768
|
return applyZoomIdentity(zoomIdentity, null, transitionDuration)
|
|
685
769
|
}
|
|
686
770
|
|
|
687
771
|
function setPlanarZoom(zoomScale: number, transitionDuration: number) {
|
|
688
|
-
|
|
689
772
|
const [x, y] = mapProjection.value(mapCenter.value)
|
|
690
|
-
const [translateX, translateY] = [
|
|
691
|
-
|
|
692
|
-
|
|
773
|
+
const [translateX, translateY] = [
|
|
774
|
+
mapWidth.value / 2 - zoomScale * x,
|
|
775
|
+
mapHeight.value / 2 - zoomScale * y
|
|
776
|
+
]
|
|
777
|
+
const zoomIdentity = d3.zoomIdentity
|
|
778
|
+
.translate(translateX, translateY)
|
|
779
|
+
.scale(zoomScale)
|
|
780
|
+
mapTransform.value = {
|
|
781
|
+
k: zoomScale,
|
|
782
|
+
x: translateX,
|
|
783
|
+
y: translateY,
|
|
784
|
+
rotateX: 0,
|
|
785
|
+
rotateY: 0
|
|
786
|
+
}
|
|
693
787
|
return applyZoomIdentity(zoomIdentity, null, transitionDuration)
|
|
694
788
|
}
|
|
695
789
|
|
|
696
|
-
watch(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
watch(
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
790
|
+
watch(
|
|
791
|
+
() => props.socialMode,
|
|
792
|
+
() => {
|
|
793
|
+
draw()
|
|
794
|
+
}
|
|
795
|
+
)
|
|
796
|
+
watch(
|
|
797
|
+
() => props.data,
|
|
798
|
+
() => {
|
|
799
|
+
update()
|
|
800
|
+
}
|
|
801
|
+
)
|
|
802
|
+
watch(
|
|
803
|
+
() => featureZoom.value,
|
|
804
|
+
() => {
|
|
805
|
+
setFeaturesClasses()
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
watch(
|
|
809
|
+
() => featureCursor.value,
|
|
810
|
+
() => {
|
|
811
|
+
setFeaturesClasses()
|
|
812
|
+
}
|
|
813
|
+
)
|
|
708
814
|
|
|
709
815
|
provide<ParentMap>(ParentKey, {
|
|
710
|
-
mapRect,
|
|
816
|
+
mapRect,
|
|
817
|
+
mapTransform,
|
|
818
|
+
rotatingMapProjection
|
|
711
819
|
})
|
|
712
820
|
|
|
713
821
|
return {
|
|
@@ -738,31 +846,58 @@ export default defineComponent({
|
|
|
738
846
|
})
|
|
739
847
|
</script>
|
|
740
848
|
<template>
|
|
741
|
-
<div
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
849
|
+
<div
|
|
850
|
+
ref="resizable"
|
|
851
|
+
:class="mapClass"
|
|
852
|
+
:style="mapStyle"
|
|
853
|
+
class="choropleth-map"
|
|
854
|
+
@click="draw"
|
|
855
|
+
>
|
|
856
|
+
<svg
|
|
857
|
+
:viewbox="`0 0 ${mapRect.width} ${mapRect.height}`"
|
|
858
|
+
class="choropleth-map__main"
|
|
859
|
+
>
|
|
860
|
+
<pattern
|
|
861
|
+
id="diagonalHatch"
|
|
862
|
+
height="1"
|
|
863
|
+
patternTransform="rotate(45 0 0)"
|
|
864
|
+
patternUnits="userSpaceOnUse"
|
|
865
|
+
width="1"
|
|
866
|
+
>
|
|
867
|
+
<rect :fill="featureColorScaleEnd" height="1" width="1" />
|
|
868
|
+
<line
|
|
869
|
+
:style="{ stroke: featureColorScaleStart, strokeWidth: 1 }"
|
|
870
|
+
x1="0"
|
|
871
|
+
x2="0"
|
|
872
|
+
y1="0"
|
|
873
|
+
y2="1"
|
|
874
|
+
/>
|
|
746
875
|
</pattern>
|
|
747
|
-
<g
|
|
876
|
+
<g
|
|
877
|
+
:transform-origin="transformOrigin"
|
|
878
|
+
class="choropleth-map__main__tracked"
|
|
879
|
+
>
|
|
748
880
|
<g v-if="graticule" class="choropleth-map__main__graticule"></g>
|
|
749
881
|
<g class="choropleth-map__main__features"></g>
|
|
750
882
|
<g v-if="outline" class="choropleth-map__main__outline"></g>
|
|
751
|
-
<slot v-if="isReady"/>
|
|
883
|
+
<slot v-if="isReady" />
|
|
752
884
|
</g>
|
|
753
885
|
</svg>
|
|
754
886
|
<scale-legend
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
887
|
+
v-if="!hideLegend && isReady"
|
|
888
|
+
:color-scale="featureColorScaleFunction"
|
|
889
|
+
:color-scale-end="featureColorScaleEnd"
|
|
890
|
+
:color-scale-start="featureColorScaleStart"
|
|
891
|
+
:cursor-value="cursorValue"
|
|
892
|
+
:max="maxValue"
|
|
893
|
+
:min="minValue"
|
|
894
|
+
class="choropleth-map__legend"
|
|
763
895
|
>
|
|
764
896
|
<template #cursor="{ value }">
|
|
765
|
-
<slot
|
|
897
|
+
<slot
|
|
898
|
+
name="legend-cursor"
|
|
899
|
+
v-bind="{ value, identifier: featureCursor }"
|
|
900
|
+
/>
|
|
766
901
|
</template>
|
|
767
902
|
</scale-legend>
|
|
768
903
|
</div>
|
|
@@ -799,7 +934,10 @@ export default defineComponent({
|
|
|
799
934
|
stroke: currentColor;
|
|
800
935
|
stroke-width: calc(1px / var(--map-scale, 1));
|
|
801
936
|
fill: currentColor;
|
|
802
|
-
transition:
|
|
937
|
+
transition:
|
|
938
|
+
opacity 750ms,
|
|
939
|
+
filter 750ms,
|
|
940
|
+
fill 750ms;
|
|
803
941
|
|
|
804
942
|
.choropleth-map__main__features__item--empty {
|
|
805
943
|
opacity: 0.8;
|
|
@@ -810,7 +948,8 @@ export default defineComponent({
|
|
|
810
948
|
}
|
|
811
949
|
}
|
|
812
950
|
|
|
813
|
-
.choropleth-map--has-zoom
|
|
951
|
+
.choropleth-map--has-zoom
|
|
952
|
+
&:not(.choropleth-map__main__features__item--zoomed) {
|
|
814
953
|
filter: grayscale(90%);
|
|
815
954
|
}
|
|
816
955
|
}
|