Skip to content

Commit

Permalink
Plot Widgets
Browse files Browse the repository at this point in the history
# Plot Widgets
New PlotLines and PlotHistogram for plotting a series of values in Iris. Both support automatic scaling and hovering over a value, and use a specific height value.

## Additions
- PlotLines widget
- PlotHistogram widget
- `_deltaTime` property under internal
- New mouse event macros
- New colour configs

## Changes
- Force state update property
- Cycle event has a dleta time argument

## Fixes
- Window tooltip ZIndex and border
- Broken tab behaviour due to state changes
  • Loading branch information
SirMallard authored Dec 10, 2024
2 parents 44aa52b + f2f4815 commit 717a016
Show file tree
Hide file tree
Showing 13 changed files with 846 additions and 154 deletions.
63 changes: 63 additions & 0 deletions lib/API.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,69 @@ return function(Iris: Types.Iris)
]=]
Iris.ProgressBar = wrapper("ProgressBar")

--[=[
@within Plot
@prop PlotLines Iris.PlotLines
@tag Widget
@tag HasState
A line graph for plotting a single line. Includes hovering to see a specific value on the graph,
and automatic scaling. Has an overlay text option at the top of the plot for displaying any
information.
```lua
hasChildren = false
hasState = true
Arguments = {
Text: string? = "Plot Lines",
Height: number? = 0,
Min: number? = min, -- Iris will use the minimum value from the values
Max: number? = max, -- Iris will use the maximum value from the values
TextOverlay: string? = ""
}
Events = {
hovered: () -> boolean
}
States = {
values: State<{number}>?,
hovered: State<{number}>? -- read-only property
}
```
]=]
Iris.PlotLines = wrapper("PlotLines")

--[=[
@within Plot
@prop PlotHistogram Iris.PlotHistogram
@tag Widget
@tag HasState
A hisogram graph for showing values. Includes hovering to see a specific block on the graph,
and automatic scaling. Has an overlay text option at the top of the plot for displaying any
information. Also supports a baseline option, which determines where the blocks start from.
```lua
hasChildren = false
hasState = true
Arguments = {
Text: string? = "Plot Histogram",
Height: number? = 0,
Min: number? = min, -- Iris will use the minimum value from the values
Max: number? = max, -- Iris will use the maximum value from the values
TextOverlay: string? = "",
BaseLine: number? = 0 -- by default, blocks swap side at 0
}
Events = {
hovered: () -> boolean
}
States = {
values: State<{number}>?,
hovered: State<{number}>? -- read-only property
}
```
]=]
Iris.PlotHistogram = wrapper("PlotHistogram")

--[[
----------------------------------
[SECTION] Table Widget API
Expand Down
12 changes: 9 additions & 3 deletions lib/Internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ return function(Iris: Types.Iris): Types.Internal
Internal._started = false -- has Iris.connect been called yet
Internal._shutdown = false
Internal._cycleTick = 0 -- increments for each call to Cycle, used to determine the relative age and freshness of generated widgets
Internal._deltaTime = 0

-- Refresh
Internal._globalRefreshRequested = false -- refresh means that all GUI is destroyed and regenerated, usually because a style change was made and needed to be propogated to all UI
Expand Down Expand Up @@ -134,12 +135,13 @@ return function(Iris: Types.Iris): Types.Internal
@within State
@method set<T>
@param newValue T
@param force boolean? -- force an update to all connections
@return T
Allows the caller to assign the state object a new value, and returns the new value.
]=]
function StateClass:set<T>(newValue: T): T
if newValue == self.value then
function StateClass:set<T>(newValue: T, force: true?): T
if newValue == self.value and force ~= true then
-- no need to update on no change.
return self.value
end
Expand Down Expand Up @@ -189,7 +191,7 @@ return function(Iris: Types.Iris): Types.Internal
Called every frame to handle all of the widget management. Any previous frame data is ammended and everything updates.
]=]
function Internal._cycle()
function Internal._cycle(deltaTime: number)
--debug.profilebegin("Iris/Cycle")
if Iris.Disabled then
return -- Stops all rendering, effectively freezes the current frame with no interaction.
Expand Down Expand Up @@ -236,6 +238,7 @@ return function(Iris: Types.Iris): Types.Internal

