@milaboratories/uikit 2.2.45 → 2.2.47
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 +12 -0
- package/dist/pl-uikit.js +7729 -5295
- package/dist/pl-uikit.umd.cjs +12 -8
- package/dist/src/components/PlChartHistogram/PlChartHistogram.vue.d.ts +6 -0
- package/dist/src/components/PlChartHistogram/createGridlines.d.ts +3 -0
- package/dist/src/components/PlChartHistogram/createLabels.d.ts +3 -0
- package/dist/src/components/PlChartHistogram/createSvgContainer.d.ts +3 -0
- package/dist/src/components/PlChartHistogram/drawBins.d.ts +2 -0
- package/dist/src/components/PlChartHistogram/drawThreshold.d.ts +2 -0
- package/dist/src/components/PlChartHistogram/histogram.d.ts +4 -0
- package/dist/src/components/PlChartHistogram/index.d.ts +1 -0
- package/dist/src/components/PlChartHistogram/logspace.d.ts +1 -0
- package/dist/src/components/PlChartHistogram/normalizeBins.d.ts +2 -0
- package/dist/src/components/PlChartHistogram/scales.spec.d.ts +1 -0
- package/dist/src/components/PlChartHistogram/types.d.ts +58 -0
- package/dist/src/components/PlDialogModal/PlDialogModal.vue.d.ts +10 -0
- package/dist/src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue.d.ts +1 -0
- package/dist/src/composition/useComponentProp.d.ts +33 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/types.d.ts +2 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -2
- package/src/colors/__tests__/colors.spec.ts +0 -2
- package/src/components/PlChartHistogram/PlChartHistogram.vue +90 -0
- package/src/components/PlChartHistogram/createGridlines.ts +42 -0
- package/src/components/PlChartHistogram/createLabels.ts +32 -0
- package/src/components/PlChartHistogram/createSvgContainer.ts +23 -0
- package/src/components/PlChartHistogram/drawBins.ts +55 -0
- package/src/components/PlChartHistogram/drawThreshold.ts +19 -0
- package/src/components/PlChartHistogram/histogram.ts +136 -0
- package/src/components/PlChartHistogram/index.ts +1 -0
- package/src/components/PlChartHistogram/logspace.ts +13 -0
- package/src/components/PlChartHistogram/normalizeBins.ts +19 -0
- package/src/components/PlChartHistogram/scales.spec.ts +10 -0
- package/src/components/PlChartHistogram/types.ts +66 -0
- package/src/components/PlDialogModal/PlDialogModal.vue +41 -2
- package/src/components/PlDropdownMulti/PlDropdownMulti.vue +1 -2
- package/src/components/PlDropdownMultiRef/PlDropdownMultiRef.vue +1 -0
- package/src/components/PlDropdownMultiRef/__tests__/PlDropdownMultiRef.spec.ts +1 -3
- package/src/components/PlProgressCell/PlProgressCell.vue +0 -2
- package/src/composition/useComponentProp.ts +36 -0
- package/src/helpers/index.ts +1 -1
- package/src/index.ts +2 -0
- package/src/types.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/uikit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/pl-uikit.umd.js",
|
|
6
6
|
"module": "dist/pl-uikit.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"./*": "./*"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"vue": "^3.5.13"
|
|
21
|
+
"vue": "^3.5.13",
|
|
22
|
+
"d3": "^7.9.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@vue/test-utils": "^2.4.6",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"vue-tsc": "^2.1.10",
|
|
33
34
|
"yarpm": "^1.2.0",
|
|
34
35
|
"svgo": "^3.3.2",
|
|
36
|
+
"@types/d3": "^7.4.3",
|
|
35
37
|
"@milaboratories/helpers": "^1.6.11",
|
|
36
38
|
"@milaboratories/eslint-config": "^1.0.1",
|
|
37
39
|
"@platforma-sdk/model": "^1.21.20"
|
|
@@ -14,8 +14,6 @@ describe('Colors', () => {
|
|
|
14
14
|
|
|
15
15
|
const viridis15 = Gradient(viridis).split(15);
|
|
16
16
|
|
|
17
|
-
console.log('viridis15', JSON.stringify(viridis15));
|
|
18
|
-
|
|
19
17
|
expect(viridis.map((it) => it + 'FF').join(',')).toEqual(viridis15.map((it) => it.hex.toUpperCase()).join(','));
|
|
20
18
|
});
|
|
21
19
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
3
|
+
import { createHistogramFromBins, createHistogramLinear, createHistogramLog } from './histogram';
|
|
4
|
+
import type { ChartOptions, PlChartHistogramSettings } from './types';
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
settings: PlChartHistogramSettings;
|
|
8
|
+
}>();
|
|
9
|
+
|
|
10
|
+
const chart = ref<HTMLElement>();
|
|
11
|
+
|
|
12
|
+
const options = computed<ChartOptions>(() => {
|
|
13
|
+
const { xAxisLabel, yAxisLabel, threshold, compact, totalWidth = 674, totalHeight = 252 } = props.settings;
|
|
14
|
+
|
|
15
|
+
const margin = compact ? { top: 0, right: 0, bottom: 0, left: 0 } : { top: 0, right: 30, bottom: 40, left: 85 };
|
|
16
|
+
const width = totalWidth - margin.left - margin.right;
|
|
17
|
+
const height = totalHeight - margin.top - margin.bottom;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
margin,
|
|
23
|
+
compact,
|
|
24
|
+
nBins: 'nBins' in props.settings ? props.settings.nBins : 10,
|
|
25
|
+
xAxisLabel: xAxisLabel,
|
|
26
|
+
yAxisLabel: yAxisLabel,
|
|
27
|
+
threshold: threshold,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const createHistogram = () => {
|
|
32
|
+
const settings = props.settings;
|
|
33
|
+
|
|
34
|
+
if (settings.type === 'log-bins') {
|
|
35
|
+
createHistogramFromBins(chart.value!, options.value, settings.bins);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (settings.log) {
|
|
40
|
+
createHistogramLog(chart.value!, options.value, settings.numbers);
|
|
41
|
+
} else {
|
|
42
|
+
createHistogramLinear(chart.value!, options.value, settings.numbers);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
watch(props, createHistogram);
|
|
47
|
+
|
|
48
|
+
onMounted(createHistogram);
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div :class="$style.component">
|
|
53
|
+
<div v-if="settings.title && !settings.compact" :class="$style.title">{{ settings.title }}</div>
|
|
54
|
+
<div ref="chart" />
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<style module>
|
|
59
|
+
.component {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 24px;
|
|
63
|
+
svg {
|
|
64
|
+
font-family: var(--font-family-base);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.title {
|
|
69
|
+
font-size: 20px;
|
|
70
|
+
font-weight: 500;
|
|
71
|
+
line-height: 24px; /* 120% */
|
|
72
|
+
letter-spacing: -0.2px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:global(.svg-tooltip) {
|
|
76
|
+
font-family: var(--font-family-base);
|
|
77
|
+
background: rgba(69,77,93,.9);
|
|
78
|
+
border-radius: .1rem;
|
|
79
|
+
color: #fff;
|
|
80
|
+
display: block;
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
max-width: 320px;
|
|
83
|
+
padding: .2rem .4rem;
|
|
84
|
+
position: absolute;
|
|
85
|
+
text-overflow: ellipsis;
|
|
86
|
+
white-space: pre;
|
|
87
|
+
z-index: 300;
|
|
88
|
+
visibility: hidden;
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as d3 from 'd3';
|
|
2
|
+
import type { ChartOptions, Scales } from './types';
|
|
3
|
+
|
|
4
|
+
export function createGridlines(
|
|
5
|
+
svg: d3.Selection<SVGGElement, unknown, null, undefined>,
|
|
6
|
+
options: ChartOptions,
|
|
7
|
+
scales: Scales,
|
|
8
|
+
xTicks: (d: d3.Axis<d3.NumberValue>) => d3.Axis<d3.NumberValue>,
|
|
9
|
+
) {
|
|
10
|
+
const { width, height } = options;
|
|
11
|
+
|
|
12
|
+
function makeYGridlines() {
|
|
13
|
+
return d3.axisLeft(scales.y) // Use the y-scale for horizontal gridlines
|
|
14
|
+
.ticks(6); // Adjust the number of gridlines
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeXGridlines() {
|
|
18
|
+
return xTicks(d3.axisBottom(scales.x));
|
|
19
|
+
}
|
|
20
|
+
// Append horizontal gridlines
|
|
21
|
+
svg.append('g')
|
|
22
|
+
.attr('class', 'grid') // Add a class for styling
|
|
23
|
+
.attr('font-family', '\'Manrope\', sans-serif') // Doesn't work
|
|
24
|
+
.call(makeYGridlines()
|
|
25
|
+
.tickSize(-width) // Extend gridlines across the chart width
|
|
26
|
+
.tickFormat(() => '')); // Remove tick labels
|
|
27
|
+
|
|
28
|
+
// Append vertical gridlines
|
|
29
|
+
svg.append('g')
|
|
30
|
+
.attr('class', 'grid') // Add a class for styling
|
|
31
|
+
.attr('font-family', '\'Manrope\', sans-serif')
|
|
32
|
+
.attr('transform', `translate(0,${height})`) // Position at the bottom of the chart
|
|
33
|
+
.call(makeXGridlines()
|
|
34
|
+
.tickSize(-height) // Extend gridlines across the chart height
|
|
35
|
+
.tickFormat(() => '')); // Remove tick labels
|
|
36
|
+
|
|
37
|
+
// Style the gridlines using CSS (or inline styles)
|
|
38
|
+
d3.selectAll('.grid line')
|
|
39
|
+
.style('stroke', '#E1E3EB') // Light gray gridlines
|
|
40
|
+
// .style('stroke-dasharray', '2,2') // Dashed gridlines
|
|
41
|
+
.style('opacity', 0.7); // Slightly transparent
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type * as d3 from 'd3';
|
|
2
|
+
import type { ChartOptions } from './types';
|
|
3
|
+
|
|
4
|
+
export function createLabels(
|
|
5
|
+
svg: d3.Selection<SVGGElement, unknown, null, undefined>,
|
|
6
|
+
options: ChartOptions,
|
|
7
|
+
) {
|
|
8
|
+
const { height, width, margin, xAxisLabel, yAxisLabel, compact } = options;
|
|
9
|
+
|
|
10
|
+
if (compact) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// X-axis label
|
|
15
|
+
svg.append('text')
|
|
16
|
+
.attr('class', 'x-axis-label')
|
|
17
|
+
.attr('font-weight', 500)
|
|
18
|
+
.attr('text-anchor', 'middle') // Center the text
|
|
19
|
+
.attr('x', width / 2) // Center horizontally
|
|
20
|
+
.attr('y', height + margin.bottom - 5) // Position below the X-axis
|
|
21
|
+
.text(xAxisLabel ?? 'Value Range'); // Set your custom label text
|
|
22
|
+
|
|
23
|
+
// Y-axis label
|
|
24
|
+
svg.append('text')
|
|
25
|
+
.attr('class', 'y-axis-label')
|
|
26
|
+
.attr('font-weight', 500)
|
|
27
|
+
.attr('text-anchor', 'middle') // Center the text
|
|
28
|
+
.attr('x', -height / 2) // Center vertically (rotated axis)
|
|
29
|
+
.attr('y', -margin.left + 15) // Position to the left of the Y-axis
|
|
30
|
+
.attr('transform', 'rotate(-90)') // Rotate text 90 degrees counter-clockwise
|
|
31
|
+
.text(yAxisLabel ?? 'Frequency'); // Set your custom label text
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as d3 from 'd3';
|
|
2
|
+
import type { ChartOptions } from './types';
|
|
3
|
+
|
|
4
|
+
export function createSvgContainer(el: HTMLElement, options: ChartOptions) {
|
|
5
|
+
const { width, height, margin, compact } = options;
|
|
6
|
+
|
|
7
|
+
el.replaceChildren();
|
|
8
|
+
|
|
9
|
+
if (compact) {
|
|
10
|
+
el.style.height = height + 'px';
|
|
11
|
+
el.style.lineHeight = height + 'px';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const svg = d3
|
|
15
|
+
.select(el) // Append the SVG element to the body
|
|
16
|
+
.append('svg')
|
|
17
|
+
.attr('width', width + margin.left + margin.right) // Set the total width
|
|
18
|
+
.attr('height', height + margin.top + margin.bottom) // Set the total height
|
|
19
|
+
.append('g') // Append a group to handle margins
|
|
20
|
+
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
21
|
+
|
|
22
|
+
return svg;
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { BinLike, ChartOptions, Scales, SVG } from './types';
|
|
2
|
+
import * as d3 from 'd3';
|
|
3
|
+
|
|
4
|
+
export function drawBins(
|
|
5
|
+
svg: SVG,
|
|
6
|
+
bins: BinLike[],
|
|
7
|
+
dimension: ChartOptions,
|
|
8
|
+
scales: Scales,
|
|
9
|
+
) {
|
|
10
|
+
const { height } = dimension;
|
|
11
|
+
|
|
12
|
+
const { x, y } = scales;
|
|
13
|
+
|
|
14
|
+
const tooltip = d3
|
|
15
|
+
.select('body')
|
|
16
|
+
.append('div')
|
|
17
|
+
.attr('class', 'svg-tooltip')
|
|
18
|
+
.style('position', 'absolute')
|
|
19
|
+
.style('visibility', 'hidden');
|
|
20
|
+
|
|
21
|
+
// Three function that change the tooltip when user hover / move / leave a cell
|
|
22
|
+
const mouseover = function (_event: MouseEvent, d: BinLike) {
|
|
23
|
+
tooltip
|
|
24
|
+
.style('visibility', 'visible')
|
|
25
|
+
.text(`count: ${d.length}\nx0: ${d.x0}\nx1: ${d.x1}`);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const mousemove = function (event: MouseEvent) {
|
|
29
|
+
tooltip
|
|
30
|
+
.style('top', event.pageY - 10 + 'px')
|
|
31
|
+
.style('left', event.pageX + 10 + 'px');
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const mouseout = function () {
|
|
35
|
+
tooltip.style('visibility', 'hidden');
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Add rectangles for the histogram bars
|
|
39
|
+
svg.selectAll('rect')
|
|
40
|
+
.data(bins)
|
|
41
|
+
.enter()
|
|
42
|
+
.append('rect')
|
|
43
|
+
.attr('x', (d) => x(d.x0!)) // Position the bar based on the bin start
|
|
44
|
+
.attr('y', (d) => y(d.length)) // Height based on bin count
|
|
45
|
+
.attr('width', (d) => x(d.x1!) - x(d.x0!)) // Bar width based on logarithmic intervals
|
|
46
|
+
.attr('height', (d) => height - y(d.length)) // Invert height to fit SVG coordinate system
|
|
47
|
+
.style('fill', '#929BAD')
|
|
48
|
+
.attr('stroke', '#fff') // Border color
|
|
49
|
+
.attr('stroke-opacity', 0.2)
|
|
50
|
+
.attr('stroke-width', 0.5)
|
|
51
|
+
.on('mouseover', mouseover)
|
|
52
|
+
.on('mousemove', mousemove)
|
|
53
|
+
.on('mouseout', mouseout)
|
|
54
|
+
.append('title').text((d) => `[${d.x0}, ${d.x1}]\n` + d.length + '\n'); // Set bar color
|
|
55
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ChartOptions, Scales, SVG } from './types';
|
|
2
|
+
|
|
3
|
+
export function drawThreshold(svg: SVG, scales: Scales, options: ChartOptions) {
|
|
4
|
+
const { threshold } = options;
|
|
5
|
+
|
|
6
|
+
if (!threshold) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
svg
|
|
11
|
+
.append('line')
|
|
12
|
+
.attr('x1', scales.x(threshold))
|
|
13
|
+
.attr('x2', scales.x(threshold))
|
|
14
|
+
.attr('y1', 0)
|
|
15
|
+
.attr('y2', options.height)
|
|
16
|
+
.style('stroke', '#F05670')
|
|
17
|
+
.style('stroke-width', '1')
|
|
18
|
+
.style('stroke-dasharray', '7.4 3.2');
|
|
19
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as d3 from 'd3';
|
|
2
|
+
import { createSvgContainer } from './createSvgContainer';
|
|
3
|
+
import { drawBins } from './drawBins';
|
|
4
|
+
import { createGridlines } from './createGridlines';
|
|
5
|
+
import { logspace } from './logspace';
|
|
6
|
+
import { createLabels } from './createLabels';
|
|
7
|
+
import type { AnyBin, BinLike, ChartOptions } from './types';
|
|
8
|
+
import { drawThreshold } from './drawThreshold';
|
|
9
|
+
import { normalizeBins } from './normalizeBins';
|
|
10
|
+
|
|
11
|
+
const gx = (svg: d3.Selection<SVGGElement, unknown, null, undefined>, height: number) => {
|
|
12
|
+
return svg.append('g')
|
|
13
|
+
.style('font-size', '14px')
|
|
14
|
+
.style('font-weight', '500')
|
|
15
|
+
.attr('transform', `translate(0,${height})`);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const gy = (svg: d3.Selection<SVGGElement, unknown, null, undefined>) => {
|
|
19
|
+
return svg.append('g')
|
|
20
|
+
.style('font-size', '14px')
|
|
21
|
+
.style('font-weight', '500');
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const createYScale = (bins: BinLike[], height: number) => {
|
|
25
|
+
return d3.scaleLinear()
|
|
26
|
+
.domain([0, d3.max(bins, (d) => d.length)!]) // Max bin count for the domain
|
|
27
|
+
.range([height, 0]); // Map to chart height (invert to match SVG coordinates)
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function createHistogramLinear(el: HTMLElement, options: ChartOptions, data: number[]) {
|
|
31
|
+
const { width, height, nBins = 10 } = options;
|
|
32
|
+
|
|
33
|
+
const svg = createSvgContainer(el, options);
|
|
34
|
+
|
|
35
|
+
const min = d3.min(data) as number;
|
|
36
|
+
const max = d3.max(data) as number;
|
|
37
|
+
|
|
38
|
+
const x = d3.scaleLinear()
|
|
39
|
+
.domain([min, max])
|
|
40
|
+
.range([0, width]);
|
|
41
|
+
|
|
42
|
+
const bins: BinLike[] = normalizeBins(d3.bin()
|
|
43
|
+
.domain(x.domain() as [number, number]) // Set the input domain to match the x-scale
|
|
44
|
+
.thresholds(x.ticks(nBins))(data)); // Apply the data to create bins
|
|
45
|
+
|
|
46
|
+
const y = createYScale(bins, height);
|
|
47
|
+
|
|
48
|
+
createGridlines(svg, options, { x, y }, (x) => x.ticks(6));
|
|
49
|
+
|
|
50
|
+
drawBins(svg, bins, options, { x, y });
|
|
51
|
+
|
|
52
|
+
drawThreshold(svg, { x, y }, options);
|
|
53
|
+
|
|
54
|
+
gx(svg, height).call(d3.axisBottom(x).tickSize(0));
|
|
55
|
+
|
|
56
|
+
gy(svg).call(d3.axisLeft(y).tickSize(0));
|
|
57
|
+
|
|
58
|
+
createLabels(svg, options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createHistogramLog(el: HTMLElement, options: ChartOptions, data: number[]) {
|
|
62
|
+
const { width, height, nBins = 10 } = options;
|
|
63
|
+
|
|
64
|
+
const svg = createSvgContainer(el, options);
|
|
65
|
+
|
|
66
|
+
const min = d3.min(data) as number;
|
|
67
|
+
const max = d3.max(data) as number;
|
|
68
|
+
|
|
69
|
+
const x = d3.scaleSymlog()
|
|
70
|
+
.domain([min, max]) // Input range (min and max values of the data)
|
|
71
|
+
.range([0, width])
|
|
72
|
+
.nice() // Output range (width of the chart)
|
|
73
|
+
;
|
|
74
|
+
|
|
75
|
+
const createThresholds = (n: number) => {
|
|
76
|
+
const res = [];
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i <= n; i++) {
|
|
79
|
+
res.push(Number(x.invert(width * (i / n)).toFixed(2)));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return res;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const bins = normalizeBins(d3.bin()
|
|
86
|
+
.domain(x.domain() as [number, number]) // Set the input domain to match the x-scale
|
|
87
|
+
.thresholds(createThresholds(nBins))(data)); // Apply the data to create bins
|
|
88
|
+
|
|
89
|
+
const y = createYScale(bins, height);
|
|
90
|
+
|
|
91
|
+
const tickValues = logspace(0, Math.ceil(Math.log10(max)), 6);
|
|
92
|
+
|
|
93
|
+
createGridlines(svg, options, { x, y }, (x) => x.tickValues(tickValues));
|
|
94
|
+
|
|
95
|
+
drawBins(svg, bins, options, { x, y });
|
|
96
|
+
|
|
97
|
+
drawThreshold(svg, { x, y }, options);
|
|
98
|
+
|
|
99
|
+
gx(svg, height).call(d3.axisBottom(x).tickValues(tickValues).tickSize(0));
|
|
100
|
+
|
|
101
|
+
gy(svg).call(d3.axisLeft(y).tickSize(0));
|
|
102
|
+
|
|
103
|
+
createLabels(svg, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createHistogramFromBins(el: HTMLElement, options: ChartOptions, _bins: AnyBin[]) {
|
|
107
|
+
const { width, height } = options;
|
|
108
|
+
|
|
109
|
+
const svg = createSvgContainer(el, options);
|
|
110
|
+
|
|
111
|
+
const bins = normalizeBins(_bins);
|
|
112
|
+
|
|
113
|
+
const min = d3.min(bins, (b) => b.x0) as number;
|
|
114
|
+
const max = d3.max(bins, (b) => b.x1) as number;
|
|
115
|
+
|
|
116
|
+
const x = d3.scaleSymlog()
|
|
117
|
+
.domain([min, max])
|
|
118
|
+
.range([0, width])
|
|
119
|
+
.nice();
|
|
120
|
+
|
|
121
|
+
const y = createYScale(bins, height);
|
|
122
|
+
|
|
123
|
+
const tickValues = logspace(0, Math.ceil(Math.log10(max)), 6);
|
|
124
|
+
|
|
125
|
+
createGridlines(svg, options, { x, y }, (x) => x.tickValues(tickValues));
|
|
126
|
+
|
|
127
|
+
drawBins(svg, bins, options, { x, y });
|
|
128
|
+
|
|
129
|
+
drawThreshold(svg, { x, y }, options);
|
|
130
|
+
|
|
131
|
+
gx(svg, height).call(d3.axisBottom(x).tickValues(tickValues).tickSize(0));
|
|
132
|
+
|
|
133
|
+
gy(svg).call(d3.axisLeft(y).tickSize(0));
|
|
134
|
+
|
|
135
|
+
createLabels(svg, options);
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PlChartHistogram } from './PlChartHistogram.vue';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function logspace(startExp: number, stopExp: number, num = 50, base = 10) {
|
|
2
|
+
if (num <= 0) return [];
|
|
3
|
+
|
|
4
|
+
const step = (stopExp - startExp) / (num - 1);
|
|
5
|
+
const result = [];
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i < num; i++) {
|
|
8
|
+
const exponent = startExp + i * step;
|
|
9
|
+
result.push(Math.pow(base, exponent));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AnyBin, BinLike } from './types';
|
|
2
|
+
|
|
3
|
+
export function normalizeBins(bins: (d3.Bin<number, number> | AnyBin)[]): BinLike[] {
|
|
4
|
+
return bins.map((it) => {
|
|
5
|
+
if ('from' in it) {
|
|
6
|
+
return {
|
|
7
|
+
x0: it.from,
|
|
8
|
+
x1: it.to,
|
|
9
|
+
length: it.weight,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
x0: it.x0!,
|
|
15
|
+
x1: it.x1!,
|
|
16
|
+
length: it.length,
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type Margin = { top: number; right: number; bottom: number; left: number };
|
|
2
|
+
|
|
3
|
+
export type ChartOptions = {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
margin: Margin;
|
|
7
|
+
nBins?: number;
|
|
8
|
+
yAxisLabel?: string;
|
|
9
|
+
xAxisLabel?: string;
|
|
10
|
+
threshold?: number;
|
|
11
|
+
compact?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type Scales = {
|
|
15
|
+
x: d3.ScaleSymLog<number, number, never> | d3.ScaleLinear<number, number>;
|
|
16
|
+
y: d3.ScaleLinear<number, number, never>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type SVG = d3.Selection<SVGGElement, unknown, null, undefined>;
|
|
20
|
+
|
|
21
|
+
export type CustomBin = {
|
|
22
|
+
from: number;
|
|
23
|
+
to: number;
|
|
24
|
+
weight: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type BinLike = {
|
|
28
|
+
x0: number;
|
|
29
|
+
x1: number;
|
|
30
|
+
length: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type AnyBin = CustomBin | BinLike;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Common case: array of numbers
|
|
37
|
+
*/
|
|
38
|
+
export type PlChartHistogramBasicSettings = {
|
|
39
|
+
type: 'basic';
|
|
40
|
+
threshold?: number;
|
|
41
|
+
numbers: number[];
|
|
42
|
+
log?: boolean;
|
|
43
|
+
nBins?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* For precalculated bins on log x scale
|
|
48
|
+
*/
|
|
49
|
+
export type PlChartHistogramLogBinsSettings = {
|
|
50
|
+
type: 'log-bins';
|
|
51
|
+
threshold?: number;
|
|
52
|
+
bins: AnyBin[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type PlChartHistogramSettings = (
|
|
56
|
+
PlChartHistogramBasicSettings |
|
|
57
|
+
PlChartHistogramLogBinsSettings
|
|
58
|
+
) & {
|
|
59
|
+
title?: string;
|
|
60
|
+
yAxisLabel?: string;
|
|
61
|
+
xAxisLabel?: string;
|
|
62
|
+
// with margins
|
|
63
|
+
totalWidth?: number;
|
|
64
|
+
totalHeight?: number;
|
|
65
|
+
compact?: boolean;
|
|
66
|
+
};
|
|
@@ -7,8 +7,9 @@ export default {
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
8
|
import { useEventListener } from '@/composition/useEventListener';
|
|
9
9
|
import './pl-dialog-modal.scss';
|
|
10
|
-
import { ref, useAttrs, useSlots } from 'vue';
|
|
10
|
+
import { computed, ref, useAttrs, useSlots } from 'vue';
|
|
11
11
|
import PlCloseModalBtn from '@/utils/PlCloseModalBtn.vue';
|
|
12
|
+
import type { Size } from '@/types';
|
|
12
13
|
|
|
13
14
|
const slots = useSlots();
|
|
14
15
|
|
|
@@ -56,6 +57,10 @@ const props = withDefaults(
|
|
|
56
57
|
* If `true`, the modal window closes when clicking outside the modal area (default: `true`)
|
|
57
58
|
*/
|
|
58
59
|
closeOnOutsideClick?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Predefined size (standard small | medium | large). Takes precedence over (min|max)(width|height) properties. Not defined by default.
|
|
62
|
+
*/
|
|
63
|
+
size?: Size | undefined;
|
|
59
64
|
}>(),
|
|
60
65
|
{
|
|
61
66
|
width: '448px',
|
|
@@ -65,11 +70,45 @@ const props = withDefaults(
|
|
|
65
70
|
closable: true,
|
|
66
71
|
noContentGutters: false,
|
|
67
72
|
actionsHasTopBorder: true,
|
|
73
|
+
size: undefined,
|
|
68
74
|
},
|
|
69
75
|
);
|
|
70
76
|
|
|
71
77
|
const modal = ref<HTMLElement>();
|
|
72
78
|
|
|
79
|
+
const style = computed(() => {
|
|
80
|
+
const { width, height, minHeight, maxHeight, size } = props;
|
|
81
|
+
|
|
82
|
+
if (size === 'small') {
|
|
83
|
+
return {
|
|
84
|
+
width: '448px',
|
|
85
|
+
height: '440px',
|
|
86
|
+
minHeight: 'auto',
|
|
87
|
+
maxHeight: 'auto',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (size === 'medium') {
|
|
92
|
+
return {
|
|
93
|
+
width: '720px',
|
|
94
|
+
height: '720px',
|
|
95
|
+
minHeight: 'auto',
|
|
96
|
+
maxHeight: 'auto',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (size === 'large') {
|
|
101
|
+
return {
|
|
102
|
+
width: '1080px',
|
|
103
|
+
height: '880px',
|
|
104
|
+
minHeight: 'auto',
|
|
105
|
+
maxHeight: 'auto',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { width, height, minHeight, maxHeight };
|
|
110
|
+
});
|
|
111
|
+
|
|
73
112
|
const $attrs = useAttrs();
|
|
74
113
|
|
|
75
114
|
function onClickShadow(ev: Event) {
|
|
@@ -94,7 +133,7 @@ useEventListener(document.body, 'keyup', (ev) => {
|
|
|
94
133
|
ref="modal"
|
|
95
134
|
class="pl-dialog-modal"
|
|
96
135
|
:class="{ 'has-title': slots.title, 'has-content': slots.default }"
|
|
97
|
-
:style="
|
|
136
|
+
:style="style"
|
|
98
137
|
>
|
|
99
138
|
<PlCloseModalBtn v-if="closable" class="close-modal-btn" @click.stop="emit('update:modelValue', false)" />
|
|
100
139
|
<div v-if="slots.title" class="pl-dialog-modal__title">
|
|
@@ -73,6 +73,7 @@ const props = withDefaults(
|
|
|
73
73
|
placeholder: '...',
|
|
74
74
|
required: false,
|
|
75
75
|
disabled: false,
|
|
76
|
+
options: undefined,
|
|
76
77
|
},
|
|
77
78
|
);
|
|
78
79
|
|
|
@@ -166,8 +167,6 @@ const toggleModel = () => (data.open = !data.open);
|
|
|
166
167
|
const onFocusOut = (event: FocusEvent) => {
|
|
167
168
|
const relatedTarget = event.relatedTarget as Node | null;
|
|
168
169
|
|
|
169
|
-
console.log('>>>> overlay.value?.$el', overlay.value?.$el);
|
|
170
|
-
|
|
171
170
|
if (!rootRef.value?.contains(relatedTarget) && !overlay.value?.listRef?.contains(relatedTarget)) {
|
|
172
171
|
data.search = '';
|
|
173
172
|
data.open = false;
|
|
@@ -43,11 +43,9 @@ describe('PlDropdownMultiRef', () => {
|
|
|
43
43
|
|
|
44
44
|
const options = getOptions();
|
|
45
45
|
|
|
46
|
-
console.log('options', options);
|
|
47
|
-
|
|
48
46
|
expect(options.length).toBe(2);
|
|
49
47
|
|
|
50
|
-
console.log(wrapper.props('modelValue'), 'mv');
|
|
48
|
+
// console.log(wrapper.props('modelValue'), 'mv');
|
|
51
49
|
options[0].click();
|
|
52
50
|
|
|
53
51
|
await delay(20);
|
|
@@ -17,8 +17,6 @@ const canShowWhiteBg = computed(() => props.stage !== 'not_started');
|
|
|
17
17
|
const currentProgress = computed(() => props.stage === 'done' ? 100 : Math.min(100, props.progress || 0));
|
|
18
18
|
|
|
19
19
|
const canShowInfinityLoader = computed(() => props.progress === undefined && props.stage !== 'done' && props.stage !== 'not_started' && !props.error);
|
|
20
|
-
|
|
21
|
-
console.log(props);
|
|
22
20
|
</script>
|
|
23
21
|
|
|
24
22
|
<template>
|