Stock composition version #1

Open
aniva wants to merge 5 commits from stock-composition into main
5 changed files with 246 additions and 1 deletions

View File

@ -2,7 +2,7 @@
julia_version = "1.11.4"
manifest_format = "2.0"
project_hash = "0a18a0873213e0e8660a5eb202a1fa9f1621a652"
project_hash = "ec502e8d73a739a6239b751344af945fa59e7806"
[[deps.AbstractFFTs]]
deps = ["LinearAlgebra"]

View File

@ -7,10 +7,12 @@ version = "0.1.0"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
Luxor = "ae8d54c2-7ccd-5906-9d76-62fc9837b5bc"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
VideoIO = "d6d074c3-1acf-5d4c-9a43-ef38773959a2"
[compat]
FileIO = "1.17.0"
Images = "0.26.2"
Luxor = "4.2.0"
Statistics = "1.11.1"
VideoIO = "1.1.1"

8
src/image.jl Normal file
View File

@ -0,0 +1,8 @@
using Images
"""
Converts image to grayscale
"""
function greyscale(c::Images.RGB)::Real
return Real(c.r)
end

View File

@ -0,0 +1,103 @@
using Base
import Luxor as L
"""
Colour of a component given its fluctuations
"""
function colour_of_diff(diff::Real)::Tuple{Real,Real,Real}
if diff > 0
return (0.5,1,0.5)
else
return (1,0.5,0.5)
end
end
component_text_size = 20
@kwdef struct Component
pos::Tuple{UInt, UInt}
size::Tuple{UInt, UInt}
name::String
diff::Real
end
sector_title_font_size = 16
sector_title_height = 20
@kwdef struct Sector
name::String
pos::Tuple{UInt, UInt}
size::Tuple{UInt, UInt}
components::Array{Component}
end
@kwdef struct CompositionDiagram
sectors::Array{Sector}
end
"""
Draws stock index composition chart
"""
function draw(diagram::CompositionDiagram, size=(800, 600))
L.Drawing(size[1], size[2], :png)
L.background("black")
for sector in diagram.sectors
L.sethue("gray")
L.rect(L.Point(sector.pos), sector.size[1], sector.size[2], action = :fillpreserve)
L.setline(2)
L.strokepath()
L.sethue("white")
L.setfont("Helvetica", sector_title_font_size)
L.settext(
sector.name,
L.Point(sector.pos),
halign="left",
valign="top",
)
L.setfont("Helvetica", component_text_size)
for component in sector.components
pos = sector.pos .+ component.pos
L.setcolor(colour_of_diff(component.diff))
L.rect(L.Point(pos), component.size[1], component.size[2], action = :fillpreserve)
L.sethue("white")
L.setline(5)
L.strokepath()
L.settext(
component.name,
L.Point(pos .+ component.size ./ 2),
halign="center",
valign="bottom",
)
L.settext(
"$(component.diff)%",
L.Point(pos .+ component.size ./ 2),
halign="center",
valign="top",
)
end
end
L.finish()
end
diagram = CompositionDiagram(
sectors=[
Sector(
name="technology",
pos=(0,0),
size=(200,300),
components=[
Component(pos=(0,20), size=(200,200),name="AAPL", diff=0.5),
Component(pos=(0,200), size=(200,100),name="GOOG", diff=-0.3),
],
),
Sector(
name="resources",
pos=(200,0),
size=(200,200),
components=[
Component(pos=(0,20), size=(200,150),name="OIL", diff=0.1),
],
),
],
)
draw(diagram)

132
src/tdpacking.jl Normal file
View File

@ -0,0 +1,132 @@
using Base
using Statistics, Images
import Luxor as L
@kwdef struct Region
pos::Tuple{Int,Int}
size::Tuple{Int,Int}
# If true, this region increases from x to y. This direction variable steers
# the direction of cut.
dir::Bool
end
# Time-dependent packing
@kwdef struct Rect
pos::Tuple{Int,Int}
size::Tuple{Int,Int}
color::Real
end
function area(r::Rect)::Int
return r.size[1] * r.size[2]
end
function aspect_ratio(r::Rect)::Real
return r.size[1] / r.size[2]
end
"""
divide_image(img::Matrix{}, size_min::Int=16, size_max::Int=256)
Divides an image into rectangles of the same colour. The rectangles are not
allowed to have extreme aspect ratios. There is a minimal size of each retangle.
"""
function divide_image(img::Matrix{}; size_min::Int=16, size_max::Int=128, threshold_aspect::Real=3.0, threshold_std::Real=0.4)::Array{Rect}
remainder = [Region(pos=(1,1), size=size(img), dir=false)]
result = Rect[]
# Loop until the entire field is filled
while length(remainder) > 0
section = pop!(remainder)
(px, py) = section.pos
(sx, sy) = section.size
@assert px + sx - 1 <= size(img)[1]
@assert py + sy - 1 <= size(img)[2]
width = 1
height = 1
width_max = min(sx, size_max)
height_max = min(sy, size_max)
# Try to expand this rectangle to the maximum size
while width < width_max || height < height_max
subimg = img[px:px+width-1, py:py+height-1]
if std(subimg) >= threshold_std
break
end
colour = mean(subimg)
ar = width / height
if width < width_max && ar < threshold_aspect
width += 1
elseif height < height_max && ar > 1/threshold_aspect
height += 1
else
break
end
end
@assert width <= sx
@assert height <= sy
subimg = img[px:px+width-1, py:py+height-1]
rect = Rect(
pos=section.pos,
size=(width, height),
color=mean(subimg),
)
push!(result, rect)
if section.dir
if width < sx
push!(remainder, Region(
pos=(px + width, py),
size=(sx - width, height),
dir=false,
))
end
if height < sy
push!(remainder, Region(
pos=(px, py + height),
size=(sx, sy - height),
dir=true,
))
end
else
if height < sy
push!(remainder, Region(
pos=(px, py + height),
size=(width, sy - height),
dir=true,
))
end
if width < sx
push!(remainder, Region(
pos=(px + width, py),
size=(sx - width, sy),
dir=false,
))
end
end
end
return result
end
"""
draw_rect(size::Tuple{Int, Int}, rects::Array{Rect})
Displays the divided image in greyscale.
"""
function draw_rect(size::Tuple{Int, Int}, rects::Array{Rect})
L.Drawing(size[1], size[2], :png)
L.background("black")
for rect in rects
c = N0f8(rect.color)
setcolor(c, c, c)
L.rect(L.Point(rect.pos), rect.size[1], rect.size[2], action = :fill)
end
L.finish()
end