cleanups: charts

This commit is contained in:
Vyacheslav Matyukhin 2022-04-30 23:39:57 +04:00
parent 12a236186f
commit 3ae7a68cb2
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
2 changed files with 42 additions and 59 deletions

View File

@ -1,3 +1,4 @@
import { format } from "date-fns";
import React from "react"; import React from "react";
import { import {
VictoryAxis, VictoryChart, VictoryGroup, VictoryLabel, VictoryLegend, VictoryLine, VictoryAxis, VictoryChart, VictoryGroup, VictoryLabel, VictoryLegend, VictoryLine,
@ -10,17 +11,18 @@ interface Props {
question: QuestionWithHistoryFragment; question: QuestionWithHistoryFragment;
} }
let formatOptionName = (name) => { let formatOptionName = (name: string) => {
return name.length > 20 ? name.slice(0, 17) + "..." : name; return name.length > 20 ? name.slice(0, 17) + "..." : name;
}; };
let getLength = (str) => { let getLength = (str: string): number => {
let capitalLetterLengthMultiplier = 1.25; // TODO - measure with temporary DOM element instead?
let smallLetterMultiplier = 0.8; const capitalLetterLengthMultiplier = 1.25;
let numUpper = (str.match(/[A-Z]/g) || []).length; const smallLetterMultiplier = 0.8;
let numSmallLetters = (str.match(/[fijlrt]/g) || []).length; const numUpper = (str.match(/[A-Z]/g) || []).length;
let numSpaces = (str.match(/[\s]/g) || []).length; const numSmallLetters = (str.match(/[fijlrt]/g) || []).length;
let length = const numSpaces = (str.match(/[\s]/g) || []).length;
const length =
str.length + str.length +
-numUpper - -numUpper -
numSmallLetters + numSmallLetters +
@ -29,78 +31,58 @@ let getLength = (str) => {
return length; return length;
}; };
let timestampToString = (x) => { type DataSet = { date: Date; probability: number; name: string }[];
// for real timestamps
// console.log(x);
let date = new Date(Date.parse(x));
let dayOfMonth = date.getDate();
let month = date.getMonth() + 1;
let year = date.getFullYear();
let dateString = `${("0" + dayOfMonth).slice(-2)}/${("0" + month).slice(
-2
)}/${year.toString().slice(-2)}`;
// console.log(dateString);
return dateString;
};
let dataAsXy = (data) => const dataAsXy = (data: DataSet) =>
data.map((datum) => ({ data.map((datum) => ({
x: timestampToString(datum.date), //getDate(datum.date * (1000 * 60 * 60 * 24)), x: format(datum.date, "yyyy-MM-dd"),
y: datum.probability, y: datum.probability,
name: datum.name, name: datum.name,
})); }));
const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"]; const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"];
const getVictoryGroup = (data, i) => { // can't be replaced with React component, VictoryChar requires VictoryGroup elements to be immediate children
const getVictoryGroup = ({ data, i }: { data: DataSet; i: number }) => {
return ( return (
<VictoryGroup color={colors[i] || "darkgray"} data={dataAsXy(data)}> <VictoryGroup color={colors[i] || "darkgray"} data={dataAsXy(data)} key={i}>
<VictoryScatter <VictoryScatter
name={`scatter-${i}`} name={`scatter-${i}`}
//style={{ labels: { display: "none" } }}
size={({ active }) => (active ? 3.75 : 3)} size={({ active }) => (active ? 3.75 : 3)}
//labels={() => null}
//labelComponent={<span></span>}
/> />
<VictoryLine <VictoryLine name={`line-${i}`} />
name={`line-${i}`}
//style={{ labels: { display: "none" } }}
//labels={() => null}
//labelComponent={<span></span>}
/>
</VictoryGroup> </VictoryGroup>
); );
}; };
export const HistoryChart: React.FC<Props> = ({ question }) => { export const HistoryChart: React.FC<Props> = ({ question }) => {
let dataSetsNames = []; let dataSetsNames: string[] = [];
question.history.forEach((item) => { question.history.forEach((item) => {
let optionNames = item.options.map((option) => option.name); let optionNames = item.options.map((option) => option.name);
dataSetsNames.push(...optionNames); dataSetsNames.push(...optionNames);
}); });
dataSetsNames = [...new Set(dataSetsNames)].slice(0, 5); // take the first 5 dataSetsNames = [...new Set(dataSetsNames)].slice(0, 5); // take the first 5
let isBinary = const isBinary =
(dataSetsNames[0] == "Yes" && dataSetsNames[1] == "No") || (dataSetsNames[0] === "Yes" && dataSetsNames[1] === "No") ||
(dataSetsNames[0] == "No" && dataSetsNames[1] == "Yes"); (dataSetsNames[0] === "No" && dataSetsNames[1] === "Yes");
if (isBinary) { if (isBinary) {
dataSetsNames = ["Yes"]; dataSetsNames = ["Yes"];
} }
let dataSets = []; let dataSets: DataSet[] = [];
let maxProbability = 0; let maxProbability = 0;
let longestNameLength = 0; let longestNameLength = 0;
for (let name of dataSetsNames) { for (const name of dataSetsNames) {
let newDataset = []; let newDataset: DataSet = [];
let previousDate = -Infinity; let previousDate = -Infinity;
for (let item of question.history) { for (let item of question.history) {
let relevantItemsArray = item.options.filter((x) => x.name == name); const relevantItemsArray = item.options.filter((x) => x.name === name);
let date = new Date(item.timestamp * 1000); const date = new Date(item.timestamp * 1000);
if ( if (
relevantItemsArray.length == 1 && relevantItemsArray.length == 1 &&
item.timestamp - previousDate > 12 * 60 * 60 item.timestamp - previousDate > 12 * 60 * 60
) { ) {
let relevantItem = relevantItemsArray[0]; let relevantItem = relevantItemsArray[0];
// if (relevantItem.type == "PROBABILITY") {
let result = { let result = {
date, date,
probability: relevantItem.probability, probability: relevantItem.probability,
@ -110,34 +92,33 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
relevantItem.probability > maxProbability relevantItem.probability > maxProbability
? relevantItem.probability ? relevantItem.probability
: maxProbability; : maxProbability;
let length = getLength(relevantItem.name); let length = getLength(formatOptionName(relevantItem.name));
longestNameLength = longestNameLength =
length > longestNameLength ? length : 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; const letterLength = 7;
let labelLegendStart = 45; const labelLegendStart = 45;
let domainMax = const domainMax =
maxProbability < 0.5 ? Math.round(10 * (maxProbability + 0.05)) / 10 : 1; maxProbability < 0.5 ? Math.round(10 * (maxProbability + 0.05)) / 10 : 1;
let dataSetsLength = dataSets.length; const dataSetsLength = dataSets.length;
let goldenRatio = (1 + Math.sqrt(5)) / 2; const goldenRatio = (1 + Math.sqrt(5)) / 2;
let width = 750; const width = 750;
let height = width / goldenRatio; const height = width / goldenRatio;
let padding = { const padding = {
top: 20, top: 20,
bottom: 50, bottom: 50,
left: 60, left: 60,
right: labelLegendStart + letterLength * longestNameLength, right: labelLegendStart + letterLength * longestNameLength,
}; };
let legendData = Array.from(Array(dataSetsLength).keys()).map((i) => ({ const legendData = Array.from(Array(dataSetsLength).keys()).map((i) => ({
name: formatOptionName(dataSetsNames[i]), name: formatOptionName(dataSetsNames[i]),
symbol: { fill: colors[i] }, symbol: { fill: colors[i] },
})); }));
@ -154,6 +135,7 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
labels={({ datum }) => `Not shown`} labels={({ datum }) => `Not shown`}
labelComponent={ labelComponent={
<VictoryTooltip <VictoryTooltip
constrainToVisibleArea
pointerLength={0} pointerLength={0}
dy={-12} dy={-12}
text={({ datum }) => text={({ datum }) =>
@ -192,7 +174,9 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
data={legendData} data={legendData}
/> />
{dataSets.slice(0, 5).map((dataset, i) => getVictoryGroup(dataset, i))} {dataSets
.slice(0, 5)
.map((dataset, i) => getVictoryGroup({ data: dataset, i }))}
<VictoryAxis <VictoryAxis
// tickValues specifies both the number of ticks and where // tickValues specifies both the number of ticks and where
// they are placed on the axis // they are placed on the axis
@ -205,7 +189,6 @@ export const HistoryChart: React.FC<Props> = ({ question }) => {
//axisLabelComponent={ //axisLabelComponent={
// <VictoryLabel dy={40} style={{ fontSize: 10, fill: "gray" }} /> // <VictoryLabel dy={40} style={{ fontSize: 10, fill: "gray" }} />
//} //}
// label="Date (dd/mm/yy)"
tickLabelComponent={ tickLabelComponent={
<VictoryLabel <VictoryLabel
dy={10} dy={10}

View File

@ -40,7 +40,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
}; };
const Section: React.FC<{ title: string }> = ({ title, children }) => ( const Section: React.FC<{ title: string }> = ({ title, children }) => (
<div className="space-y-4 flex flex-col items-center"> <div className="space-y-2 flex flex-col items-center">
<h2 className="text-xl text-gray-900">{title}</h2> <h2 className="text-xl text-gray-900">{title}</h2>
<div>{children}</div> <div>{children}</div>
</div> </div>
@ -49,7 +49,7 @@ const Section: React.FC<{ title: string }> = ({ title, children }) => (
const QuestionCardContents: React.FC<{ const QuestionCardContents: React.FC<{
question: QuestionWithHistoryFragment; question: QuestionWithHistoryFragment;
}> = ({ question }) => ( }> = ({ question }) => (
<div className="flex flex-col space-y-8 items-center pt-5"> <div className="flex flex-col space-y-4 items-center">
<h1 className="sm:text-4xl text-2xl text-center"> <h1 className="sm:text-4xl text-2xl text-center">
<a <a
className="text-black no-underline hover:text-gray-600" className="text-black no-underline hover:text-gray-600"