fix: further chart tweaks

This commit is contained in:
NunoSempere 2022-04-28 17:45:39 -04:00
parent 340d99b485
commit bbe0fd5982
3 changed files with 290 additions and 58 deletions

View File

@ -31,6 +31,25 @@ let getDate0 = (x) => {
return date.toISOString().slice(5, 10).replaceAll("-", "/"); return date.toISOString().slice(5, 10).replaceAll("-", "/");
}; };
let formatOptionName = (name) => {
return name.length > 10 ? name.slice(0, 8) + "..." : name;
};
let getLength = (str) => {
let capitalLetterLengthMultiplier = 1.25;
let smallLetterMultiplier = 0.8;
let numUpper = (str.match(/[A-Z]/g) || []).length;
let numSmallLetters = (str.match(/[fijlrt]/g) || []).length;
let numSpaces = (str.match(/[\s]/g) || []).length;
let length =
str.length +
-numUpper -
numSmallLetters +
numUpper * capitalLetterLengthMultiplier +
(numSmallLetters + numSpaces) * smallLetterMultiplier;
return length;
};
let timestampToString = (x) => { let timestampToString = (x) => {
// for real timestamps // for real timestamps
console.log(x); console.log(x);
@ -49,6 +68,7 @@ let dataAsXy = (data) =>
data.map((datum) => ({ data.map((datum) => ({
x: timestampToString(datum.date), //getDate(datum.date * (1000 * 60 * 60 * 24)), x: timestampToString(datum.date), //getDate(datum.date * (1000 * 60 * 60 * 24)),
y: datum.probability, y: datum.probability,
name: datum.name,
})); }));
const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"]; const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"];
@ -74,10 +94,6 @@ const getVictoryGroup = (data, i) => {
}; };
export const HistoryChart: React.FC<Props> = ({ question }) => { export const HistoryChart: React.FC<Props> = ({ question }) => {
let height = 300;
let width = 500;
let padding = { top: 20, bottom: 50, left: 50, right: 100 };
// let dataSetsNames = ["Yes", "No", "Maybe", "Perhaps", "Possibly"];
let dataSetsNames = []; let dataSetsNames = [];
question.history.forEach((item) => { question.history.forEach((item) => {
let optionNames = item.options.map((option) => option.name); let optionNames = item.options.map((option) => option.name);
@ -85,28 +101,10 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
}); });
dataSetsNames = [...new Set(dataSetsNames)].slice(0, 5); // take the first 5 dataSetsNames = [...new Set(dataSetsNames)].slice(0, 5); // take the first 5
let dataSets = []; let dataSets = [];
/* let maxProbability = 0;
dataSetsNames.forEach((name) => { let longestNameLength = 0;
let newDataset = [];
question.history.forEach((item) => {
let relevantItemsArray = item.options.filter((x) => x.name == name);
let date = new Date(item.timestamp * 1000);
if (relevantItemsArray.length == 1) {
let relevantItem = relevantItemsArray[0];
// if (relevantItem.type == "PROBABILITY") {
let result = {
date,
probability: relevantItem.probability,
};
newDataset.push(result);
// }
}
});
dataSets.push(newDataset);
});
*/
dataSetsNames.forEach((name) => { for (let name of dataSetsNames) {
let newDataset = []; let newDataset = [];
let previousDate = -Infinity; let previousDate = -Infinity;
for (let item of question.history) { for (let item of question.history) {
@ -121,31 +119,46 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
let result = { let result = {
date, date,
probability: relevantItem.probability, probability: relevantItem.probability,
name: relevantItem.name,
}; };
maxProbability =
relevantItem.probability > maxProbability
? relevantItem.probability
: maxProbability;
let length = getLength(relevantItem.name);
longestNameLength =
length > longestNameLength ? length : longestNameLength;
newDataset.push(result); newDataset.push(result);
// } // }
previousDate = item.timestamp; previousDate = item.timestamp;
} }
} }
dataSets.push(newDataset); dataSets.push(newDataset);
}); }
let letterLength = 7;
let labelLegendStart = 45;
let domainMax =
maxProbability < 0.5 ? Math.round(10 * (maxProbability + 0.05)) / 10 : 1;
let dataSetsLength = dataSets.length; let dataSetsLength = dataSets.length;
let goldenRatio = (1 + Math.sqrt(5)) / 2;
let width = 750;
let height = width / goldenRatio;
let padding = {
top: 20,
bottom: 50,
left: 0,
right: labelLegendStart + letterLength * longestNameLength,
};
return ( return (
<div className="flex justify-center items-center w-full"> <div className="flex justify-center items-center w-full">
<div className="w-9/12"> <div className="w-10/12">
<a <a
className="textinherit no-underline" className="textinherit no-underline"
href={question.url} href={question.url}
target="_blank" target="_blank"
> ></a>
{/*
<h1 className="text-3xl font-normal text-center mt-5">
{question.title}
</h1>
*/}
</a>
<VictoryChart <VictoryChart
domainPadding={20} domainPadding={20}
padding={padding} padding={padding}
@ -154,15 +167,16 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
width={width} width={width}
containerComponent={ containerComponent={
<VictoryVoronoiContainer <VictoryVoronoiContainer
labels={({ datum }) => labels={({ datum }) => `Not shown`}
`${datum.x}: ${Math.round(datum.y * 100)}%`
}
labelComponent={ labelComponent={
<VictoryTooltip <VictoryTooltip
pointerLength={0} pointerLength={0}
dy={-12} dy={-12}
text={({ datum }) =>
`${datum.name}: ${Math.round(datum.y * 100)}%`
}
style={{ style={{
fontSize: 10, fontSize: 15,
fill: "black", fill: "black",
strokeWidth: 0.05, strokeWidth: 0.05,
}} }}
@ -170,40 +184,31 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
stroke: "black", stroke: "black",
fill: "white", fill: "white",
}} }}
flyoutWidth={80}
cornerRadius={0} cornerRadius={0}
flyoutPadding={7} flyoutPadding={7}
/> />
} }
voronoiBlacklist={ voronoiBlacklist={
["line-0", "line-1", "line-2", "line-3", "line-4"] ["line-0", "line-1", "line-2", "line-3", "line-4"]
//Array.from(Array(5).keys()).map((x, i) => `line${i}`) //Array.from(Array(5).keys()).map((x, i) => `line${i}`)
// see: https://github.com/FormidableLabs/victory/issues/545 // see: https://github.com/FormidableLabs/victory/issues/545
} }
/> />
} }
domain={{ domain={{
y: [0, 1], y: [0, domainMax],
}} }}
> >
<VictoryLegend <VictoryLegend
x={width - 100} x={width - labelLegendStart - letterLength * longestNameLength}
y={height / 2 - 18 - (dataSetsLength - 1) * 13} y={height / 2 - 18 - (dataSetsLength - 1) * 13}
orientation="vertical" orientation="vertical"
gutter={20} gutter={20}
style={{ border: { stroke: "black" }, title: { fontSize: 20 } }} style={{ border: { stroke: "black" }, labels: { fontSize: 15 } }}
data={ data={Array.from(Array(dataSetsLength).keys()).map((i) => ({
Array.from(Array(dataSetsLength).keys()).map((i) => ({
name: dataSetsNames[i], name: dataSetsNames[i],
symbol: { fill: colors[i] }, symbol: { fill: colors[i] },
})) }))}
/*[
{ name: "One", symbol: { fill: "tomato", type: "star" } },
{ name: "Two", symbol: { fill: "orange" } },
{ name: "Three", symbol: { fill: "gold" } },
]*/
}
/> />
{dataSets {dataSets
@ -224,9 +229,9 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
// label="Date (dd/mm/yy)" // label="Date (dd/mm/yy)"
tickLabelComponent={ tickLabelComponent={
<VictoryLabel <VictoryLabel
dy={0} dy={10}
angle={-30} angle={-30}
style={{ fontSize: 10, fill: "gray" }} style={{ fontSize: 15, fill: "gray" }}
/> />
} }
/> />
@ -238,7 +243,7 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
grid: { stroke: "#D3D3D3", strokeWidth: 0.5 }, grid: { stroke: "#D3D3D3", strokeWidth: 0.5 },
}} }}
tickLabelComponent={ tickLabelComponent={
<VictoryLabel dy={0} style={{ fontSize: 10, fill: "gray" }} /> <VictoryLabel dy={0} style={{ fontSize: 15, fill: "gray" }} />
} }
/> />
</VictoryChart> </VictoryChart>

