): string {
+ return JSON.stringify(key, Object.keys(key).sort())
+}
+
+interface Constructor {
+ __isFragment?: never
+ __isTeleport?: never
+ __isSuspense?: never
+ new (...args: any[]): {
+ $props: P
+ }
+}
+
+export function componentToString
(config: MaybeRef, component: Constructor, props?: P) {
+ if (!isClient)
+ return
+
+ // This function will be called once during mount lifecycle
+ const id = useId()
+
+ // https://unovis.dev/docs/auxiliary/Crosshair#component-props
+ return (_data: any, x: number | Date) => {
+ const data = "data" in _data ? _data.data : _data
+ const normalizedX = typeof x === "number" ? Math.round(x) : x.getTime()
+ const serializedKey = `${id}-${normalizedX}-${serializeKey(data)}`
+ const cachedContent = cache.get(serializedKey)
+ if (cachedContent)
+ return cachedContent
+
+ const vnode = h(component, { ...props, payload: data, config: unref(config), x })
+ const div = document.createElement("div")
+ render(vnode, div)
+ cache.set(serializedKey, div.innerHTML)
+ return div.innerHTML
+ }
+}
diff --git a/resources/js/dashboard/components/widgets/Graph.vue b/resources/js/dashboard/components/widgets/Graph.vue
index a53c2ba5f..7c69cf18a 100644
--- a/resources/js/dashboard/components/widgets/Graph.vue
+++ b/resources/js/dashboard/components/widgets/Graph.vue
@@ -1,11 +1,12 @@
@@ -31,10 +33,11 @@
v-bind="props"
class="[&_svg]:rounded-b-[calc(.5rem-1px)] [&_svg]:overflow-visible"
:class="[
- !widget.height ? 'aspect-(--ratio)' : '',
+ widget.height ? 'h-(--height)' : 'aspect-(--ratio)',
]"
:style="{
- '--ratio': `${widget.ratioX} / ${widget.ratioY}`,
+ '--ratio': widget.height ? null : `${widget.ratioX} / ${widget.ratioY}`,
+ '--height': widget.height ? `${widget.height}px` : null,
}"
/>
diff --git a/resources/js/dashboard/components/widgets/graph/Area.vue b/resources/js/dashboard/components/widgets/graph/Area.vue
new file mode 100644
index 000000000..5de32129a
--- /dev/null
+++ b/resources/js/dashboard/components/widgets/graph/Area.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/dashboard/components/widgets/graph/Bar.vue b/resources/js/dashboard/components/widgets/graph/Bar.vue
index 2b1bc9f93..698e56bfa 100644
--- a/resources/js/dashboard/components/widgets/graph/Bar.vue
+++ b/resources/js/dashboard/components/widgets/graph/Bar.vue
@@ -1,62 +1,78 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/dashboard/components/widgets/graph/Line.vue b/resources/js/dashboard/components/widgets/graph/Line.vue
index 8121aa83b..e2fa8ab96 100644
--- a/resources/js/dashboard/components/widgets/graph/Line.vue
+++ b/resources/js/dashboard/components/widgets/graph/Line.vue
@@ -1,65 +1,77 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/dashboard/components/widgets/graph/Pie.vue b/resources/js/dashboard/components/widgets/graph/Pie.vue
index 55e068faf..f69a21965 100644
--- a/resources/js/dashboard/components/widgets/graph/Pie.vue
+++ b/resources/js/dashboard/components/widgets/graph/Pie.vue
@@ -1,69 +1,58 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/dashboard/components/widgets/graph/useApexCharts.ts b/resources/js/dashboard/components/widgets/graph/useApexCharts.ts
deleted file mode 100644
index 92efca3a5..000000000
--- a/resources/js/dashboard/components/widgets/graph/useApexCharts.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { ApexOptions } from 'apexcharts';
-import en from "apexcharts/dist/locales/en.json";
-import fr from "apexcharts/dist/locales/fr.json";
-import ru from "apexcharts/dist/locales/ru.json";
-import es from "apexcharts/dist/locales/es.json";
-import de from "apexcharts/dist/locales/de.json";
-import merge from 'lodash/merge';
-import { computed, MaybeRefOrGetter, ref, toValue, useTemplateRef } from "vue";
-import { GraphWidgetData } from "@/types";
-import { VueApexChartsComponent } from "vue3-apexcharts";
-import { useResizeObserver } from "@vueuse/core";
-import { DashboardWidgetProps } from "@/dashboard/types";
-import debounce from "lodash/debounce";
-import { useColorMode } from "@/composables/useColorMode";
-
-export function useApexCharts(
- props: DashboardWidgetProps,
- additionalOptions: ({ width }: { width: number }) => ApexOptions
-) {
- const apexChartsComponent = useTemplateRef('apexChartsComponent');
- const el = computed(() => apexChartsComponent.value?.$el);
- const width = ref(0);
- const redraw = debounce(() => {
- apexChartsComponent.value.chart?.updateOptions({}, true);
- width.value = el.value.clientWidth;
- el.value.style.overflow = 'visible';
- }, 100);
- const mode = useColorMode();
- useResizeObserver(apexChartsComponent, () => {
- el.value.style.overflow = 'hidden';
- if(!width.value) {
- width.value = el.value.clientWidth;
- }
- redraw();
- });
- const options = computed(() => {
- const widget = props.widget;
- const baseOptions: ApexOptions = {
- chart: {
- height: widget.height ?? '100%',
- width: '100%',
- parentHeightOffset: 0,
- animations: {
- enabled: false,
- },
- toolbar: {
- show: false,
- },
- zoom: {
- enabled: false,
- },
- selection: {
- enabled: false,
- },
- locales: [
- en, fr, ru, es, de,
- ],
- defaultLocale: document.documentElement.lang,
- redrawOnParentResize: false,
- redrawOnWindowResize: false,
- background: 'transparent',
- },
- legend: {
- show: widget.showLegend && !widget.minimal,
- showForSingleSeries: true,
- },
- theme: {
- mode: mode.value
- },
- tooltip: {
- y: {
- title: {
- // @ts-ignore
- formatter: (seriesName, {seriesIndex}) =>
- seriesName !== `series-${seriesIndex + 1}` ? `${seriesName}:` : ''
- }
- }
- },
- };
-
- return merge(baseOptions, additionalOptions({ width: width.value }));
- });
-
- return {
- apexChartsComponent,
- options,
- };
-}
diff --git a/resources/js/dashboard/components/widgets/graph/useXYChart.ts b/resources/js/dashboard/components/widgets/graph/useXYChart.ts
new file mode 100644
index 000000000..a99a015dd
--- /dev/null
+++ b/resources/js/dashboard/components/widgets/graph/useXYChart.ts
@@ -0,0 +1,175 @@
+import { DashboardWidgetProps } from "@/dashboard/types";
+import { AreaGraphWidgetData, BarGraphWidgetData, GraphWidgetData, LineGraphWidgetData } from "@/types";
+import { computed } from "vue";
+import { XYComponentConfigInterface } from "@unovis/ts/core/xy-component/config";
+import {
+ AxisConfigInterface,
+ ColorAccessor,
+ FitMode,
+ Scale,
+ TextAlign,
+ TrimMode,
+ getNearest,
+ XYContainerConfigInterface,
+} from "@unovis/ts";
+import { extent } from 'd3-array';
+import { ChartConfig, ChartTooltipContent, componentToString } from "@/components/ui/chart";
+import { useCurrentElement, useElementSize } from "@vueuse/core";
+export type Datum = number[] & { total?: number };
+
+export function useXYChart(props: DashboardWidgetProps) {
+ const data = computed((): Datum[] => props.value?.datasets?.reduce((res, dataset, i) => {
+ dataset.data.forEach((v, j) => {
+ res[j] ??= [];
+ res[j][i] = v;
+ if(props.widget.display === 'area' && props.widget.stacked) {
+ res[j].total ??= 0;
+ res[j].total += v;
+ }
+ });
+ return res;
+ }, []));
+ const x: XYComponentConfigInterface['x'] = (d, i) => {
+ return props.widget.displayHorizontalAxisAsTimeline
+ ? new Date(props.value.labels[i]).getTime()
+ : i;
+ };
+ const y = computed((): XYComponentConfigInterface['y'] => props.value?.datasets.map((dataset, i) => (d) => d[i]));
+ const { width, height } = useElementSize(useCurrentElement());
+ const yNumTicks = computed(() => {
+ // taken from https://github.com/f5/unovis/blob/8b9eae7d9787c8414d5caa5970f13f67a02f6654/packages/ts/src/components/axis/index.ts#L407
+ // approximate the height by remove 75px to the container (bottom labels)
+ const yAxisHeight = height.value - 75;
+ return height.value ? Math.pow(yAxisHeight, 0.85) / 25 : 5;
+ })
+ const yExtent = computed(() => extent(
+ props.widget.display === 'area' && props.widget.stacked
+ ? data.value.map(d => d.total)
+ : data.value.flatMap(d => d)
+ ));
+ const yScale = computed(() => {
+ return Scale.scaleLinear().domain(yExtent.value).nice(yNumTicks.value)
+ });
+ const containerConfig = computed((): XYContainerConfigInterface => {
+ return {
+ xScale: props.widget.displayHorizontalAxisAsTimeline
+ ? Scale.scaleUtc() as any
+ : undefined,
+ yScale: yScale.value,
+ yDomain: yScale.value.domain() as [number, number],
+ }
+ });
+ const color = computed((): ColorAccessor => props.value?.datasets.map((dataset, i) => dataset.color));
+
+ const chartConfig = computed((): ChartConfig => ({
+ ...Object.fromEntries(props.value?.datasets.map((dataset, i) => [i, ({
+ label: dataset.label,
+ color: dataset.color,
+ })])),
+ ...props.widget.display === 'area' && props.widget.showStackTotal && {
+ total: {
+ label: props.widget.stackTotalLabel,
+ tooltipOnly: true,
+ },
+ },
+ }));
+
+ const tooltipTemplate = componentToString(chartConfig, ChartTooltipContent, {
+ labelFormatter: (x) => {
+ if(props.widget.displayHorizontalAxisAsTimeline) {
+ const nearestDate = new Date(
+ getNearest(
+ props.value.labels.map((label) => new Date(label).getTime()),
+ (x as Date).getTime(),
+ v => v
+ )
+ );
+ return formatDate(nearestDate);
+ }
+ return props.value.labels[Math.round(x as number)];
+ }
+ });
+
+ const rotate = computed(() =>
+ !('horizontal' in props.widget && props.widget.horizontal)
+ && !props.widget.enableHorizontalAxisLabelSampling
+ && !props.widget.displayHorizontalAxisAsTimeline
+ && props.value?.labels?.length >= 10
+ );
+
+ const xAxisConfig = computed((): AxisConfigInterface => ({
+ tickValues: (() => {
+ if(props.widget.displayHorizontalAxisAsTimeline) {
+ return props.value.labels.length < 10
+ ? props.value.labels.map((label) => new Date(label))
+ : undefined as any; // let unovis handle number of ticks
+ }
+ if(!props.widget.enableHorizontalAxisLabelSampling) {
+ return props.value.labels.map((_, i) => i);
+ }
+ })(),
+ tickFormat: (tick, i) => {
+ if(props.widget.displayHorizontalAxisAsTimeline) {
+ return formatDate(tick as Date);
+ }
+ return props.value?.labels?.[tick as number] ?? '';
+ },
+ tickTextTrimType: TrimMode.End,
+ // tickTextAlign: rotate.value ? TextAlign.Left : props.widget.options.horizontal ? TextAlign.Right : TextAlign.Center,
+ tickTextAlign: rotate.value
+ ? TextAlign.Right
+ : ('horizontal' in props.widget && props.widget.horizontal)
+ ? TextAlign.Right
+ : TextAlign.Center,
+ tickTextFitMode: rotate.value ? FitMode.Wrap : FitMode.Trim,
+ // tickTextAngle: rotate.value ? 45 : undefined,
+ tickTextAngle: rotate.value ? -45 : undefined,
+ tickTextWidth: rotate.value ? 100 : undefined,
+ }));
+
+
+ const needsDecimals = computed(() =>
+ props.value?.datasets.every(dataset =>
+ dataset.data.every(value => Math.abs(value) > 0 && Math.abs(value) < 1)
+ )
+ );
+ const yAxisConfig = computed((): AxisConfigInterface => ({
+ tickFormat: (tick) => {
+ if(!Number.isInteger(tick) && !needsDecimals.value) {
+ return '';
+ }
+ },
+ tickValues: yScale.value.ticks(yNumTicks.value),
+ }));
+
+ return {
+ data,
+ x,
+ y,
+ color,
+ tooltipTemplate,
+ containerConfig,
+ chartConfig,
+ xAxisConfig,
+ yAxisConfig,
+ };
+}
+
+
+function utc(d: Date) {
+ const d2 = new Date(d);
+ d2.setMinutes(d2.getMinutes() + d2.getTimezoneOffset());
+ return d2;
+}
+
+function formatDate(date: Date) {
+ date = utc(date);
+ const hasHour = date.getHours() !== 0 || date.getMinutes() !== 0;
+ return new Intl.DateTimeFormat(undefined, {
+ day: '2-digit',
+ month: 'short',
+ hour: hasHour ? '2-digit' : undefined,
+ minute: hasHour ? '2-digit' : undefined,
+ })
+ .format(date);
+}
diff --git a/resources/js/dashboard/types.ts b/resources/js/dashboard/types.ts
index adff3a9ad..7518e0b88 100644
--- a/resources/js/dashboard/types.ts
+++ b/resources/js/dashboard/types.ts
@@ -2,6 +2,6 @@ import { WidgetData } from "@/types";
export type DashboardWidgetProps = {
- widget: Omit;
+ widget: Data extends any ? Omit : never;
value: Value;
}
diff --git a/resources/js/types/generated.d.ts b/resources/js/types/generated.d.ts
index ae77e0f17..a597cb7f6 100644
--- a/resources/js/types/generated.d.ts
+++ b/resources/js/types/generated.d.ts
@@ -1,3 +1,23 @@
+export type AreaGraphWidgetData = {
+ value?: GraphWidgetValueData;
+ key: string;
+ type: "graph";
+ display: "area";
+ title: string | null;
+ showLegend: boolean;
+ minimal: boolean;
+ ratioX: number | null;
+ ratioY: number | null;
+ height: number | null;
+ displayHorizontalAxisAsTimeline: boolean;
+ enableHorizontalAxisLabelSampling: boolean;
+ curved: boolean;
+ gradient: boolean;
+ stacked: boolean;
+ showStackTotal: boolean;
+ stackTotalLabel: string | null;
+ opacity: number | null;
+};
export type AutocompleteRemoteFilterData = {
value?: { id: string | number; label: string };
key: string;
@@ -8,6 +28,21 @@ export type AutocompleteRemoteFilterData = {
debounceDelay: number;
searchMinChars: number;
};
+export type BarGraphWidgetData = {
+ value?: GraphWidgetValueData;
+ key: string;
+ type: "graph";
+ display: "bar";
+ title: string | null;
+ showLegend: boolean;
+ minimal: boolean;
+ ratioX: number | null;
+ ratioY: number | null;
+ height: number | null;
+ displayHorizontalAxisAsTimeline: boolean;
+ enableHorizontalAxisLabelSampling: boolean;
+ horizontal: boolean;
+};
export type BreadcrumbData = {
items: Array;
};
@@ -645,25 +680,17 @@ export type GlobalFiltersData = {
export type GlobalSearchData = {
config: { placeholder: string };
};
-export type GraphWidgetData = {
- value?: {
- key: string;
- datasets: Array<{ label: string; data: number[]; color: string }>;
- labels: string[];
- };
+export type GraphWidgetData =
+ | AreaGraphWidgetData
+ | BarGraphWidgetData
+ | LineGraphWidgetData
+ | PieGraphWidgetData;
+export type GraphWidgetDisplay = "bar" | "line" | "pie" | "area";
+export type GraphWidgetValueData = {
key: string;
- type: "graph";
- title: string | null;
- display: GraphWidgetDisplay;
- showLegend: boolean;
- minimal: boolean;
- ratioX: number | null;
- ratioY: number | null;
- height: number | null;
- dateLabels: boolean;
- options: { curved: boolean; horizontal: boolean };
+ datasets: Array<{ label: string; data: number[]; color: string }>;
+ labels: Array;
};
-export type GraphWidgetDisplay = "bar" | "line" | "pie";
export type IconData = {
svg: string | null;
name: string | null;
@@ -680,6 +707,22 @@ export type LayoutFieldData = {
size: number;
item: Array> | null;
};
+export type LineGraphWidgetData = {
+ value?: GraphWidgetValueData;
+ key: string;
+ type: "graph";
+ display: "line";
+ title: string | null;
+ showLegend: boolean;
+ minimal: boolean;
+ ratioX: number | null;
+ ratioY: number | null;
+ height: number | null;
+ displayHorizontalAxisAsTimeline: boolean;
+ enableHorizontalAxisLabelSampling: boolean;
+ curved: boolean;
+ showDots: boolean;
+};
export type LogoData = {
svg: string | null;
url: string;
@@ -755,6 +798,18 @@ export type PanelWidgetData = {
title: string | null;
link: string | null;
};
+export type PieGraphWidgetData = {
+ value?: GraphWidgetValueData;
+ key: string;
+ type: "graph";
+ display: "pie";
+ title: string | null;
+ showLegend: boolean;
+ minimal: boolean;
+ ratioX: number | null;
+ ratioY: number | null;
+ height: number | null;
+};
export type SearchResultLinkData = {
link: string;
label: string;
diff --git a/resources/lang/en/dashboard.php b/resources/lang/en/dashboard.php
index 36cc27a2e..8be0047b1 100644
--- a/resources/lang/en/dashboard.php
+++ b/resources/lang/en/dashboard.php
@@ -3,4 +3,5 @@
return [
'commands.dashboard.label' => 'Actions',
'widget.link_label' => 'Détails',
+ 'widget.graph.total_label' => 'Total',
];
diff --git a/resources/lang/fr/dashboard.php b/resources/lang/fr/dashboard.php
index 118701446..7fe7d6544 100644
--- a/resources/lang/fr/dashboard.php
+++ b/resources/lang/fr/dashboard.php
@@ -3,4 +3,5 @@
return [
'commands.dashboard.label' => 'Actions',
'widget.link_label' => 'Voir plus',
+ 'widget.graph.total_label' => 'Total',
];
diff --git a/src/Dashboard/Widgets/IsXYChart.php b/src/Dashboard/Widgets/IsXYChart.php
new file mode 100644
index 000000000..0ca70b8c5
--- /dev/null
+++ b/src/Dashboard/Widgets/IsXYChart.php
@@ -0,0 +1,23 @@
+displayHorizontalAxisAsTimeline = $displayAsTimeline;
+
+ return $this;
+ }
+
+ public function setEnableHorizontalAxisLabelSampling(bool $enableLabelSampling = true): self
+ {
+ $this->enableHorizontalAxisLabelSampling = $enableLabelSampling;
+
+ return $this;
+ }
+}
diff --git a/src/Dashboard/Widgets/SharpAreaGraphWidget.php b/src/Dashboard/Widgets/SharpAreaGraphWidget.php
new file mode 100644
index 000000000..bb4bc6375
--- /dev/null
+++ b/src/Dashboard/Widgets/SharpAreaGraphWidget.php
@@ -0,0 +1,74 @@
+display = 'area';
+
+ return $widget;
+ }
+
+ public function setCurvedLines(bool $curvedLines = true): self
+ {
+ $this->curvedLines = $curvedLines;
+
+ return $this;
+ }
+
+ public function setOpacity(float $opacity): self
+ {
+ $this->opacity = $opacity;
+
+ return $this;
+ }
+
+ public function setShowGradient(bool $gradient = true): self
+ {
+ $this->gradient = $gradient;
+
+ return $this;
+ }
+
+ public function setStacked(bool $stacked = true): self
+ {
+ $this->stacked = $stacked;
+
+ return $this;
+ }
+
+ public function setShowStackTotal(bool $showTotal = true, ?string $label = null): self
+ {
+ $this->showStackTotal = $showTotal;
+ $this->stackTotalLabel = $label ?? __('sharp::dashboard.widget.graph.total_label');
+
+ return $this;
+ }
+
+ public function toArray(): array
+ {
+ return [
+ ...parent::toArray(),
+ 'displayHorizontalAxisAsTimeline' => $this->displayHorizontalAxisAsTimeline,
+ 'enableHorizontalAxisLabelSampling' => $this->enableHorizontalAxisLabelSampling,
+ 'curved' => $this->curvedLines,
+ 'gradient' => $this->gradient,
+ 'opacity' => $this->opacity,
+ 'stacked' => $this->stacked,
+ 'showStackTotal' => $this->showStackTotal,
+ 'stackTotalLabel' => $this->stackTotalLabel,
+ ];
+ }
+}
diff --git a/src/Dashboard/Widgets/SharpBarGraphWidget.php b/src/Dashboard/Widgets/SharpBarGraphWidget.php
index 1cf4a4738..d52a225f2 100644
--- a/src/Dashboard/Widgets/SharpBarGraphWidget.php
+++ b/src/Dashboard/Widgets/SharpBarGraphWidget.php
@@ -4,8 +4,9 @@
class SharpBarGraphWidget extends SharpGraphWidget
{
+ use IsXYChart;
+
protected bool $horizontal = false;
- protected bool $displayHorizontalAxisAsTimeline = false;
public static function make(string $key): SharpBarGraphWidget
{
@@ -22,22 +23,13 @@ public function setHorizontal(bool $horizontal = true): self
return $this;
}
- public function setDisplayHorizontalAxisAsTimeline(bool $displayAsTimeline = true): self
- {
- $this->displayHorizontalAxisAsTimeline = $displayAsTimeline;
-
- return $this;
- }
-
public function toArray(): array
{
- return array_merge(
- parent::toArray(), [
- 'dateLabels' => $this->displayHorizontalAxisAsTimeline,
- 'options' => [
- 'horizontal' => $this->horizontal,
- ],
- ],
- );
+ return [
+ ...parent::toArray(),
+ 'displayHorizontalAxisAsTimeline' => $this->displayHorizontalAxisAsTimeline,
+ 'enableHorizontalAxisLabelSampling' => $this->enableHorizontalAxisLabelSampling,
+ 'horizontal' => $this->horizontal,
+ ];
}
}
diff --git a/src/Dashboard/Widgets/SharpGraphWidget.php b/src/Dashboard/Widgets/SharpGraphWidget.php
index 53a08eb78..f27c19ea9 100644
--- a/src/Dashboard/Widgets/SharpGraphWidget.php
+++ b/src/Dashboard/Widgets/SharpGraphWidget.php
@@ -55,7 +55,7 @@ protected function validationRules(): array
return [
'display' => [
'required',
- 'in:bar,line,pie',
+ 'in:bar,line,pie,area',
],
];
}
diff --git a/src/Dashboard/Widgets/SharpGraphWidgetDataSet.php b/src/Dashboard/Widgets/SharpGraphWidgetDataSet.php
index 9ca37471c..c1430ccc0 100644
--- a/src/Dashboard/Widgets/SharpGraphWidgetDataSet.php
+++ b/src/Dashboard/Widgets/SharpGraphWidgetDataSet.php
@@ -36,11 +36,16 @@ public function setColor(string $color): self
return $this;
}
+ protected function formatValues(array $values): array
+ {
+ return array_map(fn ($value) => (float) $value, array_values($values));
+ }
+
public function toArray(): array
{
return collect()
->merge([
- 'data' => array_values($this->values),
+ 'data' => $this->formatValues($this->values),
'labels' => array_keys($this->values),
])
->when(
diff --git a/src/Dashboard/Widgets/SharpLineGraphWidget.php b/src/Dashboard/Widgets/SharpLineGraphWidget.php
index 18e34cca9..d6b5eaea5 100644
--- a/src/Dashboard/Widgets/SharpLineGraphWidget.php
+++ b/src/Dashboard/Widgets/SharpLineGraphWidget.php
@@ -4,8 +4,10 @@
class SharpLineGraphWidget extends SharpGraphWidget
{
+ use IsXYChart;
+
protected bool $curvedLines = true;
- protected bool $displayHorizontalAxisAsTimeline = false;
+ protected bool $showDots = false;
public static function make(string $key): SharpLineGraphWidget
{
@@ -22,22 +24,21 @@ public function setCurvedLines(bool $curvedLines = true): self
return $this;
}
- public function setDisplayHorizontalAxisAsTimeline(bool $displayAsTimeline = true): self
+ public function setShowDots(bool $showDots = true): self
{
- $this->displayHorizontalAxisAsTimeline = $displayAsTimeline;
+ $this->showDots = $showDots;
return $this;
}
public function toArray(): array
{
- return array_merge(
- parent::toArray(), [
- 'dateLabels' => $this->displayHorizontalAxisAsTimeline,
- 'options' => [
- 'curved' => $this->curvedLines,
- ],
- ],
- );
+ return [
+ ...parent::toArray(),
+ 'displayHorizontalAxisAsTimeline' => $this->displayHorizontalAxisAsTimeline,
+ 'enableHorizontalAxisLabelSampling' => $this->enableHorizontalAxisLabelSampling,
+ 'curved' => $this->curvedLines,
+ 'showDots' => $this->showDots,
+ ];
}
}
diff --git a/src/Data/Dashboard/Widgets/AreaGraphWidgetData.php b/src/Data/Dashboard/Widgets/AreaGraphWidgetData.php
new file mode 100644
index 000000000..3d2c155b6
--- /dev/null
+++ b/src/Data/Dashboard/Widgets/AreaGraphWidgetData.php
@@ -0,0 +1,45 @@
+value.'"')]
+ public WidgetType $type,
+ #[LiteralTypeScriptType('"'.GraphWidgetDisplay::Area->value.'"')]
+ public GraphWidgetDisplay $display,
+ public ?string $title,
+ public bool $showLegend,
+ public bool $minimal,
+ public ?int $ratioX = null,
+ public ?int $ratioY = null,
+ public ?int $height = null,
+ public bool $displayHorizontalAxisAsTimeline = false,
+ public bool $enableHorizontalAxisLabelSampling = false,
+ public bool $curved = false,
+ public bool $gradient = false,
+ public bool $stacked = false,
+ public bool $showStackTotal = false,
+ public ?string $stackTotalLabel = null,
+ public ?float $opacity = null,
+ ) {}
+
+ public static function from(array $widget): self
+ {
+ return new self(...$widget);
+ }
+}
diff --git a/src/Data/Dashboard/Widgets/BarGraphWidgetData.php b/src/Data/Dashboard/Widgets/BarGraphWidgetData.php
new file mode 100644
index 000000000..b55290f87
--- /dev/null
+++ b/src/Data/Dashboard/Widgets/BarGraphWidgetData.php
@@ -0,0 +1,40 @@
+value.'"')]
+ public WidgetType $type,
+ #[LiteralTypeScriptType('"'.GraphWidgetDisplay::Bar->value.'"')]
+ public GraphWidgetDisplay $display,
+ public ?string $title,
+ public bool $showLegend,
+ public bool $minimal,
+ public ?int $ratioX = null,
+ public ?int $ratioY = null,
+ public ?int $height = null,
+ public bool $displayHorizontalAxisAsTimeline = false,
+ public bool $enableHorizontalAxisLabelSampling = false,
+ public bool $horizontal = false,
+ ) {}
+
+ public static function from(array $widget): self
+ {
+ return new self(...$widget);
+ }
+}
diff --git a/src/Data/Dashboard/Widgets/GraphWidgetData.php b/src/Data/Dashboard/Widgets/GraphWidgetData.php
index d1df7e7ed..770380d4b 100644
--- a/src/Data/Dashboard/Widgets/GraphWidgetData.php
+++ b/src/Data/Dashboard/Widgets/GraphWidgetData.php
@@ -4,50 +4,33 @@
use Code16\Sharp\Data\Data;
use Code16\Sharp\Enums\GraphWidgetDisplay;
-use Code16\Sharp\Enums\WidgetType;
-use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;
-use Spatie\TypeScriptTransformer\Attributes\Optional;
use Spatie\TypeScriptTransformer\Attributes\TypeScriptType;
+#[TypeScriptType(
+ AreaGraphWidgetData::class
+ .'|'.BarGraphWidgetData::class
+ .'|'.LineGraphWidgetData::class
+ .'|'.PieGraphWidgetData::class
+)]
/**
* @internal
*/
final class GraphWidgetData extends Data
{
- #[Optional]
- #[LiteralTypeScriptType([
- 'key' => 'string',
- 'datasets' => 'Array<{ label:string, data:number[], color:string }>',
- 'labels' => 'string[]',
- ])]
- public array $value;
+ public function __construct() {}
- public function __construct(
- public string $key,
- #[LiteralTypeScriptType('"'.WidgetType::Graph->value.'"')]
- public WidgetType $type,
- public ?string $title,
- public GraphWidgetDisplay $display,
- public bool $showLegend,
- public bool $minimal,
- public ?int $ratioX = null,
- public ?int $ratioY = null,
- public ?int $height = null,
- public bool $dateLabels = false,
- #[TypeScriptType([
- 'curved' => 'boolean',
- 'horizontal' => 'boolean',
- ])]
- public ?array $options = null,
- ) {}
-
- public static function from(array $widget): self
+ public static function from(array $widget): Data
{
$widget = [
...$widget,
'display' => GraphWidgetDisplay::from($widget['display']),
];
- return new self(...$widget);
+ return match ($widget['display']) {
+ GraphWidgetDisplay::Area => new AreaGraphWidgetData(...$widget),
+ GraphWidgetDisplay::Bar => new BarGraphWidgetData(...$widget),
+ GraphWidgetDisplay::Line => new LineGraphWidgetData(...$widget),
+ GraphWidgetDisplay::Pie => new PieGraphWidgetData(...$widget),
+ };
}
}
diff --git a/src/Data/Dashboard/Widgets/GraphWidgetValueData.php b/src/Data/Dashboard/Widgets/GraphWidgetValueData.php
new file mode 100644
index 000000000..f524cc607
--- /dev/null
+++ b/src/Data/Dashboard/Widgets/GraphWidgetValueData.php
@@ -0,0 +1,20 @@
+')]
+ public array $datasets,
+ /** @var string[] */
+ public array $labels,
+ ) {}
+}
diff --git a/src/Data/Dashboard/Widgets/LineGraphWidgetData.php b/src/Data/Dashboard/Widgets/LineGraphWidgetData.php
new file mode 100644
index 000000000..0a818ac07
--- /dev/null
+++ b/src/Data/Dashboard/Widgets/LineGraphWidgetData.php
@@ -0,0 +1,41 @@
+value.'"')]
+ public WidgetType $type,
+ #[LiteralTypeScriptType('"'.GraphWidgetDisplay::Line->value.'"')]
+ public GraphWidgetDisplay $display,
+ public ?string $title,
+ public bool $showLegend,
+ public bool $minimal,
+ public ?int $ratioX = null,
+ public ?int $ratioY = null,
+ public ?int $height = null,
+ public bool $displayHorizontalAxisAsTimeline = false,
+ public bool $enableHorizontalAxisLabelSampling = false,
+ public bool $curved = false,
+ public bool $showDots = false,
+ ) {}
+
+ public static function from(array $widget): self
+ {
+ return new self(...$widget);
+ }
+}
diff --git a/src/Data/Dashboard/Widgets/PieGraphWidgetData.php b/src/Data/Dashboard/Widgets/PieGraphWidgetData.php
new file mode 100644
index 000000000..b6b5cd70f
--- /dev/null
+++ b/src/Data/Dashboard/Widgets/PieGraphWidgetData.php
@@ -0,0 +1,37 @@
+value.'"')]
+ public WidgetType $type,
+ #[LiteralTypeScriptType('"'.GraphWidgetDisplay::Pie->value.'"')]
+ public GraphWidgetDisplay $display,
+ public ?string $title,
+ public bool $showLegend,
+ public bool $minimal,
+ public ?int $ratioX = null,
+ public ?int $ratioY = null,
+ public ?int $height = null,
+ ) {}
+
+ public static function from(array $widget): self
+ {
+ return new self(...$widget);
+ }
+}
diff --git a/src/Data/Form/Fields/FormEditorFieldData.php b/src/Data/Form/Fields/FormEditorFieldData.php
index b60383c15..815c9804d 100644
--- a/src/Data/Form/Fields/FormEditorFieldData.php
+++ b/src/Data/Form/Fields/FormEditorFieldData.php
@@ -13,7 +13,7 @@
*/
final class FormEditorFieldData extends Data
{
- const VALUE_TS_TYPE = '{
+ public const string VALUE_TS_TYPE = '{
text: string | { [locale:string]: string|null } | null,
uploads?: { [id:string]: { file:FormUploadFieldValueData, legend?: string|null } },
embeds?: { [embedKey:string]: { [id:string]: EmbedData["value"] } },
diff --git a/src/Enums/GraphWidgetDisplay.php b/src/Enums/GraphWidgetDisplay.php
index d185cfd11..d95193f4e 100644
--- a/src/Enums/GraphWidgetDisplay.php
+++ b/src/Enums/GraphWidgetDisplay.php
@@ -7,4 +7,5 @@ enum GraphWidgetDisplay: string
case Bar = 'bar';
case Line = 'line';
case Pie = 'pie';
+ case Area = 'area';
}
diff --git a/tests/Unit/Dashboard/SharpDashboardTest.php b/tests/Unit/Dashboard/SharpDashboardTest.php
index 2564ef4c2..9a3e685b8 100644
--- a/tests/Unit/Dashboard/SharpDashboardTest.php
+++ b/tests/Unit/Dashboard/SharpDashboardTest.php
@@ -33,10 +33,9 @@ protected function buildWidgets(WidgetsContainer $widgetsContainer): void
'ratioY' => 9,
'minimal' => false,
'showLegend' => true,
- 'dateLabels' => false,
- 'options' => [
- 'horizontal' => false,
- ],
+ 'displayHorizontalAxisAsTimeline' => false,
+ 'enableHorizontalAxisLabelSampling' => false,
+ 'horizontal' => false,
],
]);
});
diff --git a/tests/Unit/Dashboard/Widgets/SharpBarGraphWidgetTest.php b/tests/Unit/Dashboard/Widgets/SharpBarGraphWidgetTest.php
index 51f753d51..d1663e862 100644
--- a/tests/Unit/Dashboard/Widgets/SharpBarGraphWidgetTest.php
+++ b/tests/Unit/Dashboard/Widgets/SharpBarGraphWidgetTest.php
@@ -48,12 +48,12 @@
$widget = SharpBarGraphWidget::make('name')
->setDisplayHorizontalAxisAsTimeline();
- expect($widget->toArray()['dateLabels'])->toBeTrue();
+ expect($widget->toArray()['displayHorizontalAxisAsTimeline'])->toBeTrue();
});
it('allows to define horizontal option attribute', function () {
$widget = SharpBarGraphWidget::make('name')
->setHorizontal();
- expect($widget->toArray()['options']['horizontal'])->toBeTrue();
+ expect($widget->toArray()['horizontal'])->toBeTrue();
});
diff --git a/vite.config.ts b/vite.config.ts
index ff885b7fd..975c8613d 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -23,6 +23,8 @@ export default defineConfig(({ mode, command }) => {
alias: {
'vue': 'vue/dist/vue.esm-bundler.js',
'ziggy-js': path.resolve(__dirname, 'vendor/tightenco/ziggy'),
+ // '@unovis/vue': path.resolve(__dirname, '../unovis/packages/vue/src'),
+ // '@unovis/ts': path.resolve(__dirname, '../unovis/packages/ts'),
// ...rekaAliases()
},
// preserveSymlinks: true,
@@ -39,6 +41,7 @@ export default defineConfig(({ mode, command }) => {
// './resources/css/app.css',
],
},
+ // watch: { usePolling: true }
},
plugins: [
// circleDependency(),