diff --git a/src/web/questions/components/HistoryChart.tsx b/src/web/questions/components/HistoryChart.tsx
index 673a3c0..852140f 100644
--- a/src/web/questions/components/HistoryChart.tsx
+++ b/src/web/questions/components/HistoryChart.tsx
@@ -1,7 +1,7 @@
import {
addDays, differenceInDays, format, startOfDay, startOfToday, startOfTomorrow
} from "date-fns";
-import React, { useState } from "react";
+import React, { useMemo, useState } from "react";
import {
VictoryAxis, VictoryChart, VictoryGroup, VictoryLabel, VictoryLine, VictoryScatter,
VictoryTheme, VictoryTooltip, VictoryVoronoiContainer
@@ -15,7 +15,17 @@ interface Props {
type DataSet = { x: Date; y: number; name: string }[];
-const colors = ["dodgerblue", "crimson", "seagreen", "darkviolet", "turquoise"];
+const MAX_LINES = 5;
+
+// number of colors should match MAX_LINES
+// colors are taken from tailwind, https://tailwindcss.com/docs/customizing-colors
+const colors = [
+ "#0284C7", // sky-600
+ "#DC2626", // red-600
+ "#15803D", // green-700
+ "#7E22CE", // purple-700
+ "#F59E0B", // amber-500
+];
// can't be replaced with React component, VictoryChart requires VictoryGroup elements to be immediate children
const getVictoryGroup = ({
@@ -29,12 +39,18 @@ const getVictoryGroup = ({
}) => {
return (
+
(active || highlight ? 3.75 : 3)}
/>
-
-
);
};
@@ -63,13 +79,11 @@ const Legend: React.FC<{
);
};
-export const HistoryChart: React.FC = ({ question }) => {
- const [highlight, setHighlight] = useState(undefined);
-
+const buildDataSets = (question: QuestionWithHistoryFragment) => {
let dataSetsNames = question.options
.sort((a, b) => (a.probability > b.probability ? -1 : 1))
- .map((o) => o.name);
- dataSetsNames = [...new Set(dataSetsNames)].slice(0, 5); // take the first 5
+ .map((o) => o.name)
+ .slice(0, MAX_LINES);
const isBinary =
(dataSetsNames[0] === "Yes" && dataSetsNames[1] === "No") ||
@@ -78,51 +92,45 @@ export const HistoryChart: React.FC = ({ question }) => {
dataSetsNames = ["Yes"];
}
- let dataSets: DataSet[] = [];
- let maxProbability = 0;
+ const nameToIndex = Object.fromEntries(
+ dataSetsNames.map((name, i) => [name, i])
+ );
+ let dataSets: DataSet[] = [...Array(dataSetsNames.length)].map((x) => []);
const sortedHistory = question.history.sort((a, b) =>
a.timestamp < b.timestamp ? -1 : 1
);
- for (const name of dataSetsNames) {
- let newDataset: DataSet = [];
+ {
let previousDate = -Infinity;
for (const item of sortedHistory) {
- const relevantItemsArray = item.options.filter((x) => x.name === name);
+ if (item.timestamp - previousDate < 12 * 60 * 60) {
+ continue;
+ }
const date = new Date(item.timestamp * 1000);
- if (
- relevantItemsArray.length === 1 &&
- item.timestamp - previousDate > 12 * 60 * 60
- ) {
- let relevantItem = relevantItemsArray[0];
+
+ for (const option of item.options) {
+ const idx = nameToIndex[option.name];
+ if (idx === undefined) {
+ continue;
+ }
const result = {
x: date,
- y: relevantItem.probability,
- name: relevantItem.name,
+ y: option.probability,
+ name: option.name,
};
- maxProbability =
- relevantItem.probability > maxProbability
- ? relevantItem.probability
- : maxProbability;
- newDataset.push(result);
- previousDate = item.timestamp;
+ dataSets[idx].push(result);
}
+ previousDate = item.timestamp;
}
- dataSets.push(newDataset);
}
- const domainMax =
- maxProbability < 0.5 ? Math.round(10 * (maxProbability + 0.05)) / 10 : 1;
- const goldenRatio = (1 + Math.sqrt(5)) / 2;
- const width = 750;
- const height = width / goldenRatio;
- const padding = {
- top: 20,
- bottom: 60,
- left: 60,
- right: 20,
- };
+ let maxProbability = 0;
+ for (const dataSet of dataSets) {
+ for (const item of dataSet) {
+ maxProbability = Math.max(maxProbability, item.y);
+ }
+ }
const minDate = sortedHistory.length
? startOfDay(new Date(sortedHistory[0].timestamp * 1000))
@@ -136,6 +144,36 @@ export const HistoryChart: React.FC = ({ question }) => {
)
: startOfTomorrow();
+ const result = {
+ dataSets,
+ dataSetsNames,
+ maxProbability,
+ minDate,
+ maxDate,
+ };
+ return result;
+};
+
+export const HistoryChart: React.FC = ({ question }) => {
+ const [highlight, setHighlight] = useState(undefined);
+
+ const { dataSets, dataSetsNames, maxProbability, minDate, maxDate } = useMemo(
+ () => buildDataSets(question),
+ [question]
+ );
+
+ const domainMax =
+ maxProbability < 0.5 ? Math.round(10 * (maxProbability + 0.05)) / 10 : 1;
+ const goldenRatio = (1 + Math.sqrt(5)) / 2;
+ const width = 750;
+ const height = width / goldenRatio;
+ const padding = {
+ top: 20,
+ bottom: 60,
+ left: 60,
+ right: 20,
+ };
+
return (
= ({ question }) => {
}
radius={50}
voronoiBlacklist={
- [...Array(5).keys()].map((i) => `line-${i}`)
+ [...Array(MAX_LINES).keys()].map((i) => `line-${i}`)
// see: https://github.com/FormidableLabs/victory/issues/545
}
/>
@@ -204,9 +242,6 @@ export const HistoryChart: React.FC = ({ question }) => {
y: [0, domainMax],
}}
>
- {dataSets.map((dataset, i) =>
- getVictoryGroup({ data: dataset, i, highlight: i === highlight })
- )}
= ({ question }) => {
// tickFormat specifies how ticks should be displayed
tickFormat={(x) => `${x * 100}%`}
/>
+ {
+ dataSets
+ .map((dataSet, i) =>
+ getVictoryGroup({ data: dataSet, i, highlight: i === highlight })
+ )
+ .reverse() // affects svg render order, we want to render largest datasets on top of others
+ }