Plotting a plunge
April 3, 2025 at 1:38 PM by Dr. Drang
Earlier today, I opened the Stocks app to see how bad things were after yesterday afternoon’s tariff announcement. I thought its one-day plots weren’t very useful. Here’s APPL:
Strictly speaking, this is correct, as the share price started way down from yesterday’s close. And I guess Apple is trying to give us a sense of this by setting the top end of the plot’s range up where the price was yesterday. But anyone looking at today’s action would want to see yesterday’s closing price explicitly.
Google Finance does a better job:
The dashed horizontal line shows you where things were yesterday, and the closing price is also given as that line’s label. Much better.
Maybe if Tim Cook gave $1 million to the team making the Stocks app, they’d make better plots.
Update 3 Apr 2025 2:39 PM
Connor Graham points out that the dashed line at the top of the Stocks plot is yesterday’s close. I saw that line but thought it was associated with the 224 axis label off to the right. Nope. There’s actually a thin gray line above the dashed line, and that’s the 224 level. Here’s a zoomed-in view (something not available in the app itself):
What’s funny is that I could see the other horizontal grid lines, just not the one that’s close to the brighter dashed line. I bet people who study perception have a cool explanation for that.
So I should revise my criticism of the Stocks plot:
- It’s grid lines are too faint to distinguish the top one from the dashed line.
- Because it uses stupid choices for the grid locations (224? not 225?), the dashed line is always close to the top grid line.
- Because it puts the grid labels too far below their associated grid lines, the top label looks like it applies to the dashed line.
- It doesn’t label the dashed line. This is a minor criticism. If I’d been able to see that the dashed line was distinct from the top grid line, I would’ve known that the dashed line was yesterday’s close.
So Tim doesn’t have to spend the whole $1 million on Stocks.
Thanks, Connor!
Synonyms via JSON
April 2, 2025 at 12:18 PM by Dr. Drang
While writing last week’s post on the built-in Apple thesaurus, I looked around for other ways to get a list of synonyms. I came across the Merriam-Webster API, which gives you free access (for light non-commercial use) to the M-W dictionary and thesaurus. I didn’t spend too much time with it—the Apple thesaurus is just so handy—but it was interesting, and M-W is certainly authoritative.
To use the M-W API, you have to register for a developer account. I don’t understand why they ask for a company name when it’s for non-commercial use (I entered “None”), but maybe the registration page covers commercial apps, too. Whatever. I got my API key almost immediately after submitting the form.
The thesaurus API works by returning JSON in response to a URL in this form:
https://www.dictionaryapi.com/api/v3/references/thesaurus/json/word?key=your-api-key
There’s extensive documentation for the API, but I just sort of looked through some output to see how easy it would be to extract what I might want. Here’s the JSON output for component. Get your scrolling finger limbered up.
[
{
"meta": {
"id": "component",
"uuid": "5989b740-1791-4d9f-9ce1-d4a8f6a49f11",
"src": "coll_thes",
"section": "alpha",
"target": {
"tuuid": "60760bc0-146a-4f7c-b424-debfb361e283",
"tsrc": "collegiate"
},
"stems": [
"component",
"componential",
"components"
],
"syns": [
[
"building block",
"constituent",
"element",
"factor",
"ingredient",
"member"
]
],
"ants": [
[
"whole"
]
],
"offensive": false
},
"hwi": {
"hw": "component"
},
"fl": "noun",
"def": [
{
"sseq": [
[
[
"sense",
{
"dt": [
[
"text",
"one of the parts that make up a whole "
],
[
"vis",
[
{
"t": "each set is composed of several distinct {it}components{/it}"
}
]
]
],
"syn_list": [
[
{
"wd": "building block"
},
{
"wd": "constituent"
},
{
"wd": "element"
},
{
"wd": "factor"
},
{
"wd": "ingredient"
},
{
"wd": "member"
}
]
],
"rel_list": [
[
{
"wd": "basis"
},
{
"wd": "part and parcel"
}
],
[
{
"wd": "detail"
},
{
"wd": "item"
},
{
"wd": "particular"
},
{
"wd": "point"
}
],
[
{
"wd": "aspect"
},
{
"wd": "characteristic"
},
{
"wd": "facet"
},
{
"wd": "feature"
},
{
"wd": "trait"
}
],
[
{
"wd": "division"
},
{
"wd": "fragment"
},
{
"wd": "particle"
},
{
"wd": "partition"
},
{
"wd": "piece"
},
{
"wd": "portion"
},
{
"wd": "section"
},
{
"wd": "sector"
},
{
"wd": "segment"
}
],
[
{
"wd": "subcomponent"
}
]
],
"near_list": [
[
{
"wd": "aggregate"
},
{
"wd": "composite"
},
{
"wd": "compound"
},
{
"wd": "mass"
}
],
[
{
"wd": "entirety"
},
{
"wd": "sum"
},
{
"wd": "summation"
},
{
"wd": "total"
},
{
"wd": "totality"
}
],
[
{
"wd": "admixture"
},
{
"wd": "amalgam"
},
{
"wd": "amalgamation"
},
{
"wd": "blend"
},
{
"wd": "combination"
},
{
"wd": "intermixture"
},
{
"wd": "mix"
},
{
"wd": "mixture"
}
]
],
"ant_list": [
[
{
"wd": "whole"
}
]
]
}
]
]
]
}
],
"shortdef": [
"one of the parts that make up a whole"
]
},
{
"meta": {
"id": "component",
"uuid": "0bb8d43d-b0c8-4869-81e7-535094385289",
"src": "CTcompile",
"section": "alpha",
"stems": [
"component"
],
"syns": [
[
"constituent",
"individual",
"particular",
"cross-sectional",
"divisional",
"fragmentary",
"partial",
"local",
"localized",
"regional",
"sectional"
]
],
"ants": [
[
"across-the-board",
"blanket",
"broad-brush",
"common",
"general",
"generic",
"global",
"overall",
"universal",
"all-embracing",
"broad",
"broad-gauge",
"broadscale",
"comprehensive",
"extensive",
"inclusionary",
"overarching",
"pervasive",
"sweeping",
"ubiquitous",
"wholesale",
"wide",
"widespread",
"aggregate",
"collective",
"complete",
"full",
"plenary"
]
],
"offensive": false
},
"hwi": {
"hw": "component"
},
"fl": "adjective",
"def": [
{
"sseq": [
[
[
"sense",
{
"dt": [
[
"text",
"as in {it}constituent{/it}"
]
],
"sim_list": [
[
{
"wd": "constituent"
}
],
[
{
"wd": "individual"
},
{
"wd": "particular"
}
],
[
{
"wd": "cross-sectional"
},
{
"wd": "divisional"
},
{
"wd": "fragmentary"
},
{
"wd": "partial"
}
],
[
{
"wd": "local"
},
{
"wd": "localized"
},
{
"wd": "regional"
},
{
"wd": "sectional"
}
]
],
"opp_list": [
[
{
"wd": "across-the-board"
},
{
"wd": "blanket"
},
{
"wd": "broad-brush"
},
{
"wd": "common"
},
{
"wd": "general"
},
{
"wd": "generic"
},
{
"wd": "global"
},
{
"wd": "overall"
},
{
"wd": "universal"
}
],
[
{
"wd": "all-embracing"
},
{
"wd": "broad"
},
{
"wd": "broad-gauge",
"wvrs": [
{
"wvl": "or",
"wva": "broad-gauged"
}
]
},
{
"wd": "broadscale"
},
{
"wd": "comprehensive"
},
{
"wd": "extensive"
},
{
"wd": "inclusionary"
},
{
"wd": "overarching"
},
{
"wd": "pervasive"
},
{
"wd": "sweeping"
},
{
"wd": "ubiquitous"
},
{
"wd": "wholesale"
},
{
"wd": "wide"
},
{
"wd": "widespread"
}
],
[
{
"wd": "aggregate"
},
{
"wd": "collective"
},
{
"wd": "complete"
},
{
"wd": "full"
},
{
"wd": "plenary"
}
]
]
}
]
]
]
}
],
"shortdef": [
"as in constituent"
]
}
]
That output came from running
curl https://dictionaryapi.com/api/v3/references/thesaurus/json/component?key=my-key | jq
Piping the output through jq
put it into the nicely indented format. Without that, you just get a long inpenetrable string.
It was relatively easy to whip up a short Python script that turned the JSON into a more compact form:
1 #!/usr/bin/env python3
2
3 import json
4 import requests
5 from textwrap import fill
6 import sys
7
8 word = sys.argv[1]
9 myKey = 'my-key'
10 mwURL = f'https://dictionaryapi.com/api/v3/references/thesaurus/json/{word}?key={myKey}'
11
12 r = requests.get(mwURL)
13 j = json.loads(r.text)
14
15 for m in j:
16 count = len(m['shortdef'])
17 for i, defn in enumerate(m['shortdef']):
18 print(fill(m['fl'] + ': ' + defn, width=70))
19 print(fill(', '.join(m['meta']['syns'][i]), width=70))
20 print()
There are no comments because this was just a quick proof of concept. Basically, it uses requests
to get the API’s response, decodes the JSON into a Python data structure using the json
module’s loads
function, and then pulls out the parts of the response that I thought would be useful. Because the output for each part could be long, I used the fill
function from the textwrap
library, to make the lines no more than 70 characters long. The output for component
is
noun: one of the parts that make up a whole
building block, constituent, element, factor, ingredient, member
adjective: as in constituent
constituent, individual, particular, cross-sectional, divisional,
fragmentary, partial, local, localized, regional, sectional
How it is that part can appear in the short definition but not in this list of synonyms is beyond me. Maybe it’s good that I didn’t pursue this further.
Update 2 Apr 2025 2:25 PM
Leon Cowle tells me that requests
has a json
method that does the work of loads
. That sounds familiar, but it’s been so long since I used requests
that I didn’t even bother to look for it. When I want to limit the time I spend on a script, I typically stick with what’s already in my brain and don’t go looking for improvements and efficiencies. With luck, though, this will remind me of json
the next time I use requests
. Thanks, Leon!
A macOS 15.4 wallpaper bug
April 1, 2025 at 12:19 PM by Dr. Drang
I updated my Mac to macOS 15.4 last night and after the reboot, my desktop was white. Pure white.
Not a particularly soothing color or one that’s easy to work with.
I opened System Settings, chose Wallpaper from the list along the right edge, and saw that although my custom color was displaying properly in the little box on the right, the miniature desktop on the left was white.
If I switched to one of Apple’s photos or built-in color choices down lower on the Wallpaper pane, the desktop changed to that choice. But whenever I switched to a custom color—any custom color—the desktop reverted to white.
You know the saying about how doing something over and over and expecting different results is a sign of insanity? That doesn’t apply to computers. We all know that quitting applications or rebooting can make a computer change its behavior. There is undoubtedly a good reason behind this—something to do with clearing memory or caches or some other bullshit programmers mention when they don’t really know what’s happening—but I always feel that I’m acting irrationally. But I do it.
This time it didn’t work, and I stopped after five reboots. The five reboots weren’t the same. I tried leaving the wallpaper setting in different states, I changed between
and , and I tried out different screen resolutions. Nothing worked.I complained on Mastodon and learned that I wasn’t the only person bitten by this bug. I also heard from someone who didn’t have the problem, which always makes me wonder how deterministic computers really are. Luckily, the workaround was simple and, unlike most workarounds, made my Mac work exactly as it would if the bug weren’t there.
I made a 1×1 PNG image of my preferred desktop color (which I found in a recent screenshot), saved it to the Photos app, and chose that photo to be my wallpaper. I made sure the
option was chosen, and now my desktop looks the way it should.If only Apple’s other bugs were so easy to deal with.
Aligned Easters
March 31, 2025 at 7:19 PM by Dr. Drang
I was reading a news article today about Trump’s hope to get a ceasefire in Ukraine arranged by April 20 because it’s Easter. I thought, “Wait, that’s our Easter, but is it also Ukraine’s Easter?” Based on the Ukrainian neighborhoods in Chicago, I assumed most of Ukraine would celebrate Orthodox Easter, not the Easter set by the Western Church.
It turns out that this year they’re on the same day, something that happens more frequently than I would’ve guessed, but still less often than not.
Here are the dates of both Easters for the 50-year period starting in 2001:
Year | Western | Orthodox | Same? | Wks apart |
---|---|---|---|---|
2001 | Apr 15 | Apr 15 | Yes | 0 |
2002 | Mar 31 | May 05 | No | 5 |
2003 | Apr 20 | Apr 27 | No | 1 |
2004 | Apr 11 | Apr 11 | Yes | 0 |
2005 | Mar 27 | May 01 | No | 5 |
2006 | Apr 16 | Apr 23 | No | 1 |
2007 | Apr 08 | Apr 08 | Yes | 0 |
2008 | Mar 23 | Apr 27 | No | 5 |
2009 | Apr 12 | Apr 19 | No | 1 |
2010 | Apr 04 | Apr 04 | Yes | 0 |
2011 | Apr 24 | Apr 24 | Yes | 0 |
2012 | Apr 08 | Apr 15 | No | 1 |
2013 | Mar 31 | May 05 | No | 5 |
2014 | Apr 20 | Apr 20 | Yes | 0 |
2015 | Apr 05 | Apr 12 | No | 1 |
2016 | Mar 27 | May 01 | No | 5 |
2017 | Apr 16 | Apr 16 | Yes | 0 |
2018 | Apr 01 | Apr 08 | No | 1 |
2019 | Apr 21 | Apr 28 | No | 1 |
2020 | Apr 12 | Apr 19 | No | 1 |
2021 | Apr 04 | May 02 | No | 4 |
2022 | Apr 17 | Apr 24 | No | 1 |
2023 | Apr 09 | Apr 16 | No | 1 |
2024 | Mar 31 | May 05 | No | 5 |
2025 | Apr 20 | Apr 20 | Yes | 0 |
2026 | Apr 05 | Apr 12 | No | 1 |
2027 | Mar 28 | May 02 | No | 5 |
2028 | Apr 16 | Apr 16 | Yes | 0 |
2029 | Apr 01 | Apr 08 | No | 1 |
2030 | Apr 21 | Apr 28 | No | 1 |
2031 | Apr 13 | Apr 13 | Yes | 0 |
2032 | Mar 28 | May 02 | No | 5 |
2033 | Apr 17 | Apr 24 | No | 1 |
2034 | Apr 09 | Apr 09 | Yes | 0 |
2035 | Mar 25 | Apr 29 | No | 5 |
2036 | Apr 13 | Apr 20 | No | 1 |
2037 | Apr 05 | Apr 05 | Yes | 0 |
2038 | Apr 25 | Apr 25 | Yes | 0 |
2039 | Apr 10 | Apr 17 | No | 1 |
2040 | Apr 01 | May 06 | No | 5 |
2041 | Apr 21 | Apr 21 | Yes | 0 |
2042 | Apr 06 | Apr 13 | No | 1 |
2043 | Mar 29 | May 03 | No | 5 |
2044 | Apr 17 | Apr 24 | No | 1 |
2045 | Apr 09 | Apr 09 | Yes | 0 |
2046 | Mar 25 | Apr 29 | No | 5 |
2047 | Apr 14 | Apr 21 | No | 1 |
2048 | Apr 05 | Apr 05 | Yes | 0 |
2049 | Apr 18 | Apr 25 | No | 1 |
2050 | Apr 10 | Apr 17 | No | 1 |
There are 16 years in which the dates match. The two Easters are typically either 1 or 5 weeks apart, with the Orthodox Easter coming later. This year is the first alignment since 2017, which is the longest stretch of Nos over this period. I have no idea whether this period is in any sense typical. The calculation of Easter is complicated and the formulas don’t have any obvious period.
Here’s the Python code I used (in a Jupyter console session) to build the table:
from dateutil.easter import *
for y in range(2001, 2051):
west = easter(y, EASTER_WESTERN)
east = easter(y, EASTER_ORTHODOX)
diff = (east - west).days // 7
print(f'| {y} | {west.strftime('%b %d')} | {east.strftime('%b %d')} | {"Yes" if diff==0 else " No"} | {diff} |')
The pipe characters in Line 7 are part of the MultiMarkdown table format.
The dateutil
module has an easter
submodule, which is perfect for making this comparison. The EASTER_ORTHODOX
flag gives the date for Orthodox Easter in the Gregorian calendar. There’s also an EASTER_JULIAN
flag, which gives Orthodox Easter in the Julian calendar. This is historically and culturally useful but not helpful when you want to compare it with a Gregorian date.