@globalbrain/sefirot 4.15.0 → 4.17.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.
|
@@ -3,6 +3,7 @@ import { useElementSize } from '@vueuse/core'
|
|
|
3
3
|
import * as d3 from 'd3'
|
|
4
4
|
import { useTemplateRef, watch } from 'vue'
|
|
5
5
|
import { type ChartColor, type KV, type Margins, c, scheme } from '../support/Chart'
|
|
6
|
+
import { getTextSize } from '../support/Text'
|
|
6
7
|
|
|
7
8
|
const props = withDefaults(defineProps<{
|
|
8
9
|
// State
|
|
@@ -19,6 +20,8 @@ const props = withDefaults(defineProps<{
|
|
|
19
20
|
yLabel?: string
|
|
20
21
|
xLabelOffset?: number
|
|
21
22
|
yLabelOffset?: number
|
|
23
|
+
xLabelTickGap?: number
|
|
24
|
+
yLabelTickGap?: number
|
|
22
25
|
xLabelFontSize?: string
|
|
23
26
|
yLabelFontSize?: string
|
|
24
27
|
ticks?: number
|
|
@@ -38,11 +41,13 @@ const props = withDefaults(defineProps<{
|
|
|
38
41
|
|
|
39
42
|
xLabelFontSize: '14px',
|
|
40
43
|
yLabelFontSize: '14px',
|
|
44
|
+
xLabelTickGap: 20,
|
|
45
|
+
yLabelTickGap: 20,
|
|
41
46
|
ticks: 5,
|
|
42
47
|
tickFontSize: '14px',
|
|
43
48
|
|
|
44
49
|
tooltip: true,
|
|
45
|
-
tooltipFormat: (d: KV) => `${d.key}
|
|
50
|
+
tooltipFormat: (d: KV) => `${d.key} – ${d.value}`,
|
|
46
51
|
|
|
47
52
|
animate: true,
|
|
48
53
|
debug: false
|
|
@@ -67,6 +72,7 @@ function renderChart({
|
|
|
67
72
|
|
|
68
73
|
// Create color scale
|
|
69
74
|
const color = scheme(props.data, props.colors)
|
|
75
|
+
const font = getComputedStyle(chartRef.value).fontFamily
|
|
70
76
|
|
|
71
77
|
// Clear any existing SVG
|
|
72
78
|
d3
|
|
@@ -77,25 +83,37 @@ function renderChart({
|
|
|
77
83
|
// Set dimensions and margins
|
|
78
84
|
const vertical = props.type === 'vertical'
|
|
79
85
|
|
|
86
|
+
const maxKeyLength = props.data.reduce((a, b) => Math.max(a, b.key.length), Number.NEGATIVE_INFINITY)
|
|
87
|
+
const maxValueLength = props.data.reduce((a, b) => Math.max(a, b.value.toLocaleString().length), Number.NEGATIVE_INFINITY)
|
|
88
|
+
|
|
89
|
+
const maxVerticalTickWidthInCh = vertical ? maxValueLength : maxKeyLength
|
|
90
|
+
const maxVerticalTickWidthInPx = getTextSize('0'.repeat(maxVerticalTickWidthInCh), `400 ${props.tickFontSize} ${font}`).width
|
|
91
|
+
const verticalLabelWidthInPx = props.yLabel ? getTextSize(props.yLabel, `400 ${props.yLabelFontSize} ${font}`).height : 0
|
|
92
|
+
const gapBetweenVerticalLabelAndTicks = props.yLabel ? props.yLabelTickGap : 0
|
|
93
|
+
|
|
94
|
+
const maxHorizontalTickHeightInPx = getTextSize('0', `400 ${props.tickFontSize} ${font}`).height // wrapping isn't supported
|
|
95
|
+
const horizontalLabelHeightInPx = props.xLabel ? getTextSize(props.xLabel, `400 ${props.xLabelFontSize} ${font}`).height : 0
|
|
96
|
+
const gapBetweenHorizontalLabelAndTicks = props.xLabel ? props.xLabelTickGap : 0
|
|
97
|
+
|
|
98
|
+
const xLabelOffset = props.xLabelOffset ?? horizontalLabelHeightInPx + 9 + maxHorizontalTickHeightInPx + gapBetweenHorizontalLabelAndTicks
|
|
99
|
+
const yLabelOffset = props.yLabelOffset ?? 9 + maxVerticalTickWidthInPx + gapBetweenVerticalLabelAndTicks
|
|
100
|
+
|
|
80
101
|
const margin = {
|
|
81
102
|
top: props.margins?.top ?? 30,
|
|
82
|
-
right: props.margins?.right ??
|
|
83
|
-
bottom: props.margins?.bottom ?? (props.xLabel ?
|
|
84
|
-
left: props.margins?.left ??
|
|
103
|
+
right: props.margins?.right ?? 30,
|
|
104
|
+
bottom: (props.margins?.bottom ?? 30) + horizontalLabelHeightInPx + xLabelOffset - (props.xLabel ? 9 : 0),
|
|
105
|
+
left: (props.margins?.left ?? 30) + verticalLabelWidthInPx + yLabelOffset
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
const width = clientWidth - margin.left - margin.right
|
|
88
109
|
const height = clientHeight - margin.top - margin.bottom
|
|
89
110
|
|
|
90
|
-
const xLabelOffset = props.xLabelOffset ?? 46
|
|
91
|
-
const yLabelOffset = props.yLabelOffset ?? (vertical ? 40 : 56)
|
|
92
|
-
|
|
93
111
|
// Create SVG
|
|
94
112
|
const svg = d3
|
|
95
113
|
.select(chartRef.value)
|
|
96
114
|
.append('svg')
|
|
97
115
|
.attr('width', '100%')
|
|
98
|
-
.attr('height',
|
|
116
|
+
.attr('height', clientHeight)
|
|
99
117
|
.append('g')
|
|
100
118
|
.attr('transform', `translate(${margin.left},${margin.top})`)
|
|
101
119
|
|
|
@@ -5,7 +5,7 @@ import xor from 'lodash-es/xor'
|
|
|
5
5
|
import { computed, nextTick, reactive, ref, shallowRef, toValue, unref, watch } from 'vue'
|
|
6
6
|
import { type Table } from '../composables/Table'
|
|
7
7
|
import { smartComputed } from '../support/Reactivity'
|
|
8
|
-
import {
|
|
8
|
+
import { getTextSize } from '../support/Text'
|
|
9
9
|
import SInputCheckbox from './SInputCheckbox.vue'
|
|
10
10
|
import SInputRadio from './SInputRadio.vue'
|
|
11
11
|
import SSpinner from './SSpinner.vue'
|
|
@@ -231,12 +231,12 @@ const actionsColumnWidth = computed(() => {
|
|
|
231
231
|
|
|
232
232
|
// has only label
|
|
233
233
|
if (label && !icon) {
|
|
234
|
-
return 1 /* border */ + 12 /* padding */ +
|
|
234
|
+
return 1 /* border */ + 12 /* padding */ + getTextSize(label, font).width + 12 /* padding */ + 1 /* border */
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
// has both icon and label
|
|
238
238
|
if (icon && label) {
|
|
239
|
-
return 1 /* border */ + 8 /* padding */ + 16 /* icon */ + 4 /* padding */ +
|
|
239
|
+
return 1 /* border */ + 8 /* padding */ + 16 /* icon */ + 4 /* padding */ + getTextSize(label, font).width + 10 /* padding */ + 1 /* border */
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
return 0
|
package/lib/support/Text.ts
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
let _canvas: HTMLCanvasElement
|
|
4
4
|
|
|
5
|
-
export function
|
|
6
|
-
export function getTextWidth(text: string, el: HTMLElement): number
|
|
7
|
-
|
|
8
|
-
export function getTextWidth(text: string, fontOrEl: string | HTMLElement): number {
|
|
5
|
+
export function getTextSize(text: string, fontOrEl: string | HTMLElement) {
|
|
9
6
|
const canvas = _canvas || (_canvas = document.createElement('canvas'))
|
|
10
7
|
const context = canvas.getContext('2d')!
|
|
11
8
|
context.font = typeof fontOrEl === 'string' ? fontOrEl : getCanvasFont(fontOrEl)
|
|
12
9
|
const metrics = context.measureText(text)
|
|
13
10
|
|
|
14
|
-
return
|
|
11
|
+
return {
|
|
12
|
+
width: metrics.width,
|
|
13
|
+
height: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
|
|
14
|
+
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function getCanvasFont(el: HTMLElement) {
|
package/package.json
CHANGED