Skip to content

Commit

Permalink
Merge pull request #56 from eyra/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
mellelieuwes authored Oct 10, 2023
2 parents fb8d309 + f1ec1dd commit f9155e5
Show file tree
Hide file tree
Showing 87 changed files with 5,324 additions and 1,556 deletions.
20 changes: 20 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"prettier.trailingComma": "none",
"prettier.tabWidth": 2,
"prettier.semi": false,
"prettier.singleQuote": true,
"prettier.printWidth": 100,
"search.exclude": {
"**/node_modules": true,
"**/dist": true
},
"tailwindCSS.includeLanguages": {
"html": "html",
"javascript": "javascript",
"typescript": "typescript",
"css": "css"
},
"editor.quickSuggestions": {
"strings": true
}
}
62 changes: 62 additions & 0 deletions dist/framework/types/visualizations.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Text } from './elements';
export interface VisualizationProps {
title: Text;
height?: number;
}
export type AggregationFunction = 'count' | 'mean' | 'sum' | 'count_pct' | 'pct';
export interface Axis {
label?: string;
column: string;
}
export interface AggregationGroup {
label?: string;
column: string;
dateFormat?: DateFormat;
tokenize?: boolean;
}
export interface AggregationValue {
label?: string;
column: string;
aggregate?: AggregationFunction;
group_by?: string;
secondAxis?: boolean;
z?: string;
zAggregate?: AggregationFunction;
addZeroes?: boolean;
}
export interface ChartVisualization extends VisualizationProps {
type: 'line' | 'bar' | 'area';
group: AggregationGroup;
values: AggregationValue[];
}
export interface TextVisualization extends VisualizationProps {
type: 'wordcloud';
textColumn: string;
valueColumn?: string;
tokenize?: boolean;
}
export interface ScoredTerm {
text: string;
value: number;
importance: number;
rowIds?: string[];
}
export type VisualizationType = ChartVisualization | TextVisualization;
export interface AxisSettings {
label: string;
secondAxis?: boolean;
tickerFormat?: TickerFormat;
}
export type TickerFormat = 'percent' | 'default';
export interface ChartVisualizationData {
type: 'line' | 'bar' | 'area';
data: Array<Record<string, any>>;
xKey: AxisSettings;
yKeys: Record<string, AxisSettings>;
}
export interface TextVisualizationData {
type: 'wordcloud';
topTerms: ScoredTerm[];
}
export type VisualizationData = ChartVisualizationData | TextVisualizationData;
export type DateFormat = 'auto' | 'year' | 'quarter' | 'month' | 'day' | 'hour' | 'month_cycle' | 'weekday_cycle' | 'day_cycle' | 'hour_cycle';
1 change: 1 addition & 0 deletions dist/framework/types/visualizations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReactNode } from 'react';
interface Props {
children: ReactNode;
size?: string;
fullSize?: boolean;
minimized?: ReactNode;
}
export declare const Minimizable: ({ children, size, fullSize, minimized }: Props) => JSX.Element;
export {};
24 changes: 24 additions & 0 deletions dist/framework/visualisation/react/ui/elements/Minimizable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState } from 'react';
export const Minimizable = ({ children, size = 'h-[15rem] w-[24rem]', fullSize, minimized }) => {
const [isMinimized, setIsMinimized] = useState(true);
const containerStyle = isMinimized
? `${size} overflow-hidden animate-fadeIn`
: (fullSize !== null && fullSize !== void 0 ? fullSize : false)
? 'w-full'
: '';
const childStyle = isMinimized
? 'scale-50 origin-top-left z-10 p-5 w-[200%] '
: 'transition-all duration-500';
const toggleStyle = isMinimized
? 'transition-all absolute top-0 left-0 h-full w-full z-20 bg-primary/0 hover:bg-primary/25 border-solid cursor-zoom-in'
: 'w-min mr-auto mt-2 cursor-zoom-out';
const iconStyle = isMinimized ? 'rounded-tr-sm bg-primary' : 'rounded-sm mb-2 bg-primary';
const minimizedTruthy = Boolean(minimized);
const child = minimizedTruthy
? minimized
: (_jsx("div", Object.assign({ className: `relative ${childStyle}` }, { children: minimizedTruthy ? minimized : children })));
return (_jsxs("div", Object.assign({ className: `overflow-auto relative ${containerStyle}` }, { children: [child, _jsx("div", Object.assign({ className: `flex items-end justify-start rounded-sm border-primary ${toggleStyle}`, onClick: () => setIsMinimized(!isMinimized) }, { children: _jsx("div", Object.assign({ className: `relative font-caption text-xl px-4 py-1 backdrop-blur-[2px] text-white z-30 ${iconStyle}` }, { children: isMinimized ? zoomInIcon : zoomOutIcon })) }))] })));
};
const zoomInIcon = (_jsx("svg", Object.assign({ className: 'h-6 w-6', fill: 'none', stroke: 'currentColor', strokeWidth: '2', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg', "aria-hidden": 'true' }, { children: _jsx("path", { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM10.5 7.5v6m3-3h-6' }) })));
const zoomOutIcon = (_jsx("svg", Object.assign({ className: 'h-6 w-6', fill: 'none', stroke: 'currentColor', strokeWidth: '2', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg', "aria-hidden": 'true' }, { children: _jsx("path", { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM13.5 10.5h-6' }) })));
14 changes: 14 additions & 0 deletions dist/framework/visualisation/react/ui/elements/figure.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference types="react" />
import { TableWithContext } from '../../../../types/elements';
import { VisualizationType } from '../../../../types/visualizations';
import { ReactFactoryContext } from '../../factory';
type Props = VisualizationProps & ReactFactoryContext;
export interface VisualizationProps {
table: TableWithContext;
visualization: VisualizationType;
locale: string;
handleDelete: (rowIds: string[]) => void;
handleUndo: () => void;
}
export declare const Figure: ({ table, visualization, locale, handleDelete, handleUndo }: Props) => JSX.Element;
export {};
56 changes: 56 additions & 0 deletions dist/framework/visualisation/react/ui/elements/figure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import TextBundle from '../../../../text_bundle';
import { Translator } from '../../../../translator';
import { memo, useMemo } from 'react';
import useVisualizationData from '../hooks/useVisualizationData';
import { Title6 } from './text';
import Lottie from 'lottie-react';
import spinnerDark from '../../../../../assets/lottie/spinner-dark.json';
import RechartsGraph from './figures/recharts_graph';
import VisxWordcloud from './figures/visx_wordcloud';
export const Figure = ({ table, visualization, locale, handleDelete, handleUndo }) => {
var _a;
const [visualizationData, status] = useVisualizationData(table, visualization);
const { title } = useMemo(() => {
const title = Translator.translate(visualization.title, locale);
return { title };
}, [visualization]);
const { errorMsg, noDataMsg } = useMemo(() => prepareCopy(locale), [locale]);
if ((visualizationData == null) && status === 'loading') {
return (_jsx("div", Object.assign({ className: 'w-12 h-12' }, { children: _jsx(Lottie, { animationData: spinnerDark, loop: true }) })));
}
if (status === 'error') {
return _jsx("div", Object.assign({ className: 'flex justify-center items-center text-error' }, { children: errorMsg }));
}
const visualizationHeightTruthy = Boolean(visualization.height);
const minHeight = visualizationHeightTruthy ? `${(_a = visualization.height) !== null && _a !== void 0 ? _a : ''} px` : '20rem';
return (_jsxs("div", Object.assign({ className: 'flex flex-col overflow-hidden' }, { children: [_jsx(Title6, { text: title, margin: 'mt-2 mb-4' }), _jsx("div", Object.assign({ className: 'relative z-50 flex max-w-full', style: { flex: `1 1 ${minHeight}`, minHeight } }, { children: _jsx(RenderVisualization, { visualizationData: visualizationData, fallbackMessage: noDataMsg }) }))] })));
};
const RenderVisualization = memo(({ visualizationData, fallbackMessage }) => {
if (visualizationData == null)
return null;
const fallback = (_jsx("div", Object.assign({ className: 'm-auto font-bodybold text-4xl text-grey2 ' }, { children: fallbackMessage })));
if (['line', 'bar', 'area'].includes(visualizationData.type)) {
const chartVisualizationData = visualizationData;
if (chartVisualizationData.data.length === 0)
return fallback;
return _jsx(RechartsGraph, { visualizationData: chartVisualizationData });
}
if (visualizationData.type === 'wordcloud') {
const textVisualizationData = visualizationData;
if (textVisualizationData.topTerms.length === 0)
return fallback;
return _jsx(VisxWordcloud, { visualizationData: textVisualizationData });
}
return null;
});
function prepareCopy(locale) {
return {
errorMsg: Translator.translate(errorMsg, locale),
noDataMsg: Translator.translate(noDataMsg, locale)
};
}
const noDataMsg = new TextBundle().add('en', 'No data').add('nl', 'Geen data');
const errorMsg = new TextBundle()
.add('en', 'Could not create visualization')
.add('nl', 'Kon visualisatie niet maken');
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="react" />
import { ChartVisualizationData } from '../../../../../types/visualizations';
interface Props {
visualizationData: ChartVisualizationData;
}
export default function RechartsGraph({ visualizationData }: Props): JSX.Element | null;
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, Tooltip, Legend, BarChart, Bar, AreaChart, Area } from 'recharts';
export default function RechartsGraph({ visualizationData }) {
function tooltip() {
return (_jsx(Tooltip, { allowEscapeViewBox: { x: false, y: false }, labelStyle: { marginBottom: '0.5rem' }, contentStyle: {
fontSize: '0.8rem',
lineHeight: '0.8rem',
background: '#fff8',
backdropFilter: 'blur(3px)'
} }));
}
function axes(minTickGap) {
const hasVisualizationData = Boolean(visualizationData);
if (!hasVisualizationData)
return null;
const secondary = Object.values(visualizationData.yKeys).findIndex((yKey) => yKey.secondAxis) !==
-1;
const { tickFormatter, tickFormatter2 } = getTickFormatters(Object.values(visualizationData.yKeys));
return (_jsxs(_Fragment, { children: [_jsx(XAxis, { dataKey: visualizationData.xKey.label, minTickGap: minTickGap }), _jsx(YAxis, { yAxisId: 'left', tickFormatter: tickFormatter }), secondary && _jsx(YAxis, { yAxisId: 'right', orientation: 'right', tickFormatter: tickFormatter2 })] }));
}
function legend() {
return (_jsx(Legend, { margin: { left: 10 }, align: 'right', verticalAlign: 'top', iconType: 'plainline', wrapperStyle: { fontSize: '0.8rem' } }));
}
let chart = null;
if (visualizationData.type === 'line') {
chart = (_jsxs(LineChart, Object.assign({ data: visualizationData.data }, { children: [axes(20), tooltip(), legend(), Object.values(visualizationData.yKeys).map((yKey, i) => {
var _a;
const { color, dash } = getLineStyle(i);
return (_jsx(Line, { yAxisId: ((_a = yKey.secondAxis) !== null && _a !== void 0 ? _a : false) ? 'right' : 'left', type: 'monotone', dataKey: yKey.label, dot: false, strokeWidth: 2, stroke: color, strokeDasharray: dash }, yKey.label));
})] })));
}
if (visualizationData.type === 'bar') {
chart = (_jsxs(BarChart, Object.assign({ data: visualizationData.data }, { children: [axes(0), tooltip(), legend(), Object.values(visualizationData.yKeys).map((yKey, i) => {
var _a;
const { color } = getLineStyle(i);
return (_jsx(Bar, { yAxisId: ((_a = yKey.secondAxis) !== null && _a !== void 0 ? _a : false) ? 'right' : 'left', dataKey: yKey.label, fill: color }, yKey.label));
})] })));
}
if (visualizationData.type === 'area') {
chart = (_jsxs(AreaChart, Object.assign({ data: visualizationData.data }, { children: [axes(20), tooltip(), legend(), Object.values(visualizationData.yKeys).map((yKey, i) => {
var _a;
const { color } = getLineStyle(i);
return (_jsx(Area, { yAxisId: ((_a = yKey.secondAxis) !== null && _a !== void 0 ? _a : false) ? 'right' : 'left', dataKey: yKey.label, fill: color }, yKey.label));
})] })));
}
if (chart == null)
return null;
return (_jsx(ResponsiveContainer, Object.assign({ width: '100%', height: '100%' }, { children: chart })));
}
function getLineStyle(index) {
const COLORS = ['#4272EF', '#FF5E5E', '#FFCF60', '#1E3FCC', '#CC3F3F', '#CC9F3F'];
const DASHES = ['1', '5 5', '10 10', '5 5 10 10'];
const cell = index % (COLORS.length * DASHES.length);
const row = index % COLORS.length;
const column = Math.floor(cell / COLORS.length);
return { color: COLORS[row], dash: DASHES[column] };
}
function getTickFormatters(yKeys) {
var _a;
let tickerFormat;
let tickerFormat2;
for (const yKey of yKeys) {
if (!((_a = yKey.secondAxis) !== null && _a !== void 0 ? _a : false)) {
if (tickerFormat === undefined)
tickerFormat = yKey.tickerFormat;
if (tickerFormat !== yKey.tickerFormat)
tickerFormat = 'default';
}
else {
if (tickerFormat2 === undefined)
tickerFormat2 = yKey.tickerFormat;
if (tickerFormat2 !== yKey.tickerFormat)
tickerFormat2 = 'default';
}
}
const tickFormatter = getTickFormatter(tickerFormat !== null && tickerFormat !== void 0 ? tickerFormat : 'default');
const tickFormatter2 = getTickFormatter(tickerFormat2 !== null && tickerFormat2 !== void 0 ? tickerFormat2 : 'default');
return { tickFormatter, tickFormatter2 };
}
function getTickFormatter(format) {
if (format === 'percent')
return (value) => `${value}%`;
return undefined;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="react" />
import { TextVisualizationData } from '../../../../../types/visualizations';
interface Props {
visualizationData: TextVisualizationData;
}
declare function VisxWordcloud({ visualizationData }: Props): JSX.Element | null;
export default VisxWordcloud;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { Text } from '@visx/text';
import Wordcloud from '@visx/wordcloud/lib/Wordcloud';
import { ParentSize } from '@visx/responsive';
import { useMemo } from 'react';
function VisxWordcloud({ visualizationData }) {
const fontRange = [12, 60];
const colors = ['#1E3FCC', '#4272EF', '#CC9F3F', '#FFCF60'];
const nWords = 100;
const words = useMemo(() => {
return visualizationData.topTerms.slice(0, nWords);
}, [visualizationData, nWords]);
return (_jsx(ParentSize, { children: (parent) => (_jsx(Wordcloud, Object.assign({ words: words, height: parent.height, width: parent.width, rotate: 0, padding: 3, spiral: 'rectangular', fontSize: (w) => w.importance * (fontRange[1] - fontRange[0]) + fontRange[0], random: () => 0.5 }, { children: (cloudWords) => {
return cloudWords.map((w, i) => {
var _a, _b, _c;
return (_jsx(Text, Object.assign({ fill: colors[Math.floor((i / cloudWords.length) * colors.length)], fontSize: w.size, textAnchor: 'middle', fontFamily: w.font, transform: `translate(${(_a = w.x) !== null && _a !== void 0 ? _a : 0}, ${(_b = w.y) !== null && _b !== void 0 ? _b : 0}) rotate(${(_c = w.rotate) !== null && _c !== void 0 ? _c : 0})` }, { children: w.text }), w.text));
});
} }))) }));
}
export default VisxWordcloud;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="react" />
export interface Props {
page: number;
setPage: (page: number) => void;
nPages: number;
}
export declare const Pagination: ({ page, setPage, nPages }: Props) => JSX.Element;
13 changes: 13 additions & 0 deletions dist/framework/visualisation/react/ui/elements/pagination.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference types="react" />
import { PropsUIQuestionMultipleChoice } from '../../../../types/elements';
import { ReactFactoryContext } from '../../factory';
interface parentSetter {
parentSetter: (arg: any) => any;
}
type Props = PropsUIQuestionMultipleChoice & parentSetter & ReactFactoryContext;
export declare const MultipleChoiceQuestion: (props: Props) => JSX.Element;
export {};
Loading

0 comments on commit f9155e5

Please sign in to comment.