-- update counters
Internal._cycleTick += 1
Internal._deltaTime = deltaTime
table.clear(Internal._usedIDs)

-- if Internal.parentInstance:IsA("GuiBase2d") and math.min(Internal.parentInstance.AbsoluteSize.X, Internal.parentInstance.AbsoluteSize.Y) < 100 then
Expand Down Expand Up @@ -552,6 +555,7 @@ return function(Iris: Types.Iris): Types.Internal
-- generate a new state.
states[index] = Internal._widgetState(stateWidget, index, state)
end
states[index].lastChangeTick = Internal._cycleTick
end

stateWidget.state = states
Expand Down Expand Up @@ -646,11 +650,13 @@ return function(Iris: Types.Iris): Types.Internal
local ID: Types.ID = thisWidget.ID .. stateName
if Internal._states[ID] then
Internal._states[ID].ConnectedWidgets[thisWidget.ID] = thisWidget
Internal._states[ID].lastChangeTick = Internal._cycleTick
return Internal._states[ID]
else
Internal._states[ID] = {
ID = ID,
value = initialValue,
lastChangeTick = Internal._cycleTick,
ConnectedWidgets = { [thisWidget.ID] = thisWidget },
ConnectedFunctions = {},
}
Expand Down
2 changes: 2 additions & 0 deletions lib/PubTypes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type InputText = Types.InputText
export type Selectable = Types.Selectable
export type Combo = Types.Combo
export type ProgressBar = Types.ProgressBar
export type PlotLines = Types.PlotLines
export type PlotHistogram = Types.PlotHistogram
export type Table = Types.Table

export type Iris = Types.Iris
Expand Down
20 changes: 17 additions & 3 deletions lib/Types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export type InputText = WidgetTypes.InputText
export type Selectable = WidgetTypes.Selectable
export type Combo = WidgetTypes.Combo
export type ProgressBar = WidgetTypes.ProgressBar
export type PlotLines = WidgetTypes.PlotLines
export type PlotHistogram = WidgetTypes.PlotHistogram
export type Table = WidgetTypes.Table

