@kiva/kv-components 3.100.0 → 3.101.1
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/CHANGELOG.md +23 -0
- package/package.json +2 -3
- package/tests/unit/specs/utils/treemap.spec.js +248 -0
- package/utils/treemap.js +138 -0
- package/vue/KvIntroductionLoanCard.vue +15 -7
- package/vue/KvTreeMapChart.vue +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,29 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [3.101.1](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.101.0...@kiva/kv-components@3.101.1) (2024-09-05)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* force primary color ([652370b](https://github.com/kiva/kv-ui-elements/commit/652370b54998b4fd9597f434c66c783448d0419e))
|
|
12
|
+
* new loan card name wasn't clickable ([27603b7](https://github.com/kiva/kv-ui-elements/commit/27603b78f1c8d271baa23e93dfde2acc8e7e5727))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# [3.101.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.100.0...@kiva/kv-components@3.101.0) (2024-09-05)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* move treemap into repo ([e75bd3c](https://github.com/kiva/kv-ui-elements/commit/e75bd3c83d7ede41dc07196392bafdf941b0bcc3))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
6
29
|
# [3.100.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.99.0...@kiva/kv-components@3.100.0) (2024-09-04)
|
|
7
30
|
|
|
8
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kiva/kv-components",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.101.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -72,7 +72,6 @@
|
|
|
72
72
|
"nanoid": "^3.1.23",
|
|
73
73
|
"numeral": "^2.0.6",
|
|
74
74
|
"popper.js": "^1.16.1",
|
|
75
|
-
"treemap-squarify": "github:kiva/treemap",
|
|
76
75
|
"vue-demi": "^0.14.7"
|
|
77
76
|
},
|
|
78
77
|
"peerDependencies": {
|
|
@@ -84,5 +83,5 @@
|
|
|
84
83
|
"optional": true
|
|
85
84
|
}
|
|
86
85
|
},
|
|
87
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "ab2bc8592e474d04339b85dd865149e3b525609a"
|
|
88
87
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { getTreemap } from '../../../../utils/treemap';
|
|
2
|
+
|
|
3
|
+
describe('treemap.js', () => {
|
|
4
|
+
describe('getTreemap', () => {
|
|
5
|
+
it('should return an error if malformed height argument', () => {
|
|
6
|
+
const shouldThrow = () => getTreemap({
|
|
7
|
+
data: [
|
|
8
|
+
{ value: 10 },
|
|
9
|
+
],
|
|
10
|
+
width: 700,
|
|
11
|
+
});
|
|
12
|
+
expect(shouldThrow).toThrow(Error('You need to specify the height of your treemap'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should return an error if malformed height argument', () => {
|
|
16
|
+
const shouldThrow = () => getTreemap({
|
|
17
|
+
data: [
|
|
18
|
+
{ value: 10 },
|
|
19
|
+
],
|
|
20
|
+
width: 700,
|
|
21
|
+
height: '600',
|
|
22
|
+
});
|
|
23
|
+
expect(shouldThrow).toThrow(Error('You need to specify the height of your treemap'));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return an error if malformed width argument', () => {
|
|
27
|
+
const shouldThrow = () => getTreemap({
|
|
28
|
+
data: [
|
|
29
|
+
{ value: 10 },
|
|
30
|
+
],
|
|
31
|
+
height: 700,
|
|
32
|
+
});
|
|
33
|
+
expect(shouldThrow).toThrow(Error('You need to specify the width of your treemap'));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return an error if malformed width argument', () => {
|
|
37
|
+
const shouldThrow = () => getTreemap({
|
|
38
|
+
data: [
|
|
39
|
+
{ value: 10 },
|
|
40
|
+
],
|
|
41
|
+
width: '700',
|
|
42
|
+
height: 600,
|
|
43
|
+
});
|
|
44
|
+
expect(shouldThrow).toThrow(Error('You need to specify the width of your treemap'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return an error if malformed data argument', () => {
|
|
48
|
+
const shouldThrow = () => getTreemap({
|
|
49
|
+
data: [
|
|
50
|
+
{ value: 10 },
|
|
51
|
+
{ value: -1 },
|
|
52
|
+
],
|
|
53
|
+
width: 700,
|
|
54
|
+
height: 600,
|
|
55
|
+
});
|
|
56
|
+
expect(shouldThrow).toThrow(Error('You data must be in this format [{ value: 1 }, { value: 2 }], \'value\' being a positive number'));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return an error if malformed data argument', () => {
|
|
60
|
+
const shouldThrow = () => getTreemap({
|
|
61
|
+
data: [
|
|
62
|
+
{ value: 10 },
|
|
63
|
+
{ value: '1' },
|
|
64
|
+
],
|
|
65
|
+
width: 700,
|
|
66
|
+
height: 600,
|
|
67
|
+
});
|
|
68
|
+
expect(shouldThrow).toThrow(Error('You data must be in this format [{ value: 1 }, { value: 2 }], \'value\' being a positive number'));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should return an error if malformed data argument', () => {
|
|
72
|
+
const shouldThrow = () => getTreemap({
|
|
73
|
+
data: [
|
|
74
|
+
{ value: 10 },
|
|
75
|
+
1,
|
|
76
|
+
],
|
|
77
|
+
width: 700,
|
|
78
|
+
height: 600,
|
|
79
|
+
});
|
|
80
|
+
expect(shouldThrow).toThrow(Error('You data must be in this format [{ value: 1 }, { value: 2 }], \'value\' being a positive number'));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return an error if malformed data argument', () => {
|
|
84
|
+
const shouldThrow = () => getTreemap({
|
|
85
|
+
data: '[{ value: 10 }]',
|
|
86
|
+
width: 700,
|
|
87
|
+
height: 600,
|
|
88
|
+
});
|
|
89
|
+
expect(shouldThrow).toThrow(Error('You data must be in this format [{ value: 1 }, { value: 2 }], \'value\' being a positive number'));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return an error if malformed data argument', () => {
|
|
93
|
+
const shouldThrow = () => getTreemap({
|
|
94
|
+
width: 700,
|
|
95
|
+
height: 600,
|
|
96
|
+
});
|
|
97
|
+
expect(shouldThrow).toThrow(Error('You data must be in this format [{ value: 1 }, { value: 2 }], \'value\' being a positive number'));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should return an error if malformed data argument', () => {
|
|
101
|
+
const shouldThrow = () => getTreemap({
|
|
102
|
+
data: [],
|
|
103
|
+
width: 700,
|
|
104
|
+
height: 600,
|
|
105
|
+
});
|
|
106
|
+
expect(shouldThrow).toThrow(Error('You data must be in this format [{ value: 1 }, { value: 2 }], \'value\' being a positive number'));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should return the result expected', () => {
|
|
110
|
+
const result = [
|
|
111
|
+
{
|
|
112
|
+
x: 0,
|
|
113
|
+
y: 0,
|
|
114
|
+
width: 330.56,
|
|
115
|
+
height: 352.94,
|
|
116
|
+
data: { value: 10, color: 'red' },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
x: 0,
|
|
120
|
+
y: 352.94,
|
|
121
|
+
width: 330.56,
|
|
122
|
+
height: 247.06,
|
|
123
|
+
data: { value: 7, color: 'black' },
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
x: 330.56,
|
|
127
|
+
y: 0,
|
|
128
|
+
width: 295.56,
|
|
129
|
+
height: 157.89,
|
|
130
|
+
data: { value: 4, color: 'blue' },
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
x: 626.11,
|
|
134
|
+
y: 0,
|
|
135
|
+
width: 73.89,
|
|
136
|
+
height: 157.89,
|
|
137
|
+
data: { value: 1, color: 'white' },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
x: 330.56,
|
|
141
|
+
y: 157.89,
|
|
142
|
+
width: 369.44,
|
|
143
|
+
height: 157.89,
|
|
144
|
+
data: { value: 5, color: 'green' },
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
x: 330.56,
|
|
148
|
+
y: 315.79,
|
|
149
|
+
width: 369.44,
|
|
150
|
+
height: 284.21,
|
|
151
|
+
data: { value: 9, color: 'grey' },
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
expect(getTreemap({
|
|
155
|
+
data: [
|
|
156
|
+
{ value: 10, color: 'red' },
|
|
157
|
+
{ value: 7, color: 'black' },
|
|
158
|
+
{ value: 4, color: 'blue' },
|
|
159
|
+
{ value: 1, color: 'white' },
|
|
160
|
+
{ value: 5, color: 'green' },
|
|
161
|
+
{ value: 9, color: 'grey' },
|
|
162
|
+
],
|
|
163
|
+
width: 700,
|
|
164
|
+
height: 600,
|
|
165
|
+
})).toEqual(result);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should return the result expected', () => {
|
|
169
|
+
const result = [
|
|
170
|
+
{
|
|
171
|
+
x: 0,
|
|
172
|
+
y: 0,
|
|
173
|
+
width: 296.97,
|
|
174
|
+
height: 257.14,
|
|
175
|
+
data: { value: 6 },
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
x: 0,
|
|
179
|
+
y: 257.14,
|
|
180
|
+
width: 296.97,
|
|
181
|
+
height: 342.86,
|
|
182
|
+
data: { value: 8 },
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
x: 296.97,
|
|
186
|
+
y: 0,
|
|
187
|
+
width: 201.52,
|
|
188
|
+
height: 315.79,
|
|
189
|
+
data: { value: 5 },
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
x: 498.48,
|
|
193
|
+
y: 0,
|
|
194
|
+
width: 201.52,
|
|
195
|
+
height: 315.79,
|
|
196
|
+
data: { value: 5 },
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
x: 296.97,
|
|
200
|
+
y: 315.79,
|
|
201
|
+
width: 358.25,
|
|
202
|
+
height: 284.21,
|
|
203
|
+
data: { value: 8 },
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
x: 655.22,
|
|
207
|
+
y: 315.79,
|
|
208
|
+
width: 44.78,
|
|
209
|
+
height: 284.21,
|
|
210
|
+
data: { value: 1 },
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
expect(getTreemap({
|
|
214
|
+
data: [
|
|
215
|
+
{ value: 6 },
|
|
216
|
+
{ value: 8 },
|
|
217
|
+
{ value: 5 },
|
|
218
|
+
{ value: 5 },
|
|
219
|
+
{ value: 8 },
|
|
220
|
+
{ value: 1 },
|
|
221
|
+
],
|
|
222
|
+
width: 700,
|
|
223
|
+
height: 600,
|
|
224
|
+
})).toEqual(result);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should return the result with one data point', () => {
|
|
228
|
+
const result = [
|
|
229
|
+
{
|
|
230
|
+
data: {
|
|
231
|
+
value: 9,
|
|
232
|
+
},
|
|
233
|
+
height: 600,
|
|
234
|
+
width: 700,
|
|
235
|
+
x: 0,
|
|
236
|
+
y: 0,
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
expect(getTreemap({
|
|
240
|
+
data: [
|
|
241
|
+
{ value: 9 },
|
|
242
|
+
],
|
|
243
|
+
width: 700,
|
|
244
|
+
height: 600,
|
|
245
|
+
})).toEqual(result);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
package/utils/treemap.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export, max-len, no-shadow */
|
|
2
|
+
|
|
3
|
+
const getMaximum = (array) => Math.max(...array);
|
|
4
|
+
|
|
5
|
+
const getMinimum = (array) => Math.min(...array);
|
|
6
|
+
|
|
7
|
+
const sumReducer = (acc, cur) => acc + cur;
|
|
8
|
+
|
|
9
|
+
const roundValue = (number) => Math.max(Math.round(number * 100) / 100, 0);
|
|
10
|
+
|
|
11
|
+
const validateArguments = ({ data, width, height }) => {
|
|
12
|
+
if (!width || typeof width !== 'number' || width < 0) {
|
|
13
|
+
throw new Error('You need to specify the width of your treemap');
|
|
14
|
+
}
|
|
15
|
+
if (!height || typeof height !== 'number' || height < 0) {
|
|
16
|
+
throw new Error('You need to specify the height of your treemap');
|
|
17
|
+
}
|
|
18
|
+
if (!data || !Array.isArray(data) || data.length === 0 || !data.every((dataPoint) => Object.prototype.hasOwnProperty.call(dataPoint, 'value') && typeof dataPoint.value === 'number' && dataPoint.value >= 0 && !Number.isNaN(dataPoint.value))) {
|
|
19
|
+
throw new Error('You data must be in this format [{ value: 1 }, { value: 2 }], \'value\' being a positive number');
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Used to calculate the coordinates of a treemap representation following the "squarify" algorithm
|
|
25
|
+
*
|
|
26
|
+
* Clément Bataille, 2020
|
|
27
|
+
* Licensed under the MIT license
|
|
28
|
+
*
|
|
29
|
+
* {@link https://github.com/clementbat/treemap}
|
|
30
|
+
*
|
|
31
|
+
* @param param0.data The coordinate data to use in the calculation
|
|
32
|
+
* @param param0.width The width of the treemap
|
|
33
|
+
* @param param0.height The height of the treemap
|
|
34
|
+
* @returns The calculated coordinates
|
|
35
|
+
*/
|
|
36
|
+
export function getTreemap({ data, width, height }) {
|
|
37
|
+
let Rectangle = {};
|
|
38
|
+
let initialData = [];
|
|
39
|
+
|
|
40
|
+
function worstRatio(row, width) {
|
|
41
|
+
const sum = row.reduce(sumReducer, 0);
|
|
42
|
+
const rowMax = getMaximum(row);
|
|
43
|
+
const rowMin = getMinimum(row);
|
|
44
|
+
return Math.max(((width ** 2) * rowMax) / (sum ** 2), (sum ** 2) / ((width ** 2) * rowMin));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const getMinWidth = () => {
|
|
48
|
+
if (Rectangle.totalHeight ** 2 > Rectangle.totalWidth ** 2) {
|
|
49
|
+
return { value: Rectangle.totalWidth, vertical: false };
|
|
50
|
+
}
|
|
51
|
+
return { value: Rectangle.totalHeight, vertical: true };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const layoutRow = (row, width, vertical) => {
|
|
55
|
+
const rowHeight = row.reduce(sumReducer, 0) / width;
|
|
56
|
+
|
|
57
|
+
row.forEach((rowItem) => {
|
|
58
|
+
const rowWidth = rowItem / rowHeight;
|
|
59
|
+
const { xBeginning } = Rectangle;
|
|
60
|
+
const { yBeginning } = Rectangle;
|
|
61
|
+
|
|
62
|
+
let data;
|
|
63
|
+
if (vertical) {
|
|
64
|
+
data = {
|
|
65
|
+
x: xBeginning,
|
|
66
|
+
y: yBeginning,
|
|
67
|
+
width: rowHeight,
|
|
68
|
+
height: rowWidth,
|
|
69
|
+
data: initialData[Rectangle.data.length],
|
|
70
|
+
};
|
|
71
|
+
Rectangle.yBeginning += rowWidth;
|
|
72
|
+
} else {
|
|
73
|
+
data = {
|
|
74
|
+
x: xBeginning,
|
|
75
|
+
y: yBeginning,
|
|
76
|
+
width: rowWidth,
|
|
77
|
+
height: rowHeight,
|
|
78
|
+
data: initialData[Rectangle.data.length],
|
|
79
|
+
};
|
|
80
|
+
Rectangle.xBeginning += rowWidth;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Rectangle.data.push(data);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (vertical) {
|
|
87
|
+
Rectangle.xBeginning += rowHeight;
|
|
88
|
+
Rectangle.yBeginning -= width;
|
|
89
|
+
Rectangle.totalWidth -= rowHeight;
|
|
90
|
+
} else {
|
|
91
|
+
Rectangle.xBeginning -= width;
|
|
92
|
+
Rectangle.yBeginning += rowHeight;
|
|
93
|
+
Rectangle.totalHeight -= rowHeight;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const layoutLastRow = (rows, children, width) => {
|
|
98
|
+
const { vertical } = getMinWidth();
|
|
99
|
+
layoutRow(rows, width, vertical);
|
|
100
|
+
layoutRow(children, width, vertical);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const squarify = (children, row, width) => {
|
|
104
|
+
if (children.length === 1) {
|
|
105
|
+
return layoutLastRow(row, children, width);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const rowWithChild = [...row, children[0]];
|
|
109
|
+
|
|
110
|
+
if (row.length === 0 || worstRatio(row, width) >= worstRatio(rowWithChild, width)) {
|
|
111
|
+
children.shift();
|
|
112
|
+
return squarify(children, rowWithChild, width);
|
|
113
|
+
}
|
|
114
|
+
layoutRow(row, width, getMinWidth().vertical);
|
|
115
|
+
return squarify(children, [], getMinWidth().value);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
validateArguments({ data, width, height });
|
|
119
|
+
Rectangle = {
|
|
120
|
+
data: [],
|
|
121
|
+
xBeginning: 0,
|
|
122
|
+
yBeginning: 0,
|
|
123
|
+
totalWidth: width,
|
|
124
|
+
totalHeight: height,
|
|
125
|
+
};
|
|
126
|
+
initialData = data;
|
|
127
|
+
const totalValue = data.map((dataPoint) => dataPoint.value).reduce(sumReducer, 0);
|
|
128
|
+
const dataScaled = data.map((dataPoint) => (dataPoint.value * height * width) / totalValue);
|
|
129
|
+
|
|
130
|
+
squarify(dataScaled, [], getMinWidth().value);
|
|
131
|
+
return Rectangle.data.map((dataPoint) => ({
|
|
132
|
+
...dataPoint,
|
|
133
|
+
x: roundValue(dataPoint.x),
|
|
134
|
+
y: roundValue(dataPoint.y),
|
|
135
|
+
width: roundValue(dataPoint.width),
|
|
136
|
+
height: roundValue(dataPoint.height),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
@@ -91,14 +91,22 @@
|
|
|
91
91
|
:style="{ width: '60%', height: '1.75rem', 'border-radius': '500rem' }"
|
|
92
92
|
/>
|
|
93
93
|
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
<component
|
|
95
|
+
:is="tag"
|
|
96
|
+
:to="readMorePath"
|
|
97
|
+
:href="readMorePath"
|
|
98
|
+
aria-label="Borrower name"
|
|
99
|
+
class="!tw-text-primary"
|
|
100
|
+
@click.native="clickReadMore('Name', $event)"
|
|
99
101
|
>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
<h3
|
|
103
|
+
class="loan-card-name"
|
|
104
|
+
:class="{ 'tw-text-center': borrowerName.length < 20 }"
|
|
105
|
+
style="font-size: 28px;"
|
|
106
|
+
>
|
|
107
|
+
{{ borrowerName }}
|
|
108
|
+
</h3>
|
|
109
|
+
</component>
|
|
102
110
|
|
|
103
111
|
<!-- Loan tag -->
|
|
104
112
|
|
package/vue/KvTreeMapChart.vue
CHANGED
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
|
|
44
44
|
<script>
|
|
45
45
|
import numeral from 'numeral';
|
|
46
|
-
import { getTreemap } from 'treemap-squarify';
|
|
47
46
|
import kvTokensPrimitives from '@kiva/kv-tokens/primitives.json';
|
|
47
|
+
import { getTreemap } from '../utils/treemap';
|
|
48
48
|
import { throttle } from '../utils/throttle';
|
|
49
49
|
import KvTooltip from './KvTooltip.vue';
|
|
50
50
|
import KvLoadingPlaceholder from './KvLoadingPlaceholder.vue';
|