42 std::uint8_t r = 0, g = 0, b = 0;
44 Rgb(std::uint8_t rr, std::uint8_t gg, std::uint8_t bb)
45 : r(rr), g(gg), b(bb) {}
46 static Rgb fromHex(
const std::string &hex)
48 if (hex.size() >= 7 && hex[0] ==
'#') {
49 auto h = [&](
size_t i) {
50 return static_cast<std::uint8_t
>(std::strtoul(hex.substr(i, 2).c_str(),
nullptr, 16));
52 return Rgb(h(1), h(3), h(5));
56 std::string hex()
const
59 std::snprintf(buf,
sizeof buf,
"#%02x%02x%02x", r, g, b);
66inline const std::vector<Rgb> &palette()
68 static const std::vector<Rgb> p = {
69 Rgb(0x7f, 0xd9, 0x9a),
Rgb(0x5f, 0xb3, 0xd4),
Rgb(0xe9, 0xb8, 0x70),
70 Rgb(0x9b, 0x8c, 0xf2),
Rgb(0xe2, 0x6a, 0x6a),
Rgb(0xd4, 0xa5, 0xe6),
71 Rgb(0xf2, 0xa3, 0x7e),
Rgb(0x6f, 0xcf, 0xbf),
77inline bool charSpecColor(
char c, Rgb &out)
80 case 'r': out = Rgb(0xf0, 0x70, 0x70);
return true;
81 case 'g': out = Rgb(0x6e, 0xe7, 0xa0);
return true;
82 case 'b': out = Rgb(0x60, 0xd0, 0xf0);
return true;
83 case 'k': out = Rgb(0x28, 0x28, 0x30);
return true;
84 case 'm': out = Rgb(0xe0, 0x70, 0xc0);
return true;
85 case 'c': out = Rgb(0x60, 0xd0, 0xf0);
return true;
86 case 'y': out = Rgb(0xe8, 0xd0, 0x60);
return true;
87 case 'w': out = Rgb(0xff, 0xff, 0xff);
return true;
88 default:
return false;
94 Rgb color{0x7f, 0xd9, 0x9a};
95 bool hasColor =
false;
96 std::string lineStyle;
99 Rgb faceColor{0x00, 0x72, 0xbd};
100 bool hasFaceColor =
false;
101 Rgb edgeColor{0xd9, 0xd9, 0xd9};
102 bool hasEdgeColor =
false;
104 double faceAlpha = 1;
116 double fontSize = 10;
123inline bool textColorSpec(
const std::string &v,
Rgb &out)
128 static const std::pair<const char *, Rgb> kNames[] = {
129 {
"red", {255, 0, 0}}, {
"green", {0, 128, 0}},
130 {
"blue", {0, 0, 255}}, {
"cyan", {0, 255, 255}},
131 {
"magenta", {255, 0, 255}}, {
"yellow", {255, 255, 0}},
132 {
"black", {0, 0, 0}}, {
"white", {255, 255, 255}},
133 {
"none", {255, 255, 255}},
137 if (c !=
' ' && c !=
'[' && c !=
']')
138 s += (char)std::tolower((
unsigned char)c);
140 for (
const auto &kv : kNames)
141 if (s[0] == kv.first[0]) {
147 for (
const auto &kv : kNames)
152 if (s.size() >= 7 && s[0] ==
'#') {
153 out = Rgb::fromHex(s);
156 double ch[3] = {0, 0, 0};
159 while (i < s.size() && n < 3) {
161 while (e < s.size() && s[e] !=
',')
164 ch[n++] = std::stod(s.substr(i, e - i));
171 out =
Rgb((std::uint8_t)std::lround(std::min(1.0, std::max(0.0, ch[0])) * 255),
172 (std::uint8_t)std::lround(std::min(1.0, std::max(0.0, ch[1])) * 255),
173 (std::uint8_t)std::lround(std::min(1.0, std::max(0.0, ch[2])) * 255));
179inline TextExtras parseTextExtras(
const std::string &style)
185 while (i < style.size()) {
186 size_t semi = style.find(
';', i);
187 std::string kv = style.substr(
188 i, (semi == std::string::npos ? style.size() : semi) - i);
189 size_t eq = kv.find(
'=');
190 if (eq != std::string::npos) {
191 std::string key = kv.substr(0, eq);
192 std::string val = kv.substr(eq + 1);
193 if (key ==
"color") {
195 if (textColorSpec(val, c))
197 }
else if (key ==
"rgb") {
199 if (textColorSpec(val, c))
201 }
else if (key ==
"fontSize") {
203 t.fontSize = std::stod(val);
206 }
else if (key ==
"halign") {
207 t.halign = val ==
"center" ? 1 : val ==
"right" ? 2 : 0;
210 i = semi == std::string::npos ? style.size() : semi + 1;
215inline StyleParts parseStyle(
const std::string &s)
220 if (s.find(
'=') != std::string::npos) {
221 for (
size_t i = 0; i < s.size();) {
222 size_t eq = s.find(
'=', i);
223 if (eq == std::string::npos)
225 size_t semi = s.find(
';', eq);
226 std::string key = s.substr(i, eq - i);
227 std::string val = s.substr(
228 eq + 1, (semi == std::string::npos ? s.size() : semi) - eq - 1);
229 if (key ==
"color") {
230 p.color = Rgb::fromHex(val);
232 }
else if (key ==
"lineStyle") {
234 }
else if (key ==
"marker") {
236 }
else if (key ==
"faceColor") {
237 p.faceColor = Rgb::fromHex(val);
238 p.hasFaceColor =
true;
239 }
else if (key ==
"edgeColor") {
240 p.edgeColor = Rgb::fromHex(val);
241 p.hasEdgeColor =
true;
242 }
else if (key ==
"binWidth") {
244 p.binWidth = std::stod(val);
247 }
else if (key ==
"faceAlpha") {
249 p.faceAlpha = std::stod(val);
253 i = semi == std::string::npos ? s.size() : semi + 1;
257 static const std::string markers =
"o+*.xsd^v<>ph";
259 while (i < s.size()) {
260 if (p.lineStyle.empty() && s.compare(i, 2,
"--") == 0) { p.lineStyle =
"--"; i += 2;
continue; }
261 if (p.lineStyle.empty() && s.compare(i, 2,
"-.") == 0) { p.lineStyle =
"-."; i += 2;
continue; }
263 if (p.lineStyle.empty() && (c ==
'-' || c ==
':')) { p.lineStyle = std::string(1, c); ++i;
continue; }
266 if (charSpecColor(c, col)) {
273 if (p.marker.empty() && markers.find(c) != std::string::npos) {
274 p.marker = std::string(1, c);
294 {
"parula",
"3e26a83e27ac3f28af3f29b2402ab4402bb7412cba412dbd422ebf422fc24330c54331c84332ca4433cd4434d04535d24537d54538d74639d9463adc463bde463de0473ee1473fe34741e54742e64744e84745e94746eb4848ec4849ed484bee484cf0484ef1484ff24850f34852f44853f54854f64756f74757f74759f8475af9475bfa475dfa465efb4660fb4661fc4562fc4564fd4465fd4367fd4368fe426afe416bfe406dfe3f6eff3e70ff3c71ff3b73ff3974ff3876fe3677fe3579fd337afd327cfc317dfc307ffb2f80fa2f82fa2e83f92e84f82e86f82e87f72d88f62d8af52d8bf42d8cf32d8ef22c8ff12c90f02b91ef2a93ee2994ed2895ec2797eb2798ea2699e9269ae8259be8259ce7249ee6249fe523a0e523a1e422a2e421a3e320a5e31fa6e21ea7e11da8e11da9e01caadf1babde1aacdd19addc17aeda16afd914b0d812b1d610b2d50eb3d40bb3d208b4d106b5cf04b6ce02b7cc01b7ca00b8c900b9c700bac601bac402bbc204bbc106bcbf09bdbd0dbdbc10beba14beb817bfb61ac0b51dc0b320c1b123c1af25c2ae27c2ac29c3aa2bc3a82cc4a62ec4a52fc5a331c5a132c69f33c79d35c79b36c89938c89639c9943bc9923dca9040ca8d42ca8b45cb8948cb864bcb844ecc8151cc7f54cc7c57cc7a5acc775ecd7461cd7264cd6f67cd6c6bcd696ecd6672cd6476cc6179cc5e7dcc5b81cc5984cc5688cb538bcb518fcb4e93ca4b96ca489ac9469dc943a1c840a4c83ea7c73babc739aec637b2c635b5c533b8c431bbc42fbec32dc2c32cc5c22ac8c129cbc128cec027d0bf27d3bf27d6be27d9be28dbbd28debc29e1bc2ae3bc2be6bb2de8bb2eeaba30ecba32efba35f1ba37f3ba39f5ba3bf7ba3df9ba3efbbb3efcbc3efebd3dfebe3cfec03bfec13afec239fec438fec537fec735fec834feca33fdcb32fdcd31fdce31fcd030fbd22ffbd32efad52ef9d62df9d82cf8d92bf7db2af7dd2af6de29f6e028f5e128f5e327f5e526f5e626f5e825f5e924f5eb23f5ec22f5ee21f6ef20f6f11ff6f21ef7f41cf7f51bf8f71af8f818f9f916f9fb15"},
295 {
"turbo",
"30123b32154333184a341b51351e5836215f37246638276d392a733a2d793b2f803c32863d358b3e38913f3b973f3e9c4040a24143a74146ac4249b1424bb5434eba4451bf4454c34456c74559cb455ccf455ed34661d64664da4666dd4669e0466be3476ee64771e94773eb4776ee4778f0477bf2467df44680f64682f84685fa4687fb458afc458cfd448ffe4391fe4294ff4196ff4099ff3e9bfe3d9efe3ba0fd3aa3fc38a5fb37a8fa35abf833adf731aff52fb2f42eb4f22cb7f02ab9ee28bceb27bee925c0e723c3e422c5e220c7df1fc9dd1ecbda1ccdd81bd0d51ad2d21ad4d019d5cd18d7ca18d9c818dbc518ddc218dec018e0bd19e2bb19e3b91ae4b61ce6b41de7b21fe9af20eaac22ebaa25eca727eea42aefa12cf09e2ff19b32f29835f39438f4913cf58e3ff68a43f78746f8844af8804ef97d52fa7a55fa7659fb735dfc6f61fc6c65fd6969fd666dfe6271fe5f75fe5c79fe597dff5680ff5384ff5188ff4e8bff4b8fff4992ff4796fe4499fe429cfe409ffd3fa1fd3da4fc3ca7fc3aa9fb39acfb38affa37b1f936b4f836b7f735b9f635bcf534bef434c1f334c3f134c6f034c8ef34cbed34cdec34d0ea34d2e935d4e735d7e535d9e436dbe236dde037dfdf37e1dd37e3db38e5d938e7d739e9d539ebd339ecd13aeecf3aefcd3af1cb3af2c93af4c73af5c53af6c33af7c13af8be39f9bc39faba39fbb838fbb637fcb336fcb136fdae35fdac34fea933fea732fea431fea130fe9e2ffe9b2dfe992cfe962bfe932afe9029fd8d27fd8a26fc8725fc8423fb8122fb7e21fa7b1ff9781ef9751df8721cf76f1af66c19f56918f46617f36315f26014f15d13f05b12ef5811ed5510ec530feb500eea4e0de84b0ce7490ce5470be4450ae2430ae14109df3f08dd3d08dc3b07da3907d83706d63506d43305d23105d02f05ce2d04cc2b04ca2a04c82803c52603c32503c12302be2102bc2002b91e02b71d02b41b01b21a01af1801ac1701a91601a71401a41301a112019e10019b0f01980e01950d01920b018e0a018b09028808028507028106027e05027a0403"},
296 {
"jet",
"00008300008700008b00008f00009300009700009b00009f0000a30000a70000ab0000af0000b30000b70000bb0000bf0000c30000c70000cb0000cf0000d30000d70000db0000df0000e30000e70000eb0000ef0000f30000f70000fb0000ff0004ff0008ff000cff0010ff0014ff0018ff001cff0020ff0024ff0028ff002cff0030ff0034ff0038ff003cff0040ff0044ff0048ff004cff0050ff0054ff0058ff005cff0060ff0064ff0068ff006cff0070ff0074ff0078ff007cff0080ff0083ff0087ff008bff008fff0093ff0097ff009bff009fff00a3ff00a7ff00abff00afff00b3ff00b7ff00bbff00bfff00c3ff00c7ff00cbff00cfff00d3ff00d7ff00dbff00dfff00e3ff00e7ff00ebff00efff00f3ff00f7ff00fbff00ffff04fffb08fff70cfff310ffef14ffeb18ffe71cffe320ffdf24ffdb28ffd72cffd330ffcf34ffcb38ffc73cffc340ffbf44ffbb48ffb74cffb350ffaf54ffab58ffa75cffa360ff9f64ff9b68ff976cff9370ff8f74ff8b78ff877cff8380ff8083ff7c87ff788bff748fff7093ff6c97ff689bff649fff60a3ff5ca7ff58abff54afff50b3ff4cb7ff48bbff44bfff40c3ff3cc7ff38cbff34cfff30d3ff2cd7ff28dbff24dfff20e3ff1ce7ff18ebff14efff10f3ff0cf7ff08fbff04ffff00fffb00fff700fff300ffef00ffeb00ffe700ffe300ffdf00ffdb00ffd700ffd300ffcf00ffcb00ffc700ffc300ffbf00ffbb00ffb700ffb300ffaf00ffab00ffa700ffa300ff9f00ff9b00ff9700ff9300ff8f00ff8b00ff8700ff8300ff8000ff7c00ff7800ff7400ff7000ff6c00ff6800ff6400ff6000ff5c00ff5800ff5400ff5000ff4c00ff4800ff4400ff4000ff3c00ff3800ff3400ff3000ff2c00ff2800ff2400ff2000ff1c00ff1800ff1400ff1000ff0c00ff0800ff0400ff0000fb0000f70000f30000ef0000eb0000e70000e30000df0000db0000d70000d30000cf0000cb0000c70000c30000bf0000bb0000b70000b30000af0000ab0000a70000a300009f00009b00009700009300008f00008b0000870000830000800000"},
297 {
"hot",
"0300000500000800000b00000d00001000001300001500001800001b00001d00002000002300002500002800002b00002d00003000003200003500003800003a00003d00004000004200004500004800004a00004d00005000005200005500005800005a00005d00006000006200006500006800006a00006d00007000007200007500007800007a00007d00008000008200008500008700008a00008d00008f00009200009500009700009a00009d00009f0000a20000a50000a70000aa0000ad0000af0000b20000b50000b70000ba0000bd0000bf0000c20000c50000c70000ca0000cd0000cf0000d20000d50000d70000da0000dc0000df0000e20000e40000e70000ea0000ec0000ef0000f20000f40000f70000fa0000fc0000ff0000ff0300ff0500ff0800ff0b00ff0d00ff1000ff1300ff1500ff1800ff1b00ff1d00ff2000ff2300ff2500ff2800ff2b00ff2d00ff3000ff3200ff3500ff3800ff3a00ff3d00ff4000ff4200ff4500ff4800ff4a00ff4d00ff5000ff5200ff5500ff5800ff5a00ff5d00ff6000ff6200ff6500ff6800ff6a00ff6d00ff7000ff7200ff7500ff7800ff7a00ff7d00ff8000ff8200ff8500ff8700ff8a00ff8d00ff8f00ff9200ff9500ff9700ff9a00ff9d00ff9f00ffa200ffa500ffa700ffaa00ffad00ffaf00ffb200ffb500ffb700ffba00ffbd00ffbf00ffc200ffc500ffc700ffca00ffcd00ffcf00ffd200ffd500ffd700ffda00ffdc00ffdf00ffe200ffe400ffe700ffea00ffec00ffef00fff200fff400fff700fffa00fffc00ffff00ffff04ffff08ffff0cffff10ffff14ffff18ffff1cffff20ffff24ffff28ffff2cffff30ffff34ffff38ffff3cffff40ffff44ffff48ffff4cffff50ffff54ffff58ffff5cffff60ffff64ffff68ffff6cffff70ffff74ffff78ffff7cffff80ffff83ffff87ffff8bffff8fffff93ffff97ffff9bffff9fffffa3ffffa7ffffabffffafffffb3ffffb7ffffbbffffbfffffc3ffffc7ffffcbffffcfffffd3ffffd7ffffdbffffdfffffe3ffffe7ffffebffffeffffff3fffff7fffffbffffff"},
298 {
"cool",
"00ffff01feff02fdff03fcff04fbff05faff06f9ff07f8ff08f7ff09f6ff0af5ff0bf4ff0cf3ff0df2ff0ef1ff0ff0ff10efff11eeff12edff13ecff14ebff15eaff16e9ff17e8ff18e7ff19e6ff1ae5ff1be4ff1ce3ff1de2ff1ee1ff1fe0ff20dfff21deff22ddff23dcff24dbff25daff26d9ff27d8ff28d7ff29d6ff2ad5ff2bd4ff2cd3ff2dd2ff2ed1ff2fd0ff30cfff31ceff32cdff33ccff34cbff35caff36c9ff37c8ff38c7ff39c6ff3ac5ff3bc4ff3cc3ff3dc2ff3ec1ff3fc0ff40bfff41beff42bdff43bcff44bbff45baff46b9ff47b8ff48b7ff49b6ff4ab5ff4bb4ff4cb3ff4db2ff4eb1ff4fb0ff50afff51aeff52adff53acff54abff55aaff56a9ff57a8ff58a7ff59a6ff5aa5ff5ba4ff5ca3ff5da2ff5ea1ff5fa0ff609fff619eff629dff639cff649bff659aff6699ff6798ff6897ff6996ff6a95ff6b94ff6c93ff6d92ff6e91ff6f90ff708fff718eff728dff738cff748bff758aff7689ff7788ff7887ff7986ff7a85ff7b84ff7c83ff7d82ff7e81ff7f80ff807fff817eff827dff837cff847bff857aff8679ff8778ff8877ff8976ff8a75ff8b74ff8c73ff8d72ff8e71ff8f70ff906fff916eff926dff936cff946bff956aff9669ff9768ff9867ff9966ff9a65ff9b64ff9c63ff9d62ff9e61ff9f60ffa05fffa15effa25dffa35cffa45bffa55affa659ffa758ffa857ffa956ffaa55ffab54ffac53ffad52ffae51ffaf50ffb04fffb14effb24dffb34cffb44bffb54affb649ffb748ffb847ffb946ffba45ffbb44ffbc43ffbd42ffbe41ffbf40ffc03fffc13effc23dffc33cffc43bffc53affc639ffc738ffc837ffc936ffca35ffcb34ffcc33ffcd32ffce31ffcf30ffd02fffd12effd22dffd32cffd42bffd52affd629ffd728ffd827ffd926ffda25ffdb24ffdc23ffdd22ffde21ffdf20ffe01fffe11effe21dffe31cffe41bffe51affe619ffe718ffe817ffe916ffea15ffeb14ffec13ffed12ffee11ffef10fff00ffff10efff20dfff30cfff40bfff50afff609fff708fff807fff906fffa05fffb04fffc03fffd02fffe01ffff00ff"},
299 {
"gray",
"0000000101010202020303030404040505050606060707070808080909090a0a0a0b0b0b0c0c0c0d0d0d0e0e0e0f0f0f1010101111111212121313131414141515151616161717171818181919191a1a1a1b1b1b1c1c1c1d1d1d1e1e1e1f1f1f2020202121212222222323232424242525252626262727272828282929292a2a2a2b2b2b2c2c2c2d2d2d2e2e2e2f2f2f3030303131313232323333333434343535353636363737373838383939393a3a3a3b3b3b3c3c3c3d3d3d3e3e3e3f3f3f4040404141414242424343434444444545454646464747474848484949494a4a4a4b4b4b4c4c4c4d4d4d4e4e4e4f4f4f5050505151515252525353535454545555555656565757575858585959595a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606161616262626363636464646565656666666767676868686969696a6a6a6b6b6b6c6c6c6d6d6d6e6e6e6f6f6f7070707171717272727373737474747575757676767777777878787979797a7a7a7b7b7b7c7c7c7d7d7d7e7e7e7f7f7f8080808181818282828383838484848585858686868787878888888989898a8a8a8b8b8b8c8c8c8d8d8d8e8e8e8f8f8f9090909191919292929393939494949595959696969797979898989999999a9a9a9b9b9b9c9c9c9d9d9d9e9e9e9f9f9fa0a0a0a1a1a1a2a2a2a3a3a3a4a4a4a5a5a5a6a6a6a7a7a7a8a8a8a9a9a9aaaaaaabababacacacadadadaeaeaeafafafb0b0b0b1b1b1b2b2b2b3b3b3b4b4b4b5b5b5b6b6b6b7b7b7b8b8b8b9b9b9babababbbbbbbcbcbcbdbdbdbebebebfbfbfc0c0c0c1c1c1c2c2c2c3c3c3c4c4c4c5c5c5c6c6c6c7c7c7c8c8c8c9c9c9cacacacbcbcbcccccccdcdcdcecececfcfcfd0d0d0d1d1d1d2d2d2d3d3d3d4d4d4d5d5d5d6d6d6d7d7d7d8d8d8d9d9d9dadadadbdbdbdcdcdcdddddddedededfdfdfe0e0e0e1e1e1e2e2e2e3e3e3e4e4e4e5e5e5e6e6e6e7e7e7e8e8e8e9e9e9eaeaeaebebebecececedededeeeeeeefefeff0f0f0f1f1f1f2f2f2f3f3f3f4f4f4f5f5f5f6f6f6f7f7f7f8f8f8f9f9f9fafafafbfbfbfcfcfcfdfdfdfefefeffffff"},
300 {
"bone",
"00000001010202020303030404040504040605050806060907070a08080b09090c0a0a0e0b0b0f0b0b100c0c110d0d120e0e140f0f1510101611111712121812121a13131b14141c15151d16161f1717201818211818221919231a1a251b1b261c1c271d1d281e1e291f1f2b20202c20202d21212e22222f2323312424322525332626342727352727372828382929392a2a3a2b2b3b2c2c3d2d2d3e2e2e3f2e2e402f2f4230304331314432324533334634344835354935354a36364b37374c38384e39394f3a3a503b3b513c3c523c3c543d3d553e3e563f3f5740405841415a42425b43435c43435d44445e4545604646614747624848634949654a4a664a4a674b4b684c4c694d4d6b4e4e6c4f4f6d50506e51516f515171525272535373545474555675565776575877585977585a78595c795a5d7a5b5e7b5c5f7c5d607d5e627e5f637e5f647f606580616681626882636983646a84656b84666c85666e86676f876870886971896a738a6b748b6c758c6d768c6d778d6e798e6f7a8f707b90717c91727d92737f937480937481947582957683967785977886987987997a889a7b899a7b8b9b7c8c9c7d8d9d7e8e9e7f8f9f8091a08192a18293a18294a28396a38497a48598a58699a6879aa7889ca8899da8899ea98a9faa8ba0ab8ca2ac8da3ad8ea4ae8fa5af90a6af90a8b091a9b192aab293abb394acb495aeb596afb597b0b697b1b798b2b899b4b99ab5ba9bb6bb9cb7bc9db9bc9ebabd9ebbbe9fbcbfa0bdc0a1bfc1a2c0c2a3c1c3a4c2c3a5c3c4a5c5c5a6c6c6a7c7c7a8c8c8aac9c9abcacaadcbcbaecbcbafccccb1cdcdb2ceceb3cfcfb5d0d0b6d1d1b8d2d2b9d2d2bad3d3bcd4d4bdd5d5bed6d6c0d7d7c1d8d8c3d9d9c4d9d9c5dadac7dbdbc8dcdcc9ddddcbdedeccdfdfcee0e0cfe0e0d0e1e1d2e2e2d3e3e3d4e4e4d6e5e5d7e6e6d9e6e6dae7e7dbe8e8dde9e9deeaeadfebebe1ecece2edede4edede5eeeee6efefe8f0f0e9f1f1eaf2f2ecf3f3edf4f4eff4f4f0f5f5f1f6f6f3f7f7f4f8f8f5f9f9f7fafaf8fbfbfafbfbfbfcfcfcfdfdfefefeffffff"},
301 {
"copper",
"0000000101000302010402010503020604020805030905030a06040b07040d08050e09050f0906100a06120b07130c07140c08150d08160e09180f0919100a1a100a1c110b1d120b1e130c1f140c21140d22150d23160e24170e26170f27180f281910291a102b1b112c1b112d1c122e1d12301e13311e13321f143320143521153622153722163823163a24173b25173c25183d26183f271940281941291a42291a442a1b452b1b462c1c472d1c492d1d4a2e1d4b2f1e4c301e4e301f4f311f5032205133205334215434215535225636225837235937235a38245b39245d3a255e3b255f3b26603c26623d27633e27643e28653f2867402968412969422a6a422a6c432b6d442b6e452c6f462c71462d72472d73482e74492e76492f774a2f784b30794c307b4d317c4d317d4e327e4f328050338150338251348352348553358654358754368855368a56378b57378c57388d58388f5939905a39915b3a925b3a945c3b955d3b965e3c975f3c995f3d9a603d9b613e9c623e9e623f9f633fa06440a16540a36641a46641a56742a66842a86943a96943aa6a44ab6b44ac6c45ae6d45af6d46b06e46b26f47b37047b47048b57148b67249b87349b9744aba744abc754bbd764bbe774cbf784cc1784dc2794dc37a4ec47b4ec67b4fc77c4fc87d50c97e50cb7f51cc7f51cd8052ce8152d08253d18253d28354d38454d48555d68655d78656d88756da8857db8957dc8958dd8a58df8b59e08c59e18d5ae28d5ae48e5be58f5be6905ce7915ce9915dea925deb935eec945eee945fef955ff09660f19760f39861f49861f59962f69a62f89b63f99b63fa9c64fb9d64fc9e64fe9f65ff9f65ffa066ffa166ffa267ffa267ffa368ffa468ffa569ffa669ffa66affa76affa86bffa96bffaa6cffaa6cffab6dffac6dffad6effad6effae6fffaf6fffb070ffb170ffb171ffb271ffb372ffb472ffb473ffb573ffb674ffb774ffb875ffb875ffb976ffba76ffbb77ffbb77ffbc78ffbd78ffbe79ffbf79ffbf7affc07affc17bffc27bffc37cffc37cffc47dffc57dffc67effc67effc77f"},
302 {
"spring",
"ff00ffff01feff02fdff03fcff04fbff05faff06f9ff07f8ff08f7ff09f6ff0af5ff0bf4ff0cf3ff0df2ff0ef1ff0ff0ff10efff11eeff12edff13ecff14ebff15eaff16e9ff17e8ff18e7ff19e6ff1ae5ff1be4ff1ce3ff1de2ff1ee1ff1fe0ff20dfff21deff22ddff23dcff24dbff25daff26d9ff27d8ff28d7ff29d6ff2ad5ff2bd4ff2cd3ff2dd2ff2ed1ff2fd0ff30cfff31ceff32cdff33ccff34cbff35caff36c9ff37c8ff38c7ff39c6ff3ac5ff3bc4ff3cc3ff3dc2ff3ec1ff3fc0ff40bfff41beff42bdff43bcff44bbff45baff46b9ff47b8ff48b7ff49b6ff4ab5ff4bb4ff4cb3ff4db2ff4eb1ff4fb0ff50afff51aeff52adff53acff54abff55aaff56a9ff57a8ff58a7ff59a6ff5aa5ff5ba4ff5ca3ff5da2ff5ea1ff5fa0ff609fff619eff629dff639cff649bff659aff6699ff6798ff6897ff6996ff6a95ff6b94ff6c93ff6d92ff6e91ff6f90ff708fff718eff728dff738cff748bff758aff7689ff7788ff7887ff7986ff7a85ff7b84ff7c83ff7d82ff7e81ff7f80ff807fff817eff827dff837cff847bff857aff8679ff8778ff8877ff8976ff8a75ff8b74ff8c73ff8d72ff8e71ff8f70ff906fff916eff926dff936cff946bff956aff9669ff9768ff9867ff9966ff9a65ff9b64ff9c63ff9d62ff9e61ff9f60ffa05fffa15effa25dffa35cffa45bffa55affa659ffa758ffa857ffa956ffaa55ffab54ffac53ffad52ffae51ffaf50ffb04fffb14effb24dffb34cffb44bffb54affb649ffb748ffb847ffb946ffba45ffbb44ffbc43ffbd42ffbe41ffbf40ffc03fffc13effc23dffc33cffc43bffc53affc639ffc738ffc837ffc936ffca35ffcb34ffcc33ffcd32ffce31ffcf30ffd02fffd12effd22dffd32cffd42bffd52affd629ffd728ffd827ffd926ffda25ffdb24ffdc23ffdd22ffde21ffdf20ffe01fffe11effe21dffe31cffe41bffe51affe619ffe718ffe817ffe916ffea15ffeb14ffec13ffed12ffee11ffef10fff00ffff10efff20dfff30cfff40bfff50afff609fff708fff807fff906fffa05fffb04fffc03fffd02fffe01ffff00"},
303 {
"summer",
"0080660180660281660381660482660582660683660783660884660984660a85660b85660c86660d86660e87660f8766108866118866128966138966148a66158a66168b66178b66188c66198c661a8d661b8d661c8e661d8e661e8f661f8f662090662190662291662391662492662592662693662793662893662994662a95662b95662c96662d96662e97662f9766309866319866329966339966349a66359a66369b66379b66389c66399c663a9d663b9d663c9e663d9e663e9f663f9f6640a06641a06642a16643a16644a26645a26646a36647a36648a36649a4664aa5664ba5664ca6664da6664ea7664fa76650a86651a86652a96653a96654aa6655aa6656ab6657ab6658ac6659ac665aad665bad665cae665dae665eaf665faf6660b06661b06662b16663b16664b26665b26666b36667b36668b36669b4666ab5666bb5666cb6666db6666eb7666fb76670b86671b86672b96673b96674ba6675ba6676bb6677bb6678bc6679bc667abd667bbd667cbe667dbe667ebf667fbf6680c06681c06682c16683c16684c26685c26686c36687c36688c36689c4668ac4668bc5668cc6668dc6668ec7668fc76690c86691c86692c96693c96694ca6695ca6696cb6697cb6698cc6699cc669acd669bcd669cce669dce669ecf669fcf66a0d066a1d066a2d166a3d166a4d266a5d266a6d366a7d366a8d366a9d466aad466abd566acd666add666aed766afd766b0d866b1d866b2d966b3d966b4da66b5da66b6db66b7db66b8dc66b9dc66badd66bbdd66bcde66bdde66bedf66bfdf66c0e066c1e066c2e166c3e166c4e266c5e266c6e366c7e366c8e366c9e466cae466cbe566cce666cde666cee766cfe766d0e866d1e866d2e966d3e966d4ea66d5ea66d6eb66d7eb66d8ec66d9ec66daed66dbed66dcee66ddee66deef66dfef66e0f066e1f066e2f166e3f166e4f266e5f266e6f366e7f366e8f366e9f466eaf466ebf566ecf666edf666eef766eff766f0f866f1f866f2f966f3f966f4fa66f5fa66f6fb66f7fb66f8fc66f9fc66fafd66fbfd66fcfe66fdfe66feff66ffff66"},
304 {
"autumn",
"ff0000ff0100ff0200ff0300ff0400ff0500ff0600ff0700ff0800ff0900ff0a00ff0b00ff0c00ff0d00ff0e00ff0f00ff1000ff1100ff1200ff1300ff1400ff1500ff1600ff1700ff1800ff1900ff1a00ff1b00ff1c00ff1d00ff1e00ff1f00ff2000ff2100ff2200ff2300ff2400ff2500ff2600ff2700ff2800ff2900ff2a00ff2b00ff2c00ff2d00ff2e00ff2f00ff3000ff3100ff3200ff3300ff3400ff3500ff3600ff3700ff3800ff3900ff3a00ff3b00ff3c00ff3d00ff3e00ff3f00ff4000ff4100ff4200ff4300ff4400ff4500ff4600ff4700ff4800ff4900ff4a00ff4b00ff4c00ff4d00ff4e00ff4f00ff5000ff5100ff5200ff5300ff5400ff5500ff5600ff5700ff5800ff5900ff5a00ff5b00ff5c00ff5d00ff5e00ff5f00ff6000ff6100ff6200ff6300ff6400ff6500ff6600ff6700ff6800ff6900ff6a00ff6b00ff6c00ff6d00ff6e00ff6f00ff7000ff7100ff7200ff7300ff7400ff7500ff7600ff7700ff7800ff7900ff7a00ff7b00ff7c00ff7d00ff7e00ff7f00ff8000ff8100ff8200ff8300ff8400ff8500ff8600ff8700ff8800ff8900ff8a00ff8b00ff8c00ff8d00ff8e00ff8f00ff9000ff9100ff9200ff9300ff9400ff9500ff9600ff9700ff9800ff9900ff9a00ff9b00ff9c00ff9d00ff9e00ff9f00ffa000ffa100ffa200ffa300ffa400ffa500ffa600ffa700ffa800ffa900ffaa00ffab00ffac00ffad00ffae00ffaf00ffb000ffb100ffb200ffb300ffb400ffb500ffb600ffb700ffb800ffb900ffba00ffbb00ffbc00ffbd00ffbe00ffbf00ffc000ffc100ffc200ffc300ffc400ffc500ffc600ffc700ffc800ffc900ffca00ffcb00ffcc00ffcd00ffce00ffcf00ffd000ffd100ffd200ffd300ffd400ffd500ffd600ffd700ffd800ffd900ffda00ffdb00ffdc00ffdd00ffde00ffdf00ffe000ffe100ffe200ffe300ffe400ffe500ffe600ffe700ffe800ffe900ffea00ffeb00ffec00ffed00ffee00ffef00fff000fff100fff200fff300fff400fff500fff600fff700fff800fff900fffa00fffb00fffc00fffd00fffe00ffff00"},
305 {
"winter",
"0000ff0001ff0002fe0003fe0004fd0005fd0006fc0007fc0008fb0009fb000afa000bfa000cf9000df9000ef8000ff80010f70011f70012f60013f60014f50015f40016f40017f30018f30019f3001af2001bf2001cf1001df1001ef0001ff00020ef0021ef0022ee0023ee0024ed0025ed0026ec0027ec0028eb0029eb002aea002bea002ce9002de9002ee8002fe80030e70031e70032e60033e60034e50035e40036e40037e30038e30039e3003ae2003be2003ce1003de1003ee0003fe00040df0041df0042de0043de0044dd0045dd0046dc0047dc0048db0049db004ada004bda004cd9004dd9004ed8004fd80050d70051d70052d60053d60054d50055d50056d40057d30058d30059d3005ad2005bd2005cd1005dd1005ed0005fd00060cf0061cf0062ce0063ce0064cd0065cd0066cc0067cc0068cb0069cb006aca006bca006cc9006dc9006ec8006fc80070c70071c70072c60073c60074c50075c50076c40077c30078c30079c3007ac2007bc2007cc1007dc1007ec0007fc00080bf0081bf0082be0083be0084bd0085bd0086bc0087bc0088bb0089bb008aba008bba008cb9008db9008eb8008fb80090b70091b70092b60093b60094b50095b40096b40097b30098b30099b3009ab2009bb2009cb1009db1009eb0009fb000a0af00a1af00a2ae00a3ae00a4ad00a5ad00a6ac00a7ac00a8ab00a9ab00aaaa00abaa00aca900ada900aea800afa800b0a700b1a700b2a600b3a600b4a500b5a400b6a400b7a300b8a300b9a300baa200bba200bca100bda100bea000bfa000c09f00c19f00c29e00c39e00c49d00c59d00c69c00c79c00c89b00c99b00ca9a00cb9a00cc9900cd9900ce9800cf9800d09700d19700d29600d39600d49500d59400d69400d79300d89300d99300da9200db9200dc9100dd9100de9000df9000e08f00e18f00e28e00e38e00e48d00e58d00e68c00e78c00e88b00e98b00ea8a00eb8a00ec8900ed8900ee8800ef8800f08700f18700f28600f38600f48500f58400f68400f78400f88300f98300fa8200fb8200fc8100fd8100fe8000ff80"},
306 {
"hsv",
"ff0000ff0600ff0c00ff1200ff1800ff1e00ff2400ff2a00ff3000ff3600ff3c00ff4200ff4800ff4e00ff5400ff5a00ff6000ff6600ff6c00ff7200ff7800ff7e00ff8300ff8900ff8f00ff9500ff9b00ffa100ffa700ffad00ffb300ffb900ffbf00ffc500ffcb00ffd100ffd700ffdd00ffe300ffe900ffef00fff500fffb00fdff00f7ff00f1ff00ebff00e5ff00dfff00d9ff00d3ff00cdff00c7ff00c1ff00bbff00b5ff00afff00a9ff00a3ff009dff0097ff0091ff008bff0085ff0080ff007aff0074ff006eff0068ff0062ff005cff0056ff0050ff004aff0044ff003eff0038ff0032ff002cff0026ff0020ff001aff0014ff000eff0008ff0002ff0000ff0400ff0a00ff1000ff1600ff1c00ff2200ff2800ff2e00ff3400ff3a00ff4000ff4600ff4c00ff5200ff5800ff5e00ff6400ff6a00ff7000ff7600ff7c00ff8100ff8700ff8d00ff9300ff9900ff9f00ffa500ffab00ffb100ffb700ffbd00ffc300ffc900ffcf00ffd500ffdb00ffe100ffe700ffed00fff300fff900ffff00f9ff00f3ff00edff00e7ff00e1ff00dbff00d5ff00cfff00c9ff00c3ff00bdff00b7ff00b1ff00abff00a5ff009fff0099ff0093ff008dff0087ff0081ff007cff0076ff0070ff006aff0064ff005eff0058ff0052ff004cff0046ff0040ff003aff0034ff002eff0028ff0022ff001cff0016ff0010ff000aff0004ff0200ff0800ff0e00ff1400ff1a00ff2000ff2600ff2c00ff3200ff3800ff3e00ff4400ff4a00ff5000ff5600ff5c00ff6200ff6800ff6e00ff7400ff7a00ff8000ff8500ff8b00ff9100ff9700ff9d00ffa300ffa900ffaf00ffb500ffbb00ffc100ffc700ffcd00ffd300ffd900ffdf00ffe500ffeb00fff100fff700fffd00ffff00fbff00f5ff00efff00e9ff00e3ff00ddff00d7ff00d1ff00cbff00c5ff00bfff00b9ff00b3ff00adff00a7ff00a1ff009bff0095ff008fff0089ff0083ff007eff0078ff0072ff006cff0066ff0060ff005aff0054ff004eff0048ff0042ff003cff0036ff0030ff002aff0024ff001eff0018ff0012ff000cff0006"},
307 {
"viridis",
"44015444035544045745055845075945085a45095c450b5d450c5e450d5f460f6146106246126346136546146646166747176847186a471a6b471b6c471c6d471e6f471f70482071482273482374482475482676472776472877472978462b79462c79462d7a462e7b45307b45317c45327d44337e44357e44367f443780433881433a81433b82423c83423d83423f844140854141864142864144874045874046883f47883f48883e49883e4a893d4b893d4c893c4d893c4e893b4f8a3b508a3b518a3a538a3a548b39558b39568b38578b38588c37598c375a8c365b8c365c8c355d8d355e8d355f8d34608d34618d33628d33638d32648d32658d32668d31678d31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c728e2c738e2b748e2b758e2b768e2a778e2a788e29798e297a8e297b8e287c8e287d8e287e8e277f8e27808e27818e26828e26838e25848d25848d25858d24868d24878d24888d23898d238a8d238b8d228c8d228d8d228e8d218f8d21908d21918c21928c21938c21938b21948b21958b21968a21978a21988a219989219a89219b89229c88229d88229e88229f8722a08722a08722a18622a28622a38622a48522a58522a68522a78422a88424a98325aa8226ab8228ac8129ac802aad7f2cae7f2daf7e2eb07d30b17c31b27c32b37b34b47a35b57936b57937b67839b7773ab8763bb9753dba753ebb743fbc7341bd7242bd7243be7145bf7047c06f49c16d4bc16c4dc26b50c36a52c46854c46756c56658c6655ac7645cc7625ec86160c96063c95f65ca5d67cb5c69cc5b6bcc5a6dcd586fce5771cf5673cf5576d05478d1527ad1517cd24f7fd24e82d34c84d44a87d4498ad5478cd5458fd64492d64294d74097d73f99d83d9cd83b9fd93aa1d938a4da36a7da35a9db33acdb31afdc30b1dc2eb4dd2cb6dd2bb9de29bcde27bedf26c1df26c3df26c6e026c8e026cbe026cde126d0e126d2e126d5e226d8e226dae226dde326dfe325e2e325e4e425e7e425e9e425ece525eee525f1e525f3e625f6e625f8e625fbe725fde725"},
311inline const std::uint8_t *namedPalette(
const std::string &name,
int &n)
313 static const std::map<std::string, std::vector<std::uint8_t>> tables = [] {
314 std::map<std::string, std::vector<std::uint8_t>> m;
315 for (
const auto &p : kNamedPaletteHex) {
316 std::vector<std::uint8_t> v;
318 for (
size_t i = 0; i + 1 < strlen(p.hex); i += 2)
319 v.push_back((std::uint8_t)strtoul(std::string(p.hex, i, 2).c_str(),
321 m[p.name] = std::move(v);
327 key += (char)std::tolower((
unsigned char)c);
328 auto it = tables.find(key);
329 if (it == tables.end())
330 it = tables.find(
"parula");
332 return it->second.data();
339inline std::vector<Rgb> parseCustomPalette(
const std::string &json)
341 std::string flat =
"[";
343 if (ch !=
'[' && ch !=
']')
346 auto v = figdetail::parseFlatDoubles(flat);
347 std::vector<Rgb> out;
348 out.reserve(v.size() / 3);
349 for (
size_t k = 0; k + 3 <= v.size(); k += 3)
350 out.push_back(
Rgb((std::uint8_t)std::lround(v[k] * 255),
351 (std::uint8_t)std::lround(v[k + 1] * 255),
352 (std::uint8_t)std::lround(v[k + 2] * 255)));
358 enum Kind { LINE, POLY, TEXT, MARK, IMAGE } kind;
359 std::vector<double> xs, ys;
367 double fontSize = 12;
371 std::string fontFamily;
383 std::vector<std::uint8_t> img;
384 int imgW = 0, imgH = 0;
385 double ix0 = 0, ix1 = 1, iy0 = 0, iy1 = 1;
386 double qLo = 0, qHi = 1;
387 double climLo = 0, climHi = 1;
388 std::string cmap =
"parula";
389 std::string cmapCustom;
390 bool rgbDirect =
false;
399inline std::vector<std::uint8_t> imageCmdRgb(
const Cmd &c)
401 double sx0 = std::min(c.xs[0], c.xs[1]), sx1 = std::max(c.xs[0], c.xs[1]);
402 double sy0 = std::min(c.ys[0], c.ys[1]), sy1 = std::max(c.ys[0], c.ys[1]);
403 int pw = std::max(1, (
int)std::lround(sx1 - sx0));
404 int ph = std::max(1, (
int)std::lround(sy1 - sy0));
405 std::vector<std::uint8_t> buf((
size_t)pw * ph * 3, 255);
410 auto frac = [](
double lo,
double hi,
double v) {
411 return hi > lo ? (v - lo) / (hi - lo) : 0.5;
413 std::vector<Rgb> custom;
414 if (!c.cmapCustom.empty())
415 custom = parseCustomPalette(c.cmapCustom);
417 const std::uint8_t *pal = c.cmapCustom.empty()
418 ? namedPalette(c.cmap, nPal) : nullptr;
420 nPal = (int)custom.size();
422 for (
int py = 0; py < ph; ++py) {
423 double cy = sy0 + py + 0.5;
424 double v = c.ys[0] <= c.ys[1] ? frac(sy0, sy1, cy)
425 : 1.0 - frac(sy0, sy1, cy);
426 int row = (int)(v * c.imgH);
427 if (row < 0) row = 0;
428 if (row >= c.imgH) row = c.imgH - 1;
429 for (
int px = 0; px < pw; ++px) {
430 double cx = sx0 + px + 0.5;
431 double u = c.xs[0] <= c.xs[1] ? frac(sx0, sx1, cx)
432 : 1.0 - frac(sx0, sx1, cx);
433 int col = (int)(u * c.imgW);
434 if (col < 0) col = 0;
435 if (col >= c.imgW) col = c.imgW - 1;
436 Rgb rgb(255, 255, 255);
438 size_t o = ((size_t)row * c.imgW + col) * 4;
439 rgb = Rgb(c.img[o], c.img[o + 1], c.img[o + 2]);
441 std::uint8_t idx = c.img[(size_t)col * c.imgH + row];
444 double val = c.qLo + (idx / 254.0) * (c.qHi - c.qLo);
445 double t = (val - c.climLo) / (c.climHi - c.climLo);
448 e = (int)std::lround(t * (nPal - 1));
450 if (e >= nPal) e = nPal - 1;
453 rgb = Rgb(pal[e * 3], pal[e * 3 + 1], pal[e * 3 + 2]);
455 rgb = custom[std::min((
size_t)e, custom.size() - 1)];
458 size_t o = ((size_t)py * pw + px) * 3;
471inline void parseNestedDoubles(
const std::string &j, std::vector<double> &out,
472 size_t &rows,
size_t &cols)
479 while (i < j.size()) {
486 }
else if (c ==
']') {
494 }
else if (c ==
',' || c ==
' ' || c ==
'\n' || c ==
'\t') {
496 }
else if (depth >= 2) {
498 while (e < j.size() && j[e] !=
',' && j[e] !=
']')
500 std::string tok = j.substr(i, e - i);
501 bool nullTok = tok.find(
"null") != std::string::npos;
503 out.push_back(nullTok ? std::nan(
"") : std::stod(tok));
505 out.push_back(std::nan(
""));
521inline std::vector<double> niceTicks(
double mn,
double mx,
int target = 10)
523 std::vector<double> major;
524 double range = mx - mn;
527 double rough = range / target;
528 double pow = std::pow(10.0, std::floor(std::log10(rough)));
529 double norm = rough /
pow;
530 double step = norm <= 1.0 ?
pow
531 : norm <= 2.0 ? 2 *
pow
532 : norm <= 5.0 ? 5 *
pow : 10 *
pow;
533 for (
double v = std::ceil(mn / step) * step; v <= mx + step * 1e-6; v += step)
534 major.push_back(std::stod(std::to_string(v).substr(0, 12)));
538inline std::vector<double> logTicks(
double mn,
double mx)
540 std::vector<double> major;
541 if (!(mn > 0) || !(mx > mn))
543 int lmin = (int)std::floor(std::log10(mn));
544 int lmax = (int)std::ceil(std::log10(mx));
545 for (
int p = lmin; p <= lmax; ++p) {
546 double base = std::pow(10.0, p);
547 if (base >= mn && base <= mx)
548 major.push_back(base);
555inline std::string tickLabel(
double v)
560 std::snprintf(buf,
sizeof buf,
"%.6g", v);
562 size_t e = s.find(
'e');
563 if (e != std::string::npos) {
564 std::string mant = s.substr(0, e);
565 std::string
exp = s.substr(e + 1);
568 while (k <
exp.size() && (exp[k] ==
'+' || exp[k] ==
'-'))
570 while (k + 1 <
exp.size() && exp[k] ==
'0')
572 if (mant.size() > 1 && mant.compare(mant.size() - 1, 1,
"0") == 0
573 && mant.find(
'.') != std::string::npos)
574 while (mant.back() ==
'0')
576 if (!mant.empty() && mant.back() ==
'.')
578 s = mant +
"e" + (neg ?
"-" :
"") +
exp.substr(k);
579 }
else if (s.find(
'.') != std::string::npos) {
580 while (!s.empty() && s.back() ==
'0')
582 if (!s.empty() && s.back() ==
'.')
590 double lo = 0, hi = 1;
592 bool valid()
const {
return hi > lo; }
593 double map(
double v,
double p0,
double p1)
const
597 double a = std::log10(lo), b = std::log10(hi);
598 t = (std::log10(v) - a) / (b - a);
600 t = (v - lo) / (hi - lo);
602 return p0 + t * (p1 - p0);
606inline void dataRange(
const std::vector<double> &v,
double &lo,
double &hi)
611 if (std::isfinite(x)) {
612 lo = std::min(lo, x);
613 hi = std::max(hi, x);
617inline std::vector<double> parseVec(
const std::string &json)
619 return figdetail::parseFlatDoubles(json);
623inline std::vector<std::string> parseTickLabelVec(
const std::string &json)
625 std::vector<std::string> labels;
627 while (i < json.size()) {
628 size_t q1 = json.find(
'"', i);
629 if (q1 == std::string::npos)
631 size_t q2 = json.find(
'"', q1 + 1);
632 if (q2 == std::string::npos)
634 labels.push_back(json.substr(q1 + 1, q2 - q1 - 1));
641struct Rect {
double x0, y0, x1, y1; };
643inline void addLine(std::vector<Cmd> &out, std::vector<double> xs,
644 std::vector<double> ys,
Rgb c,
double w,
bool dash)
647 cmd.kind = Cmd::LINE;
648 cmd.xs = std::move(xs);
649 cmd.ys = std::move(ys);
653 out.push_back(std::move(cmd));
656inline void addText(std::vector<Cmd> &out,
const std::string &s,
double x,
657 double y,
double size, Rgb c,
bool rot90 =
false,
658 bool bold =
false,
const std::string &family =
"")
663 cmd.kind = Cmd::TEXT;
671 cmd.fontFamily = family;
672 out.push_back(std::move(cmd));
675inline void addMarker(std::vector<Cmd> &out,
double x,
double y,
double size,
676 Rgb c,
const std::string &kind)
679 cmd.kind = Cmd::MARK;
686 out.push_back(std::move(cmd));
689inline void addPoly(std::vector<Cmd> &out, std::vector<double> xs,
690 std::vector<double> ys, Rgb c,
bool fill)
695 cmd.kind = Cmd::POLY;
696 cmd.xs = std::move(xs);
697 cmd.ys = std::move(ys);
701 out.push_back(std::move(cmd));
708 std::vector<Cmd> &out)
710 const Rgb kAxisCol(0x40, 0x40, 0x44);
711 const Rgb kGridCol(0xd8, 0xd8, 0xdc);
712 const Rgb kTextCol(0x28, 0x28, 0x30);
717 double ml = 64, mr = 14, mt = 20, mb = 48;
718 if (!ax.title.empty())
722 std::string cbSide = ax.colorbarLocation;
723 for (
char &c : cbSide)
724 c = (char)std::tolower((
unsigned char)c);
725 if (cbSide.size() > 7
726 && cbSide.compare(cbSide.size() - 7, 7,
"outside") == 0)
727 cbSide.erase(cbSide.size() - 7);
728 if (cbSide.empty() || cbSide ==
"best")
729 cbSide = ax.colorbarLocation.empty() ?
"" :
"east";
730 if (!cbSide.empty() && cbSide !=
"none") {
731 if (cbSide ==
"east")
733 else if (cbSide ==
"west")
735 else if (cbSide ==
"south")
737 else if (cbSide ==
"north")
740 Rect plot{box.x0 + ml, box.y0 + mt, box.x1 - mr, box.y1 - mb};
741 if (!ax.axisVisible) {
742 plot = Rect{box.x0 + 4, box.y0 + 4, box.x1 - 4, box.y1 - 4};
743 if (!ax.title.empty())
748 auto applyLim = [](
const std::string &json,
double &lo,
double &hi) {
751 auto v = figdetail::parseFlatDoubles(json);
757 double xlo = +INFINITY, xhi = -INFINITY, ylo = +INFINITY, yhi = -INFINITY;
758 std::vector<std::vector<double>> xs(ax.datasets.size()), ys(ax.datasets.size());
759 int imgDatasets = 0, seriesDatasets = 0;
770 auto imageEdges = [](
const std::vector<double> &v,
size_t cells,
771 double &e0,
double &e1,
double &
pad) {
772 if (v.size() < 2 || cells == 0) {
774 e1 = cells > 0 ? cells + 0.5 : 1.5;
778 double span = v.back() - v.front();
779 if (v.size() == cells + 1) {
783 double sp = cells > 1 ? span / (double)(cells - 1) : (span > 0 ? span : 1.0);
784 e0 = v.front() - sp / 2;
785 e1 = v.back() + sp / 2;
787 pad = cells > 1 ? span / (2.0 * (double)(cells - 1)) : (span > 0 ? span / 2 : 0.5);
789 for (
size_t i = 0; i < ax.datasets.size(); ++i) {
793 if (ds.type ==
"surf" || ds.type ==
"mesh" || ds.type ==
"waterfall"
794 || (ds.type ==
"plot3" && !ds.zJson.empty())) {
797 if (ds.type ==
"imagesc" || ds.type ==
"image-rgb") {
799 size_t cols = ds.originalCols ? ds.originalCols : 1;
800 size_t rows = ds.originalRows ? ds.originalRows : 1;
802 imageEdges(parseVec(ds.xJson), cols, e0, e1, pd);
803 xlo = std::min(xlo, e0 - pd);
804 xhi = std::max(xhi, e1 + pd);
805 imageEdges(parseVec(ds.yJson), rows, e0, e1, pd);
806 ylo = std::min(ylo, e0 - pd);
807 yhi = std::max(yhi, e1 + pd);
810 if (ds.type ==
"text")
813 xs[i] = parseVec(ds.xJson);
814 ys[i] = parseVec(ds.yJson);
816 xs[i].resize(ys[i].
size());
817 for (
size_t k = 0; k < ys[i].size(); ++k)
818 xs[i][k] = (
double)(k + 1);
821 dataRange(xs[i], a, b);
822 xlo = std::min(xlo, a);
823 xhi = std::max(xhi, b);
824 dataRange(ys[i], a, b);
825 ylo = std::min(ylo, a);
826 yhi = std::max(yhi, a * 0 + b);
828 if (ds.type ==
"quiver") {
829 auto uv = parseVec(ds.uJson);
830 auto vv = parseVec(ds.vJson);
831 const size_t nq = std::min(xs[i].
size(), ys[i].
size());
832 for (
size_t k = 0; k < nq && k < uv.size(); ++k) {
833 xlo = std::min(xlo, xs[i][k] + uv[k]);
834 xhi = std::max(xhi, xs[i][k] + uv[k]);
836 for (
size_t k = 0; k < nq && k < vv.size(); ++k) {
837 ylo = std::min(ylo, ys[i][k] + vv[k]);
838 yhi = std::max(yhi, ys[i][k] + vv[k]);
842 auto ext = [&](
const std::string &j,
bool pos) {
843 auto e = parseVec(j);
846 for (
size_t k = 0; k < e.size() && k < ys[i].
size(); ++k) {
847 ylo = std::min(ylo, ys[i][k] - (pos ? 0 : e[k]));
848 yhi = std::max(yhi, ys[i][k] + (pos ? e[k] : 0));
852 ext(ds.eNegJson,
false);
853 ext(ds.ePosJson,
true);
854 if (ds.type ==
"stem") {
855 ylo = std::min(ylo, 0.0);
856 yhi = std::max(yhi, 0.0);
861 xhi = xhi > xlo ? xhi : xlo + 1;
868 applyLim(ax.xlimJson, xlo, xhi);
869 applyLim(ax.ylimJson, ylo, yhi);
871 auto pad = [](
double &lo,
double &hi,
const std::string &lim) {
874 double d = (hi - lo) * 0.05;
882 bool imageOnly = imgDatasets > 0 && seriesDatasets == 0;
884 pad(xlo, xhi, ax.xlimJson);
885 pad(ylo, yhi, ax.ylimJson);
887 if (ax.xscale ==
"log") {
888 xlo = std::max(xlo, 1e-12);
892 if (ax.yscale ==
"log") {
893 ylo = std::max(ylo, 1e-12);
901 if (ax.axisMode ==
"image" || ax.axisMode ==
"equal") {
902 double dataAspect = (xhi - xlo) / (yhi - ylo);
903 double pxAspect = (plot.x1 - plot.x0) / (plot.y1 - plot.y0);
904 if (dataAspect > 0 && pxAspect > 0) {
905 if (pxAspect > dataAspect) {
906 double w = (plot.y1 - plot.y0) * dataAspect;
907 double cx = (plot.x0 + plot.x1) / 2;
908 plot.x0 = cx - w / 2;
909 plot.x1 = cx + w / 2;
911 double h = (plot.x1 - plot.x0) / dataAspect;
912 double cy = (plot.y0 + plot.y1) / 2;
913 plot.y0 = cy - h / 2;
914 plot.y1 = cy + h / 2;
919 AxisRange XR{xlo, xhi, ax.xscale ==
"log"};
920 AxisRange YR{ylo, yhi, ax.yscale ==
"log"};
921 double px0 = plot.x0, px1 = plot.x1;
922 double py0 = plot.y1, py1 = plot.y0;
923 if (ax.xDir ==
"reverse")
925 if (ax.yDir ==
"reverse")
927 auto X = [&](
double v) {
return XR.map(v, px0, px1); };
928 auto Y = [&](
double v) {
return YR.map(v, py0, py1); };
931 if (ax.gridMajor && ax.gridUserTouched) {
932 auto gx = XR.log ? logTicks(xlo, xhi) : niceTicks(xlo, xhi);
933 auto gy = YR.log ? logTicks(ylo, yhi) : niceTicks(ylo, yhi);
935 addLine(out, {X(v), X(v)}, {plot.y0, plot.y1}, kGridCol, 1,
false);
937 addLine(out, {plot.x0, plot.x1}, {Y(v), Y(v)}, kGridCol, 1,
false);
941 for (
size_t i = 0; i < ax.datasets.size(); ++i) {
943 if (!ds.visible || (ds.type !=
"imagesc" && ds.type !=
"image-rgb"))
945 size_t cols = ds.originalCols ? ds.originalCols : 1;
946 size_t rows = ds.originalRows ? ds.originalRows : 1;
948 imageEdges(parseVec(ds.xJson), cols, e0, e1, pd);
949 double xe0 = e0, xe1 = e1;
950 imageEdges(parseVec(ds.yJson), rows, e0, e1, pd);
951 double ye0 = e0, ye1 = e1;
954 if (ds.type ==
"imagesc") {
955 c.img = ds.zQuantized;
960 auto cl = figdetail::parseFlatDoubles(ax.climJson);
961 if (cl.size() == 2 && cl[0] < cl[1]) {
968 c.cmap = ax.colormapName.empty() ?
"parula" : ax.colormapName;
969 c.cmapCustom = ax.customColormapJson;
980 c.xs = {X(xe0), X(xe1)};
981 c.ys = {Y(ye0), Y(ye1)};
982 out.push_back(std::move(c));
994 for (
const auto &ds : ax.datasets)
996 && (ds.type ==
"surf" || ds.type ==
"mesh"
997 || ds.type ==
"waterfall"
998 || (ds.type ==
"plot3" && !ds.zJson.empty()))
999 && !ds.zJson.empty())
1003 double xlo3 = 1e300, xhi3 = -1e300, ylo3 = 1e300,
1004 yhi3 = -1e300, zlo3 = 1e300, zhi3 = -1e300;
1005 auto acc = [](
double &lo,
double &hi,
double v) {
1006 if (!std::isfinite(v))
1008 lo = std::min(lo, v);
1009 hi = std::max(hi, v);
1012 std::vector<double> z;
1013 std::vector<double> gx;
1014 std::vector<double> gy;
1017 std::vector<SurfGrid> grids;
1018 for (
const auto &ds : ax.datasets) {
1019 if (!ds.visible || ds.zJson.empty())
1021 if (ds.type ==
"surf" || ds.type ==
"waterfall") {
1022 auto xv = parseVec(ds.xJson);
1023 auto yv = parseVec(ds.yJson);
1024 std::vector<double> zf;
1025 size_t r = 0, c = 0;
1026 parseNestedDoubles(ds.zJson, zf, r, c);
1027 if (xv.empty() || yv.empty() || !r || !c)
1030 g.z = std::move(zf);
1031 for (
size_t cc = 0; cc < c; ++cc)
1032 g.gx.push_back(xv[std::min(cc, xv.size() - 1)]);
1033 for (
size_t rr = 0; rr < r; ++rr)
1034 g.gy.push_back(yv[std::min(rr, yv.size() - 1)]);
1037 for (
size_t rr = 0; rr < r; ++rr)
1038 for (
size_t cc = 0; cc < c; ++cc) {
1039 acc(xlo3, xhi3, g.gx[cc]);
1040 acc(ylo3, yhi3, g.gy[rr]);
1041 acc(zlo3, zhi3, g.z[rr * c + cc]);
1043 grids.push_back(std::move(g));
1044 }
else if (ds.type ==
"mesh" || ds.type ==
"plot3") {
1045 auto xv = parseVec(ds.xJson);
1046 auto yv = parseVec(ds.yJson);
1047 auto zv = parseVec(ds.zJson);
1048 size_t n = std::min(std::min(xv.size(), yv.size()),
1050 for (
size_t k = 0; k < n; ++k) {
1051 acc(xlo3, xhi3, xv[k]);
1052 acc(ylo3, yhi3, yv[k]);
1053 acc(zlo3, zhi3, zv[k]);
1057 if (!(xhi3 > xlo3)) { xlo3 = 0; xhi3 = 1; }
1058 if (!(yhi3 > ylo3)) { ylo3 = 0; yhi3 = 1; }
1059 if (!(zhi3 > zlo3)) { zlo3 = 0; zhi3 = 1; }
1060 applyLim(ax.xlimJson, xlo3, xhi3);
1061 applyLim(ax.ylimJson, ylo3, yhi3);
1062 applyLim(ax.zlimJson, zlo3, zhi3);
1064 double clo3 = zlo3, chi3 = zhi3;
1066 auto cl = figdetail::parseFlatDoubles(ax.climJson);
1067 if (cl.size() == 2 && cl[0] < cl[1]) {
1072 std::vector<Rgb> custom3;
1073 if (!ax.customColormapJson.empty())
1074 custom3 = parseCustomPalette(ax.customColormapJson);
1076 const std::uint8_t *pal3 =
1078 ? namedPalette(ax.colormapName.empty()
1079 ? std::string(
"parula")
1080 : ax.colormapName, nPal3)
1082 if (!custom3.empty())
1083 nPal3 = (int)custom3.size();
1084 auto zColor = [&](
double z) -> Rgb {
1085 if (!std::isfinite(z))
1087 double t = (z - clo3) / (chi3 - clo3);
1090 int e = (int)std::lround(t * (nPal3 - 1));
1092 if (e >= nPal3) e = nPal3 - 1;
1093 return pal3 ? Rgb(pal3[e * 3], pal3[e * 3 + 1],
1095 : custom3[(size_t)e];
1098 double azDeg = -37.5, elDeg = 30;
1100 auto vv = figdetail::parseFlatDoubles(ax.viewJson);
1101 if (vv.size() == 2) {
1106 const double az = azDeg * 3.14159265358979323846 / 180.0;
1107 const double el = elDeg * 3.14159265358979323846 / 180.0;
1108 const double sAz = std::sin(az), cAz = std::cos(az);
1109 const double sEl = std::sin(el), cEl = std::cos(el);
1110 auto norm3 = [&](
double v,
double lo,
double hi) {
1111 return hi > lo ? (v - lo) / (hi - lo) - 0.5 : 0.0;
1120 auto proj = [&](
double x,
double y,
double z,
1121 double &sx,
double &sy,
double &depth) {
1122 double xn = norm3(x, xlo3, xhi3);
1123 double yn = norm3(y, ylo3, yhi3);
1124 double zn = norm3(z, zlo3, zhi3);
1125 sx = -xn * sAz + yn * cAz;
1126 sy = (xn * cAz + yn * sAz) * sEl + zn * cEl;
1127 depth = zn * sEl - cEl * (xn * cAz + yn * sAz);
1130 double minSx = 1e300, maxSx = -1e300, minSy = 1e300,
1134 proj(corner & 1 ? xhi3 : xlo3, corner & 2 ? yhi3 : ylo3,
1135 corner & 4 ? zhi3 : zlo3, sx, sy, d);
1136 minSx = std::min(minSx, sx);
1137 maxSx = std::max(maxSx, sx);
1138 minSy = std::min(minSy, sy);
1139 maxSy = std::max(maxSy, sy);
1141 double spanX = std::max(1e-9, maxSx - minSx);
1142 double spanY = std::max(1e-9, maxSy - minSy);
1143 double availW = (plot.x1 - plot.x0) * 0.92;
1144 double availH = (plot.y1 - plot.y0) * 0.92;
1145 double sc = std::min(availW / spanX, availH / spanY);
1146 auto PX = [&](
double sx) {
1147 return (plot.x0 + plot.x1) / 2
1148 + (sx - (minSx + maxSx) / 2) * sc;
1150 auto PY = [&](
double sy) {
1151 return (plot.y0 + plot.y1) / 2
1152 - (sy - (minSy + maxSy) / 2) * sc;
1155 if (ax.axisVisible) {
1156 const double bx[2] = {xlo3, xhi3}, by[2] = {ylo3, yhi3},
1157 bz[2] = {zlo3, zhi3};
1158 for (
int axis = 0; axis < 3; ++axis)
1159 for (
int f1 = 0; f1 <= 1; ++f1)
1160 for (
int f2 = 0; f2 <= 1; ++f2) {
1161 double q0[3], q1[3];
1162 int o1 = (axis + 1) % 3, o2 = (axis + 2) % 3;
1163 double fd0[3] = {0, 0, 0}, fd1[3] = {0, 0, 0};
1164 fd0[axis] = 0; fd1[axis] = 1;
1165 fd0[o1] = f1; fd1[o1] = f1;
1166 fd0[o2] = f2; fd1[o2] = f2;
1167 double w0[3] = {bx[(int)fd0[0]], by[(
int)fd0[1]],
1169 double w1[3] = {bx[(int)fd1[0]], by[(
int)fd1[1]],
1172 double s0x, s0y, d0, s1x, s1y, d1;
1173 proj(w0[0], w0[1], w0[2], s0x, s0y, d0);
1174 proj(w1[0], w1[1], w1[2], s1x, s1y, d1);
1175 addLine(out, {PX(s0x), PX(s1x)},
1176 {PY(s0y), PY(s1y)}, kAxisCol, 0.75,
1181 for (
const auto &g : grids) {
1182 if (g.r < 2 || g.c < 2)
1185 double px[4], py[4], depth;
1188 std::vector<Q> quads;
1189 for (
size_t rr = 0; rr + 1 < g.r; ++rr)
1190 for (
size_t cc = 0; cc + 1 < g.c; ++cc) {
1191 double z00 = g.z[rr * g.c + cc];
1192 double z10 = g.z[(rr + 1) * g.c + cc];
1193 double z01 = g.z[rr * g.c + cc + 1];
1194 double z11 = g.z[(rr + 1) * g.c + cc + 1];
1195 if (!std::isfinite(z00) || !std::isfinite(z10)
1196 || !std::isfinite(z01) || !std::isfinite(z11))
1198 double xs[4] = {g.gx[cc], g.gx[cc + 1], g.gx[cc + 1],
1200 double ys[4] = {g.gy[rr], g.gy[rr], g.gy[rr + 1],
1202 double zs[4] = {z00, z01, z11, z10};
1205 for (
int k = 0; k < 4; ++k) {
1207 proj(xs[k], ys[k], zs[k], sx, sy, d);
1213 q.col = zColor(z00);
1216 std::sort(quads.begin(), quads.end(),
1217 [](
const Q &a,
const Q &b) {
1218 return a.depth < b.depth;
1220 const Rgb edge3(33, 33, 33);
1221 for (
const auto &q : quads) {
1222 addPoly(out, {q.px[0], q.px[1], q.px[2], q.px[3]},
1223 {q.py[0], q.py[1], q.py[2], q.py[3]}, q.col,
1227 {q.px[0], q.px[1], q.px[2], q.px[3],
1229 {q.py[0], q.py[1], q.py[2], q.py[3],
1230 q.py[0]}, edge3, 0.75,
false);
1235 for (
const auto &ds : ax.datasets) {
1236 if (!ds.visible || ds.zJson.empty())
1238 if (ds.type !=
"mesh" && ds.type !=
"plot3"
1239 && ds.type !=
"waterfall")
1241 if (ds.type ==
"waterfall") {
1242 auto xv = parseVec(ds.xJson);
1243 auto yv = parseVec(ds.yJson);
1244 std::vector<double> zf;
1246 parseNestedDoubles(ds.zJson, zf, r, c);
1249 for (
size_t rr = 0; rr < r; ++rr)
1250 for (
size_t cc = 0; cc + 1 < c; ++cc) {
1251 double z0 = zf[rr * c + cc];
1252 double z1 = zf[rr * c + cc + 1];
1253 if (!std::isfinite(z0) || !std::isfinite(z1))
1255 double s0x, s0y, d0, s1x, s1y, d1;
1256 proj(xv[std::min(cc, xv.size() - 1)],
1257 yv[std::min(rr, yv.size() - 1)], z0, s0x,
1259 proj(xv[std::min(cc + 1, xv.size() - 1)],
1260 yv[std::min(rr, yv.size() - 1)], z1, s1x,
1262 addLine(out, {PX(s0x), PX(s1x)},
1263 {PY(s0y), PY(s1y)}, zColor(z0), 1.2,
1268 auto xv = parseVec(ds.xJson);
1269 auto yv = parseVec(ds.yJson);
1270 auto zv = parseVec(ds.zJson);
1271 size_t n = std::min(std::min(xv.size(), yv.size()),
1273 StyleParts st3 = parseStyle(ds.style);
1274 for (
size_t k = 0; k + 1 < n; ++k) {
1275 if (!std::isfinite(xv[k]) || !std::isfinite(yv[k])
1276 || !std::isfinite(zv[k]) || !std::isfinite(xv[k + 1])
1277 || !std::isfinite(yv[k + 1])
1278 || !std::isfinite(zv[k + 1]))
1280 double s0x, s0y, d0, s1x, s1y, d1;
1281 proj(xv[k], yv[k], zv[k], s0x, s0y, d0);
1282 proj(xv[k + 1], yv[k + 1], zv[k + 1], s1x, s1y, d1);
1283 Rgb col = ds.type ==
"mesh"
1285 : (st3.hasColor ? st3.color
1286 : Rgb(0x7f, 0xd9, 0x9a));
1287 addLine(out, {PX(s0x), PX(s1x)}, {PY(s0y), PY(s1y)},
1295 size_t seriesIdx = 0;
1296 for (
size_t i = 0; i < ax.datasets.size(); ++i) {
1298 if (!ds.visible || xs[i].empty())
1300 if (ds.type ==
"surf" || ds.type ==
"mesh" || ds.type ==
"waterfall"
1301 || (ds.type ==
"plot3" && !ds.zJson.empty()))
1303 StyleParts st = parseStyle(ds.style);
1305 st.color = palette()[seriesIdx % palette().size()];
1308 double lw = ds.lineWidth > 0 ? ds.lineWidth : (st.lineStyle ==
"-" || st.lineStyle.empty() ? 1.4 : 1.4);
1309 double ms = ds.markerSize > 0 ? ds.markerSize * 1.4 : 6.0;
1310 bool dashed = st.lineStyle ==
"--" || st.lineStyle ==
":" || st.lineStyle ==
"-.";
1311 const size_t n = std::min(xs[i].
size(), ys[i].
size());
1312 const std::string &t = ds.type;
1314 if (t ==
"line" || t ==
"comet" || t ==
"animatedline") {
1318 std::vector<double> px, py;
1319 auto flushRun = [&]() {
1320 if (st.lineStyle !=
"none" && px.size() >= 2)
1321 addLine(out, px, py, col, lw, dashed);
1325 for (
size_t k = 0; k < n; ++k) {
1326 if (std::isfinite(xs[i][k]) && std::isfinite(ys[i][k])) {
1327 px.push_back(X(xs[i][k]));
1328 py.push_back(Y(ys[i][k]));
1334 if (!st.marker.empty())
1335 for (
size_t k = 0; k < px.size(); ++k)
1336 addMarker(out, px[k], py[k], ms, col, st.marker);
1337 }
else if (t ==
"stairs") {
1338 std::vector<double> px, py;
1339 for (
size_t k = 0; k < n; ++k) {
1340 if (k && px.size()) {
1341 px.push_back(X(xs[i][k]));
1342 py.push_back(py.back());
1344 px.push_back(X(xs[i][k]));
1345 py.push_back(Y(ys[i][k]));
1348 addLine(out, px, py, col, lw, dashed);
1349 if (!st.marker.empty())
1350 for (
size_t k = 0; k < n; ++k)
1351 addMarker(out, X(xs[i][k]), Y(ys[i][k]), ms, col, st.marker);
1352 }
else if (t ==
"stem") {
1354 if (base >= YR.lo && base <= YR.hi)
1355 for (
size_t k = 0; k < n; ++k)
1356 addLine(out, {X(xs[i][k]), X(xs[i][k])},
1357 {Y(base), Y(ys[i][k])}, col, lw, dashed);
1358 for (
size_t k = 0; k < n; ++k)
1359 addMarker(out, X(xs[i][k]), Y(ys[i][k]), ms,
1360 col, st.marker.empty() ?
"o" : st.marker);
1361 }
else if (t ==
"scatter" || t ==
"bubble") {
1362 std::vector<double> sz;
1363 if (!ds.sizeJson.empty())
1364 sz = parseVec(ds.sizeJson);
1365 for (
size_t k = 0; k < n; ++k)
1366 addMarker(out, X(xs[i][k]), Y(ys[i][k]),
1367 sz.empty() ? ms : std::max(3.0, std::sqrt(sz[k] / 3.14) * 1.6),
1369 }
else if (t ==
"histogram") {
1374 Rgb face = st.hasFaceColor ? st.faceColor : Rgb(0x00, 0x72, 0xbd);
1375 Rgb
edge = st.edgeColor;
1376 if (st.faceAlpha < 1) {
1381 double a = std::min(1.0, std::max(0.0, st.faceAlpha));
1382 face = Rgb((std::uint8_t)std::lround(a * face.r + (1 - a) * 255),
1383 (std::uint8_t)std::lround(a * face.g + (1 - a) * 255),
1384 (std::uint8_t)std::lround(a * face.b + (1 - a) * 255));
1386 double bw = st.binWidth;
1387 if (!(bw > 0) && n > 1)
1388 bw = std::abs(xs[i][1] - xs[i][0]);
1389 for (
size_t k = 0; k < n; ++k) {
1390 double x0p = X(xs[i][k] - bw / 2);
1391 double x1p = X(xs[i][k] + bw / 2);
1392 double yBase = Y(std::min(std::max(0.0, YR.lo), YR.hi));
1393 double yTop = Y(ys[i][k]);
1394 addPoly(out, {x0p, x1p, x1p, x0p},
1395 {yBase, yBase, yTop, yTop}, face,
true);
1396 addLine(out, {x0p, x1p}, {yTop, yTop},
edge, 0.75,
false);
1398 }
else if (t ==
"bar") {
1400 const int groups = (int)n;
1403 for (
size_t k = 0; k < n; ++k) {
1404 double slot = groups > 1 ? (xhi - xlo) / (groups - 1) : 1;
1405 double w = std::min(slot * 0.8, (plot.x1 - plot.x0) * 0.05) /
series;
1406 double x0p = X(xs[i][k]) - w / 2;
1407 double yBase = Y(std::min(std::max(0.0, YR.lo), YR.hi));
1408 double yTop = Y(ys[i][k]);
1409 addPoly(out, {x0p, x0p + w, x0p + w, x0p},
1410 {yBase, yBase, yTop, yTop}, col,
true);
1412 }
else if (t ==
"quiver") {
1416 auto uv = parseVec(ds.uJson);
1417 auto vv = parseVec(ds.vJson);
1418 for (
size_t k = 0; k < n; ++k) {
1419 double u = k < uv.size() ? uv[k] : 0.0;
1420 double v = k < vv.size() ? vv[k] : 0.0;
1421 if (!std::isfinite(xs[i][k]) || !std::isfinite(ys[i][k])
1422 || !std::isfinite(u) || !std::isfinite(v))
1424 double x0 = X(xs[i][k]), y0 = Y(ys[i][k]);
1425 double x1 = X(xs[i][k] + u), y1 = Y(ys[i][k] + v);
1426 addLine(out, {x0, x1}, {y0, y1}, col, 1.2,
false);
1427 double dx = x1 - x0, dy = y1 - y0;
1428 double len = std::hypot(dx, dy);
1431 double hl = std::max(4.0, len * 0.12);
1432 double ux = dx / len, uy = dy / len;
1435 double c1 = std::cos(a1), s1 = std::sin(a1);
1436 double bxl = x1 - hl * (ux * c1 - uy * s1);
1437 double byl = y1 - hl * (uy * c1 + ux * s1);
1438 double bxr = x1 - hl * (ux * c1 + uy * s1);
1439 double byr = y1 - hl * (uy * c1 - ux * s1);
1440 addLine(out, {bxl, x1}, {byl, y1}, col, 1.2,
false);
1441 addLine(out, {bxr, x1}, {byr, y1}, col, 1.2,
false);
1443 }
else if (t ==
"polygon") {
1446 std::vector<double> px, py;
1447 auto flushRing = [&]() {
1449 addPoly(out, px, py, col,
true);
1453 for (
size_t k = 0; k < n; ++k) {
1454 if (std::isfinite(xs[i][k]) && std::isfinite(ys[i][k])) {
1455 px.push_back(X(xs[i][k]));
1456 py.push_back(Y(ys[i][k]));
1462 }
else if (t ==
"area") {
1463 std::vector<double> px, py;
1464 double yBase = Y(std::min(std::max(0.0, YR.lo), YR.hi));
1465 px.push_back(X(xs[i].front()));
1466 py.push_back(yBase);
1467 for (
size_t k = 0; k < n; ++k) {
1468 px.push_back(X(xs[i][k]));
1469 py.push_back(Y(ys[i][k]));
1471 px.push_back(X(xs[i][n - 1]));
1472 py.push_back(yBase);
1473 addPoly(out, px, py, col,
true);
1474 }
else if (t ==
"errorbar") {
1475 auto sym = parseVec(ds.eJson);
1476 auto eneg = parseVec(ds.eNegJson);
1477 auto epos = parseVec(ds.ePosJson);
1478 for (
size_t k = 0; k < n; ++k) {
1479 double loV = ys[i][k], hiV = ys[i][k];
1480 if (k < sym.size()) {
1484 if (k < eneg.size())
1485 loV = ys[i][k] - eneg[k];
1486 if (k < epos.size())
1487 hiV = ys[i][k] + epos[k];
1488 double pxk = X(xs[i][k]);
1489 addLine(out, {pxk, pxk}, {Y(loV), Y(hiV)}, col, 1,
false);
1491 addLine(out, {pxk - cap, pxk + cap}, {Y(loV), Y(loV)}, col, 1,
false);
1492 addLine(out, {pxk - cap, pxk + cap}, {Y(hiV), Y(hiV)}, col, 1,
false);
1494 if (!st.marker.empty())
1495 for (
size_t k = 0; k < n; ++k)
1496 addMarker(out, X(xs[i][k]), Y(ys[i][k]), ms, col, st.marker);
1505 for (
size_t i = 0; i < ax.datasets.size(); ++i) {
1507 if (!ds.visible || ds.type !=
"text")
1509 auto xv = parseVec(ds.xJson);
1510 auto yv = parseVec(ds.yJson);
1511 if (xv.empty() || yv.empty())
1513 TextExtras te = parseTextExtras(ds.style);
1514 double scale = std::max(1.0, (
double)std::lround(te.fontSize / 7.0));
1515 double w = 6.0 * scale * ds.label.size();
1516 double x = X(xv[0]) - (te.halign == 1 ? w / 2
1517 : te.halign == 2 ? w : 0.0);
1520 double y = Y(yv[0]) - 3.5 * scale;
1521 addText(out, ds.label, x, y, te.fontSize, te.color,
false,
false,
1528 const double boxW = std::max(0.75, ax.lineWidth);
1529 const double tickFS = ax.fontSize;
1530 const double labelFS = ax.fontSize * 1.1;
1531 const bool fBold = ax.fontWeight ==
"bold";
1532 const std::string &fam = ax.fontName;
1533 if (ax.axisVisible) {
1534 addLine(out, {plot.x0, plot.x1, plot.x1, plot.x0, plot.x0},
1535 {plot.y0, plot.y0, plot.y1, plot.y1, plot.y0}, kAxisCol, boxW,
1537 auto tx = !ax.xTicksJson.empty()
1538 ? parseVec(ax.xTicksJson)
1539 : (XR.
log ? logTicks(xlo, xhi) : niceTicks(xlo, xhi));
1540 auto ty = !ax.yTicksJson.empty()
1541 ? parseVec(ax.yTicksJson)
1542 : (YR.log ? logTicks(ylo, yhi) : niceTicks(ylo, yhi));
1543 std::vector<std::string> txLab, tyLab;
1544 if (!ax.xTickLabelsJson.empty())
1545 txLab = parseTickLabelVec(ax.xTickLabelsJson);
1546 if (!ax.yTickLabelsJson.empty())
1547 tyLab = parseTickLabelVec(ax.yTickLabelsJson);
1548 for (
size_t k = 0; k < tx.size(); ++k) {
1549 const double v = tx[k];
1551 addLine(out, {p, p}, {plot.y1, plot.y1 + 4}, kAxisCol, boxW,
false);
1552 std::string lab = k < txLab.size()
1555 if (k >= txLab.size() && !ax.xTickFormat.empty()
1556 && ax.xTickFormat.size() > 1 && ax.xTickFormat[0] ==
'%') {
1558 std::snprintf(b,
sizeof b, ax.xTickFormat.c_str(), v);
1561 addText(out, lab, p - 3.0 * lab.size(), plot.y1 + 8, tickFS,
1562 kTextCol,
false, fBold, fam);
1564 for (
size_t k = 0; k < ty.size(); ++k) {
1565 const double v = ty[k];
1567 addLine(out, {plot.x0 - 4, plot.x0}, {p, p}, kAxisCol, boxW,
false);
1568 std::string lab = k < tyLab.size()
1571 if (k >= tyLab.size() && !ax.yTickFormat.empty()
1572 && ax.yTickFormat.size() > 1 && ax.yTickFormat[0] ==
'%') {
1574 std::snprintf(b,
sizeof b, ax.yTickFormat.c_str(), v);
1577 double w = 6.0 * lab.size();
1578 addText(out, lab, plot.x0 - 8 - w, p + 4, tickFS, kTextCol,
false,
1581 if (!ax.xlabel.empty())
1582 addText(out, ax.xlabel, (plot.x0 + plot.x1) / 2 - 3.2 * ax.xlabel.size(),
1583 plot.y1 + 34, labelFS, kTextCol,
false, fBold, fam);
1584 if (!ax.ylabel.empty())
1585 addText(out, ax.ylabel, plot.x0 - 46,
1586 (plot.y0 + plot.y1) / 2 + 3.2 * ax.ylabel.size(), labelFS,
1587 kTextCol,
true, fBold, fam);
1589 if (!ax.title.empty())
1590 addText(out, ax.title, (plot.x0 + plot.x1) / 2 - 3.2 * ax.title.size(),
1591 plot.y0 - 18, labelFS + 2, kTextCol,
false, fBold, fam);
1597 if (!cbSide.empty() && cbSide !=
"none") {
1598 double lo = 0, hi = 1;
1599 auto cl = figdetail::parseFlatDoubles(ax.climJson);
1600 if (cl.size() == 2 && cl[0] < cl[1]) {
1604 for (
const auto &ds : ax.datasets)
1605 if (!ds.zQuantized.empty()) {
1611 std::vector<Rgb> customPal;
1612 if (!ax.customColormapJson.empty())
1613 customPal = parseCustomPalette(ax.customColormapJson);
1615 const std::uint8_t *pal =
1617 ? namedPalette(ax.colormapName.empty()
1618 ? std::string(
"parula")
1622 auto entryColor = [&](
double t) -> Rgb {
1625 int e = (int)std::lround(t * (nPal - 1));
1627 if (e >= nPal) e = nPal - 1;
1629 return Rgb(pal[e * 3], pal[e * 3 + 1], pal[e * 3 + 2]);
1630 return customPal[(size_t)e];
1632 const double cbFS = 9;
1633 const double thick = 14;
1634 bool vert = cbSide ==
"east" || cbSide ==
"west";
1635 double sx, sy, sw, sh;
1637 sx = cbSide ==
"west" ? plot.x0 - 8 - thick : plot.x1 + 8;
1640 sh = plot.y1 - plot.y0;
1643 sw = plot.x1 - plot.x0;
1645 sy = cbSide ==
"north" ? plot.y0 - 8 - thick : plot.y1 + 8;
1647 int pw = std::max(1, (
int)std::lround(sw));
1648 int ph = std::max(1, (
int)std::lround(sh));
1649 std::vector<std::uint8_t> grad((
size_t)pw * ph * 4, 255);
1650 for (
int py = 0; py < ph; ++py)
1651 for (
int px = 0; px < pw; ++px) {
1652 double t = vert ? 1.0 - (py + 0.5) / ph
1654 Rgb c = entryColor(t);
1655 size_t o = ((size_t)py * pw + px) * 4;
1661 strip.kind = Cmd::IMAGE;
1662 strip.rgbDirect =
true;
1665 strip.img = std::move(grad);
1666 strip.xs = {sx, sx + pw};
1667 strip.ys = {sy, sy + ph};
1668 out.push_back(std::move(strip));
1670 addLine(out, {sx, sx + sw, sx + sw, sx, sx},
1671 {sy, sy, sy + sh, sy + sh, sy}, kAxisCol, 0.75,
false);
1672 auto ticks = niceTicks(lo, hi, vert ? 10 : 5);
1673 for (
double v : ticks) {
1674 double t = (v - lo) / (hi - lo);
1675 if (t < -1e-9 || t > 1 + 1e-9)
1677 std::string lab = tickLabel(v);
1678 double w = 6.0 * lab.size();
1680 double pyv = sy + sh - t * sh;
1681 bool west = cbSide ==
"west";
1683 {west ? sx - 4 : sx + sw, west ? sx : sx + sw + 4},
1684 {pyv, pyv}, kAxisCol, 0.75,
false);
1685 addText(out, lab, west ? sx - 7 - w : sx + sw + 7,
1686 pyv - 3.5, cbFS, kTextCol);
1688 double pxv = sx + t * sw;
1689 bool north = cbSide ==
"north";
1690 addLine(out, {pxv, pxv},
1691 {north ? sy - 4 : sy + sh, north ? sy : sy + sh + 4},
1692 kAxisCol, 0.75,
false);
1693 addText(out, lab, pxv - w / 2,
1694 north ? sy - 9 - 7 : sy + sh + 9, cbFS, kTextCol);
1700 if (!ax.legendLabels.empty() && !ax.legendHidden) {
1701 double rowH = 16, pad2 = 6;
1702 double lw2 = 22, tw = 0;
1703 for (
auto &s2 : ax.legendLabels)
1704 tw = std::max(tw, 6.0 * s2.size());
1705 double wLegend = pad2 + lw2 + 6 + tw + pad2;
1706 double hLegend = pad2 + rowH * ax.legendLabels.size() + pad2;
1707 double lx = plot.x1 - 10 - wLegend, ly = plot.y0 + 10;
1708 if (ax.legendLocation.find(
"south") != std::string::npos)
1709 ly = plot.y1 - 10 - hLegend;
1710 if (ax.legendLocation.find(
"west") != std::string::npos)
1712 if (ax.legendLocation.find(
"outside") != std::string::npos)
1714 addPoly(out, {lx, lx + wLegend, lx + wLegend, lx},
1715 {ly, ly, ly + hLegend, ly + hLegend}, kAxisCol,
false);
1716 for (
size_t k = 0; k < ax.legendLabels.size(); ++k) {
1717 double ry = ly + pad2 + rowH * k + rowH / 2;
1718 Rgb c = palette()[k % palette().size()];
1719 for (
size_t d = 0; d < ax.datasets.size(); ++d) {
1720 if (ax.datasets[d].label == ax.legendLabels[k]) {
1721 StyleParts sp = parseStyle(ax.datasets[d].style);
1727 addLine(out, {lx + pad2, lx + pad2 + lw2}, {ry, ry}, c, 1.6,
false);
1728 addText(out, ax.legendLabels[k], lx + pad2 + lw2 + 6, ry + 4, 11,
1736inline std::vector<Cmd> renderFigureDisplay(
const FigureState &fig,
int W,
1739 std::vector<Cmd> out;
1740 int rows = std::max(1, fig.subplotRows);
1741 int cols = std::max(1, fig.subplotCols);
1743 std::vector<const AxesState *> ordered;
1744 for (
int p = 1; p <= rows * cols; ++p)
1745 for (
const auto &ax : fig.axes)
1746 if (ax.subplotIndex == p) {
1747 ordered.push_back(&ax);
1750 if (ordered.empty())
1751 for (
const auto &ax : fig.axes)
1752 ordered.push_back(&ax);
1753 for (
size_t i = 0; i < ordered.size(); ++i) {
1754 int r = rows > 1 || cols > 1 ? (int)i / cols : 0;
1755 int c = rows > 1 || cols > 1 ? (int)i % cols : 0;
1757 box.x0 = 10 + c * (W - 20.0) / cols + 6;
1758 box.x1 = 10 + (c + 1) * (W - 20.0) / cols - 6;
1759 box.y0 = 10 + r * (H - 20.0) / rows + 6;
1760 box.y1 = 10 + (r + 1) * (H - 20.0) / rows - 6;
1761 if (fig.subplotRows == 0) {
1762 box = Rect{10, 10, (double)W - 10, (
double)H - 10};
1764 if (!fig.superTitle.empty() && r == 0 && c == (cols - 1) / 2) {
1765 addText(out, fig.superTitle,
1766 W / 2.0 - 3.4 * fig.superTitle.size(), 6, 13,
1767 Rgb(0x28, 0x28, 0x30));
1770 renderAxes(fig, *ordered[i], box, out);
1776 for (
const auto &an : fig.annotations) {
1777 Rgb col = an.colorHex.empty() ? Rgb(33, 33, 33)
1778 : Rgb::fromHex(an.colorHex);
1779 double ax0 = an.x1 * W, ay0 = (1.0 - an.y1) * H;
1780 double ax1 = an.x2 * W, ay1 = (1.0 - an.y2) * H;
1781 if (an.type ==
"line" || an.type ==
"arrow"
1782 || an.type ==
"textarrow") {
1783 addLine(out, {ax0, ax1}, {ay0, ay1}, col, 0.75,
false);
1784 if (an.type !=
"line") {
1785 double dx = ax1 - ax0, dy = ay1 - ay0;
1786 double len = std::hypot(dx, dy);
1788 double hl = std::max(4.0, len * 0.12);
1789 double ux = dx / len, uy = dy / len;
1791 double c1 = std::cos(a1), s1 = std::sin(a1);
1793 {ax1 - hl * (ux * c1 - uy * s1), ax1},
1794 {ay1 - hl * (uy * c1 + ux * s1), ay1},
1797 {ax1 - hl * (ux * c1 + uy * s1), ax1},
1798 {ay1 - hl * (uy * c1 - ux * s1), ay1},
1802 if (an.type ==
"textarrow" && !an.text.empty()) {
1803 double w = 6.0 * (an.fontSize / 10.0) * an.text.size();
1804 addText(out, an.text, ax0 - w - 6, ay0 - 3.5,
1807 }
else if (an.type ==
"rectangle" || an.type ==
"textbox") {
1808 double x0p = ax0, y0p = ay1, x1p = ax1, y1p = ay0;
1809 if (!an.faceHex.empty()) {
1810 Rgb fc = Rgb::fromHex(an.faceHex);
1811 addPoly(out, {x0p, x1p, x1p, x0p}, {y0p, y0p, y1p, y1p},
1814 addLine(out, {x0p, x1p, x1p, x0p, x0p},
1815 {y0p, y0p, y1p, y1p, y0p}, col, 0.75,
false);
1816 if (an.type ==
"textbox" && !an.text.empty())
1817 addText(out, an.text, x0p + 6, (y0p + y1p) / 2 - 3.5,
1818 an.fontSize, Rgb(0x28, 0x28, 0x30));
1819 }
else if (an.type ==
"ellipse") {
1820 double cx = (ax0 + ax1) / 2, cy = (ay0 + ay1) / 2;
1821 double rx = std::abs(ax1 - ax0) / 2, ry = std::abs(ay1 - ay0) / 2;
1822 std::vector<double> ex, ey;
1823 for (
int k = 0; k <= 64; ++k) {
1824 double t = 2 * 3.14159265358979323846 * k / 64.0;
1825 ex.push_back(cx + rx * std::cos(t));
1826 ey.push_back(cy + ry * std::sin(t));
1828 if (!an.faceHex.empty()) {
1829 Rgb fc = Rgb::fromHex(an.faceHex);
1830 addPoly(out, ex, ey, fc,
true);
1832 for (
size_t k = 0; k + 1 < ex.size(); ++k)
1833 addLine(out, {ex[k], ex[k + 1]}, {ey[k], ey[k + 1]}, col,
1845using SvgImageEmbedder =
1846 std::function<std::string(
const std::vector<std::uint8_t> &,
int,
int)>;
1848inline std::string renderFigureSvg(
const FigureState &fig,
int W,
int H,
1849 const SvgImageEmbedder &embed = {})
1851 std::vector<Cmd> cmds = renderFigureDisplay(fig, W, H);
1852 std::string s =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1853 "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"";
1854 s += std::to_string(W) +
"\" height=\"" + std::to_string(H)
1855 +
"\" viewBox=\"0 0 " + std::to_string(W) +
" " + std::to_string(H)
1856 +
"\">\n<rect width=\"100%\" height=\"100%\" fill=\"white\"/>\n";
1857 auto esc = [](
const std::string &t) {
1871 for (
const Cmd &c : cmds) {
1872 if (c.kind == Cmd::IMAGE) {
1875 std::vector<std::uint8_t> buf = imageCmdRgb(c);
1876 int pw = std::max(1, (
int)std::lround(std::max(c.xs[0], c.xs[1])
1877 - std::min(c.xs[0], c.xs[1])));
1878 int ph = std::max(1, (
int)std::lround(std::max(c.ys[0], c.ys[1])
1879 - std::min(c.ys[0], c.ys[1])));
1881 + std::to_string(std::lround(std::min(c.xs[0], c.xs[1])))
1883 + std::to_string(std::lround(std::min(c.ys[0], c.ys[1])))
1884 +
"\" width=\"" + std::to_string(pw) +
"\" height=\""
1885 + std::to_string(ph) +
"\" preserveAspectRatio=\"none\" href=\""
1886 + embed(buf, pw, ph) +
"\"/>\n";
1887 }
else if (c.kind == Cmd::TEXT) {
1888 s +=
"<text x=\"" + std::to_string(c.x) +
"\" y=\""
1889 + std::to_string(c.y) +
"\" font-size=\""
1890 + std::to_string(c.fontSize)
1891 +
"\" font-family=\""
1892 + (c.fontFamily.empty() ? std::string(
"monospace")
1894 +
"\"" + std::string(c.bold ?
" font-weight=\"bold\"" :
"")
1895 +
" fill=\"" + c.color.hex() +
"\"";
1897 s +=
" transform=\"rotate(-90 " + std::to_string(c.x) +
" "
1898 + std::to_string(c.y) +
")\"";
1899 s +=
">" + esc(c.text) +
"</text>\n";
1900 }
else if (c.kind == Cmd::MARK) {
1901 double r = c.width / 2;
1902 if (c.text ==
"s" || c.text ==
"d") {
1903 s +=
"<rect x=\"" + std::to_string(c.x - r) +
"\" y=\""
1904 + std::to_string(c.y - r) +
"\" width=\""
1905 + std::to_string(c.width) +
"\" height=\""
1906 + std::to_string(c.width) +
"\" fill=\""
1907 + (c.text ==
"d" ?
"none" : c.color.hex()) +
"\" stroke=\""
1908 + c.color.hex() +
"\"/>\n";
1909 }
else if (c.text ==
"+" || c.text ==
"x") {
1910 s +=
"<path d=\"M" + std::to_string(c.x - r) +
" "
1911 + std::to_string(c.y) +
" L" + std::to_string(c.x + r) +
" "
1912 + std::to_string(c.y) +
" M" + std::to_string(c.x) +
" "
1913 + std::to_string(c.y - r) +
" L" + std::to_string(c.x)
1914 +
" " + std::to_string(c.y + r)
1915 + (c.text ==
"x" ?
" M" + std::to_string(c.x - r) +
" "
1916 + std::to_string(c.y - r) +
" L"
1917 + std::to_string(c.x + r) +
" "
1918 + std::to_string(c.y + r) +
" M"
1919 + std::to_string(c.x + r) +
" "
1920 + std::to_string(c.y - r) +
" L"
1921 + std::to_string(c.x - r) +
" "
1922 + std::to_string(c.y + r)
1924 +
"\" stroke=\"" + c.color.hex() +
"\" stroke-width=\"1.2\" fill=\"none\"/>\n";
1926 s +=
"<circle cx=\"" + std::to_string(c.x) +
"\" cy=\""
1927 + std::to_string(c.y) +
"\" r=\"" + std::to_string(r)
1928 +
"\" fill=\"none\" stroke=\"" + c.color.hex()
1929 +
"\" stroke-width=\"1.4\"/>\n";
1934 for (
size_t i = 0; i < c.xs.size(); ++i) {
1938 std::snprintf(b,
sizeof b,
"%.1f,%.1f", c.xs[i], c.ys[i]);
1941 if (c.kind == Cmd::POLY && c.fill) {
1942 s +=
"<polygon points=\"" + pts +
"\" fill=\"" + c.color.hex()
1943 +
"\" stroke=\"none\"/>\n";
1945 s +=
"<polyline points=\"" + pts +
"\" fill=\"none\" stroke=\""
1946 + c.color.hex() +
"\" stroke-width=\""
1947 + std::to_string(c.width) +
"\"";
1949 s +=
" stroke-dasharray=\"7 5\"";
1961inline const std::uint8_t *font5x7(
char c)
1966 static const std::uint8_t F[][5] = {
1967 {0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x5F,0x00,0x00},{0x00,0x07,0x00,0x07,0x00},
1968 {0x14,0x7F,0x14,0x7F,0x14},{0x24,0x2A,0x7F,0x2A,0x12},{0x23,0x13,0x08,0x64,0x62},
1969 {0x36,0x49,0x56,0x20,0x50},{0x00,0x08,0x07,0x03,0x00},{0x00,0x1C,0x22,0x41,0x00},
1970 {0x00,0x41,0x22,0x1C,0x00},{0x2A,0x1C,0x7F,0x1C,0x2A},{0x08,0x08,0x3E,0x08,0x08},
1971 {0x00,0x80,0x70,0x30,0x00},{0x08,0x08,0x08,0x08,0x08},{0x00,0x00,0x60,0x60,0x00},
1972 {0x20,0x10,0x08,0x04,0x02},{0x3E,0x51,0x49,0x45,0x3E},{0x00,0x42,0x7F,0x40,0x00},
1973 {0x72,0x49,0x49,0x49,0x46},{0x21,0x41,0x49,0x4D,0x33},{0x18,0x14,0x12,0x7F,0x10},
1974 {0x27,0x45,0x45,0x45,0x39},{0x3C,0x4A,0x49,0x49,0x31},{0x41,0x21,0x11,0x09,0x07},
1975 {0x36,0x49,0x49,0x49,0x36},{0x46,0x49,0x49,0x29,0x1E},{0x00,0x00,0x14,0x00,0x00},
1976 {0x00,0x40,0x34,0x00,0x00},{0x00,0x08,0x14,0x22,0x41},{0x14,0x14,0x14,0x14,0x14},
1977 {0x00,0x41,0x22,0x14,0x08},{0x02,0x01,0x59,0x09,0x06},{0x3E,0x41,0x5D,0x59,0x4E},
1978 {0x7C,0x12,0x11,0x12,0x7C},{0x7F,0x49,0x49,0x49,0x36},{0x3E,0x41,0x41,0x41,0x22},
1979 {0x7F,0x41,0x41,0x41,0x3E},{0x7F,0x49,0x49,0x49,0x41},{0x7F,0x09,0x09,0x09,0x01},
1980 {0x3E,0x41,0x41,0x51,0x73},{0x7F,0x08,0x08,0x08,0x7F},{0x00,0x41,0x7F,0x41,0x00},
1981 {0x20,0x40,0x41,0x3F,0x01},{0x7F,0x08,0x14,0x22,0x41},{0x7F,0x40,0x40,0x40,0x40},
1982 {0x7F,0x02,0x1C,0x02,0x7F},{0x7F,0x04,0x08,0x10,0x7F},{0x3E,0x41,0x41,0x41,0x3E},
1983 {0x7F,0x09,0x09,0x09,0x06},{0x3E,0x41,0x51,0x21,0x5E},{0x7F,0x09,0x19,0x29,0x46},
1984 {0x26,0x49,0x49,0x49,0x32},{0x03,0x01,0x7F,0x01,0x03},{0x3F,0x40,0x40,0x40,0x3F},
1985 {0x1F,0x20,0x40,0x20,0x1F},{0x3F,0x40,0x38,0x40,0x3F},{0x63,0x14,0x08,0x14,0x63},
1986 {0x03,0x04,0x78,0x04,0x03},{0x61,0x59,0x49,0x4D,0x43},{0x00,0x7F,0x41,0x41,0x41},
1987 {0x02,0x04,0x08,0x10,0x20},{0x00,0x41,0x41,0x41,0x7F},{0x04,0x02,0x01,0x02,0x04},
1988 {0x40,0x40,0x40,0x40,0x40},{0x00,0x03,0x07,0x08,0x00},{0x20,0x54,0x54,0x78,0x40},
1989 {0x7F,0x28,0x44,0x44,0x38},{0x38,0x44,0x44,0x44,0x28},{0x38,0x44,0x44,0x28,0x7F},
1990 {0x38,0x54,0x54,0x54,0x18},{0x00,0x08,0x7E,0x09,0x02},{0x18,0xA4,0xA4,0x9C,0x78},
1991 {0x7F,0x08,0x04,0x04,0x78},{0x00,0x44,0x7D,0x40,0x00},{0x20,0x40,0x40,0x3D,0x00},
1992 {0x7F,0x10,0x28,0x44,0x00},{0x00,0x41,0x7F,0x40,0x00},{0x7C,0x04,0x78,0x04,0x78},
1993 {0x7C,0x08,0x04,0x04,0x78},{0x38,0x44,0x44,0x44,0x38},{0xFC,0x18,0x24,0x24,0x18},
1994 {0x18,0x24,0x24,0x18,0xFC},{0x7C,0x08,0x04,0x04,0x08},{0x48,0x54,0x54,0x54,0x24},
1995 {0x04,0x04,0x3F,0x44,0x24},{0x3C,0x40,0x40,0x20,0x7C},{0x1C,0x20,0x40,0x20,0x1C},
1996 {0x3C,0x40,0x30,0x40,0x3C},{0x44,0x28,0x10,0x28,0x44},{0x4C,0x90,0x90,0x90,0x7C},
1997 {0x44,0x64,0x54,0x4C,0x44},{0x00,0x08,0x36,0x41,0x00},{0x00,0x00,0x77,0x00,0x00},
1998 {0x00,0x41,0x36,0x08,0x00},{0x02,0x01,0x02,0x04,0x02}
2000 static_assert(
sizeof F /
sizeof F[0] == 95,
2001 "font5x7 must cover exactly ASCII 0x20..0x7E");
2002 if (c < 0x20 || c > 0x7E)
2010 Raster(
int w,
int h) : w_(w), h_(h), px_(w * h * 3, 255) {}
2012 void px(
int x,
int y,
Rgb c)
2014 if (x < 0 || y < 0 || x >= w_ || y >= h_)
2016 size_t o = (size_t)(y * w_ + x) * 3;
2023 void line(
double x0,
double y0,
double x1,
double y1,
Rgb c,
double wd,
2026 double len = std::hypot(x1 - x0, y1 - y0);
2029 int steps = (int)std::ceil(len * 2);
2030 double dashOn = 7, dashOff = 5;
2032 int side = (int)std::max(1.0, std::round(wd));
2033 for (
int i = 0; i <= steps; ++i) {
2034 double t = (double)i / steps;
2035 double x = x0 + (x1 - x0) * t;
2036 double y = y0 + (y1 - y0) * t;
2038 double m = std::fmod(run, dashOn + dashOff);
2043 for (
int dx = 0; dx < side; ++dx)
2044 for (
int dy = 0; dy < side; ++dy)
2045 px((
int)x + dx - side / 2, (
int)y + dy - side / 2, c);
2049 void polygon(
const std::vector<double> &xs,
const std::vector<double> &ys,
2056 double ymn = ys[0], ymx = ys[0];
2057 for (
double y : ys) {
2058 ymn = std::min(ymn, y);
2059 ymx = std::max(ymx, y);
2061 for (
int y = (
int)std::floor(ymn); y <= (int)std::ceil(ymx); ++y) {
2062 std::vector<double> xi;
2063 for (
size_t i = 0; i < xs.size(); ++i) {
2064 size_t j = (i + 1) % xs.size();
2065 double ya = ys[i], yb = ys[j];
2066 if ((y + 0.5 >= ya && y + 0.5 < yb)
2067 || (y + 0.5 >= yb && y + 0.5 < ya))
2068 xi.push_back(xs[i] + (y + 0.5 - ya) / (yb - ya)
2071 std::sort(xi.begin(), xi.end());
2072 for (
size_t k = 0; k + 1 < xi.size(); k += 2)
2073 for (
int x = (
int)std::ceil(xi[k]); x <= (int)xi[k + 1]; ++x)
2077 for (
size_t i = 0; i + 1 < xs.size(); ++i)
2078 line(xs[i], ys[i], xs[i + 1], ys[i + 1], c, 1,
false);
2079 line(xs.back(), ys.back(), xs.front(), ys.front(), c, 1,
false);
2083 void marker(
double x,
double y,
double size,
Rgb c,
const std::string &k)
2085 int r = (int)std::max(2.0, size / 2);
2087 for (
int dy = -r; dy <= r; ++dy)
2088 for (
int dx = -r; dx <= r; ++dx)
2089 px((
int)x + dx, (
int)y + dy, c);
2090 }
else if (k ==
"+" || k ==
"x") {
2091 for (
int d = -r; d <= r; ++d) {
2092 px((
int)x + d, (
int)y, c);
2093 px((
int)x, (
int)y + d, c);
2095 px((
int)x + d, (
int)y + d, c);
2096 px((
int)x + d, (
int)y - d, c);
2100 for (
int a = 0; a < 360; a += 2) {
2101 double rad = a * 3.14159265358979 / 180.0;
2102 px((
int)std::lround(x + r * std::cos(rad)),
2103 (
int)std::lround(y + r * std::sin(rad)), c);
2106 for (
int d = -r - 1; d <= r + 1; ++d) {
2107 px((
int)x + d, (
int)y, c);
2108 px((
int)x, (
int)y + d, c);
2109 px((
int)x + d, (
int)y + d, c);
2110 px((
int)x + d, (
int)y - d, c);
2117 void text(
double x,
double y,
const std::string &s,
double fontSize,
Rgb c,
2120 int scale = std::max(1, (
int)std::lround(fontSize / 7.0));
2124 const std::uint8_t *g = font5x7(ch);
2125 for (
int col = 0; col < 5; ++col)
2126 for (
int row = 0; row < 7; ++row)
2127 if (g[col] & (1 << row))
2128 for (
int sx = 0; sx < scale; ++sx)
2129 for (
int sy = 0; sy < scale; ++sy)
2130 px((
int)cx + col * scale + sx,
2131 (
int)y + row * scale + sy, c);
2138 for (
auto it = s.rbegin(); it != s.rend(); ++it) {
2139 const std::uint8_t *g = font5x7(*it);
2140 for (
int row = 0; row < 7; ++row)
2141 for (
int col = 0; col < 5; ++col)
2142 if (g[col] & (1 << row))
2143 for (
int sx = 0; sx < scale; ++sx)
2144 for (
int sy = 0; sy < scale; ++sy)
2145 px((
int)x + (6 - row) * scale + sx,
2146 (
int)cy - (5 - col) * scale - sy, c);
2154 void image(
int x0,
int y0,
const std::vector<std::uint8_t> &buf,
int pw,
2157 for (
int y = 0; y < ph; ++y) {
2159 if (dy < 0 || dy >= h_)
2161 for (
int x = 0; x < pw; ++x) {
2163 if (dx < 0 || dx >= w_)
2165 size_t src = ((size_t)y * pw + x) * 3;
2166 size_t o = ((size_t)dy * w_ + dx) * 3;
2168 px_[o + 1] = buf[src + 1];
2169 px_[o + 2] = buf[src + 2];
2174 const std::vector<std::uint8_t> &bytes()
const {
return px_; }
2175 int width()
const {
return w_; }
2176 int height()
const {
return h_; }
2180 std::vector<std::uint8_t> px_;
2184inline std::vector<std::uint8_t> renderFigureRgb(
const FigureState &fig,
2188 for (
const Cmd &c : renderFigureDisplay(fig, W, H)) {
2191 r.text(c.x, c.y, c.text, c.fontSize, c.color, c.rot90);
2193 r.text(c.x + 1, c.y, c.text, c.fontSize, c.color, c.rot90);
2196 r.marker(c.x, c.y, c.width, c.color, c.text);
2199 r.polygon(c.xs, c.ys, c.color, c.fill);
2202 for (
size_t i = 0; i + 1 < c.xs.size(); ++i)
2203 r.line(c.xs[i], c.ys[i], c.xs[i + 1], c.ys[i + 1], c.color,
2207 std::vector<std::uint8_t> buf = imageCmdRgb(c);
2208 int pw = std::max(1, (
int)std::lround(std::max(c.xs[0], c.xs[1])
2209 - std::min(c.xs[0], c.xs[1])));
2210 int ph = std::max(1, (
int)std::lround(std::max(c.ys[0], c.ys[1])
2211 - std::min(c.ys[0], c.ys[1])));
2212 r.image((
int)std::lround(std::min(c.xs[0], c.xs[1])),
2213 (
int)std::lround(std::min(c.ys[0], c.ys[1])), buf, pw, ph);
static Value empty()
Returns a default-constructed (Unset) Value — NOT a MATLAB empty matrix despite the name.
Value series(const Value &sys1, const Value &sys2, std::pmr::memory_resource *mr=nullptr)
Cascade two LTI blocks (series(sys1, sys2)).
Value pow(const Value &x, const Value &y, std::pmr::memory_resource *mr=nullptr)
Elementwise power (z = x .
Value exp(const Value &x, std::pmr::memory_resource *mr=nullptr)
Natural exponential (y = exp(x)).
Value log(const Value &x, std::pmr::memory_resource *mr=nullptr)
Natural logarithm (ln(x)).
Value size(const Value &x, std::pmr::memory_resource *mr=nullptr)
Returns array dimensions as a row vector (size(x)).
Value edge(const Value &I, const std::string &method, double thresh_lo, double thresh_hi, std::pmr::memory_resource *mr=nullptr)
Binary edge map (BW = edge(I, method, thresh_lo, thresh_hi)).
Value corner(const Value &I, const std::string &method, int maxN, double quality_level, double sensitivity, const Value &filter_coef, std::pmr::memory_resource *mr=nullptr)
Detect corner points (C = corner(I, ...)).
Value eq(const Value &a, const Value &b, std::pmr::memory_resource *mr=nullptr)
Elementwise equality (y = a == b).
Value pad(const Value &s, size_t n, const Value &side=Value(), const Value &padChar=Value(), std::pmr::memory_resource *mr=nullptr)
Pads string to fixed length (pad(s, n, side, padChar)).
Value strip(const Value &s, std::pmr::memory_resource *mr=nullptr)
Strips leading and trailing whitespace from string array (strip(s)).
Per-axes state — one subplot panel has one AxesState.