export type InputDataType = number | Vector2 | Vector3 | UDim | UDim2 | Color3 | Rect | { number }
Expand All @@ -64,6 +66,7 @@ export type Arguments = {
[string]: Argument,
Text: string,
TextHint: string,
TextOverlay: string,
ReadOnly: boolean,
MultiLine: boolean,
Wrapped: boolean,
Expand All @@ -78,8 +81,10 @@ export type Arguments = {
UseHSV: boolean,
UseHex: boolean,
Prefix: { string },
BaseLine: number,

Width: number,
Height: number,
VerticalAlignment: Enum.VerticalAlignment,
HorizontalAlignment: Enum.HorizontalAlignment,
Index: any,
Expand Down Expand Up @@ -158,6 +163,7 @@ export type WidgetStates = {
position: State<Vector2>?,
progress: State<number>?,
scrollDistance: State<number>?,
values: State<number>?,

isChecked: State<boolean>?,
isOpened: State<boolean>?,
Expand Down Expand Up @@ -194,6 +200,7 @@ export type Internal = {
_started: boolean,
_shutdown: boolean,
_cycleTick: number,
_deltaTime: number,
_eventConnection: RBXScriptConnection?,

-- Refresh
Expand Down Expand Up @@ -254,7 +261,7 @@ export type Internal = {
FUNCTIONS
-------------
]]
_cycle: () -> (),
_cycle: (deltaTime: number) -> (),
_NoOp: () -> (),

-- Widget
Expand Down Expand Up @@ -322,8 +329,9 @@ export type WidgetUtility = {

applyButtonClick: (thisInstance: GuiButton, callback: () -> ()) -> (),
applyButtonDown: (thisInstance: GuiButton, callback: (x: number, y: number) -> ()) -> (),
applyMouseEnter: (thisInstance: GuiObject, callback: () -> ()) -> (),
applyMouseLeave: (thisInstance: GuiObject, callback: () -> ()) -> (),
applyMouseEnter: (thisInstance: GuiObject, callback: (x: number, y: number) -> ()) -> (),
applyMouseMoved: (thisInstance: GuiObject, callback: (x: number, y: number) -> ()) -> (),
applyMouseLeave: (thisInstance: GuiObject, callback: (x: number, y: number) -> ()) -> (),
applyInputBegan: (thisInstance: GuiObject, callback: (input: InputObject) -> ()) -> (),
applyInputEnded: (thisInstance: GuiObject, callback: (input: InputObject) -> ()) -> (),

Expand Down Expand Up @@ -429,6 +437,10 @@ export type Config = {
CheckMarkColor: Color3,
CheckMarkTransparency: number,

PlotLinesColor: Color3,
PlotLinesTransparency: number,
PlotLinesHoveredColor: Color3,
PlotLinesHoveredTransparency: number,
PlotHistogramColor: Color3,
PlotHistogramTransparency: number,
PlotHistogramHoveredColor: Color3,
Expand Down Expand Up @@ -561,6 +573,8 @@ export type Iris = {
InputEnum: WidgetCall<Combo, WidgetArguments, WidgetStates?, Enum>,

ProgressBar: WidgetCall<ProgressBar, WidgetArguments, WidgetStates?>,
PlotLines: WidgetCall<PlotLines, WidgetArguments, WidgetStates?>,
PlotHistogram: WidgetCall<PlotHistogram, WidgetArguments, WidgetStates?>,

Image: WidgetCall<Image, WidgetArguments, nil>,
ImageButton: WidgetCall<ImageButton, WidgetArguments, nil>,
Expand Down
43 changes: 40 additions & 3 deletions lib/WidgetTypes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type State<T> = {
ConnectedFunctions: { (newValue: T) -> () },

get: (self: State<T>) -> T,
set: (self: State<T>, newValue: T) -> (),
set: (self: State<T>, newValue: T, force: true?) -> (),
onChange: (self: State<T>, funcToConnect: (newValue: T) -> ()) -> () -> (),
}

Expand Down Expand Up @@ -417,8 +417,6 @@ export type Selectable = Widget & {
} & Selected & Unselected & Clicked & RightClicked & DoubleClicked & CtrlClicked & Hovered

export type Combo = ParentWidget & {
ComboChildrenHeight: number,

arguments: {
Text: string?,
NoButton: boolean?,
Expand All @@ -444,6 +442,45 @@ export type ProgressBar = Widget & {
},
} & Changed & Hovered

export type PlotLines = Widget & {
Lines: { Frame },
HoveredLine: Frame | false,
Tooltip: TextLabel,

arguments: {
Text: string,
Height: number,
Min: number,
Max: number,
TextOverlay: string,
},

state: {
values: State<{ number }>,
hovered: State<{ number }?>,
},
}

export type PlotHistogram = Widget & {
Blocks: { Frame },
HoveredBlock: Frame | false,
Tooltip: TextLabel,

arguments: {
Text: string,
Height: number,
Min: number,
Max: number,
TextOverlay: string,
BaseLine: number,
},

state: {
values: State<{ number }>,
hovered: State<number?>,
},
}

export type Table = ParentWidget & {
RowColumnIndex: number,
InitialNumColumns: number,
Expand Down
8 changes: 8 additions & 0 deletions lib/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ local TemplateConfig = {
CheckMarkColor = Color3.fromRGB(66, 150, 250),
CheckMarkTransparency = 0,

PlotLinesColor = Color3.fromRGB(156, 156, 156),
PlotLinesTransparency = 0,
PlotLinesHoveredColor = Color3.fromRGB(255, 110, 89),
PlotLinesHoveredTransparency = 0,
PlotHistogramColor = Color3.fromRGB(230, 179, 0),
PlotHistogramTransparency = 0,
PlotHistogramHoveredColor = Color3.fromRGB(255, 153, 0),
Expand Down Expand Up @@ -194,6 +198,10 @@ local TemplateConfig = {
CheckMarkColor = Color3.fromRGB(66, 150, 250),
CheckMarkTransparency = 0,

PlotLinesColor = Color3.fromRGB(99, 99, 99),
PlotLinesTransparency = 0,
PlotLinesHoveredColor = Color3.fromRGB(255, 110, 89),
PlotLinesHoveredTransparency = 0,
PlotHistogramColor = Color3.fromRGB(230, 179, 0),
PlotHistogramTransparency = 0,
PlotHistogramHoveredColor = Color3.fromRGB(255, 153, 0),
Expand Down
50 changes: 50 additions & 0 deletions lib/demoWindow.lua
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ return function(Iris: Types.Iris)
Plotting = function()
Iris.Tree({ "Plotting" })
do
Iris.SeparatorText({ "Progress" })
local curTime = os.clock() * 15

local Progress = Iris.State(0)
Expand All @@ -573,6 +574,55 @@ return function(Iris: Types.Iris)

Iris.ProgressBar({ "Progress Bar" }, { progress = Progress })
Iris.ProgressBar({ "Progress Bar", `{math.floor(Progress:get() * 1753)}/1753` }, { progress = Progress })

Iris.SeparatorText({ "Graphs" })

do
local ValueState = Iris.State({ 0.5, 0.8, 0.2, 0.9, 0.1, 0.6, 0.4, 0.7, 0.3, 0.0 })

Iris.PlotHistogram({ "Histogram", 100, 0, 1, "random" }, { values = ValueState })
Iris.PlotLines({ "Lines", 100, 0, 1, "random" }, { values = ValueState })
end

do
local FunctionState = Iris.State("Cos")
local SampleState = Iris.State(37)
local BaseLineState = Iris.State(0)
local ValueState = Iris.State({})
local TimeState = Iris.State(0)

local Animated = Iris.Checkbox({ "Animate" })
local plotFunc = Iris.ComboArray({ "Plotting Function" }, { index = FunctionState }, { "Sin", "Cos", "Tan", "Saw" })
local samples = Iris.SliderNum({ "Samples", 1, 1, 145, "%d samples" }, { number = SampleState })
if Iris.SliderNum({ "Baseline", 0.1, -1, 1 }, { number = BaseLineState }).numberChanged() then
ValueState:set(ValueState.value, true)
end

if Animated.state.isChecked.value or plotFunc.closed() or samples.numberChanged() or #ValueState.value == 0 then
if Animated.state.isChecked.value then
TimeState:set(TimeState.value + Iris.Internal._deltaTime)
end
local offset: number = math.floor(TimeState.value * 30) - 1
local func: string = FunctionState.value
table.clear(ValueState.value)
for i = 1, SampleState.value do
if func == "Sin" then
ValueState.value[i] = math.sin(math.rad(5 * (i + offset)))
elseif func == "Cos" then
ValueState.value[i] = math.cos(math.rad(5 * (i + offset)))
elseif func == "Tan" then
ValueState.value[i] = math.tan(math.rad(5 * (i + offset)))
elseif func == "Saw" then
ValueState.value[i] = if (i % 2) == (offset % 2) then 1 else -1
end
end

ValueState:set(ValueState.value, true)
end

Iris.PlotHistogram({ "Histogram", 100, -1, 1, "", BaseLineState:get() }, { values = ValueState })
Iris.PlotLines({ "Lines", 100, -1, 1 }, { values = ValueState })
end
end
Iris.End()
end,
Expand Down
Loading

0 comments on commit 717a016

Please sign in to comment.