Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Data Visualizer streamlines working with ScriptableObject-heavy systems by centr

Data Visualizer is free forever: no subscriptions, no paid upgrades, no feature-gated tiers. The full source is MIT-licensed, and every capability documented here ships in the free package.

This guide captures the key points from the companion [video walkthrough](https://youtu.be/3oUxUSKNyhw) while keeping the instructions project-agnostic.
This guide captures the key points from the companion [video walkthrough](https://youtu.be/3oUxUSKNyhw) while keeping the instructions project-agnostic. A browsable version of this documentation, with per-topic pages, is published at <https://wallstop.github.io/DataVisualizer/>.

## Getting Started

Expand All @@ -21,20 +21,20 @@ Open **Tools → Wallstop Studios → Data Visualizer** and dock it alongside th

The window uses a three-panel layout:

**Namespace & Type Panel (left)** organizes ScriptableObject types by C# namespace. Click a namespace to expose its types, then select a type to load all instances. Reorder namespaces and types with the up and down arrow buttons—your ordering persists across sessions.
**Namespace & Type Panel (left)** organizes ScriptableObject types by C# namespace. Click a namespace to expose its types, then select a type to load all instances. Reorder namespaces and types by dragging them, or with the up and down arrow buttons, which move a row to the top or bottom of its list. Your ordering persists across sessions.

**Objects Panel (center)** lists every instance of the selected type and keeps one selection at a time. Arrow buttons let you reorder instances without dragging through long lists, and batch edits run through the per-type processors area (scoped to all instances or the filtered set) and per-row actions.
**Objects Panel (center)** lists every instance of the selected type and keeps one selection at a time. Drag a row to place an instance precisely, or use its arrow buttons to move it to the top or bottom. Batch edits run through the per-type processors area, scoped to all instances or to the filtered set, and through the per-row actions.

**Inspector Panel (right)** displays the full inspector for the selected asset, including Odin Inspector integrations and custom editors. Changes save immediately, just like Unity's default Inspector.

## Instance Management

![Clone, rename, move, and delete controls highlighted above the object list](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-instance-actions.jpg)
![Clone, rename, move, and delete controls on an object row](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-instance-actions.jpg)
*Instance actions demo at 03:35.*

Asset management controls live above the Objects panel:
Asset management controls sit on the right of each object row:

**Clone** duplicates the selected asset with a "Clone" suffix in the same folder as the original. Useful for creating variants without leaving the window.
**Clone** (`++`) duplicates that row's asset in the same folder as the original, directly after it in the list. Any existing `(Clone)` or `(Clone n)` suffix is stripped from the source name first, then reapplied, so repeated clones read `(Clone)`, `(Clone 1)`, `(Clone 2)`, and so on. Useful for creating variants without leaving the window.

**Rename** opens a draggable prompt that renames the asset on disk. No need to coordinate between multiple panels or windows.

Expand All @@ -49,7 +49,7 @@ Inspector edits save immediately. Your selection persists when switching between
![Create button and data folder selector above object list](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-create.jpg)
*New asset workflow at 06:45.*

The **Create** button spawns a new instance of the active type in your configured **Data Folder** (see Settings below). Clones stay beside their originals regardless of the Data Folder setting. Chain create with rename or move to place new assets exactly where you need them.
The **Create** button asks for a name and spawns a new instance of the active type under your configured **Data Folder** (see Settings below), in a per-type folder named after the type's full namespace, such as `Assets/Data/MyGame/Items/WeaponData/`. Those folders are created for you. Clones stay beside their originals regardless of the Data Folder setting. Chain create with rename or move to place new assets exactly where you need them.

## Building Your Type Catalog

Expand All @@ -70,18 +70,26 @@ Organize the catalog to match your team's mental model. The structure persists a

## Search & Filtering

The filter field above the Namespace list narrows type rows by display name with case-insensitive matching; namespace headers are not filtered. The global search box above the Objects panel finds text across all loaded instances, letting you jump directly to specific assets without manual scanning.
Three finders narrow different things.

The **Search** box in the window header row, next to the Settings button, searches every tracked type rather than only the selected one. A space-separated query matches an asset when any one of its terms matches an asset name, a type name, an exact asset GUID, or a string field on the asset or a nested plain object; field matching is case-insensitive and skips primitives, vectors, colors, and references to other Unity objects. Results are ordered by asset name then full type name, list up to two matched fields for context, highlight the matched terms, and cap at 25 results. Use Up and Down to move, Enter to open the highlighted asset, Escape to dismiss.

The filter field above the Namespace list narrows type rows by display name with case-insensitive matching; namespace headers are not filtered.

Dragging labels from **Available** into the **AND:** or **OR:** rows filters the selected type's instances by Unity asset labels. The **AND &&** and **OR ||** switch chooses between requiring every dragged label and requiring any one of them; in OR mode an empty clause never counts as a match. The Advanced row refuses to collapse while OR mode or any OR label is active, so a filter that hides rows is never left concealed. A line under the filter reports how many objects are hidden. The selected asset's inspector panel adds and removes its labels, and the filter re-applies immediately.

## Settings

![Settings dropdown showing persistence options and data folder field](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-settings.jpg)
*State management settings at 18:20.*

**Persist state in user settings** stores layout, ordering, and tracked types in your local user cache instead of a shared project asset. Enable this to avoid merge conflicts when multiple developers customize their own workspace.
**Persist State in UserState** (the default) stores the selected namespace and type, the selected object per type, namespace, type, and object ordering, collapse state, tracked types, per-type label filters, and the per-type processor scope in a per-user JSON file instead of a shared project asset. Each developer keeps a private arrangement and version control stays free of layout churn.

**Select active object** syncs selection between Data Visualizer and Unity's Inspector window. Useful for cross-referencing assets in other editor windows.
Turning the setting off stores the same state inside a `DataVisualizerSettings` asset in the project, which suits a team that wants one shared arrangement. Data Visualizer creates one at `Assets/Editor/DataVisualizerSettings.asset` on first use if no such asset exists. Switching between the two copies the current state across.

**Data Folder** defines where new assets land. Click to ping the current folder or browse to set a new default.
**Select Active Object** syncs selection between Data Visualizer and Unity's Inspector window. Useful for cross-referencing assets in other editor windows.

**Data Folder** defines where new assets land, each in a per-type folder named after its full namespace. Click the path to ping the current folder, or browse to set a new default.

### Themes

Expand Down Expand Up @@ -112,7 +120,7 @@ The override stylesheet is applied after the package stylesheet. Standard contro

Data Visualizer exposes several extension points for custom workflows:

**Attributes** let you override display namespace or friendly names on ScriptableObject classes. Useful when code organization doesn't match your content taxonomy.
**Attributes** let you override display namespace or friendly names on ScriptableObject classes. `[CustomDataVisualization(Namespace = "...", TypeName = "...")]` replaces the namespace group and the display name the window shows. Without it the window files a type under the last segment of its C# namespace. Useful when code organization doesn't match your content taxonomy.

**BaseDataObject** provides a ready-made base class for ScriptableObjects with built-in lifecycle support:
- Stores an asset GUID, title, and description for display and stable identity
Expand All @@ -123,7 +131,9 @@ Derived classes override only what they need—GUID generation, cache resets, co

**Lifecycle Interfaces** hook into asset events before and after clone, create, and rename operations. Enforce invariants like regenerating IDs or pushing audit logs to telemetry without writing per-asset editor scripts.

**UI Toolkit Extensions** render custom UI alongside the default inspector. Return a `VisualElement` tree—graphs, thumbnails, validation badges, or any UI Toolkit component—and Data Visualizer slots it in automatically. Because the entire window runs on UI Toolkit, this approach scales to complex dashboards without leaving the unified workflow.
**UI Toolkit Extensions** render custom UI alongside the default inspector. Return a `VisualElement` tree—graphs, thumbnails, validation badges, or any UI Toolkit component—from `IGUIProvider.BuildGUI` (or `BaseDataObject.BuildGUI`) and Data Visualizer slots it in below the inspector. The `DataVisualizerGUIContext` argument carries the selected asset's `SerializedObject` for writing changes back. Because the entire window runs on UI Toolkit, this approach scales to complex dashboards without leaving the unified workflow.

**Processors** are plain `IDataProcessor` classes that the window discovers with no registration. `Name` labels the button, `Description` is its tooltip, `Accepts` lists the types it applies to, and `Process(Type, IEnumerable<ScriptableObject>)` receives the **ALL** or **FILTERED** object set you chose. The window instantiates processors through their public parameterless constructor, skips any that lack one, and surfaces a thrown exception in a dialog without stopping other processors.

## Workflow Tips

Expand Down
213 changes: 213 additions & 0 deletions docs/extending.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Extending the window

The runtime assembly is part of the package and carries the extension surface, so
your types compile against it in both the editor and a player build. Everything
on this page is public API.

## Display attributes

`[CustomDataVisualization]` overrides how a type appears in the catalog.

```csharp
using WallstopStudios.DataVisualizer;

[CustomDataVisualization(Namespace = "Weapons", TypeName = "Weapon")]
public sealed class WeaponData : ScriptableObject
{
}
```

`Namespace` replaces the namespace group the type is filed under. Without it the
window uses the last segment of your C# namespace, so
`MyGame.Items.Weapons` appears under `Weapons`, and a type in no namespace appears
under `No Namespace`.

`TypeName` replaces the type's display name in the list, in the type filter, and
in the dialogs that name the type. Without it the C# type name is used.

With the Odin Inspector available, the attribute also carries `UseOdinInspector`,
which defaults to `true`. See [Odin Inspector](#odin-inspector).

## BaseDataObject

`BaseDataObject` is an abstract `ScriptableObject` that carries identity and the
lifecycle hooks. Derive from it instead of `ScriptableObject` to get them.

```csharp
using UnityEngine;
using WallstopStudios.DataVisualizer;

public sealed class ItemData : BaseDataObject
{
[SerializeField]
private int value;
}
```

It provides:

- `Id`, the asset's GUID, kept in sync with the AssetDatabase.
- `Title`, the display name. A blank title falls back to the asset name, so you
only set it when the asset name is not what you want to show.
- `Description`.
- Ordering by title, then asset name, then Id, then description.

The serialized fields `_assetGuid`, `_title`, and `_description` are shown in the
inspector under a **Base Data** header, read-only.

## Lifecycle hooks

Each hook is a `virtual` method on `BaseDataObject` and also a standalone
interface, so you can implement one without deriving.

| Interface | Methods | When |
| --- | --- | --- |
| `IDuplicable` | `BeforeClone(ScriptableObject previous)`, `AfterClone(ScriptableObject previous)` | Around [Clone](managing-assets.md#clone) |
| `ICreatable` | `BeforeCreate()`, `AfterCreate()` | Around [Create](managing-assets.md#create) |
| `IRenamable` | `BeforeRename(string newName)`, `AfterRename(string newName)` | Around [Rename](managing-assets.md#rename) |

Override only what you need. The default implementations already do the work you
would otherwise repeat: `BeforeClone` clears the inherited asset GUID so the copy
does not share the original's identity, and `AfterClone` refreshes the GUID from
the new asset path and applies the `(Clone)`, `(Clone 1)`, `(Clone 2)`, … suffix
to the title.

```csharp
public sealed class ItemData : BaseDataObject
{
public override void BeforeRename(string newName)
{
// Audit before the file on disk changes.
}

public override void AfterRename(string newName)
{
EditorUtility.SetDirty(this);
}
}
```

## Custom inspector content

Implement `IGUIProvider` and return a `VisualElement` from `BuildGUI`. The window
adds it below the standard inspector.

```csharp
using UnityEngine.UIElements;
using WallstopStudios.DataVisualizer;

public sealed class ItemData : BaseDataObject
{
public override VisualElement BuildGUI(DataVisualizerGUIContext context)
{
return new Label("Weight: ") { name = "weight-label" };
}
}
```

Return `null` to add nothing. `BaseDataObject` implements `IGUIProvider` and
returns `null` by default, so a derived type only overrides the method when it
has something to show.

The `DataVisualizerGUIContext` carries the `SerializedObject` for the selected
asset in the editor, which is what you need to write back through. The context
type has no editor-only members in a player build, so a `BuildGUI` override
compiles in both.

## Display name

Implement `IDisplayable` to control the name shown for an object. `BaseDataObject`
implements it through `Title`. For a plain `ScriptableObject`, implement it
yourself:

```csharp
public sealed class ItemData : ScriptableObject, IDisplayable
{
[SerializeField]
private string displayName;

public string Title => string.IsNullOrWhiteSpace(displayName) ? name : displayName;
}
```

The row label, the search result name, and the drag ghost all read `Title` when
the type provides it.

## Processors

A processor is a plain class implementing `IDataProcessor`. The window discovers
every implementation in your project, so no registration is needed.

```csharp
using System;
using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.DataVisualizer;

public sealed class RecalculateRarity : IDataProcessor
{
public string Name => "Recalculate rarity";

public string Description => "Recomputes each item's rarity from its level.";

public IEnumerable<Type> Accepts => new[] { typeof(ItemData) };

public void Process(Type type, IEnumerable<ScriptableObject> objects)
{
foreach (ScriptableObject obj in objects)
{
if (obj is ItemData item)
{
item.Rarity = Rarity.FromLevel(item.Level);
EditorUtility.SetDirty(item);
}
}
}
}
```

- `Name` is the button label and sorts the list.
- `Description` is the button tooltip.
- `Accepts` limits the processor to the types you list, and the processor is
only offered for a selected type that appears in that list. Return `null` or an
empty list and the processor is never offered.
- `Process` receives the type and the objects, which is the **ALL** or
**FILTERED** set chosen in the window. See
[Processors](managing-assets.md#processors) for scope, the confirmation, and
the load-in-progress refusal.
- `WillEffect(Type, IEnumerable<ScriptableObject>)` is a default interface method
that returns the number of objects. Override it when a processor will skip some.

The window instantiates each processor with its public parameterless constructor.
A processor that is abstract, generic, or lacks that constructor is skipped and
logged. Exceptions from `Process` are caught, logged, and surfaced in a dialog;
one failing processor does not stop the others.

The window saves assets and refreshes after a processor runs, so call
`EditorUtility.SetDirty` on anything you change.

## Odin Inspector

When the Odin Inspector is installed, the window renders assets through Odin
instead of the standard inspector in two cases: the type carries
`[CustomDataVisualization]` with `UseOdinInspector` set, and the type derives
from `SerializedScriptableObject` (which `BaseDataObject` does when Odin is
present). A type that opts out shows the standard inspector.

Odin integration is optional. The package has no dependency on Odin and compiles
and runs without it.

## Removing a type from the catalog

Types deriving from `BaseDataObject` and types carrying
`[CustomDataVisualization]` are managed for you and have no remove button. Other
tracked types can be removed with the **X** on their row or their namespace
header. Removing a type stops Data Visualizer from tracking it; the assets stay on
disk.

## Next steps

- [Managing assets](managing-assets.md) — the operations your hooks run inside.
- [Search and filtering](search-and-filter.md) — the label filter your processors
can be scoped to.
- [Organizing your data](organizing.md) — themes, layout, and persistence.
7 changes: 7 additions & 0 deletions docs/extending.md.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading