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