Skip to content

Commit d23f650

Browse files
committed
Add cross-sectional charts and some examples to docs
1 parent d07286d commit d23f650

8 files changed

+417
-84
lines changed

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[deps]
2+
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
23
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
34
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
45
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"

docs/crosssectionalcharts.jl

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
2+
module CrossSectionalCharts
3+
4+
using Colors
5+
using Base64
6+
7+
struct CrossSectionalChartSVG <: Main.SVG
8+
buf::IOBuffer
9+
end
10+
11+
struct Axis
12+
index::Int # compornent index
13+
label::String
14+
range::AbstractRange
15+
end
16+
Base.first(a::Axis) = first(a.range)
17+
Base.last(a::Axis) = last(a.range)
18+
19+
function crosssection(::Type{C};
20+
x::Tuple=(1, "X", 0:1),
21+
y::Tuple=(2, "Y", 0:1),
22+
z::Tuple=(3, "Z", 0:1)) where C <: Color
23+
crosssection(C, Axis(x...), Axis(y...), Axis(z...))
24+
end
25+
function crosssection(::Type{C}, x::Axis, y::Axis, z::Axis) where C <: Color
26+
io = IOBuffer()
27+
id = String(nameof(C))
28+
29+
write(io,
30+
"""
31+
<svg xmlns="http://www.w3.org/2000/svg"
32+
xmlns:xlink="http://www.w3.org/1999/xlink"
33+
id="svg_$id" version="1.1"
34+
viewBox="0 0 40 30" width="27.09mm" height="20.32mm"
35+
stroke="none" style="display:inline; margin-left:2em; margin-bottom:1em">
36+
<defs>
37+
<filter id="filter_g_$id" filterUnits="userSpaceOnUse" x="0" y="-0.5" width="15.5" height="16">
38+
<feGaussianBlur stdDeviation="0.1" result="blur"/>
39+
<feMerge>
40+
<feMergeNode in="SourceGraphic"/>
41+
<feMergeNode in="blur"/>
42+
</feMerge>
43+
</filter>
44+
<style type="text/css"><![CDATA[
45+
#svg_$id g, #svg_$id image {
46+
transition: all 400ms ease 200ms;
47+
}
48+
#svg_$id path.b {
49+
stroke: currentColor;
50+
stroke-width: 0.5;
51+
opacity: 0;
52+
transition: all 200ms ease 0ms;
53+
}
54+
#svg_$id image {
55+
opacity: 0;
56+
filter:url(#filter_g_$id);
57+
}
58+
#svg_$id rect:active ~ image {
59+
opacity: 1;
60+
transition: all 200ms ease 0ms;
61+
}
62+
#svg_$id rect:hover ~ image {
63+
opacity: 1;
64+
transition: all 200ms ease 0ms;
65+
}
66+
#svg_$id rect:active ~ path.b {
67+
opacity: 0.8;
68+
}
69+
#svg_$id rect:hover ~ path.b {
70+
opacity: 0.8;
71+
}
72+
#svg_$id text {
73+
fill: currentColor;
74+
fill-opacity: 0.8;
75+
stroke: #aaa;
76+
stroke-width: 0.2;
77+
stroke-opacity: 0.4;
78+
font-size: 3px;
79+
}
80+
#svg_$id text.n {
81+
opacity:0;
82+
}
83+
#svg_$id:hover text.n {
84+
opacity:1;
85+
}
86+
#svg_$id:active text.n {
87+
opacity:1;
88+
}
89+
]]></style>
90+
</defs>
91+
""")
92+
93+
xs = [xv for xv in range(first(x), stop=last(x), length=16)]
94+
ys = [yv for yv in range(last(y), stop=first(y), length=16)]
95+
zs = [zv for zv in range(first(z), stop=last(z), length=11)]
96+
xmidf = (first(x) + last(x)) * 0.5
97+
ymidf = (first(y) + last(y)) * 0.5
98+
xmid = isinteger(xmidf) ? Int(xmidf) : xmidf
99+
ymid = isinteger(ymidf) ? Int(ymidf) : ymidf
100+
101+
vec = [0.0, 0.0, 0.0]
102+
function col(xv, yv, zv)
103+
vec[x.index] = xv
104+
vec[y.index] = yv
105+
vec[z.index] = zv
106+
# TODO: Add more appropriate out-of-gamut color handling
107+
xyz = convert(XYZ, C(vec...))
108+
rgb = convert(RGB, XYZ(max(xyz.x,0), max(xyz.y,0), max(xyz.z,0)))
109+
end
110+
111+
# add swatches of color bar and planes by layer
112+
for i = 1:11
113+
zi = isodd(i) ? 6 - i÷2 : 6 + i÷2 # zigzag order
114+
plane = [col(xs[xi], ys[yi], zs[zi]) for yi = 1:16, xi = 1:16]
115+
ccolor = col(xmid, ymid, zs[zi]) # center color (for color bar)
116+
barh = i == 1 ? 30 : 16.5 - 3*(i÷2)
117+
op = i == 1 ? "style=\"opacity:1;\"" : ""
118+
write(io,
119+
"""
120+
<g>
121+
<rect fill="#$(hex(ccolor))" width="4" height="$(barh)" x="36" y="$(isodd(i) ? 30 - barh : 0)" />
122+
<image width="16" height="16" transform="scale(2) translate(-.5,0)" $op xlink:href="data:image/png;base64,""")
123+
b64enc = Base64EncodePipe(io)
124+
write_png(b64enc, plane)
125+
close(b64enc)
126+
write(io, "\" />\n")
127+
write(io,
128+
"""
129+
<path d="m35,$(33-3zi) h 5" class="b"/>
130+
</g>
131+
""")
132+
end
133+
# add labels
134+
if first(x.range) * last(x.range) < 0
135+
write(io,
136+
"""
137+
<path d="M0,15 h30 M15,0 v30" style="stroke:currentColor;stroke-width:0.125"/>
138+
<text x="29.5" y="14" style="text-anchor:end;">$(x.label)</text>
139+
<text x="29.5" y="18" style="text-anchor:end;" class="n">$(last(x))</text>
140+
<text x="16" y="3" style="text-anchor:start;">$(y.label)</text>
141+
<text x="14" y="3" style="text-anchor:end;" class="n">$(last(y))</text>
142+
<text x="14" y="18" style="text-anchor:end;" class="n">0</text>
143+
""")
144+
else
145+
write(io,
146+
"""
147+
<text x="29.5" y="26" style="text-anchor:end;">$(x.label)</text>
148+
<text x="29.5" y="29" style="text-anchor:end;" class="n">$(last(x))</text>
149+
<text x="15" y="29" style="text-anchor:middle;" class="n">$xmid</text>
150+
<text x="6" y="3" style="text-anchor:start;">$(y.label)</text>
151+
<text x="0.5" y="3" style="text-anchor:start;" class="n">$(last(y))</text>
152+
<text x="0.5" y="16" style="text-anchor:start;" class="n">$ymid</text>
153+
<text x="0.5" y="29" style="text-anchor:start;" class="n">0</text>
154+
""")
155+
end
156+
write(io,
157+
"""
158+
<text style="text-anchor:middle;" transform="translate(35,15) rotate(-90)">$(z.label)</text>
159+
<text x="36" y="3" style="text-anchor:end;" class="n">$(last(z))</text>
160+
<text x="36" y="29" style="text-anchor:end;" class="n">$(first(z))</text>
161+
<text x="2" y="26" style="fill:#fff;fill-opacity:1;text-anchor:start;">$id</text>
162+
<path d="m0,0 h40 v30 h-40 z" style="fill:none;stroke:none;" />
163+
</svg>""")
164+
CrossSectionalChartSVG(io)
165+
end
166+
167+
168+
function write_png(io::IO, cs::AbstractArray{T}) where T <: Color
169+
buf = IOBuffer() # to calculate chunk CRCs
170+
n = 16 # 16 x 16
171+
u8(x) = write(buf, UInt8(x & 0xFF))
172+
u16(x) = (u8((x & 0xFFFF)>>8); u8(x))
173+
u32(x) = (u16((x & 0xFFFFFFFF)>>16); u16(x))
174+
b(bstr) = write(buf, bstr)
175+
function pallet(c::Color)
176+
rgb24 = convert(RGB24,c)
177+
u8(rgb24.color>>16); u8(rgb24.color>>8); u8(rgb24.color)
178+
end
179+
crct(x) = (for i = 1:8; x = x & 1==1 ? 0xEDB88320 (x>>1) : x>>1 end; x)
180+
table = UInt32[crct(i) for i = 0x00:0xFF]
181+
function crc32()
182+
seekstart(buf)
183+
crc = 0xFFFFFFFF
184+
while !eof(buf)
185+
crc = (crc>>8) table[(crc&0xFF) read(buf, UInt8) + 1]
186+
end
187+
u32(crc 0xFFFFFFFF)
188+
end
189+
flush() = write(io, take!(seekstart(buf)))
190+
191+
# The following is a pre-encoded 256-indexed-color PNG with size of 16x16.
192+
# We only rewrite "pallets".
193+
b(b"\x89PNG\x0D\x0A\x1A\x0A")
194+
# Image header
195+
u32(13); flush(); b(b"IHDR"); u32(n); u32(n); u8(8); u8(3); u8(0); u8(0); u8(0); crc32()
196+
# Palette
197+
u32(n * n * 3); flush();
198+
b(b"PLTE")
199+
for y = 1:n, x = 1:n
200+
pallet(cs[y,x])
201+
end
202+
crc32()
203+
# Image data
204+
u32(58); flush(); b(b"IDAT")
205+
b(b"\x78\xDA\x63\x64\x60\x44\x03\x02\xE8\x02\x0A\xE8\x02\x06\xE8\x02")
206+
b(b"\x0E\xE8\x02\x01\xE8\x02\x09\xE8\x02\x05\xE8\x02\x0D\xE8\x02\x13")
207+
b(b"\xD0\x05\x16\xA0\x0B\x6C\x40\x17\x38\x80\x2E\x70\x01\x5D\xE0\x01")
208+
b(b"\xBA\xC0\x07\x34\x3E\x00\x54\x4D\x08\x81"); crc32()
209+
# Image trailer
210+
u32(0); flush(); b(b"IEND"); crc32()
211+
flush()
212+
end
213+
214+
"""
215+
# Image data
216+
using CodecZlib
217+
raw = IOBuffer()
218+
for y = 0:15
219+
write(raw, UInt8(1)) # filter: SUB
220+
write(raw, UInt8(y*16)) # line head
221+
write(raw, UInt8[1 for i=1:15]) # left + 1
222+
end
223+
flush(raw)
224+
cd = ZlibCompressorStream(raw,level=9)
225+
flush(cd)
226+
seekstart(cd)
227+
@show read(cd) # UInt8[0x78, 0xda, 0x63, 0x64, ...
228+
"""
229+
230+
crosssection(::Type{HSV}) = crosssection(HSV, x=(2, "S", 0:1),
231+
y=(3, "V", 0:1),
232+
z=(1, "H", 0:360))
233+
crosssection(::Type{HSL}) = crosssection(HSL, x=(2, "S", 0:1),
234+
y=(3, "L", 0:1),
235+
z=(1, "H", 0:360))
236+
crosssection(::Type{HSI}) = crosssection(HSI, x=(2, "S", 0:1),
237+
y=(3, "I", 0:1),
238+
z=(1, "H", 0:360))
239+
240+
crosssection(::Type{Lab}) = crosssection(Lab, x=(2, "a*", -100:100),
241+
y=(3, "b*", -100:100),
242+
z=(1, "L*", 0:100))
243+
crosssection(::Type{Luv}) = crosssection(Luv, x=(2, "u*", -100:100),
244+
y=(3, "v*", -100:100),
245+
z=(1, "L*", 0:100))
246+
crosssection(::Type{LCHab}) = crosssection(LCHab, x=(2, "C*", 0:100),
247+
y=(1, "L*", 0:100),
248+
z=(3, "H", 0:360))
249+
crosssection(::Type{LCHuv}) = crosssection(LCHuv, x=(2, "C*", 0:100),
250+
y=(1, "L*", 0:100),
251+
z=(3, "H", 0:360))
252+
253+
crosssection(::Type{YIQ}) = crosssection(YIQ, x=(2, "I", -1:1),
254+
y=(3, "Q", -1:1),
255+
z=(1, "Y", 0:1))
256+
crosssection(::Type{YCbCr}) = crosssection(YCbCr, x=(2, "Cb", 0:256),
257+
y=(3, "Cr", 0:256),
258+
z=(1, "Y", 0:256))
259+
end

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ function Base.show(io::IO, mime::MIME"image/svg+xml", svg::SVG)
66
flush(io)
77
end
88

9+
include("crosssectionalcharts.jl")
910
include("colormaps.jl")
1011
include("namedcolorcharts.jl")
1112

docs/src/advancedfunctions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ whitebalance
3434
```
3535

3636

37-
## Simulation of color deficiency ("color blindness")
37+
## [Simulation of color deficiency ("color blindness")](@id color_deficiency)
3838

3939
Three functions are provided that map colors to a reduced gamut to simulate different types of *dichromacy*, the loss of one of the three types of human photopigments.
4040

docs/src/colordifferences.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
The `colordiff` function gives an approximate value for the difference between two colors.
44

5-
```jldoctest example
6-
julia> using Colors; #hide
7-
5+
```jldoctest example; setup = :(using Colors)
86
julia> colordiff(colorant"red", parse(Colorant, HSV(360, 0.75, 1)))
97
8.178248292426845
108
```

0 commit comments

Comments
 (0)