@torpor/ui 0.3.5 → 0.3.7
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/Chart/BarChart.torp +159 -0
- package/dist/Chart/DataSeries.ts +5 -0
- package/dist/Chart/GridLines.torp +62 -0
- package/dist/Chart/LineChart.torp +196 -0
- package/dist/Chart/PieChart.torp +145 -0
- package/dist/Chart/ScatterChart.torp +215 -0
- package/dist/Chart/SparkLine.torp +71 -0
- package/dist/Chart/XAxis.torp +60 -0
- package/dist/Chart/YAxis.torp +41 -0
- package/dist/Chart/index.d.ts +220 -0
- package/dist/Chart/index.js +34 -0
- package/dist/Chart/index.js.map +1 -0
- package/dist/Form/TextArea.torp +3 -0
- package/dist/Form/index.d.ts +2 -0
- package/dist/metafile-esm.json +1 -1
- package/dist/utils/chartColors.ts +17 -0
- package/dist/utils/chartUtils.ts +71 -0
- package/package.json +10 -3
- package/CHANGELOG.md +0 -344
- package/scripts/postbuild.ts +0 -127
- package/src/Accordion/Accordion.torp +0 -230
- package/src/Accordion/AccordionContent.torp +0 -46
- package/src/Accordion/AccordionHeader.torp +0 -45
- package/src/Accordion/AccordionItem.torp +0 -101
- package/src/Accordion/AccordionTrigger.torp +0 -70
- package/src/Accordion/AccordionTypes.ts +0 -36
- package/src/Accordion/index.ts +0 -7
- package/src/Breadcrumb/Breadcrumb.torp +0 -45
- package/src/Breadcrumb/BreadcrumbItem.torp +0 -30
- package/src/Breadcrumb/BreadcrumbSeparator.torp +0 -27
- package/src/Breadcrumb/index.ts +0 -5
- package/src/Calendar/Calendar.torp +0 -277
- package/src/Calendar/CalendarDay.torp +0 -78
- package/src/Calendar/CalendarTypes.ts +0 -22
- package/src/Calendar/index.ts +0 -4
- package/src/Contextual/Contextual.torp +0 -129
- package/src/Contextual/ContextualContent.torp +0 -112
- package/src/Contextual/ContextualTrigger.torp +0 -70
- package/src/Contextual/ContextualTypes.ts +0 -30
- package/src/Contextual/index.ts +0 -5
- package/src/Dialog/Dialog.torp +0 -136
- package/src/Dialog/DialogBody.torp +0 -44
- package/src/Dialog/DialogButton.torp +0 -66
- package/src/Dialog/DialogFooter.torp +0 -25
- package/src/Dialog/DialogHeader.torp +0 -45
- package/src/Dialog/DialogTypes.ts +0 -17
- package/src/Dialog/index.ts +0 -7
- package/src/Disclosure/Disclosure.torp +0 -82
- package/src/Disclosure/DisclosureContent.torp +0 -46
- package/src/Disclosure/DisclosureHeader.torp +0 -45
- package/src/Disclosure/DisclosureTrigger.torp +0 -47
- package/src/Disclosure/DisclosureTypes.ts +0 -13
- package/src/Disclosure/index.ts +0 -6
- package/src/Drawer/Drawer.torp +0 -127
- package/src/Drawer/DrawerContent.torp +0 -83
- package/src/Drawer/DrawerOverlay.torp +0 -42
- package/src/Drawer/DrawerTrigger.torp +0 -39
- package/src/Drawer/DrawerTypes.ts +0 -24
- package/src/Drawer/index.ts +0 -6
- package/src/Form/CheckBox.torp +0 -94
- package/src/Form/Field.torp +0 -95
- package/src/Form/File.torp +0 -102
- package/src/Form/Form.torp +0 -147
- package/src/Form/FormTypes.ts +0 -35
- package/src/Form/Hidden.torp +0 -25
- package/src/Form/Input.torp +0 -112
- package/src/Form/Label.torp +0 -35
- package/src/Form/Message.torp +0 -39
- package/src/Form/Option.torp +0 -28
- package/src/Form/Select.torp +0 -101
- package/src/Form/StandardSchemaV1.ts +0 -70
- package/src/Form/TextArea.torp +0 -105
- package/src/Form/index.ts +0 -13
- package/src/Menu/Menu.torp +0 -174
- package/src/Menu/MenuButton.torp +0 -74
- package/src/Menu/MenuCheck.torp +0 -89
- package/src/Menu/MenuGroup.torp +0 -31
- package/src/Menu/MenuIndicator.torp +0 -40
- package/src/Menu/MenuPopout.torp +0 -83
- package/src/Menu/MenuPopoutContent.torp +0 -84
- package/src/Menu/MenuPopoutTrigger.torp +0 -128
- package/src/Menu/MenuRadio.torp +0 -103
- package/src/Menu/MenuRadioGroup.torp +0 -109
- package/src/Menu/MenuSeparator.torp +0 -26
- package/src/Menu/MenuTypes.ts +0 -60
- package/src/Menu/index.ts +0 -25
- package/src/MenuBar/MenuBar.torp +0 -162
- package/src/MenuBar/MenuBarItem.torp +0 -116
- package/src/MenuBar/MenuBarItemContent.torp +0 -119
- package/src/MenuBar/MenuBarItemTrigger.torp +0 -99
- package/src/MenuBar/MenuBarTypes.ts +0 -36
- package/src/MenuBar/index.ts +0 -6
- package/src/Modal/Modal.torp +0 -122
- package/src/Modal/ModalContent.torp +0 -81
- package/src/Modal/ModalOverlay.torp +0 -42
- package/src/Modal/ModalTrigger.torp +0 -54
- package/src/Modal/ModalTypes.ts +0 -22
- package/src/Modal/index.ts +0 -10
- package/src/Modal/showModal.ts +0 -19
- package/src/Modal/showPrompt.ts +0 -23
- package/src/NavMenu/NavMenu.torp +0 -190
- package/src/NavMenu/NavMenuButton.torp +0 -55
- package/src/NavMenu/NavMenuElement.torp +0 -55
- package/src/NavMenu/NavMenuGroup.torp +0 -41
- package/src/NavMenu/NavMenuGroupHeader.torp +0 -41
- package/src/NavMenu/NavMenuLink.torp +0 -60
- package/src/NavMenu/NavMenuPopout.torp +0 -89
- package/src/NavMenu/NavMenuPopoutButton.torp +0 -129
- package/src/NavMenu/NavMenuPopoutContent.torp +0 -92
- package/src/NavMenu/NavMenuSeparator.torp +0 -24
- package/src/NavMenu/NavMenuTypes.ts +0 -32
- package/src/NavMenu/index.ts +0 -23
- package/src/Notification/Notification.torp +0 -71
- package/src/Notification/NotificationClose.torp +0 -39
- package/src/Notification/NotificationContainer.torp +0 -26
- package/src/Notification/NotificationContent.torp +0 -25
- package/src/Notification/NotificationTypes.ts +0 -5
- package/src/Notification/index.ts +0 -13
- package/src/Notification/showNotification.ts +0 -30
- package/src/Popover/Popover.torp +0 -129
- package/src/Popover/PopoverContent.torp +0 -117
- package/src/Popover/PopoverTrigger.torp +0 -62
- package/src/Popover/PopoverTypes.ts +0 -30
- package/src/Popover/index.ts +0 -6
- package/src/Popover/showPopover.ts +0 -24
- package/src/Slider/Slider.torp +0 -84
- package/src/Slider/index.ts +0 -3
- package/src/TabGroup/TabContent.torp +0 -49
- package/src/TabGroup/TabGroup.torp +0 -250
- package/src/TabGroup/TabGroupTypes.ts +0 -44
- package/src/TabGroup/TabHeader.torp +0 -81
- package/src/TabGroup/TabList.torp +0 -61
- package/src/TabGroup/TabTrigger.torp +0 -76
- package/src/TabGroup/index.ts +0 -7
- package/src/ambient.d.ts +0 -7
- package/src/icons/ArrowDown.torp +0 -18
- package/src/icons/ArrowLeft.torp +0 -18
- package/src/icons/ArrowRight.torp +0 -18
- package/src/icons/ArrowUp.torp +0 -18
- package/src/icons/Bars.torp +0 -20
- package/src/icons/Check.torp +0 -18
- package/src/icons/ChevronDown.torp +0 -18
- package/src/icons/ChevronLeft.torp +0 -18
- package/src/icons/ChevronRight.torp +0 -18
- package/src/icons/ChevronUp.torp +0 -18
- package/src/icons/Cross.torp +0 -19
- package/src/icons/IconTypes.ts +0 -10
- package/src/icons/Loading.torp +0 -19
- package/src/icons/Minus.torp +0 -18
- package/src/icons/Plus.torp +0 -19
- package/src/icons/index.ts +0 -31
- package/src/motion/fade.ts +0 -16
- package/src/motion/grow.ts +0 -23
- package/src/motion/index.ts +0 -6
- package/src/motion/measure.ts +0 -28
- package/src/motion/slide.ts +0 -45
- package/src/mount/clickPopover.ts +0 -51
- package/src/mount/contextPopover.ts +0 -51
- package/src/mount/disableScroll.ts +0 -28
- package/src/mount/hoverPopover.ts +0 -58
- package/src/mount/hoverTrigger.ts +0 -59
- package/src/mount/trapFocus.ts +0 -58
- package/src/utils/dateUtils.ts +0 -244
- package/src/utils/documentEvents.ts +0 -17
- package/src/utils/getId.ts +0 -6
- package/src/utils/getScrollParent.ts +0 -22
- package/src/utils/setContextPosition.ts +0 -31
- package/src/utils/setPopoverPosition.ts +0 -142
- package/test/Accordion/accessibility.test.ts +0 -59
- package/test/Accordion/components/AccordionAccessibility.torp +0 -44
- package/test/Accordion/components/AccordionKeyboard.torp +0 -32
- package/test/Accordion/components/AccordionList.torp +0 -23
- package/test/Accordion/components/AccordionMultiple.torp +0 -32
- package/test/Accordion/components/AccordionSingle.torp +0 -32
- package/test/Accordion/for-items.test.ts +0 -52
- package/test/Accordion/keyboard.test.ts +0 -64
- package/test/Accordion/multiple-item-toggle.test.ts +0 -32
- package/test/Accordion/multiple-item-value.test.ts +0 -17
- package/test/Accordion/single-item-toggle.test.ts +0 -31
- package/test/Accordion/single-item-value.test.ts +0 -17
- package/test/Breadcrumb/accessibility.test.ts +0 -29
- package/test/Breadcrumb/basic.test.ts +0 -17
- package/test/Breadcrumb/components/BreadcrumbTest.torp +0 -15
- package/test/Calendar/components/CalendarTest.torp +0 -9
- package/test/Calendar/default.test.ts +0 -18
- package/test/Dialog/accessibility-alert.test.ts +0 -40
- package/test/Dialog/accessibility-modal.test.ts +0 -52
- package/test/Dialog/components/DialogAccessibility.torp +0 -25
- package/test/Dialog/components/DialogFunction.torp +0 -36
- package/test/Dialog/components/DialogModal.torp +0 -30
- package/test/Dialog/components/DialogVariable.torp +0 -33
- package/test/Dialog/function.test.ts +0 -26
- package/test/Dialog/variable.test.ts +0 -27
- package/test/Disclosure/accessibility.test.ts +0 -31
- package/test/Disclosure/components/DisclosureTest.torp +0 -17
- package/test/Disclosure/default.test.ts +0 -15
- package/test/Disclosure/toggle.test.ts +0 -20
- package/test/Form/components/FieldTest.torp +0 -25
- package/test/Form/components/FormTest.torp +0 -31
- package/test/Form/components/InputTest.torp +0 -9
- package/test/Form/field.test.ts +0 -34
- package/test/Form/form.test.ts +0 -34
- package/test/Form/input.test.ts +0 -19
- package/test/Menu/accessibility.test.ts +0 -83
- package/test/Menu/basic.test.ts +0 -58
- package/test/Menu/components/MenuAccessibility.torp +0 -59
- package/test/Menu/components/MenuBasic.torp +0 -58
- package/test/MenuBar/accessibility.test.ts +0 -87
- package/test/MenuBar/basic.test.ts +0 -22
- package/test/MenuBar/components/MenuBarAccessibility.torp +0 -34
- package/test/MenuBar/components/MenuBarBasic.torp +0 -25
- package/test/NavMenu/accessibility.test.ts +0 -97
- package/test/NavMenu/basic.test.ts +0 -22
- package/test/NavMenu/components/NavMenuAccessibility.torp +0 -35
- package/test/NavMenu/components/NavMenuBasic.torp +0 -28
- package/test/Notification/accessibility.test.ts +0 -21
- package/test/Notification/components/CustomNotification.torp +0 -14
- package/test/Notification/components/NotificationAccessibility.torp +0 -14
- package/test/Notification/components/NotificationFunction.torp +0 -17
- package/test/Notification/components/NotificationVariable.torp +0 -22
- package/test/Notification/function.test.ts +0 -25
- package/test/Notification/variable.test.ts +0 -25
- package/test/Popover/components/CustomDialog.torp +0 -25
- package/test/Popover/components/PopoverFunction.torp +0 -37
- package/test/Popover/components/PopoverTrigger.torp +0 -36
- package/test/Popover/components/PopoverVariable.torp +0 -40
- package/test/Popover/function.test.ts +0 -21
- package/test/Popover/trigger.test.ts +0 -27
- package/test/Popover/variable.test.ts +0 -27
- package/test/TabGroup/accessibility.test.ts +0 -67
- package/test/TabGroup/components/TabGroupAccessibility.torp +0 -28
- package/test/TabGroup/components/TabGroupKeyboard.torp +0 -28
- package/test/TabGroup/components/TabGroupList.torp +0 -26
- package/test/TabGroup/components/TabGroupTrigger.torp +0 -28
- package/test/TabGroup/for-items.test.ts +0 -52
- package/test/TabGroup/item-trigger.test.ts +0 -33
- package/test/TabGroup/item-value.test.ts +0 -17
- package/test/TabGroup/keyboard.test.ts +0 -72
- package/tsconfig.json +0 -4
- package/tsup.config.ts +0 -37
- package/vitest.config.ts +0 -12
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
calculateMaxValue,
|
|
7
|
+
calculateStepValue,
|
|
8
|
+
calculateStepLabels,
|
|
9
|
+
calculateItemWidth,
|
|
10
|
+
calculateValueHeight,
|
|
11
|
+
calculateChartBottom,
|
|
12
|
+
calculateChartLeft
|
|
13
|
+
} from "../utils/chartUtils";
|
|
14
|
+
import { chartColors } from "../utils/chartColors";
|
|
15
|
+
|
|
16
|
+
import type DataSeries from "./DataSeries";
|
|
17
|
+
|
|
18
|
+
interface BarChartProps {
|
|
19
|
+
/** An ID for the root element */
|
|
20
|
+
id?: string;
|
|
21
|
+
/** Classes for the root element */
|
|
22
|
+
class?: ClassValue,
|
|
23
|
+
/** Styles for the root element */
|
|
24
|
+
style?: StyleValue,
|
|
25
|
+
series: DataSeries[],
|
|
26
|
+
height?: number,
|
|
27
|
+
width?: number,
|
|
28
|
+
xLabel?: string,
|
|
29
|
+
yLabel?: string,
|
|
30
|
+
maxValue?: number,
|
|
31
|
+
stepCount?: number,
|
|
32
|
+
stepValue?: number,
|
|
33
|
+
hideXAxis?: boolean,
|
|
34
|
+
hideYAxis?: boolean,
|
|
35
|
+
showHLines?: boolean,
|
|
36
|
+
showVLines?: boolean,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function BarChart($props: BarChartProps) {
|
|
40
|
+
const defaultHeight = 200;
|
|
41
|
+
const defaultWidth = 400;
|
|
42
|
+
|
|
43
|
+
let container: HTMLElement;
|
|
44
|
+
let measurer: SVGTextElement;
|
|
45
|
+
|
|
46
|
+
let $state = $watch({
|
|
47
|
+
textWidth: 0,
|
|
48
|
+
textHeight: 0,
|
|
49
|
+
get labels() {
|
|
50
|
+
return $props.series.map((s) => s.name);
|
|
51
|
+
},
|
|
52
|
+
get calculatedHeight() {
|
|
53
|
+
return $props.height || defaultHeight;
|
|
54
|
+
},
|
|
55
|
+
get calculatedWidth() {
|
|
56
|
+
return $props.width || container?.clientWidth || defaultWidth;
|
|
57
|
+
},
|
|
58
|
+
get maxValue () {
|
|
59
|
+
return $props.maxValue || calculateMaxValue($props.series);
|
|
60
|
+
},
|
|
61
|
+
get calculatedStepValue() {
|
|
62
|
+
return $props.stepValue || calculateStepValue($state.maxValue, $props.stepCount);
|
|
63
|
+
},
|
|
64
|
+
get stepLabels() {
|
|
65
|
+
return calculateStepLabels($props.stepCount || 2, $state.calculatedStepValue);
|
|
66
|
+
},
|
|
67
|
+
get itemWidth() {
|
|
68
|
+
return calculateItemWidth($state.calculatedWidth, $state.chartLeft, $state.labels);
|
|
69
|
+
},
|
|
70
|
+
get valueHeight() {
|
|
71
|
+
return calculateValueHeight($state.chartBottom, $state.textHeight, $state.calculatedStepValue, $props.stepCount || 2);
|
|
72
|
+
},
|
|
73
|
+
get chartBottom() {
|
|
74
|
+
return calculateChartBottom($props.xLabel ?? "", $state.calculatedHeight, $state.textHeight);
|
|
75
|
+
},
|
|
76
|
+
get chartLeft() {
|
|
77
|
+
return calculateChartLeft($state.stepLabels, $props.yLabel ?? "", $state.textHeight, $state.textWidth);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
$mount(() => {
|
|
82
|
+
// HACK: Wait until the measurer is really available
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
const bbox = measurer.getBBox();
|
|
85
|
+
$state.textWidth = bbox.width;
|
|
86
|
+
$state.textHeight = bbox.height * 1.5;
|
|
87
|
+
measurer.remove();
|
|
88
|
+
}, 1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
@render {
|
|
92
|
+
<div
|
|
93
|
+
{$props?.id}
|
|
94
|
+
class={["torp-bar-chart", $props?.class]}
|
|
95
|
+
{$props?.style}
|
|
96
|
+
&ref={container}
|
|
97
|
+
>
|
|
98
|
+
<svg version="1.1" width={$state.calculatedWidth} height={$state.calculatedHeight} xmlns="http://www.w3.org/2000/svg">
|
|
99
|
+
<g>
|
|
100
|
+
<text &ref={measurer} x="-999" y="-999">8</text>
|
|
101
|
+
|
|
102
|
+
@if ($state.textWidth && $state.textHeight) {
|
|
103
|
+
<XAxis
|
|
104
|
+
showAxis={!$props.hideXAxis}
|
|
105
|
+
height={$state.calculatedHeight}
|
|
106
|
+
width={$state.calculatedWidth}
|
|
107
|
+
{$state.itemWidth}
|
|
108
|
+
{$state.textHeight}
|
|
109
|
+
xLabel={$props.xLabel ?? ""}
|
|
110
|
+
stepLabels={[]}
|
|
111
|
+
stepValue={0}
|
|
112
|
+
{$state.labels}
|
|
113
|
+
{$state.chartLeft}
|
|
114
|
+
{$state.chartBottom}
|
|
115
|
+
/>
|
|
116
|
+
<YAxis
|
|
117
|
+
showAxis={!$props.hideYAxis}
|
|
118
|
+
{$state.valueHeight}
|
|
119
|
+
{$state.textHeight}
|
|
120
|
+
yLabel={$props.yLabel ?? ""}
|
|
121
|
+
{$state.stepLabels}
|
|
122
|
+
stepValue={$state.calculatedStepValue}
|
|
123
|
+
{$state.chartLeft}
|
|
124
|
+
{$state.chartBottom}
|
|
125
|
+
/>
|
|
126
|
+
<GridLines
|
|
127
|
+
{$props.showHLines}
|
|
128
|
+
{$props.showVLines}
|
|
129
|
+
width={$state.calculatedWidth}
|
|
130
|
+
{$state.labels}
|
|
131
|
+
xStepValue={0}
|
|
132
|
+
xStepLabels={[]}
|
|
133
|
+
yStepValue={$state.calculatedStepValue}
|
|
134
|
+
yStepLabels={$state.stepLabels}
|
|
135
|
+
{$state.itemWidth}
|
|
136
|
+
{$state.valueHeight}
|
|
137
|
+
{$state.chartLeft}
|
|
138
|
+
{$state.chartBottom}
|
|
139
|
+
type="bar"
|
|
140
|
+
/>
|
|
141
|
+
@for (let [i, series] of $props.series.entries()) {
|
|
142
|
+
<rect
|
|
143
|
+
x={$state.chartLeft +
|
|
144
|
+
i * $state.itemWidth +
|
|
145
|
+
$state.itemWidth / 4}
|
|
146
|
+
y={$state.chartBottom - Math.max(1, series.data[0] * $state.valueHeight)}
|
|
147
|
+
width={Math.max(1, $state.itemWidth / 2)}
|
|
148
|
+
height={Math.max(1, series.data[0] * $state.valueHeight)}
|
|
149
|
+
fill={series.color || chartColors[i % chartColors.length]}
|
|
150
|
+
>
|
|
151
|
+
<title>{`${[series.name, series.data[0]].filter(Boolean).join("\n")}`}</title>
|
|
152
|
+
</rect>
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
</g>
|
|
156
|
+
</svg>
|
|
157
|
+
</div>
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
interface GridLinesProps {
|
|
4
|
+
/** An ID for the root element */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Classes for the root element */
|
|
7
|
+
class?: ClassValue,
|
|
8
|
+
/** Styles for the root element */
|
|
9
|
+
style?: StyleValue,
|
|
10
|
+
showHLines?: boolean,
|
|
11
|
+
showVLines?: boolean,
|
|
12
|
+
width: number;
|
|
13
|
+
labels: string[];
|
|
14
|
+
xStepValue: number;
|
|
15
|
+
xStepLabels: number[];
|
|
16
|
+
yStepValue: number;
|
|
17
|
+
yStepLabels: number[];
|
|
18
|
+
itemWidth: number;
|
|
19
|
+
valueHeight: number;
|
|
20
|
+
chartLeft: number;
|
|
21
|
+
chartBottom: number;
|
|
22
|
+
type: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function GridLines($props: GridLinesProps) {
|
|
26
|
+
@render {
|
|
27
|
+
<svg>
|
|
28
|
+
@if ($props.showHLines && $props.yStepLabels) {
|
|
29
|
+
@for (let [i, label] of $props.yStepLabels!.entries()) {
|
|
30
|
+
<line
|
|
31
|
+
x1={$props.chartLeft ?? 0}
|
|
32
|
+
y1={$props.chartBottom - i * $props.yStepValue * $props.valueHeight}
|
|
33
|
+
x2={$props.width ?? 0}
|
|
34
|
+
y2={$props.chartBottom - i * $props.yStepValue * $props.valueHeight}
|
|
35
|
+
/>
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@if ($props.showVLines) {
|
|
40
|
+
@if ($props.xStepLabels.length && $props.xStepValue) {
|
|
41
|
+
@for (let [i, label] of $props.xStepLabels.entries()) {
|
|
42
|
+
<line
|
|
43
|
+
x1={$props.chartLeft + i * $props.xStepValue * $props.itemWidth}
|
|
44
|
+
y1={$props.chartBottom}
|
|
45
|
+
x2={$props.chartLeft + i * $props.xStepValue * $props.itemWidth}
|
|
46
|
+
y2={0}
|
|
47
|
+
/>
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
@for (let [i, label] of $props.labels.entries()) {
|
|
51
|
+
<line
|
|
52
|
+
x1={$props.chartLeft + i * $props.itemWidth + ($props.type === "bar" ? 0 : $props.itemWidth / 2)}
|
|
53
|
+
y1={$props.chartBottom}
|
|
54
|
+
x2={$props.chartLeft + i * $props.itemWidth + ($props.type === "bar" ? 0 : $props.itemWidth / 2)}
|
|
55
|
+
y2={0}
|
|
56
|
+
/>
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
</svg>
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { ClassValue, StyleValue } from "@torpor/view";
|
|
2
|
+
|
|
3
|
+
import XAxis from "./XAxis.torp";
|
|
4
|
+
import YAxis from "./YAxis.torp";
|
|
5
|
+
import GridLines from "./GridLines.torp";
|
|
6
|
+
import {
|
|
7
|
+
calculateMaxValue,
|
|
8
|
+
calculateStepValue,
|
|
9
|
+
calculateStepLabels,
|
|
10
|
+
calculateItemWidth,
|
|
11
|
+
calculateValueHeight,
|
|
12
|
+
calculateChartBottom,
|
|
13
|
+
calculateChartLeft
|
|
14
|
+
} from "../utils/chartUtils";
|
|
15
|
+
import { chartColors } from "../utils/chartColors";
|
|
16
|
+
|
|
17
|
+
import type DataSeries from "./DataSeries";
|
|
18
|
+
|
|
19
|
+
interface LineChartProps {
|
|
20
|
+
/** An ID for the root element */
|
|
21
|
+
id?: string;
|
|
22
|
+
/** Classes for the root element */
|
|
23
|
+
class?: ClassValue,
|
|
24
|
+
/** Styles for the root element */
|
|
25
|
+
style?: StyleValue,
|
|
26
|
+
series: DataSeries[],
|
|
27
|
+
height?: number,
|
|
28
|
+
width?: number,
|
|
29
|
+
xLabel?: string,
|
|
30
|
+
yLabel?: string,
|
|
31
|
+
maxValue?: number,
|
|
32
|
+
stepCount?: number,
|
|
33
|
+
stepValue?: number,
|
|
34
|
+
showPoints?: boolean,
|
|
35
|
+
hideXAxis?: boolean,
|
|
36
|
+
hideYAxis?: boolean,
|
|
37
|
+
showHLines?: boolean,
|
|
38
|
+
showVLines?: boolean,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default function LineChart($props: LineChartProps) {
|
|
42
|
+
const defaultHeight = 200;
|
|
43
|
+
const defaultWidth = 400;
|
|
44
|
+
|
|
45
|
+
let container: HTMLElement;
|
|
46
|
+
let measurer: SVGTextElement;
|
|
47
|
+
|
|
48
|
+
let $state = $watch({
|
|
49
|
+
textWidth: 0,
|
|
50
|
+
textHeight: 0,
|
|
51
|
+
get labels() {
|
|
52
|
+
return $props.series.map((s) => s.name);
|
|
53
|
+
},
|
|
54
|
+
get calculatedHeight() {
|
|
55
|
+
return $props.height || defaultHeight;
|
|
56
|
+
},
|
|
57
|
+
get calculatedWidth() {
|
|
58
|
+
return $props.width || container?.clientWidth || defaultWidth;
|
|
59
|
+
},
|
|
60
|
+
get maxValue() {
|
|
61
|
+
return $props.maxValue || calculateMaxValue($props.series);
|
|
62
|
+
},
|
|
63
|
+
get calculatedStepValue() {
|
|
64
|
+
return $props.stepValue || calculateStepValue($state.maxValue, $props.stepCount);
|
|
65
|
+
},
|
|
66
|
+
get stepLabels() {
|
|
67
|
+
return calculateStepLabels($props.stepCount || 2, $state.calculatedStepValue);
|
|
68
|
+
},
|
|
69
|
+
get itemWidth() {
|
|
70
|
+
return calculateItemWidth($state.calculatedWidth, $state.chartLeft, $state.labels);
|
|
71
|
+
},
|
|
72
|
+
get valueHeight() {
|
|
73
|
+
return calculateValueHeight($state.chartBottom, $state.textHeight, $state.calculatedStepValue, $props.stepCount || 2);
|
|
74
|
+
},
|
|
75
|
+
get chartBottom() {
|
|
76
|
+
return calculateChartBottom($props.xLabel ?? "", $state.calculatedHeight, $state.textHeight);
|
|
77
|
+
},
|
|
78
|
+
get chartLeft() {
|
|
79
|
+
return calculateChartLeft($state.stepLabels, $props.yLabel ?? "", $state.textHeight, $state.textWidth);
|
|
80
|
+
},
|
|
81
|
+
get pointSeries() {
|
|
82
|
+
return buildPointSeries(
|
|
83
|
+
$props.series,
|
|
84
|
+
$state.chartLeft,
|
|
85
|
+
$state.chartBottom,
|
|
86
|
+
$state.itemWidth,
|
|
87
|
+
$state.valueHeight
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
$mount(() => {
|
|
93
|
+
// HACK: Wait until the measurer is really available
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
const bbox = measurer.getBBox();
|
|
96
|
+
$state.textWidth = bbox.width;
|
|
97
|
+
$state.textHeight = bbox.height * 1.5;
|
|
98
|
+
measurer.remove();
|
|
99
|
+
}, 1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function buildPointSeries(
|
|
103
|
+
theSeries: DataSeries[],
|
|
104
|
+
theChartLeft: number,
|
|
105
|
+
theChartBottom: number,
|
|
106
|
+
theItemWidth: number,
|
|
107
|
+
theValueHeight: number
|
|
108
|
+
): { x: number; y: number }[][] {
|
|
109
|
+
return theSeries.map((series) => {
|
|
110
|
+
return series.data.map((value, i) => {
|
|
111
|
+
const x = +(theChartLeft + i * theItemWidth + theItemWidth / 2).toFixed(2);
|
|
112
|
+
const y = +(theChartBottom - value * theValueHeight).toFixed(2);
|
|
113
|
+
return { x, y };
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@render {
|
|
119
|
+
<div
|
|
120
|
+
{$props?.id}
|
|
121
|
+
class={["torp-line-chart", $props?.class]}
|
|
122
|
+
{$props?.style}
|
|
123
|
+
&ref={container}
|
|
124
|
+
>
|
|
125
|
+
<svg version="1.1" width={$state.calculatedWidth} height={$state.calculatedHeight}>
|
|
126
|
+
<g>
|
|
127
|
+
<text &ref={measurer} x="-999" y="-999">8</text>
|
|
128
|
+
|
|
129
|
+
@if ($state.textWidth && $state.textHeight) {
|
|
130
|
+
<XAxis
|
|
131
|
+
showAxis={!$props.hideXAxis}
|
|
132
|
+
height={$state.calculatedHeight}
|
|
133
|
+
width={$state.calculatedWidth}
|
|
134
|
+
{$state.itemWidth}
|
|
135
|
+
{$state.textHeight}
|
|
136
|
+
xLabel={$props.xLabel ?? ""}
|
|
137
|
+
{$state.labels}
|
|
138
|
+
stepLabels={[]}
|
|
139
|
+
stepValue={0}
|
|
140
|
+
{$state.chartLeft}
|
|
141
|
+
{$state.chartBottom}
|
|
142
|
+
/>
|
|
143
|
+
<YAxis
|
|
144
|
+
showAxis={!$props.hideYAxis}
|
|
145
|
+
{$state.valueHeight}
|
|
146
|
+
{$state.textHeight}
|
|
147
|
+
yLabel={$props.yLabel ?? ""}
|
|
148
|
+
{$state.stepLabels}
|
|
149
|
+
stepValue={$state.calculatedStepValue}
|
|
150
|
+
{$state.chartLeft}
|
|
151
|
+
{$state.chartBottom}
|
|
152
|
+
/>
|
|
153
|
+
<GridLines
|
|
154
|
+
{$props.showHLines}
|
|
155
|
+
{$props.showVLines}
|
|
156
|
+
width={$state.calculatedWidth}
|
|
157
|
+
{$state.labels}
|
|
158
|
+
xStepValue={0}
|
|
159
|
+
xStepLabels={[]}
|
|
160
|
+
yStepValue={$state.calculatedStepValue}
|
|
161
|
+
yStepLabels={[]}
|
|
162
|
+
{$state.stepLabels}
|
|
163
|
+
{$state.itemWidth}
|
|
164
|
+
{$state.valueHeight}
|
|
165
|
+
{$state.chartLeft}
|
|
166
|
+
{$state.chartBottom}
|
|
167
|
+
type="line"
|
|
168
|
+
/>
|
|
169
|
+
@for (let [i, series] of $props.series.entries()) {
|
|
170
|
+
<polyline
|
|
171
|
+
points={$state.pointSeries[i].map((p) => `${p.x},${p.y}`).join(" ")}
|
|
172
|
+
stroke={series.color || chartColors[i % chartColors.length]}
|
|
173
|
+
stroke-width="2"
|
|
174
|
+
fill="none"
|
|
175
|
+
/>
|
|
176
|
+
@if ($props.showPoints) {
|
|
177
|
+
@for (let [j, point] of $state.pointSeries[i].entries()) {
|
|
178
|
+
<circle
|
|
179
|
+
cx={point.x}
|
|
180
|
+
cy={point.y}
|
|
181
|
+
r="4"
|
|
182
|
+
stroke={series.color || chartColors[i % chartColors.length]}
|
|
183
|
+
stroke-width="2"
|
|
184
|
+
fill="white"
|
|
185
|
+
>
|
|
186
|
+
<title>{`${[series.name, $state.labels[j], series.data[j]].filter(Boolean).join("\n")}`}</title>
|
|
187
|
+
</circle>
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
</g>
|
|
193
|
+
</svg>
|
|
194
|
+
</div>
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -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
|
+
}
|