@ocelescope/filter 0.10.0 → 0.10.2

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.
Files changed (3) hide show
  1. package/README.md +3 -5
  2. package/dist/index.js +111 -203
  3. package/package.json +19 -17
package/README.md CHANGED
@@ -31,14 +31,12 @@ export default {
31
31
  } satisfies OcelescopeConfig;
32
32
  ```
33
33
 
34
- This module's charts and date filters rely on two Mantine stylesheets. Add
35
- `@mantine/dates` and `@mantine/charts` to your app's `dependencies` and import
36
- their styles once in your app entry point (`pages/_app.tsx`), after
37
- `@mantine/core/styles.css`:
34
+ This module's date filters rely on the Mantine dates stylesheet. Add
35
+ `@mantine/dates` to your app's `dependencies` and import its styles once in
36
+ your app entry point (`pages/_app.tsx`), after `@mantine/core/styles.css`:
38
37
 
39
38
  ```tsx
40
39
  import "@mantine/dates/styles.css";
41
- import "@mantine/charts/styles.css";
42
40
  ```
43
41
 
44
42
  ## About
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { AttributesTable, RelationTable, defineModule, defineModuleRoute, useCurrentOcel, useInvalidate } from "@ocelescope/core";
1
+ import { ActivityPicker, AttributesTable, ObjectTypePicker, RelationTable, defineModule, defineModuleRoute, useCurrentOcel, useInvalidate } from "@ocelescope/core";
2
2
  import { CheckIcon, FilterIcon, PlusIcon, RotateCcwIcon, Trash2Icon, XIcon } from "lucide-react";
3
- import { ActionIcon, ActionIconGroup, Box, Grid, Group, LoadingOverlay, MultiSelect, NumberInput, Paper, RangeSlider, ScrollArea, Scroller, Stack, Switch, Tabs, Text, TextInput } from "@mantine/core";
3
+ import { ActionIcon, ActionIconGroup, Box, Grid, Group, InlineStyles, LoadingOverlay, NumberInput, RangeSlider, Scroller, Stack, Switch, Tabs, Text, TextInput, useMantineTheme } from "@mantine/core";
4
4
  import hash from "object-hash";
5
5
  import { useMutation, useQuery } from "@tanstack/react-query";
6
6
  import { customFetch } from "@ocelescope/api-client";
@@ -9,8 +9,8 @@ import { useRouter } from "next/router.js";
9
9
  import { memo, useMemo, useState } from "react";
10
10
  import { DateTimePicker } from "@mantine/dates";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
- import { BarChart } from "@mantine/charts";
13
- import { useEventCounts, useObjectCounts, useTimeInfo } from "@ocelescope/api-base";
12
+ import { useTimeInfo } from "@ocelescope/api-base";
13
+ import EChartsReact from "echarts-for-react";
14
14
  import dayjs from "dayjs";
15
15
  import isBetween from "dayjs/plugin/isBetween.js";
16
16
  //#region src/lib/fetcher.ts
@@ -572,41 +572,57 @@ dayjs.extend(isBetween);
572
572
  var dayjs_default = dayjs;
573
573
  //#endregion
574
574
  //#region src/components/FilterViews/TimeFrameFilter.tsx
575
+ const DATE_FORMAT = "YYYY-MM-DD HH:mm";
575
576
  const TimeGraph = memo(({ timeInfo, startDate, endDate }) => {
576
- const data = useMemo(() => {
577
- return timeInfo.date_distribution.map(({ start_timestamp, end_timestamp, entity_count }) => {
578
- const isInRange = (!startDate || dayjs_default(end_timestamp).isAfter(dayjs_default(startDate))) && (!endDate || dayjs_default(start_timestamp).isBefore(dayjs_default(endDate)));
579
- return {
580
- date: `${dayjs_default(start_timestamp).format("YYYY-MM-DD HH:mm")}-${dayjs_default(end_timestamp).format("YYYY-MM-DD HH:mm")} `,
581
- ...isInRange ? { value: Object.values(entity_count).reduce((acc, curr) => acc + curr, 0) } : { disabledValue: Object.values(entity_count).reduce((acc, curr) => acc + curr, 0) }
582
- };
583
- });
577
+ const theme = useMantineTheme();
578
+ const option = useMemo(() => {
579
+ const buckets = timeInfo.date_distribution.map(({ start_timestamp, end_timestamp, entity_count }) => ({
580
+ label: `${dayjs_default(start_timestamp).format(DATE_FORMAT)} - ${dayjs_default(end_timestamp).format(DATE_FORMAT)}`,
581
+ count: Object.values(entity_count).reduce((acc, curr) => acc + curr, 0),
582
+ isInRange: (!startDate || dayjs_default(end_timestamp).isAfter(dayjs_default(startDate))) && (!endDate || dayjs_default(start_timestamp).isBefore(dayjs_default(endDate)))
583
+ }));
584
+ return {
585
+ tooltip: {
586
+ trigger: "axis",
587
+ axisPointer: { type: "shadow" }
588
+ },
589
+ grid: {
590
+ left: 0,
591
+ right: 0,
592
+ top: 8,
593
+ bottom: 0
594
+ },
595
+ xAxis: {
596
+ type: "category",
597
+ data: buckets.map(({ label }) => label),
598
+ show: false
599
+ },
600
+ yAxis: {
601
+ type: "value",
602
+ show: false
603
+ },
604
+ series: [{
605
+ type: "bar",
606
+ name: "count",
607
+ barCategoryGap: 0,
608
+ data: buckets.map(({ count, isInRange }) => ({
609
+ value: count,
610
+ itemStyle: {
611
+ color: theme.colors.blue[6],
612
+ opacity: isInRange ? 1 : .25
613
+ }
614
+ }))
615
+ }]
616
+ };
584
617
  }, [
585
618
  timeInfo,
586
619
  startDate,
587
- endDate
620
+ endDate,
621
+ theme
588
622
  ]);
589
- return /* @__PURE__ */ jsx(BarChart, {
590
- h: 300,
591
- w: "100%",
592
- data,
593
- dataKey: "date",
594
- type: "stacked",
595
- series: [{
596
- name: "value",
597
- color: "blue",
598
- label: "count"
599
- }, {
600
- name: "disabledValue",
601
- color: "red",
602
- label: "count"
603
- }],
604
- withYAxis: false,
605
- withXAxis: false,
606
- barChartProps: {
607
- barCategoryGap: 0,
608
- barGap: 0
609
- }
623
+ return /* @__PURE__ */ jsx(EChartsReact, {
624
+ option,
625
+ style: { height: 300 }
610
626
  });
611
627
  });
612
628
  const TimeFrameSlider = ({ timeInfo, startTime, endTime, onChange }) => {
@@ -632,7 +648,7 @@ const TimeFrameSlider = ({ timeInfo, startTime, endTime, onChange }) => {
632
648
  value: [startIndex, endIndex],
633
649
  label: (value) => {
634
650
  const bucket = distribution[value];
635
- return bucket ? dayjs_default(bucket.start_timestamp).format("YYYY-MM-DD HH:mm") : null;
651
+ return bucket ? dayjs_default(bucket.start_timestamp).format(DATE_FORMAT) : null;
636
652
  },
637
653
  onChange: ([start, end]) => {
638
654
  onChange([start === startIndex ? startTime : distribution[start]?.start_timestamp ?? startTime, distribution[end]?.end_timestamp ?? endTime]);
@@ -659,13 +675,11 @@ const TimeFrameFilter = {
659
675
  children: /* @__PURE__ */ jsx(Watch, {
660
676
  control,
661
677
  name: ["time_frame.0.time_range.0", "time_frame.0.time_range.1"],
662
- render: ([startTime, endTime]) => {
663
- return /* @__PURE__ */ jsx(TimeGraph, {
664
- timeInfo,
665
- startDate: startTime ?? void 0,
666
- endDate: endTime ?? void 0
667
- });
668
- }
678
+ render: ([startTime, endTime]) => /* @__PURE__ */ jsx(TimeGraph, {
679
+ timeInfo,
680
+ startDate: startTime ?? void 0,
681
+ endDate: endTime ?? void 0
682
+ })
669
683
  })
670
684
  }), /* @__PURE__ */ jsx(Controller, {
671
685
  control,
@@ -718,173 +732,70 @@ const TimeFrameFilter = {
718
732
  }
719
733
  };
720
734
  //#endregion
721
- //#region src/util/color.ts
722
- const assignUniqueColors = (strings) => {
723
- const numStrings = strings.length;
724
- const uniqueStrings = Array.from(new Set(strings));
725
- const colorMap = {};
726
- uniqueStrings.forEach((str, index) => {
727
- const hsl = `hsl(${index * 360 / numStrings}, 70%, 50%)`;
728
- colorMap[str] = hsl;
729
- });
730
- return colorMap;
731
- };
732
- const BarChartSelect = memo(({ values, selected, onSelect }) => {
733
- const colorMap = useMemo(() => {
734
- return assignUniqueColors(Array.from(new Set(values.map(({ key }) => key))));
735
- }, [values]);
736
- const data = useMemo(() => [...values].sort((a, b) => b.value - a.value).map(({ value, key }) => ({
737
- key,
738
- value,
739
- color: selected.includes(key) ? colorMap[key] : "rgba(128, 128, 128, 0.3)"
740
- })), [
741
- values,
742
- selected,
743
- colorMap
744
- ]);
745
- return /* @__PURE__ */ jsx(BarChart, {
746
- h: 30 * values.length,
747
- data,
748
- minBarSize: 30,
749
- tooltipProps: { content: ({ label }) => /* @__PURE__ */ jsx(Paper, {
750
- px: "md",
751
- py: "sm",
752
- withBorder: true,
753
- shadow: "md",
754
- radius: "md",
755
- children: /* @__PURE__ */ jsx(Text, { children: label })
756
- }) },
757
- dataKey: "key",
758
- orientation: "vertical",
759
- yAxisProps: { width: 130 },
760
- series: [{
761
- name: "value",
762
- color: "gray.6"
763
- }],
764
- gridAxis: "none",
765
- barChartProps: { onClick: ({ activeLabel }) => {
766
- if (!activeLabel) return;
767
- onSelect(String(activeLabel));
768
- } }
769
- });
770
- });
771
- //#endregion
772
- //#region src/components/inputs/EntityTypeFilter.tsx
773
- const EntityTypeFilterInput = ({ entityTypes, onChange, showGraph = false, selectedEntityTypes, label, mode, onModeChange }) => {
774
- const isExclude = mode === "exclude";
775
- const maxCount = useMemo(() => entityTypes.reduce((max, { value }) => Math.max(max, value), 0), [entityTypes]);
776
- const [range, setRange] = useState([0, 100]);
777
- const frequencyMarks = useMemo(() => {
778
- if (maxCount === 0) return [];
779
- const percentages = new Set(entityTypes.map(({ value }) => Math.round(value / maxCount * 100)));
780
- return Array.from(percentages).map((value) => ({ value }));
781
- }, [entityTypes, maxCount]);
782
- const applyRange = ([low, high]) => {
783
- setRange([low, high]);
784
- const minCount = low / 100 * maxCount;
785
- const maxCountInBand = high / 100 * maxCount;
786
- const inBand = (count) => count >= minCount && count <= maxCountInBand;
787
- onChange(entityTypes.filter(({ value: count }) => isExclude ? !inBand(count) : inBand(count)).map(({ key }) => key));
788
- };
789
- const highlighted = isExclude ? entityTypes.map(({ key }) => key).filter((key) => !selectedEntityTypes.includes(key)) : selectedEntityTypes;
790
- const toggle = (key) => onChange(selectedEntityTypes.includes(key) ? selectedEntityTypes.filter((v) => v !== key) : [...selectedEntityTypes, key]);
791
- return /* @__PURE__ */ jsxs(Stack, {
792
- gap: "md",
793
- children: [showGraph && /* @__PURE__ */ jsxs(Stack, {
794
- gap: "xs",
795
- style: { minHeight: 0 },
796
- children: [/* @__PURE__ */ jsx(ScrollArea.Autosize, {
797
- mah: "60vh",
798
- type: "auto",
799
- children: /* @__PURE__ */ jsx(BarChartSelect, {
800
- selected: highlighted,
801
- values: entityTypes ?? [],
802
- onSelect: toggle
803
- })
804
- }), /* @__PURE__ */ jsxs(Stack, {
805
- gap: 2,
806
- children: [/* @__PURE__ */ jsx(Text, {
807
- size: "sm",
808
- fw: 500,
809
- children: "Frequency range (% of most frequent)"
810
- }), /* @__PURE__ */ jsx(RangeSlider, {
811
- min: 0,
812
- max: 100,
813
- value: range,
814
- onChange: applyRange,
815
- disabled: maxCount === 0,
816
- label: (value) => `${value}%`,
817
- minRange: 0,
818
- marks: frequencyMarks,
819
- inverted: mode === "include",
820
- mb: "sm"
821
- })]
822
- })]
823
- }), /* @__PURE__ */ jsxs(Group, {
824
- align: "flex-end",
825
- gap: "sm",
826
- wrap: "nowrap",
827
- children: [/* @__PURE__ */ jsx(MultiSelect, {
828
- flex: 1,
829
- label: isExclude ? `Excluded ${label}` : label,
830
- data: entityTypes.map(({ key }) => key),
831
- value: selectedEntityTypes,
832
- searchable: true,
833
- hidePickedOptions: true,
834
- nothingFoundMessage: "No event type found",
835
- onChange: (newValues) => onChange(newValues),
836
- clearable: true,
837
- styles: { input: {
838
- flexWrap: "nowrap",
839
- overflowX: "auto",
840
- overflowY: "hidden"
841
- } }
842
- }), /* @__PURE__ */ jsx(Switch, {
843
- mb: 8,
844
- label: "Exclude",
845
- checked: isExclude,
846
- onChange: (event) => onModeChange(event.currentTarget.checked ? "exclude" : "include")
847
- })]
848
- })]
849
- });
850
- };
851
- //#endregion
852
735
  //#region src/components/FilterViews/TypeForm.tsx
736
+ const PICKER_CLASS = "ocelescope-type-filter-picker";
737
+ const FillHeightStyles = () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(InlineStyles, {
738
+ deduplicate: true,
739
+ selector: `.${PICKER_CLASS} :has([role="listbox"])`,
740
+ styles: {
741
+ flex: 1,
742
+ minHeight: 0
743
+ }
744
+ }), /* @__PURE__ */ jsx(InlineStyles, {
745
+ deduplicate: true,
746
+ selector: `.${PICKER_CLASS} [role="listbox"]`,
747
+ styles: {
748
+ flex: 1,
749
+ minHeight: 0,
750
+ maxHeight: "none !important"
751
+ }
752
+ })] });
853
753
  const EntityFilter = (entityType) => ({ ocelId, control }) => {
854
754
  const isEvents = entityType === "events";
855
- const { data: entityCounts } = (isEvents ? useEventCounts : useObjectCounts)(ocelId, { ocel_version: "original" });
856
- return /* @__PURE__ */ jsx("form", { children: /* @__PURE__ */ jsx(Controller, {
857
- name: isEvents ? `activity.0.mode` : `object_type.0.mode`,
755
+ const Picker = isEvents ? ActivityPicker : ObjectTypePicker;
756
+ return /* @__PURE__ */ jsx(Controller, {
757
+ name: isEvents ? `activity.0` : `object_type.0`,
858
758
  control,
859
- render: ({ field: modeField }) => /* @__PURE__ */ jsx(Controller, {
860
- name: isEvents ? `activity.0.event_types` : `object_type.0.object_types`,
861
- control,
862
- render: ({ field }) => /* @__PURE__ */ jsx(EntityTypeFilterInput, {
863
- entityTypes: Object.entries(entityCounts ?? {}).map(([activity, count]) => ({
864
- key: activity,
865
- value: count
866
- })),
867
- selectedEntityTypes: field.value ?? [],
868
- mode: modeField.value ?? "exclude",
869
- onModeChange: modeField.onChange,
870
- showGraph: true,
871
- label: isEvents ? "Activities" : "Object Types",
872
- onChange: field.onChange
873
- })
874
- })
875
- }) });
759
+ render: ({ field }) => {
760
+ const filter = field.value;
761
+ const selected = filter?.type === "activity" ? filter.event_types : filter?.type === "object_type" ? filter.object_types : [];
762
+ return /* @__PURE__ */ jsxs(Stack, {
763
+ gap: 4,
764
+ flex: 1,
765
+ mih: 0,
766
+ className: PICKER_CLASS,
767
+ children: [/* @__PURE__ */ jsx(FillHeightStyles, {}), /* @__PURE__ */ jsx(Picker, {
768
+ variant: "list",
769
+ multiple: true,
770
+ cutoff: true,
771
+ ocelId,
772
+ ocelVersion: "original",
773
+ value: selected,
774
+ onChange: (next) => field.onChange(isEvents ? {
775
+ type: "activity",
776
+ event_types: next,
777
+ mode: "include"
778
+ } : {
779
+ type: "object_type",
780
+ object_types: next,
781
+ mode: "include"
782
+ })
783
+ })]
784
+ });
785
+ }
786
+ });
876
787
  };
877
788
  //#endregion
878
789
  //#region src/components/FilterViews/index.tsx
879
790
  const FILTER_MAP = {
880
791
  activity: {
881
792
  title: "Activity",
882
- description: "Filters the event log by activity (the event type). In include mode only events of the selected activities are kept; in exclude mode the selected activities are dropped and everything else is kept. The bar chart shows how many events exist per activity to help you decide what to keep.",
793
+ description: "Filters the event log by activity (the event type). Only events of the selected activities are kept. The list shows how many events exist per activity to help you decide what to keep.",
883
794
  ViewComponent: EntityFilter("events"),
884
795
  generateDefault: () => [{
885
796
  type: "activity",
886
797
  event_types: [],
887
- mode: "exclude"
798
+ mode: "include"
888
799
  }],
889
800
  cleanUpFilters: (filter) => {
890
801
  if (!filter[0] || filter[0].event_types.length === 0) return [];
@@ -893,12 +804,12 @@ const FILTER_MAP = {
893
804
  },
894
805
  object_type: {
895
806
  title: "Object Type",
896
- description: "Filters the log by object type. In include mode only objects of the selected types are kept; in exclude mode the selected types are dropped and everything else is kept. The bar chart shows how many objects exist per type to help you decide what to keep.",
807
+ description: "Filters the log by object type. Only objects of the selected types are kept. The list shows how many objects exist per type to help you decide what to keep.",
897
808
  ViewComponent: EntityFilter("objects"),
898
809
  generateDefault: () => [{
899
810
  type: "object_type",
900
811
  object_types: [],
901
- mode: "exclude"
812
+ mode: "include"
902
813
  }],
903
814
  cleanUpFilters: (filter) => {
904
815
  if (!filter[0] || filter[0].object_types.length === 0) return [];
@@ -912,12 +823,9 @@ const FILTER_MAP = {
912
823
  o2o_count: O2OCountFilter
913
824
  };
914
825
  const generateDefaultFilter = (currentFilters) => {
915
- const groupedFilter = currentFilters.reduce((acc, curr) => {
916
- return {
917
- ...acc,
918
- [curr.type]: acc[curr.type] ? [...acc[curr.type] ?? [], curr] : [curr]
919
- };
920
- }, {});
826
+ const grouped = {};
827
+ for (const filter of currentFilters) grouped[filter.type] = [...grouped[filter.type] ?? [], filter];
828
+ const groupedFilter = grouped;
921
829
  const missingFilterKeys = Object.keys(FILTER_MAP).filter((key) => !(key in groupedFilter));
922
830
  return {
923
831
  ...groupedFilter,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ocelescope/filter",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.10.2",
5
5
  "description": "A module for basic filtering of OCELs",
6
6
  "author": "Görkem-Emre Öztürk <goerkem.oeztuerk@rwth-aachen.de>",
7
7
  "license": "MIT",
@@ -28,34 +28,36 @@
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@dagrejs/dagre": "^3.0.0",
31
- "@mantine/charts": "^9.5.1",
32
- "@mantine/core": "^9.5.1",
33
- "@mantine/dates": "^9.5.1",
34
- "@mantine/hooks": "^9.5.1",
35
- "@mantine/notifications": "^9.5.1",
36
- "@ocelescope/api-base": "^0.10.0",
37
- "@ocelescope/api-client": "^0.10.0",
38
- "@ocelescope/core": "^0.10.0",
39
- "@tanstack/react-query": "^5.90.21",
31
+ "@mantine/core": "^9.7.2",
32
+ "@mantine/dates": "^9.7.2",
33
+ "@mantine/hooks": "^9.7.2",
34
+ "@mantine/notifications": "^9.7.2",
35
+ "@ocelescope/api-base": "^0.10.2",
36
+ "@ocelescope/api-client": "^0.10.2",
37
+ "@ocelescope/core": "^0.10.2",
38
+ "@tanstack/react-query": "^5.104.2",
40
39
  "axios": "^1.20.0",
41
40
  "dayjs": "^1.11.23",
42
- "lucide-react": "^1.0.0",
41
+ "echarts": "^6.1.0",
42
+ "echarts-for-react": "^3.0.6",
43
+ "lucide-react": "^1.56.0",
43
44
  "mantine-datatable": "^9.4.0",
44
- "next": "16.3.6",
45
+ "next": "16.4.0",
45
46
  "object-hash": "^3.0.0",
46
47
  "react": "^19.3.0",
47
48
  "react-dom": "^19.3.0",
48
- "react-hook-form": "^7.71.2",
49
- "recharts": "^3.2.1"
49
+ "react-hook-form": "^7.90.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@mantine/notifications": "^9.5.1",
53
- "@ocelescope/api-config": "^0.10.0",
52
+ "@mantine/notifications": "^9.7.2",
53
+ "@ocelescope/api-config": "^0.10.2",
54
54
  "@types/node": "^26.6.1",
55
55
  "@types/object-hash": "^3.0.6",
56
56
  "@types/react": "^19.3.0",
57
57
  "@types/react-dom": "^19.3.0",
58
- "tsdown": "^0.23.0",
58
+ "echarts": "^6.1.0",
59
+ "echarts-for-react": "^3.0.6",
60
+ "tsdown": "^0.23.1",
59
61
  "typescript": "^6.0.3"
60
62
  },
61
63
  "scripts": {