@torpor/ui 0.3.6 → 0.4.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.
- package/dist/Accordion/Accordion.torp +26 -25
- package/dist/Accordion/AccordionItem.torp +3 -3
- package/dist/Accordion/AccordionTypes.ts +5 -5
- package/dist/Accordion/index.d.ts +2 -2
- package/dist/Calendar/CalendarTypes.ts +1 -1
- package/dist/Charts/BarChart.torp +159 -0
- package/dist/Charts/DataSeries.ts +5 -0
- package/dist/Charts/GridLines.torp +65 -0
- package/dist/Charts/LineChart.torp +196 -0
- package/dist/Charts/PieChart.torp +145 -0
- package/dist/Charts/ScatterChart.torp +215 -0
- package/dist/Charts/SparkLine.torp +71 -0
- package/dist/Charts/XAxis.torp +66 -0
- package/dist/Charts/YAxis.torp +47 -0
- package/dist/Charts/index.d.ts +220 -0
- package/dist/Charts/index.js +34 -0
- package/dist/Charts/index.js.map +1 -0
- package/dist/Contextual/Contextual.torp +8 -3
- package/dist/Contextual/ContextualTypes.ts +1 -1
- package/dist/Dialog/Dialog.torp +12 -16
- package/dist/Dialog/DialogButton.torp +3 -0
- package/dist/Dialog/DialogTypes.ts +2 -2
- package/dist/Dialog/index.d.ts +2 -0
- package/dist/Disclosure/Disclosure.torp +3 -3
- package/dist/Disclosure/DisclosureTypes.ts +1 -1
- package/dist/Drawer/Drawer.torp +2 -2
- package/dist/Drawer/DrawerTypes.ts +1 -1
- package/dist/Form/CheckBox.torp +1 -2
- package/dist/Form/Input.torp +1 -1
- package/dist/Form/MessageSummary.torp +38 -0
- package/dist/Form/TextArea.torp +3 -0
- package/dist/Form/index.d.ts +25 -1
- package/dist/Form/index.js +4 -0
- package/dist/ListBox/ListBox.torp +271 -0
- package/dist/ListBox/ListBoxItem.torp +107 -0
- package/dist/ListBox/ListBoxSeparator.torp +26 -0
- package/dist/ListBox/ListBoxTypes.ts +23 -0
- package/dist/ListBox/index.d.ts +97 -0
- package/dist/ListBox/index.js +14 -0
- package/dist/ListBox/index.js.map +1 -0
- package/dist/Menu/Menu.torp +19 -14
- package/dist/Menu/MenuTypes.ts +5 -4
- package/dist/MenuBar/MenuBarItem.torp +5 -8
- package/dist/MenuBar/MenuBarTypes.ts +2 -2
- package/dist/Modal/Modal.torp +7 -2
- package/dist/Modal/ModalTypes.ts +1 -1
- package/dist/NavMenu/NavMenu.torp +7 -0
- package/dist/NavMenu/NavMenuButton.torp +5 -2
- package/dist/NavMenu/NavMenuPopout.torp +1 -1
- package/dist/NavMenu/{NavMenuPopoutButton.torp → NavMenuPopoutTrigger.torp} +4 -4
- package/dist/NavMenu/NavMenuSeparator.torp +2 -1
- package/dist/NavMenu/NavMenuTypes.ts +4 -3
- package/dist/NavMenu/index.d.ts +22 -18
- package/dist/NavMenu/index.js +4 -4
- package/dist/Pagination/Pagination.torp +145 -0
- package/dist/Pagination/PaginationItem.torp +100 -0
- package/dist/Pagination/PaginationTypes.ts +25 -0
- package/dist/Pagination/buildPages.ts +57 -0
- package/dist/Pagination/index.d.ts +53 -0
- package/dist/Pagination/index.js +40 -0
- package/dist/Pagination/index.js.map +1 -0
- package/dist/Popover/Popover.torp +8 -3
- package/dist/Popover/PopoverTypes.ts +1 -1
- package/dist/SelectBox/SelectBox.torp +136 -0
- package/dist/SelectBox/SelectBoxContent.torp +81 -0
- package/dist/SelectBox/SelectBoxInput.torp +95 -0
- package/dist/SelectBox/SelectBoxTrigger.torp +41 -0
- package/dist/SelectBox/SelectBoxTypes.ts +18 -0
- package/dist/SelectBox/index.d.ts +127 -0
- package/dist/SelectBox/index.js +18 -0
- package/dist/SelectBox/index.js.map +1 -0
- package/dist/TabGroup/TabGroup.torp +3 -3
- package/dist/TabGroup/TabGroupTypes.ts +2 -2
- package/dist/ToolBar/ToolBar.torp +198 -0
- package/dist/ToolBar/ToolBarButton.torp +57 -0
- package/dist/ToolBar/ToolBarElement.torp +55 -0
- package/dist/ToolBar/ToolBarPopout.torp +97 -0
- package/dist/ToolBar/ToolBarPopoutContent.torp +92 -0
- package/dist/ToolBar/ToolBarPopoutTrigger.torp +138 -0
- package/dist/ToolBar/ToolBarSeparator.torp +25 -0
- package/dist/ToolBar/ToolBarTypes.ts +35 -0
- package/dist/ToolBar/index.d.ts +212 -0
- package/dist/ToolBar/index.js +30 -0
- package/dist/ToolBar/index.js.map +1 -0
- package/dist/metafile-esm.json +1 -1
- package/dist/utils/PopoutTypes.ts +35 -0
- package/dist/utils/chartColors.ts +17 -0
- package/dist/utils/chartUtils.ts +71 -0
- package/package.json +22 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { chartColors } from "../utils/chartColors";
|
|
3
|
+
|
|
4
|
+
interface PieData {
|
|
5
|
+
name: string;
|
|
6
|
+
value: number;
|
|
7
|
+
color: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SegmentData {
|
|
11
|
+
name: string;
|
|
12
|
+
value: number;
|
|
13
|
+
color: string;
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface PieChartProps {
|
|
18
|
+
/** An ID for the root element */
|
|
19
|
+
id?: string;
|
|
20
|
+
/** Classes for the root element */
|
|
21
|
+
class?: ClassValue,
|
|
22
|
+
/** Styles for the root element */
|
|
23
|
+
style?: StyleValue,
|
|
24
|
+
data: PieData[],
|
|
25
|
+
size?: number,
|
|
26
|
+
radius?: number,
|
|
27
|
+
cutout?: number,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function PieChart($props: PieChartProps) {
|
|
31
|
+
// Default values
|
|
32
|
+
$props.size ||= 200;
|
|
33
|
+
$props.radius ||= 50;
|
|
34
|
+
|
|
35
|
+
// NOTE: Take two pixels off the radius so that the stroke doesn't go outside the border
|
|
36
|
+
let $state = $watch({
|
|
37
|
+
get radius() {
|
|
38
|
+
return ($props.radius! / 100) * $props.size! - 2;
|
|
39
|
+
},
|
|
40
|
+
get cutout() {
|
|
41
|
+
return ($props.cutout! / 100) * $props.size! - 2;
|
|
42
|
+
},
|
|
43
|
+
get segments() {
|
|
44
|
+
return buildSegments($props.size!, $props.data, $props.radius!, $props.cutout!);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function buildSegments(
|
|
49
|
+
theSize: number,
|
|
50
|
+
theData: PieData[],
|
|
51
|
+
theRadiusSize: number,
|
|
52
|
+
theCutoutSize: number
|
|
53
|
+
): SegmentData[] {
|
|
54
|
+
const segments = [];
|
|
55
|
+
const x = theSize / 2;
|
|
56
|
+
const y = theSize / 2;
|
|
57
|
+
const totalValue = theData.reduce((a, b) => a + b.value, 0);
|
|
58
|
+
|
|
59
|
+
// Start at the top of the chart, at 180 + 360 so that we don't have to
|
|
60
|
+
// take wrapping angles into account
|
|
61
|
+
let startAngle = 540;
|
|
62
|
+
for (let i = 0; i < theData.length; i++) {
|
|
63
|
+
// Subtract the angle to go clockwise
|
|
64
|
+
const endAngle = startAngle - (theData[i].value / totalValue) * 360;
|
|
65
|
+
const largeArc = startAngle - endAngle > 180 ? 1 : 0;
|
|
66
|
+
segments.push({
|
|
67
|
+
name: theData[i].name,
|
|
68
|
+
value: theData[i].value,
|
|
69
|
+
color: theData[i].color,
|
|
70
|
+
path: buildPath(x, y, theRadiusSize, theCutoutSize, startAngle, endAngle, largeArc)
|
|
71
|
+
});
|
|
72
|
+
startAngle = endAngle;
|
|
73
|
+
}
|
|
74
|
+
return segments;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function buildPath(
|
|
78
|
+
x: number,
|
|
79
|
+
y: number,
|
|
80
|
+
theRadiusSize: number,
|
|
81
|
+
theCutoutSize: number,
|
|
82
|
+
startAngle: number,
|
|
83
|
+
endAngle: number,
|
|
84
|
+
largeArc: number
|
|
85
|
+
): string {
|
|
86
|
+
// HACK: Drawing an arc of 360 degrees doesn't work
|
|
87
|
+
if (startAngle - endAngle === 360) {
|
|
88
|
+
endAngle = 359.9999;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Use some trig to find the points on the circumference at the start and end angles
|
|
92
|
+
const startx = x + theRadiusSize * Math.sin((Math.PI * startAngle) / 180);
|
|
93
|
+
const starty = x + theRadiusSize * Math.cos((Math.PI * startAngle) / 180);
|
|
94
|
+
|
|
95
|
+
const endx = x + theRadiusSize * Math.sin((Math.PI * endAngle) / 180);
|
|
96
|
+
const endy = y + theRadiusSize * Math.cos((Math.PI * endAngle) / 180);
|
|
97
|
+
|
|
98
|
+
if ($props.cutout) {
|
|
99
|
+
// Use some trig to find the points on the cutout at the start and end angles
|
|
100
|
+
const cutoutstartx = x + theCutoutSize * Math.sin((Math.PI * startAngle) / 180);
|
|
101
|
+
const cutoutstarty = x + theCutoutSize * Math.cos((Math.PI * startAngle) / 180);
|
|
102
|
+
|
|
103
|
+
const cutoutendx = x + theCutoutSize * Math.sin((Math.PI * endAngle) / 180);
|
|
104
|
+
const cutoutendy = y + theCutoutSize * Math.cos((Math.PI * endAngle) / 180);
|
|
105
|
+
|
|
106
|
+
// Build the path - move to the cutout start, line to the start angle on the circumference,
|
|
107
|
+
// arc to the end angle on the circumference, line to the cutout end, and then arc to the
|
|
108
|
+
// cutout start
|
|
109
|
+
const path = `
|
|
110
|
+
M${cutoutstartx},${cutoutstarty}
|
|
111
|
+
L${startx},${starty}
|
|
112
|
+
A${theRadiusSize},${theRadiusSize} 0 ${largeArc} 1 ${endx},${endy}
|
|
113
|
+
L${cutoutendx},${cutoutendy}
|
|
114
|
+
A${theCutoutSize},${theCutoutSize} 0 ${largeArc} 0 ${cutoutstartx},${cutoutstarty}`.trim();
|
|
115
|
+
return path;
|
|
116
|
+
} else {
|
|
117
|
+
// Build the path - move to the origin, line to the start angle on the circumference,
|
|
118
|
+
// arc to the end angle on the circumference, and then close back to the start
|
|
119
|
+
const path = `M${x},${y} L${startx},${starty} A${theRadiusSize},${theRadiusSize} 0 ${largeArc} 1 ${endx},${endy} Z`;
|
|
120
|
+
return path;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@render {
|
|
125
|
+
<div
|
|
126
|
+
{$props?.id}
|
|
127
|
+
class={["torp-pie-chart", $props?.class]}
|
|
128
|
+
{$props?.style}
|
|
129
|
+
>
|
|
130
|
+
<svg
|
|
131
|
+
version="1.1"
|
|
132
|
+
width={$props.size}
|
|
133
|
+
height={$props.size}
|
|
134
|
+
>
|
|
135
|
+
<g>
|
|
136
|
+
@for (let [i, segment] of $state.segments.entries()) {
|
|
137
|
+
<path d={segment.path} fill={segment.color || chartColors[i % chartColors.length]}>
|
|
138
|
+
<title>{`${segment.name}\n${segment.value}`}</title>
|
|
139
|
+
</path>
|
|
140
|
+
}
|
|
141
|
+
</g>
|
|
142
|
+
</svg>
|
|
143
|
+
</div>
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import XAxis from "./XAxis.torp";
|
|
3
|
+
import YAxis from "./YAxis.torp";
|
|
4
|
+
import GridLines from "./GridLines.torp";
|
|
5
|
+
import {
|
|
6
|
+
calculateStepValue,
|
|
7
|
+
calculateStepLabels,
|
|
8
|
+
calculateValueHeight,
|
|
9
|
+
calculateChartBottom,
|
|
10
|
+
calculateChartLeft
|
|
11
|
+
} from "../utils/chartUtils";
|
|
12
|
+
import { chartColors } from "../utils/chartColors";
|
|
13
|
+
|
|
14
|
+
interface Pair {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
label: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface DataSeries {
|
|
21
|
+
name: string;
|
|
22
|
+
color: string;
|
|
23
|
+
data: Pair[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ScatterChartProps {
|
|
27
|
+
/** An ID for the root element */
|
|
28
|
+
id?: string;
|
|
29
|
+
/** Classes for the root element */
|
|
30
|
+
class?: ClassValue,
|
|
31
|
+
/** Styles for the root element */
|
|
32
|
+
style?: StyleValue,
|
|
33
|
+
series: DataSeries[],
|
|
34
|
+
height?: number,
|
|
35
|
+
width?: number,
|
|
36
|
+
xLabel?: string,
|
|
37
|
+
yLabel?: string,
|
|
38
|
+
xStepCount?: number,
|
|
39
|
+
xStepValue?: number,
|
|
40
|
+
yStepCount?: number,
|
|
41
|
+
yStepValue?: number,
|
|
42
|
+
hideXAxis?: boolean,
|
|
43
|
+
hideYAxis?: boolean,
|
|
44
|
+
showHLines?: boolean,
|
|
45
|
+
showVLines?: boolean,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default function ScatterChart($props: ScatterChartProps) {
|
|
49
|
+
const defaultHeight = 200;
|
|
50
|
+
const defaultWidth = 400;
|
|
51
|
+
|
|
52
|
+
let container: HTMLElement;
|
|
53
|
+
let measurer: SVGTextElement;
|
|
54
|
+
|
|
55
|
+
let $state = $watch({
|
|
56
|
+
textWidth: 0,
|
|
57
|
+
textHeight: 0,
|
|
58
|
+
get calculatedHeight() {
|
|
59
|
+
return $props.height || defaultHeight;
|
|
60
|
+
},
|
|
61
|
+
get calculatedWidth() {
|
|
62
|
+
return $props.width || container?.clientWidth || defaultWidth;
|
|
63
|
+
},
|
|
64
|
+
get maxXValue() {
|
|
65
|
+
return calculateMaxValue($props.series, "x");
|
|
66
|
+
},
|
|
67
|
+
get calculatedXStepValue() {
|
|
68
|
+
return $props.xStepValue || calculateStepValue($state.maxXValue, $props.xStepCount);
|
|
69
|
+
},
|
|
70
|
+
get xStepLabels() {
|
|
71
|
+
return calculateStepLabels($props.xStepCount || 2, $state.calculatedXStepValue);
|
|
72
|
+
},
|
|
73
|
+
get maxYValue() {
|
|
74
|
+
return calculateMaxValue($props.series, "y");
|
|
75
|
+
},
|
|
76
|
+
get calculatedYStepValue() {
|
|
77
|
+
return $props.yStepValue || calculateStepValue($state.maxYValue, $props.yStepCount);
|
|
78
|
+
},
|
|
79
|
+
get yStepLabels() {
|
|
80
|
+
return calculateStepLabels($props.yStepCount || 2, $state.calculatedYStepValue);
|
|
81
|
+
},
|
|
82
|
+
get itemWidth() {
|
|
83
|
+
return calculateValueHeight(
|
|
84
|
+
$state.calculatedWidth - $state.chartLeft,
|
|
85
|
+
$state.textHeight,
|
|
86
|
+
$state.calculatedXStepValue,
|
|
87
|
+
$props.xStepCount || 2
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
get valueHeight() {
|
|
91
|
+
return calculateValueHeight($state.chartBottom, $state.textHeight, $state.calculatedYStepValue, $props.yStepCount || 2);
|
|
92
|
+
},
|
|
93
|
+
get chartBottom() {
|
|
94
|
+
return calculateChartBottom($props.xLabel ?? "", $state.calculatedHeight, $state.textHeight);
|
|
95
|
+
},
|
|
96
|
+
get chartLeft() {
|
|
97
|
+
return calculateChartLeft($state.yStepLabels, $props.yLabel ?? "", $state.textHeight, $state.textWidth);
|
|
98
|
+
},
|
|
99
|
+
get pointSeries() {
|
|
100
|
+
return buildPointSeries(
|
|
101
|
+
$props.series,
|
|
102
|
+
$state.chartLeft,
|
|
103
|
+
$state.chartBottom,
|
|
104
|
+
$state.itemWidth,
|
|
105
|
+
$state.valueHeight
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
$mount(() => {
|
|
111
|
+
// HACK: Wait until the measurer is really available
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
const bbox = measurer.getBBox();
|
|
114
|
+
$state.textWidth = bbox.width;
|
|
115
|
+
$state.textHeight = bbox.height * 1.5;
|
|
116
|
+
measurer.remove();
|
|
117
|
+
}, 1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
function calculateMaxValue(series: DataSeries[], field: "x" | "y") {
|
|
121
|
+
// HACK: Yeah, nested reduces
|
|
122
|
+
return series.reduce((a, b) => {
|
|
123
|
+
return Math.max(
|
|
124
|
+
a,
|
|
125
|
+
b.data.reduce((c, d) => Math.max(c, field === "x" ? d.x : d.y), 0)
|
|
126
|
+
);
|
|
127
|
+
}, 0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function buildPointSeries(
|
|
131
|
+
theSeries: DataSeries[],
|
|
132
|
+
theChartLeft: number,
|
|
133
|
+
theChartBottom: number,
|
|
134
|
+
theItemWidth: number,
|
|
135
|
+
theValueHeight: number
|
|
136
|
+
) {
|
|
137
|
+
return theSeries.map((series) => {
|
|
138
|
+
return series.data.map((item) => {
|
|
139
|
+
const x = +(theChartLeft + item.x * theItemWidth).toFixed(2);
|
|
140
|
+
const y = +(theChartBottom - item.y * theValueHeight).toFixed(2);
|
|
141
|
+
return { x, y };
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@render {
|
|
147
|
+
<div
|
|
148
|
+
{$props?.id}
|
|
149
|
+
class={["torp-scatter-chart", $props?.class]}
|
|
150
|
+
{$props?.style}
|
|
151
|
+
&ref={container}
|
|
152
|
+
>
|
|
153
|
+
<svg version="1.1" width={$state.calculatedWidth} height={$state.calculatedHeight}>
|
|
154
|
+
<g>
|
|
155
|
+
<text &ref={measurer} x="-999" y="-999">8</text>
|
|
156
|
+
|
|
157
|
+
@if ($state.textWidth && $state.textHeight) {
|
|
158
|
+
<XAxis
|
|
159
|
+
showAxis={!$props.hideXAxis}
|
|
160
|
+
height={$state.calculatedHeight}
|
|
161
|
+
width={$state.calculatedWidth}
|
|
162
|
+
{$state.itemWidth}
|
|
163
|
+
{$state.textHeight}
|
|
164
|
+
xLabel={$props.xLabel ?? ""}
|
|
165
|
+
labels={[]}
|
|
166
|
+
stepLabels={$state.xStepLabels}
|
|
167
|
+
stepValue={$state.calculatedXStepValue}
|
|
168
|
+
{$state.chartLeft}
|
|
169
|
+
{$state.chartBottom}
|
|
170
|
+
/>
|
|
171
|
+
<YAxis
|
|
172
|
+
showAxis={!$props.hideYAxis}
|
|
173
|
+
{$state.valueHeight}
|
|
174
|
+
{$state.textHeight}
|
|
175
|
+
yLabel={$props.yLabel ?? ""}
|
|
176
|
+
stepLabels={$state.yStepLabels}
|
|
177
|
+
stepValue={$state.calculatedYStepValue}
|
|
178
|
+
{$state.chartLeft}
|
|
179
|
+
{$state.chartBottom}
|
|
180
|
+
/>
|
|
181
|
+
<GridLines
|
|
182
|
+
{$props.showHLines}
|
|
183
|
+
{$props.showVLines}
|
|
184
|
+
width={$state.calculatedWidth}
|
|
185
|
+
labels={[]}
|
|
186
|
+
xStepValue={$state.calculatedXStepValue}
|
|
187
|
+
{$state.xStepLabels}
|
|
188
|
+
yStepValue={$state.calculatedYStepValue}
|
|
189
|
+
{$state.yStepLabels}
|
|
190
|
+
{$state.itemWidth}
|
|
191
|
+
{$state.valueHeight}
|
|
192
|
+
{$state.chartLeft}
|
|
193
|
+
{$state.chartBottom}
|
|
194
|
+
type="line"
|
|
195
|
+
/>
|
|
196
|
+
@for (let [i, series] of $props.series.entries()) {
|
|
197
|
+
@for (let [j, point] of $state.pointSeries[i].entries()) {
|
|
198
|
+
<circle
|
|
199
|
+
cx={point.x}
|
|
200
|
+
cy={point.y}
|
|
201
|
+
r="4"
|
|
202
|
+
fill={series.color || chartColors[i % chartColors.length]}
|
|
203
|
+
>
|
|
204
|
+
<title>{
|
|
205
|
+
`${[series.name, series.data[j].label, series.data[j].x].filter(Boolean).join("\n")}, ${series.data[j].y}`
|
|
206
|
+
}</title>
|
|
207
|
+
</circle>
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
</g>
|
|
212
|
+
</svg>
|
|
213
|
+
</div>
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
import { calculateMaxValue } from "../utils/chartUtils";
|
|
3
|
+
import { chartColors } from "../utils/chartColors";
|
|
4
|
+
|
|
5
|
+
interface SparkLineProps {
|
|
6
|
+
/** An ID for the root element */
|
|
7
|
+
id?: string;
|
|
8
|
+
/** Classes for the root element */
|
|
9
|
+
class?: ClassValue,
|
|
10
|
+
/** Styles for the root element */
|
|
11
|
+
style?: StyleValue,
|
|
12
|
+
data: number[];
|
|
13
|
+
color?: string,
|
|
14
|
+
height?: number,
|
|
15
|
+
width?: number,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function SparkLine($props: SparkLineProps) {
|
|
19
|
+
const defaultHeight = 20;
|
|
20
|
+
const defaultWidth = 100;
|
|
21
|
+
|
|
22
|
+
let container: HTMLElement;
|
|
23
|
+
|
|
24
|
+
let $state = $watch({
|
|
25
|
+
get calculatedHeight() {
|
|
26
|
+
return $props.height || defaultHeight;
|
|
27
|
+
},
|
|
28
|
+
get calculatedWidth() {
|
|
29
|
+
return $props.width || container?.clientWidth || defaultWidth;
|
|
30
|
+
},
|
|
31
|
+
get calculatedSeries() {
|
|
32
|
+
return [{ name: "", color: $props.color ?? "", data: $props.data }];
|
|
33
|
+
},
|
|
34
|
+
get maxValue() {
|
|
35
|
+
return calculateMaxValue($state.calculatedSeries);
|
|
36
|
+
},
|
|
37
|
+
get itemWidth() {
|
|
38
|
+
return $state.calculatedWidth / $props.data.length;
|
|
39
|
+
},
|
|
40
|
+
get valueHeight() {
|
|
41
|
+
return ($state.calculatedHeight - 4) / $state.maxValue;
|
|
42
|
+
},
|
|
43
|
+
get pointSeries() {
|
|
44
|
+
return $props.data.map((value, i) => {
|
|
45
|
+
const x = +(i * $state.itemWidth).toFixed(2);
|
|
46
|
+
const y = +($state.calculatedHeight - 2 - value * $state.valueHeight).toFixed(2);
|
|
47
|
+
return { x, y };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
@render {
|
|
53
|
+
<div
|
|
54
|
+
{$props?.id}
|
|
55
|
+
class={["torp-spark-line", $props?.class]}
|
|
56
|
+
{$props?.style}
|
|
57
|
+
&ref={container}
|
|
58
|
+
>
|
|
59
|
+
<svg version="1.1" width={$state.calculatedWidth} height={$state.calculatedHeight}>
|
|
60
|
+
<g>
|
|
61
|
+
<polyline
|
|
62
|
+
points={$state.pointSeries.map((p) => `${p.x},${p.y}`).join(" ")}
|
|
63
|
+
stroke={$props.color || chartColors[0]}
|
|
64
|
+
stroke-width="2"
|
|
65
|
+
fill="none"
|
|
66
|
+
/>
|
|
67
|
+
</g>
|
|
68
|
+
</svg>
|
|
69
|
+
</div>
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
interface XAxisProps {
|
|
2
|
+
showAxis: boolean,
|
|
3
|
+
height: number,
|
|
4
|
+
width: number,
|
|
5
|
+
itemWidth: number,
|
|
6
|
+
textHeight: number,
|
|
7
|
+
xLabel: string,
|
|
8
|
+
labels: string[] ,
|
|
9
|
+
stepLabels:number[] ,
|
|
10
|
+
stepValue: number,
|
|
11
|
+
chartLeft: number,
|
|
12
|
+
chartBottom: number,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function XAxis($props: XAxisProps) {
|
|
16
|
+
let $state = $watch({
|
|
17
|
+
chartLabelBottom: $props.xLabel ? $props.height - $props.textHeight : $props.height,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
@render {
|
|
21
|
+
<svg>
|
|
22
|
+
@if ($props.showAxis) {
|
|
23
|
+
<line
|
|
24
|
+
x1={$props.chartLeft}
|
|
25
|
+
y1={$props.chartBottom}
|
|
26
|
+
x2={$props.width}
|
|
27
|
+
y2={$props.chartBottom}
|
|
28
|
+
stroke="currentcolor"
|
|
29
|
+
/>
|
|
30
|
+
}
|
|
31
|
+
@if ($props.xLabel) {
|
|
32
|
+
<text
|
|
33
|
+
text-anchor="middle"
|
|
34
|
+
dominant-baseline="text-after-edge"
|
|
35
|
+
x={$props.width - ($props.width - $props.chartLeft) / 2}
|
|
36
|
+
y={$props.height}
|
|
37
|
+
>
|
|
38
|
+
{$props.xLabel}
|
|
39
|
+
</text>
|
|
40
|
+
}
|
|
41
|
+
@if ($props.stepLabels.length && $props.stepValue) {
|
|
42
|
+
@for (let [i, label] of $props.stepLabels.entries()) {
|
|
43
|
+
<text
|
|
44
|
+
text-anchor="middle"
|
|
45
|
+
dominant-baseline="text-after-edge"
|
|
46
|
+
x={$props.chartLeft + i * $props.stepValue * $props.itemWidth}
|
|
47
|
+
y={$state.chartLabelBottom}
|
|
48
|
+
>
|
|
49
|
+
{label}
|
|
50
|
+
</text>
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
@for (let [i, label] of $props.labels.entries()) {
|
|
54
|
+
<text
|
|
55
|
+
text-anchor="middle"
|
|
56
|
+
dominant-baseline="text-after-edge"
|
|
57
|
+
x={$props.chartLeft + $props.itemWidth * i + $props.itemWidth / 2}
|
|
58
|
+
y={$state.chartLabelBottom}
|
|
59
|
+
>
|
|
60
|
+
{label}
|
|
61
|
+
</text>
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</svg>
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface YAxisProps {
|
|
2
|
+
showAxis: boolean,
|
|
3
|
+
valueHeight: number,
|
|
4
|
+
textHeight: number,
|
|
5
|
+
yLabel: string,
|
|
6
|
+
stepLabels: number[],
|
|
7
|
+
stepValue: number,
|
|
8
|
+
chartLeft: number,
|
|
9
|
+
chartBottom: number,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function YAxis($props: YAxisProps) {
|
|
13
|
+
@render {
|
|
14
|
+
<svg>
|
|
15
|
+
@if ($props.showAxis) {
|
|
16
|
+
<line
|
|
17
|
+
x1={$props.chartLeft}
|
|
18
|
+
y1={$props.chartBottom}
|
|
19
|
+
x2={$props.chartLeft}
|
|
20
|
+
y2={0}
|
|
21
|
+
stroke="currentcolor"
|
|
22
|
+
/>
|
|
23
|
+
}
|
|
24
|
+
@if ($props.yLabel) {
|
|
25
|
+
<text
|
|
26
|
+
text-anchor="middle"
|
|
27
|
+
dominant-baseline="hanging"
|
|
28
|
+
x={0}
|
|
29
|
+
y={$props.chartBottom / 2}
|
|
30
|
+
transform={`rotate(-90, ${0}, ${$props.chartBottom / 2})`}
|
|
31
|
+
>
|
|
32
|
+
{$props.yLabel}
|
|
33
|
+
</text>
|
|
34
|
+
}
|
|
35
|
+
@for (let [i, label] of $props.stepLabels.entries()) {
|
|
36
|
+
<text
|
|
37
|
+
text-anchor="end"
|
|
38
|
+
dominant-baseline="middle"
|
|
39
|
+
x={$props.chartLeft - $props.textHeight / 4}
|
|
40
|
+
y={$props.chartBottom - i * $props.stepValue * $props.valueHeight}
|
|
41
|
+
>
|
|
42
|
+
{label}
|
|
43
|
+
</text>
|
|
44
|
+
}
|
|
45
|
+
</svg>
|
|
46
|
+
}
|
|
47
|
+
}
|