@icij/murmur-next 4.0.17 → 4.1.0

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.
@@ -7,16 +7,9 @@ import kebabCase from 'lodash/kebabCase'
7
7
  import keys from 'lodash/keys'
8
8
  import without from 'lodash/without'
9
9
  import sortBy from 'lodash/sortBy'
10
- import {
11
- ComponentPublicInstance,
12
- computed,
13
- defineComponent,
14
- nextTick,
15
- PropType,
16
- ref,
17
- watch
18
- } from 'vue'
10
+ import { ComponentPublicInstance, computed, defineComponent, PropType, ref, watch } from 'vue'
19
11
  import { chartProps, getChartProps, useChart } from '@/composables/chart.js'
12
+ import { useQueryObserver } from '@/composables/queryObserver.js'
20
13
  import { isArray } from 'lodash'
21
14
 
22
15
  export default defineComponent({
@@ -147,8 +140,14 @@ export default defineComponent({
147
140
  const highlightTimeout = ref<NodeJS.Timeout | undefined>(undefined)
148
141
  const isLoaded = ref(false)
149
142
  const el = ref<ComponentPublicInstance<HTMLElement> | null>(null)
150
- const { loadedData, baseHeightRatio, d3Formatter, dataHasHighlights } =
151
- useChart(el, getChartProps(props), { emit }, isLoaded)
143
+ const {
144
+ loadedData,
145
+ mounted,
146
+ baseHeightRatio,
147
+ d3Formatter,
148
+ dataHasHighlights
149
+ } = useChart(el, getChartProps(props), { emit }, isLoaded)
150
+ const { querySelectorAll } = useQueryObserver(el)
152
151
 
153
152
  const hasConstraintHeight = computed(() => {
154
153
  return props.fixedHeight !== null || props.socialMode
@@ -162,6 +161,7 @@ export default defineComponent({
162
161
  ? loadedData.value
163
162
  : sortBy(loadedData.value, props.sortBy)
164
163
  })
164
+
165
165
  const discoveredKeys = computed((): any[] => {
166
166
  if (props.keys.length) {
167
167
  return props.keys
@@ -177,22 +177,30 @@ export default defineComponent({
177
177
  .domain(discoveredKeys.value)
178
178
  .range(props.barColors)
179
179
  })
180
+
180
181
  const maxValue = computed(() => {
181
182
  return d3.max(loadedData.value || [], (datum, i) => {
182
183
  return totalRowValue(i)
183
184
  })
184
185
  })
186
+
185
187
  const hasHighlights = computed(() => {
186
188
  return !!highlightedKeys.value.length
187
189
  })
190
+
188
191
  const hasRowHighlights = computed(() => {
189
192
  return !!props.rowHighlights.length
190
193
  })
194
+
195
+ const hasAnyHighlights = computed(() => {
196
+ return hasHighlights.value || hasRowHighlights.value || dataHasHighlights.value
197
+ })
198
+
191
199
  const height = computed(() => {
192
200
  if (props.fixedHeight !== null) {
193
201
  return `${props.fixedHeight}px`
194
202
  }
195
- return props.socialMode && el.value
203
+ return props.socialMode && mounted.value && el.value
196
204
  ? `${el.value.offsetWidth * baseHeightRatio.value}px`
197
205
  : 'auto'
198
206
  })
@@ -258,14 +266,10 @@ export default defineComponent({
258
266
  return { width, backgroundColor }
259
267
  }
260
268
 
261
- // function barHeightBounds(height:number) {
262
- // return Math.min(Math.max(height, props.minBarHeight), props.maxBarHeight)
263
- // }
264
- async function stackBarAndValue(i: number | string) {
265
- if (!sortedData.value) {
269
+ function stackBarAndValue(i: number | string) {
270
+ if (!mounted.value) {
266
271
  return []
267
272
  }
268
- await nextTick()
269
273
  // Collect sizes first
270
274
  const stack = discoveredKeys.value.map((key: string) => {
271
275
  const { bar, row, value } = queryBarAndValue(i as number, key)
@@ -276,31 +280,34 @@ export default defineComponent({
276
280
  const barWidth = bar.offsetWidth
277
281
  const rowEdge = row.getBoundingClientRect().left + row.offsetWidth
278
282
  const valueWidth = value.offsetWidth
279
- return { key, barEdge, barWidth, rowEdge, valueWidth }
283
+ const overflow = false
284
+ const pushed = false
285
+ return { key, barEdge, barWidth, rowEdge, valueWidth, overflow, pushed }
280
286
  })
281
287
  // Infer value's display
282
288
  return stack.map((desc, index) => {
289
+ // Value must be visible out of the bar (overflow) if the size of the value label is bigger than the bar
283
290
  desc.overflow = desc.valueWidth >= desc.barWidth
284
- if (index > 0) {
291
+ // If the value not out of the bar, we must ensure its predecesor isn't.
292
+ if (!desc.overflow && index > 0) {
285
293
  const prevDesc = stack[index - 1]
286
294
  const bothValuesWidth = desc.valueWidth + prevDesc.valueWidth
287
- desc.overflow =
288
- desc.overflow ||
289
- (prevDesc.overflow && desc.barWidth < bothValuesWidth)
295
+ desc.overflow = prevDesc.overflow && desc.barWidth < bothValuesWidth
290
296
  }
291
- desc.pushed =
292
- desc.barEdge + desc.valueWidth > desc.rowEdge && desc.overflow
297
+ // Value must be pushed to the other side (left) if the value label is outside of the bar (overflow)
298
+ // and if the size of the value label isn't fitting in the row
299
+ desc.pushed = desc.overflow && desc.barEdge + desc.valueWidth > desc.rowEdge
293
300
  return desc
294
301
  })
295
302
  }
296
303
 
297
304
  function queryBarAndValue(i: number, key: string) {
298
- if (!sortedData.value) {
305
+ if (!mounted.value) {
299
306
  return {}
300
307
  }
301
308
  const barClass = 'stacked-bar-chart__groups__item__bars__item'
302
309
  const rowSelector = '.stacked-bar-chart__groups__item'
303
- const row = el.value?.querySelectorAll(rowSelector)[i] as HTMLElement
310
+ const row = querySelectorAll(rowSelector)[i] as HTMLElement
304
311
  const normalizedKey = normalizeKey(key)
305
312
  const barSelector = `.${barClass}--${normalizedKey}`
306
313
  const bar = row?.querySelector(barSelector) as HTMLElement
@@ -309,24 +316,32 @@ export default defineComponent({
309
316
  return { bar, row, value }
310
317
  }
311
318
 
312
- async function hasValueOverflow(i: number | string, key: string) {
313
- const stack = await stackBarAndValue(i)
314
- return get(find(stack, { key }), 'overflow')
319
+ function hasValueOverflow(i: number | string, key: string) {
320
+ try {
321
+ const stack = stackBarAndValue(i)
322
+ return find(stack, { key })?.overflow
323
+ } catch {
324
+ return false
325
+ }
315
326
  }
316
-
317
- async function hasValuePushed(i: number | string, key: string) {
318
- const stack = await stackBarAndValue(i)
319
- return get(find(stack, { key }), 'pushed')
327
+
328
+ function hasValuePushed(i: number | string, key: string) {
329
+ try {
330
+ const stack = stackBarAndValue(i)
331
+ return find(stack, { key })?.pushed
332
+ } catch {
333
+ return false
334
+ }
320
335
  }
321
336
 
322
- async function hasValueHidden(i: number | string, key: string) {
337
+ function hasValueHidden(i: number | string, key: string) {
323
338
  const keyIndex = discoveredKeys.value.indexOf(key)
324
339
  const nextKey = discoveredKeys.value[keyIndex + 1]
325
340
  if (!nextKey) {
326
341
  return false
327
342
  }
328
- const keyC = await hasValueOverflow(i, key)
329
- const keyN = await hasValueOverflow(i, nextKey)
343
+ const keyC = hasValueOverflow(i, key)
344
+ const keyN = hasValueOverflow(i, nextKey)
330
345
  return keyC && keyN
331
346
  }
332
347
 
@@ -334,47 +349,20 @@ export default defineComponent({
334
349
  return props.hideEmptyValues && !sortedData.value[i][key]
335
350
  }
336
351
 
337
- // async function barItemClasses(i,key){
338
- // const stack = await stackBarAndValue(i)
339
- // const hasOv = get(find(stack, {key}), 'overflow')
340
- // const hasPu = get(find(stack, {key}), 'pushed')
341
- //
342
- // const keyIndex = discoveredKeys.value.indexOf(key)
343
- // const nextKey = discoveredKeys.value[keyIndex + 1]
344
- // let hasNextOv= false
345
- // if(nextKey){
346
- // const stackNext = await stackBarAndValue(keyIndex + 1)
347
- // hasNextOv = get(find(stackNext, {key}), 'overflow')
348
- // }
349
- // const hasHiddenV = hasOv && hasNextOv
350
- // const classes = {
351
- // hiddenValue:isHidden(keyIndex, key),
352
- // overflow:hasOv,
353
- // pushed:hasPu,
354
- // hidden:hasHiddenV
355
- // }
356
- // return classes
357
- // }
358
-
359
352
  function formatXDatum(d: string) {
360
353
  return d3Formatter(d, props.xAxisTickFormat)
361
354
  }
362
355
 
363
- watch(
364
- () => props.highlights,
365
- (newHighlights) => {
366
- highlightedKeys.value = newHighlights
367
- }
368
- )
356
+ watch(() => props.highlights, (newHighlights) => {
357
+ highlightedKeys.value = newHighlights
358
+ })
369
359
 
370
360
  return {
371
361
  colorScale,
372
- dataHasHighlights,
373
362
  discoveredKeys,
374
363
  el,
375
364
  hasConstraintHeight,
376
- hasHighlights,
377
- hasRowHighlights,
365
+ hasAnyHighlights,
378
366
  height,
379
367
  sortedData,
380
368
  barStyle,
@@ -392,24 +380,6 @@ export default defineComponent({
392
380
  restoreHighlights
393
381
  }
394
382
  }
395
-
396
- // watch: {
397
- // relative() {
398
- // this.$nextTick(this.$forceUpdate)
399
- // },
400
- // height() {
401
- // this.$nextTick(this.$forceUpdate)
402
- // },
403
- // sortBy() {
404
- // this.$nextTick(this.$forceUpdate)
405
- // },
406
- // highlights:{
407
- // deep:true,
408
- // handler() {
409
- // this.highlightedKeys = this.highlights
410
- // }
411
- // }
412
- // },
413
383
  })
414
384
  </script>
415
385
  <template>
@@ -418,8 +388,7 @@ export default defineComponent({
418
388
  :class="{
419
389
  'stacked-bar-chart--social-mode': socialMode,
420
390
  'stacked-bar-chart--label-above': labelAbove,
421
- 'stacked-bar-chart--has-highlights':
422
- hasHighlights || hasRowHighlights || dataHasHighlights,
391
+ 'stacked-bar-chart--has-highlights': hasAnyHighlights,
423
392
  'stacked-bar-chart--has-constraint-height': hasConstraintHeight,
424
393
  'stacked-bar-chart--has-label-above': labelAbove
425
394
  }"
@@ -456,7 +425,7 @@ export default defineComponent({
456
425
  <div
457
426
  v-for="(datum, i) in sortedData"
458
427
  :key="i"
459
- :class="{ 'flex-column': labelAbove }"
428
+ :class="{ 'flex-column': labelAbove, }"
460
429
  class="stacked-bar-chart__groups__item border-bottom flex-fill d-flex align-items-center"
461
430
  >
462
431
  <div
@@ -474,18 +443,11 @@ export default defineComponent({
474
443
  :class="{
475
444
  [`stacked-bar-chart__groups__item__bars__item--${normalizeKey(key)}`]: true,
476
445
  [`stacked-bar-chart__groups__item__bars__item--${j}n`]: true,
477
- 'stacked-bar-chart__groups__item__bars__item--highlighted':
478
- isHighlighted(key) || isRowHighlighted(i),
479
- 'stacked-bar-chart__groups__item__bars__item--hidden': isHidden(
480
- i,
481
- key
482
- ),
483
- 'stacked-bar-chart__groups__item__bars__item--value-overflow':
484
- hasValueOverflow(i, key),
485
- 'stacked-bar-chart__groups__item__bars__item--value-pushed':
486
- hasValuePushed(i, key),
487
- 'stacked-bar-chart__groups__item__bars__item--value-hidden':
488
- hasValueHidden(i, key)
446
+ 'stacked-bar-chart__groups__item__bars__item--highlighted': isHighlighted(key) || isRowHighlighted(i),
447
+ 'stacked-bar-chart__groups__item__bars__item--hidden': isHidden(i, key),
448
+ 'stacked-bar-chart__groups__item__bars__item--value-overflow': hasValueOverflow(i, key),
449
+ 'stacked-bar-chart__groups__item__bars__item--value-pushed': hasValuePushed(i, key),
450
+ 'stacked-bar-chart__groups__item__bars__item--value-hidden': hasValueHidden(i, key)
489
451
  }"
490
452
  :style="barStyle(i, key)"
491
453
  class="stacked-bar-chart__groups__item__bars__item"
@@ -11,11 +11,13 @@ import {
11
11
  ComponentPublicInstance,
12
12
  computed,
13
13
  defineComponent,
14
+ getCurrentInstance,
14
15
  ref,
15
16
  nextTick,
16
17
  watch
17
18
  } from 'vue'
18
19
  import { chartProps, getChartProps, useChart } from '@/composables/chart.js'
20
+ import { useQueryObserver } from '@/composables/queryObserver.js'
19
21
 
20
22
  export default defineComponent({
21
23
  name: 'StackedColumnChart',
@@ -186,14 +188,17 @@ export default defineComponent({
186
188
  const highlightTimeout = ref<NodeJS.Timeout | undefined>(undefined)
187
189
  const isLoaded = ref(false)
188
190
  const el = ref<ComponentPublicInstance<HTMLElement> | null>(null)
191
+ const { querySelector, querySelectorAll } = useQueryObserver(el)
189
192
 
190
193
  const {
191
194
  elementsMaxBBox,
192
- baseHeightRatio,
195
+ baseHeightRatio,
193
196
  loadedData,
197
+ mounted,
194
198
  d3Formatter,
195
199
  dataHasHighlights
196
200
  } = useChart(el, getChartProps(props), { emit }, isLoaded, setSizes)
201
+
197
202
  const sortedData = computed(() => {
198
203
  if (!isLoaded.value) {
199
204
  return []
@@ -223,8 +228,6 @@ export default defineComponent({
223
228
  return !!highlightedKeys.value.length
224
229
  })
225
230
 
226
- // different
227
-
228
231
  const hasColumnHighlights = computed(() => {
229
232
  return !!props.columnHighlights.length
230
233
  })
@@ -242,7 +245,7 @@ export default defineComponent({
242
245
  .tickFormat((d) => d3Formatter(d, props.yAxisTickFormat))
243
246
  .tickSize(width.value - leftAxisLabelsWidth.value)
244
247
  .tickPadding(props.yAxisTickPadding)
245
- } /*, {cache: false}*/
248
+ }
246
249
  )
247
250
  const leftAxisLabelsWidth = computed(
248
251
  () => {
@@ -252,7 +255,7 @@ export default defineComponent({
252
255
  elementsMaxBBox({ selector, defaultWidth }).width +
253
256
  props.yAxisTickPadding
254
257
  )
255
- } /*, {cache: false}*/
258
+ }
256
259
  )
257
260
 
258
261
  const leftAxisCanvas = computed(() => {
@@ -260,6 +263,7 @@ export default defineComponent({
260
263
  .select(el.value)
261
264
  .select('.stacked-column-chart__left-axis__canvas')
262
265
  })
266
+
263
267
  const paddedStyle = computed(() => {
264
268
  return {
265
269
  marginLeft: props.noDirectLabeling
@@ -267,9 +271,11 @@ export default defineComponent({
267
271
  : 0
268
272
  }
269
273
  })
274
+
270
275
  const barTooltipDelay = computed(() => {
271
276
  return hasHighlights.value ? 0 : props.highlightDelay
272
277
  })
278
+
273
279
  const maxRowValue = computed(() => {
274
280
  return (
275
281
  props.maxValue ||
@@ -289,6 +295,7 @@ export default defineComponent({
289
295
  ? props.fixedHeight
290
296
  : width.value * baseHeightRatio.value
291
297
  }
298
+
292
299
  function groupName(key: string) {
293
300
  const index = discoveredKeys.value.indexOf(key)
294
301
  return props.groups[index] || key
@@ -353,12 +360,16 @@ export default defineComponent({
353
360
  return props.tooltipDisplay({ value, formattedValue, key, formattedKey })
354
361
  }
355
362
 
356
- async function stackBarAndValue(i: string | number) {
363
+ function barUniqueId(i: string | number, key: string) {
364
+ // @ts-ignore
365
+ const { uid } = getCurrentInstance()
366
+ return `bar-${uid}-${i}-${key}`
367
+ }
368
+
369
+ function stackBarAndValue(i: string | number) {
357
370
  if (!sortedData.value) {
358
371
  return []
359
372
  }
360
- await nextTick()
361
-
362
373
  // Collect sizes first
363
374
  const stack = discoveredKeys.value.map((key: string) => {
364
375
  const { bar, row, value } = queryBarAndValue(i as number, key)
@@ -396,15 +407,14 @@ export default defineComponent({
396
407
  }
397
408
 
398
409
  function queryBarAndValue(i: number, key: string) {
399
- if (!sortedData.value) {
410
+ if (!mounted.value) {
400
411
  return {}
401
412
  }
402
413
  const rowSelector = '.stacked-column-chart__groups__item'
403
- const row = el.value?.querySelectorAll(rowSelector)[i] as HTMLElement
414
+ const row = querySelectorAll(rowSelector)[i] as HTMLElement
404
415
  const barSelector = `.stacked-column-chart__groups__item__bars__item--${key}`
405
416
  const bar = row.querySelector(barSelector) as HTMLElement
406
- const valueSelector =
407
- '.stacked-column-chart__groups__item__bars__item__value'
417
+ const valueSelector = '.stacked-column-chart__groups__item__bars__item__value'
408
418
  const value = bar.querySelector(valueSelector) as HTMLElement
409
419
  return { bar, row, value }
410
420
  }
@@ -414,13 +424,21 @@ export default defineComponent({
414
424
  }
415
425
 
416
426
  function hasValueOverflow(i: string | number, key: string) {
417
- const stack = stackBarAndValue(i)
418
- return find(stack, { key })?.overflow
427
+ try {
428
+ const stack = stackBarAndValue(i)
429
+ return find(stack, { key })?.overflow
430
+ } catch {
431
+ return false
432
+ }
419
433
  }
420
434
 
421
435
  function hasValuePushed(i: string | number, key: string) {
422
- const stack = stackBarAndValue(i)
423
- return find(stack, { key })?.pushed
436
+ try {
437
+ const stack = stackBarAndValue(i)
438
+ return find(stack, { key })?.pushed
439
+ } catch {
440
+ return false
441
+ }
424
442
  }
425
443
 
426
444
  function hasValueHidden(i: string | number, key: string) {
@@ -446,16 +464,16 @@ export default defineComponent({
446
464
  watch(sortedData, async (newVal) => {
447
465
  await nextTick()
448
466
  // This must be set after the column have been rendered
449
- const element = el.value?.querySelector(
450
- '.stacked-column-chart__groups__item__bars'
451
- )
452
- leftAxisHeight.value = (element as HTMLElement).offsetHeight
453
- //@ts-ignore
454
- leftAxisCanvas.value.call(leftAxis.value)
467
+ const element = querySelector('.stacked-column-chart__groups__item__bars')
468
+ // Update the left axis only if the bars exists
469
+ if (element) {
470
+ leftAxisHeight.value = (element as HTMLElement).offsetHeight
471
+ //@ts-ignore
472
+ leftAxisCanvas.value.call(leftAxis.value)
473
+ }
455
474
  })
456
475
 
457
476
  return {
458
- barTooltipDelay,
459
477
  colorScale,
460
478
  dataHasHighlights,
461
479
  discoveredKeys,
@@ -467,7 +485,9 @@ export default defineComponent({
467
485
  paddedStyle,
468
486
  sortedData,
469
487
  width,
488
+ barTooltipDelay,
470
489
  barTitle,
490
+ barUniqueId,
471
491
  barStyle,
472
492
  delayHighlight,
473
493
  formatXDatum,
@@ -540,28 +560,19 @@ export default defineComponent({
540
560
  >
541
561
  <div
542
562
  v-for="(key, j) in discoveredKeys"
543
- :key="j"
544
- v-b-tooltip.html="{
545
- delay: barTooltipDelay,
546
- disabled: noTooltips,
547
- title: barTitle(i, key)
548
- }"
549
- :style="barStyle(i, key)"
550
563
  class="stacked-column-chart__groups__item__bars__item"
551
564
  :class="{
552
565
  [`stacked-column-chart__groups__item__bars__item--${key}`]: true,
553
566
  [`stacked-column-chart__groups__item__bars__item--${j}n`]: true,
554
- 'stacked-column-chart__groups__item__bars__item--hidden':
555
- isHidden(i, key),
556
- 'stacked-column-chart__groups__item__bars__item--highlighted':
557
- isHighlighted(key) || isColumnHighlighted(i),
558
- 'stacked-column-chart__groups__item__bars__item--value-overflow':
559
- hasValueOverflow(i, key),
560
- 'stacked-column-chart__groups__item__bars__item--value-pushed':
561
- hasValuePushed(i, key),
562
- 'stacked-column-chart__groups__item__bars__item--value-hidden':
563
- hasValueHidden(i, key)
567
+ 'stacked-column-chart__groups__item__bars__item--hidden': isHidden(i, key),
568
+ 'stacked-column-chart__groups__item__bars__item--highlighted': isHighlighted(key) || isColumnHighlighted(i),
569
+ 'stacked-column-chart__groups__item__bars__item--value-overflow': hasValueOverflow(i, key),
570
+ 'stacked-column-chart__groups__item__bars__item--value-pushed': hasValuePushed(i, key),
571
+ 'stacked-column-chart__groups__item__bars__item--value-hidden': hasValueHidden(i, key)
564
572
  }"
573
+ :style="barStyle(i, key)"
574
+ :key="j"
575
+ :id="barUniqueId(i, key)"
565
576
  @mouseover="delayHighlight(key)"
566
577
  @mouseleave="restoreHighlights()"
567
578
  >
@@ -571,6 +582,11 @@ export default defineComponent({
571
582
  >
572
583
  {{ formatYDatum(datum[key]) }}
573
584
  </div>
585
+ <teleport to="body">
586
+ <b-tooltip v-if="!noTooltips" :delay="barTooltipDelay" :target="barUniqueId(i, key)" placement="top">
587
+ <span v-html="barTitle(i, key)"></span>
588
+ </b-tooltip>
589
+ </teleport>
574
590
  </div>
575
591
  </div>
576
592
  <div class="stacked-column-chart__groups__item__label small py-2">
@@ -91,10 +91,10 @@ $tooltip-opacity: 1 !default;
91
91
 
92
92
  $alert-border-width: 0;
93
93
 
94
- // Bootstrap variables and functions must be available everywhere
94
+ /* Bootstrap variables and functions must be available everywhere */
95
95
  @import 'node_modules/bootstrap/scss/_functions.scss';
96
96
  @import 'node_modules/bootstrap/scss/_variables.scss';
97
97
 
98
- // Get theses variables to make them available in the doc
98
+ /* Get theses variables to make them available in the doc */
99
99
  $theme-colors: none !default;
100
100
  $font-family-monospace: none !default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icij/murmur-next",
3
- "version": "4.0.17",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "description": "Murmur is ICIJ's Design System for Bootstrap 5 and Vue.js",
6
6
  "author": "promera@icij.org",