tweak: Charts
Add line, reorganize display Also fix nasty bug where probabilities are inverted in frontpage
This commit is contained in:
parent
55b320c528
commit
340d99b485
6
.eslintrc
Normal file
6
.eslintrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": ["next", "prettier"],
|
||||
"rules": {
|
||||
"next/no-document-import-in-page": "off"
|
||||
}
|
||||
}
|
|
@ -1,7 +1,15 @@
|
|||
import React from "react";
|
||||
import {
|
||||
VictoryAxis, VictoryChart, VictoryGroup, VictoryLabel, VictoryLegend, VictoryScatter,
|
||||
VictoryTheme, VictoryTooltip, VictoryVoronoiContainer
|
||||
VictoryAxis,
|
||||
VictoryChart,
|
||||
VictoryGroup,
|
||||
VictoryLabel,
|
||||
VictoryLegend,
|
||||
VictoryScatter,
|
||||
VictoryLine,
|
||||
VictoryTheme,
|
||||
VictoryTooltip,
|
||||
VictoryVoronoiContainer,
|
||||
} from "victory";
|
||||
|
||||
import { QuestionWithHistoryFragment } from "../../fragments.generated";
|
||||
|
@ -48,26 +56,25 @@ const getVictoryGroup = (data, i) => {
|
|||
return (
|
||||
<VictoryGroup color={colors[i] || "darkgray"} data={dataAsXy(data)}>
|
||||
<VictoryScatter
|
||||
name={`scatter-${i}`}
|
||||
//style={{ labels: { display: "none" } }}
|
||||
size={({ active }) => (active ? 3.75 : 3)}
|
||||
//labels={() => null}
|
||||
//labelComponent={<span></span>}
|
||||
/>
|
||||
|
||||
{/* Doesn't work well with tooltips
|
||||
<VictoryLine
|
||||
name={`line${i}`}
|
||||
//style={{ labels: { display: "none" } }}
|
||||
//labels={() => null}
|
||||
//labelComponent={<span></span>}
|
||||
/>
|
||||
*/}
|
||||
<VictoryLine
|
||||
name={`line-${i}`}
|
||||
//style={{ labels: { display: "none" } }}
|
||||
//labels={() => null}
|
||||
//labelComponent={<span></span>}
|
||||
/>
|
||||
</VictoryGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const HistoryChart: React.FC<Props> = ({ question }) => {
|
||||
let height = 400;
|
||||
let height = 300;
|
||||
let width = 500;
|
||||
let padding = { top: 20, bottom: 50, left: 50, right: 100 };
|
||||
// let dataSetsNames = ["Yes", "No", "Maybe", "Perhaps", "Possibly"];
|
||||
|
@ -78,6 +85,7 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
|
|||
});
|
||||
dataSetsNames = [...new Set(dataSetsNames)].slice(0, 5); // take the first 5
|
||||
let dataSets = [];
|
||||
/*
|
||||
dataSetsNames.forEach((name) => {
|
||||
let newDataset = [];
|
||||
question.history.forEach((item) => {
|
||||
|
@ -96,112 +104,145 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
|
|||
});
|
||||
dataSets.push(newDataset);
|
||||
});
|
||||
*/
|
||||
|
||||
dataSetsNames.forEach((name) => {
|
||||
let newDataset = [];
|
||||
let previousDate = -Infinity;
|
||||
for (let item of question.history) {
|
||||
let relevantItemsArray = item.options.filter((x) => x.name == name);
|
||||
let date = new Date(item.timestamp * 1000);
|
||||
if (
|
||||
relevantItemsArray.length == 1 &&
|
||||
item.timestamp - previousDate > 12 * 60 * 60
|
||||
) {
|
||||
let relevantItem = relevantItemsArray[0];
|
||||
// if (relevantItem.type == "PROBABILITY") {
|
||||
let result = {
|
||||
date,
|
||||
probability: relevantItem.probability,
|
||||
};
|
||||
newDataset.push(result);
|
||||
// }
|
||||
previousDate = item.timestamp;
|
||||
}
|
||||
}
|
||||
dataSets.push(newDataset);
|
||||
});
|
||||
|
||||
let dataSetsLength = dataSets.length;
|
||||
|
||||
return (
|
||||
<div className="grid grid-rows-1 bg-white p-10">
|
||||
<a
|
||||
className="text‑inherit no-underline"
|
||||
href={question.url}
|
||||
target="_blank"
|
||||
>
|
||||
<div className="flex justify-center items-center w-full">
|
||||
<div className="w-9/12">
|
||||
<a
|
||||
className="text‑inherit no-underline"
|
||||
href={question.url}
|
||||
target="_blank"
|
||||
>
|
||||
{/*
|
||||
<h1 className="text-3xl font-normal text-center mt-5">
|
||||
{question.title}
|
||||
</h1>
|
||||
</a>
|
||||
<VictoryChart
|
||||
domainPadding={20}
|
||||
padding={padding}
|
||||
theme={VictoryTheme.material}
|
||||
height={height}
|
||||
width={width}
|
||||
containerComponent={
|
||||
<VictoryVoronoiContainer
|
||||
labels={({ datum }) => `${datum.x}: ${Math.round(datum.y * 100)}%`}
|
||||
labelComponent={
|
||||
<VictoryTooltip
|
||||
pointerLength={0}
|
||||
dy={-12}
|
||||
style={{
|
||||
fontSize: 10,
|
||||
fill: "black",
|
||||
strokeWidth: 0.05,
|
||||
}}
|
||||
flyoutStyle={{
|
||||
stroke: "black",
|
||||
fill: "white",
|
||||
}}
|
||||
flyoutWidth={80}
|
||||
cornerRadius={0}
|
||||
flyoutPadding={7}
|
||||
/>
|
||||
}
|
||||
voronoiBlacklist={
|
||||
["line0", "line1", "line2", "line3", "line4"]
|
||||
*/}
|
||||
</a>
|
||||
<VictoryChart
|
||||
domainPadding={20}
|
||||
padding={padding}
|
||||
theme={VictoryTheme.material}
|
||||
height={height}
|
||||
width={width}
|
||||
containerComponent={
|
||||
<VictoryVoronoiContainer
|
||||
labels={({ datum }) =>
|
||||
`${datum.x}: ${Math.round(datum.y * 100)}%`
|
||||
}
|
||||
labelComponent={
|
||||
<VictoryTooltip
|
||||
pointerLength={0}
|
||||
dy={-12}
|
||||
style={{
|
||||
fontSize: 10,
|
||||
fill: "black",
|
||||
strokeWidth: 0.05,
|
||||
}}
|
||||
flyoutStyle={{
|
||||
stroke: "black",
|
||||
fill: "white",
|
||||
}}
|
||||
flyoutWidth={80}
|
||||
cornerRadius={0}
|
||||
flyoutPadding={7}
|
||||
/>
|
||||
}
|
||||
voronoiBlacklist={
|
||||
["line-0", "line-1", "line-2", "line-3", "line-4"]
|
||||
|
||||
//Array.from(Array(5).keys()).map((x, i) => `line${i}`)
|
||||
// see: https://github.com/FormidableLabs/victory/issues/545
|
||||
}
|
||||
/>
|
||||
}
|
||||
domain={{
|
||||
y: [0, 1],
|
||||
}}
|
||||
>
|
||||
<VictoryLegend
|
||||
x={width - 100}
|
||||
y={height / 2 - 18 - (dataSetsLength - 1) * 13}
|
||||
orientation="vertical"
|
||||
gutter={20}
|
||||
style={{ border: { stroke: "black" }, title: { fontSize: 20 } }}
|
||||
data={
|
||||
Array.from(Array(dataSetsLength).keys()).map((i) => ({
|
||||
name: dataSetsNames[i],
|
||||
symbol: { fill: colors[i] },
|
||||
}))
|
||||
/*[
|
||||
//Array.from(Array(5).keys()).map((x, i) => `line${i}`)
|
||||
// see: https://github.com/FormidableLabs/victory/issues/545
|
||||
}
|
||||
/>
|
||||
}
|
||||
domain={{
|
||||
y: [0, 1],
|
||||
}}
|
||||
>
|
||||
<VictoryLegend
|
||||
x={width - 100}
|
||||
y={height / 2 - 18 - (dataSetsLength - 1) * 13}
|
||||
orientation="vertical"
|
||||
gutter={20}
|
||||
style={{ border: { stroke: "black" }, title: { fontSize: 20 } }}
|
||||
data={
|
||||
Array.from(Array(dataSetsLength).keys()).map((i) => ({
|
||||
name: dataSetsNames[i],
|
||||
symbol: { fill: colors[i] },
|
||||
}))
|
||||
/*[
|
||||
{ name: "One", symbol: { fill: "tomato", type: "star" } },
|
||||
{ name: "Two", symbol: { fill: "orange" } },
|
||||
{ name: "Three", symbol: { fill: "gold" } },
|
||||
]*/
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{dataSets.slice(0, 5).map((dataset, i) => getVictoryGroup(dataset, i))}
|
||||
<VictoryAxis
|
||||
// tickValues specifies both the number of ticks and where
|
||||
// they are placed on the axis
|
||||
// tickValues={dataAsXy.map((datum) => datum.x)}
|
||||
// tickFormat={dataAsXy.map((datum) => datum.x)}
|
||||
tickCount={7}
|
||||
style={{
|
||||
grid: { stroke: null, strokeWidth: 0.5 },
|
||||
}}
|
||||
//axisLabelComponent={
|
||||
// <VictoryLabel dy={40} style={{ fontSize: 10, fill: "gray" }} />
|
||||
//}
|
||||
// label="Date (dd/mm/yy)"
|
||||
tickLabelComponent={
|
||||
<VictoryLabel
|
||||
dy={0}
|
||||
angle={-30}
|
||||
style={{ fontSize: 10, fill: "gray" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<VictoryAxis
|
||||
dependentAxis
|
||||
// tickFormat specifies how ticks should be displayed
|
||||
tickFormat={(x) => `${x * 100}%`}
|
||||
style={{
|
||||
grid: { stroke: "#D3D3D3", strokeWidth: 0.5 },
|
||||
}}
|
||||
tickLabelComponent={
|
||||
<VictoryLabel dy={0} style={{ fontSize: 10, fill: "gray" }} />
|
||||
}
|
||||
/>
|
||||
</VictoryChart>
|
||||
{dataSets
|
||||
.slice(0, 5)
|
||||
.map((dataset, i) => getVictoryGroup(dataset, i))}
|
||||
<VictoryAxis
|
||||
// tickValues specifies both the number of ticks and where
|
||||
// they are placed on the axis
|
||||
// tickValues={dataAsXy.map((datum) => datum.x)}
|
||||
// tickFormat={dataAsXy.map((datum) => datum.x)}
|
||||
tickCount={7}
|
||||
style={{
|
||||
grid: { stroke: null, strokeWidth: 0.5 },
|
||||
}}
|
||||
//axisLabelComponent={
|
||||
// <VictoryLabel dy={40} style={{ fontSize: 10, fill: "gray" }} />
|
||||
//}
|
||||
// label="Date (dd/mm/yy)"
|
||||
tickLabelComponent={
|
||||
<VictoryLabel
|
||||
dy={0}
|
||||
angle={-30}
|
||||
style={{ fontSize: 10, fill: "gray" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<VictoryAxis
|
||||
dependentAxis
|
||||
// tickFormat specifies how ticks should be displayed
|
||||
tickFormat={(x) => `${x * 100}%`}
|
||||
style={{
|
||||
grid: { stroke: "#D3D3D3", strokeWidth: 0.5 },
|
||||
}}
|
||||
tickLabelComponent={
|
||||
<VictoryLabel dy={0} style={{ fontSize: 10, fill: "gray" }} />
|
||||
}
|
||||
/>
|
||||
</VictoryChart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -112,7 +112,8 @@ export const QuestionOptions: React.FC<{ options: Option[] }> = ({
|
|||
const isBinary =
|
||||
options.length === 2 &&
|
||||
(options[0].name === "Yes" || options[0].name === "No");
|
||||
|
||||
const getYesOption = (options) =>
|
||||
options.find((option) => option.name == "Yes");
|
||||
const optionsSorted = options.sort((a, b) => b.probability - a.probability);
|
||||
const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
|
||||
|
||||
|
@ -121,17 +122,17 @@ export const QuestionOptions: React.FC<{ options: Option[] }> = ({
|
|||
<div className="space-x-2">
|
||||
<span
|
||||
className={`${primaryForecastColor(
|
||||
options[0].probability
|
||||
getYesOption(options).probability
|
||||
)} text-white w-16 rounded-md px-1.5 py-0.5 font-bold`}
|
||||
>
|
||||
{formatProbability(options[0].probability)}
|
||||
{formatProbability(getYesOption(options).probability)}
|
||||
</span>
|
||||
<span
|
||||
className={`${textColor(
|
||||
options[0].probability
|
||||
getYesOption(options).probability
|
||||
)} text-gray-500 inline-block`}
|
||||
>
|
||||
{primaryEstimateAsText(options[0].probability)}
|
||||
{primaryEstimateAsText(getYesOption(options).probability)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -40,8 +40,8 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
|||
const QuestionCardContents: React.FC<{
|
||||
question: QuestionWithHistoryFragment;
|
||||
}> = ({ question }) => (
|
||||
<div className="space-y-4">
|
||||
<h1>
|
||||
<div className="grid grid-cols-1 space-y-4 place-items-center">
|
||||
<h1 className="text-4xl place-self-center w-full text-center mt-10">
|
||||
<a
|
||||
className="text-black no-underline"
|
||||
href={question.url}
|
||||
|
@ -50,21 +50,31 @@ const QuestionCardContents: React.FC<{
|
|||
{question.title}
|
||||
</a>
|
||||
</h1>
|
||||
<QuestionFooter question={question} expandFooterToFullWidth={true} />
|
||||
<HistoryChart question={question} />
|
||||
{/*
|
||||
<div className="flex justify-center items-center w-full">
|
||||
<div className="w-6/12">
|
||||
<QuestionFooter question={question} expandFooterToFullWidth={true} />
|
||||
</div>
|
||||
</div>
|
||||
<QuestionOptions options={question.options} />
|
||||
|
||||
<ReactMarkdown linkTarget="_blank" className="font-normal">
|
||||
*/}
|
||||
|
||||
<h1 className="text-xl place-self-center w-full text-center mt-20">
|
||||
{"Question description"}
|
||||
</h1>
|
||||
|
||||
<ReactMarkdown linkTarget="_blank" className="font-normal w-9/12">
|
||||
{question.description}
|
||||
</ReactMarkdown>
|
||||
|
||||
<HistoryChart question={question} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const QuestionPage: NextPage<Props> = ({ id }) => {
|
||||
return (
|
||||
<Layout page="question">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<Card highlightOnHover={false}>
|
||||
<Query document={QuestionPageDocument} variables={{ id }}>
|
||||
{({ data }) => <QuestionCardContents question={data.result} />}
|
||||
|
|
Loading…
Reference in New Issue
Block a user