View File

@ -0,0 +1,227 @@
import { QuestionFragment } from "../../fragments.generated";
type QualityIndicator = QuestionFragment["qualityIndicators"];
type IndicatorName = keyof QualityIndicator;
// this duplication can probably be simplified with typescript magic, but this is good enough for now
type UsedIndicatorName =
| "volume"
| "numForecasters"
| "spread"
| "sharesVolume"
| "liquidity"
| "tradeVolume"
| "openInterest";
const qualityIndicatorLabels: { [k in UsedIndicatorName]: string } = {
// numForecasts: null,
// stars: null,
// yesBid: "Yes bid",
// yesAsk: "Yes ask",
volume: "Volume",
numForecasters: "Forecasters",
spread: "Spread",
sharesVolume: "Shares vol.",
liquidity: "Liquidity",
tradeVolume: "Volume",
openInterest: "Interest",
};
const formatNumber = (num) => {
if (Number(num) < 1000) {
return Number(num).toFixed(0);
} else if (num < 10000) {
return (Number(num) / 1000).toFixed(1) + "k";
} else {
return (Number(num) / 1000).toFixed(0) + "k";
}
};
/* Display functions*/
const getPercentageSymbolIfNeeded = ({
indicator,
platform,
}: {
indicator: UsedIndicatorName;
platform: string;
}) => {
let indicatorsWhichNeedPercentageSymbol: IndicatorName[] = ["spread"];
if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
return "%";
} else {
return "";
}
};
const getCurrencySymbolIfNeeded = ({
indicator,
platform,
}: {
indicator: UsedIndicatorName;
platform: string;
}) => {
const indicatorsWhichNeedCurrencySymbol: IndicatorName[] = [
"volume",
"tradeVolume",
"openInterest",
"liquidity",
];
let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
if (dollarPlatforms.includes(platform)) {
return "$";
} else {
return "£";
}
} else {
return "";
}
};
const FirstQualityIndicator: React.FC<{
question: QuestionFragment;
}> = ({ question }) => {
if (question.qualityIndicators.numForecasts) {
return (
<div className="flex">
<span>Forecasts:</span>&nbsp;
<span className="font-bold">
{Number(question.qualityIndicators.numForecasts).toFixed(0)}
</span>
</div>
);
} else {
return null;
}
};
const QualityIndicatorsList: React.FC<{
question: QuestionFragment;
}> = ({ question }) => {
return (
<div className="text-sm">
<FirstQualityIndicator question={question} />
{Object.entries(question.qualityIndicators).map((entry, i) => {
const indicatorLabel = qualityIndicatorLabels[entry[0]];
if (!indicatorLabel || entry[1] === null) return;
const indicator = entry[0] as UsedIndicatorName; // guaranteed by the previous line
const value = entry[1];
return (
<div key={indicator}>
<span>{indicatorLabel}:</span>&nbsp;
<span className="font-bold">
{`${getCurrencySymbolIfNeeded({
indicator,
platform: question.platform.id,
})}${formatNumber(value)}${getPercentageSymbolIfNeeded({
indicator,
platform: question.platform.id,
})}`}
</span>
</div>
);
})}
</div>
);
};
// Database-like functions
export function getstars(numstars: number) {
let stars = "★★☆☆☆";
switch (numstars) {
case 0:
stars = "☆☆☆☆☆";
break;
case 1:
stars = "★☆☆☆☆";
break;
case 2:
stars = "★★☆☆☆";
break;
case 3:
stars = "★★★☆☆";
break;
case 4:
stars = "★★★★☆";
break;
case 5:
stars = "★★★★★";
break;
default:
stars = "★★☆☆☆";
}
return stars;
}
function getStarsColor(numstars: number) {
let color = "text-yellow-400";
switch (numstars) {
case 0:
color = "text-red-400";
break;
case 1:
color = "text-red-400";
break;
case 2:
color = "text-orange-400";
break;
case 3:
color = "text-yellow-400";
break;
case 4:
color = "text-green-400";
break;
case 5:
color = "text-blue-400";
break;
default:
color = "text-yellow-400";
}
return color;
}
interface Props {
question: QuestionFragment;
expandFooterToFullWidth: boolean;
}
export const QuestionFooter: React.FC<Props> = ({
question,
expandFooterToFullWidth,
}) => {
return (
<div
className={`grid grid-cols-3 ${
expandFooterToFullWidth ? "justify-between" : ""
} text-gray-500 mb-2 mt-1`}
>
<div
className={`self-center col-span-1 ${getStarsColor(
question.qualityIndicators.stars
)}`}
>
{getstars(question.qualityIndicators.stars)}
</div>
<div
className={`${
expandFooterToFullWidth ? "place-self-center" : "self-center"
} col-span-1 font-bold`}
>
{question.platform.label
.replace("Good Judgment Open", "GJOpen")
.replace(/ /g, "\u00a0")}
</div>
<div
className={`${
expandFooterToFullWidth
? "justify-self-end mr-4"
: "justify-self-center"
} col-span-1`}
>
<QualityIndicatorsList question={question} />
</div>
</div>
);
};

View File

@ -61,7 +61,7 @@ const QuestionCardContents: React.FC<{
*/} */}
<h1 className="text-xl place-self-center w-full text-center mt-20"> <h1 className="pt-10 text-xl place-self-center w-full text-center ">
{"Question description"} {"Question description"}
</h1> </h1>