mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-26 04:57:55 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { styled } from '../styled.js';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function BaseChart ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Chart`}>
|
||||
{children}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 1em 1em 0;
|
||||
height: 15vw;
|
||||
width: 15vw;
|
||||
`;
|
||||
|
||||
export default React.memo(BaseChart);
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import React from 'react';
|
||||
import { Doughnut } from 'react-chartjs-2';
|
||||
|
||||
import { bnToBn } from '@pezkuwi/util';
|
||||
|
||||
import Base from './Base.js';
|
||||
|
||||
interface DoughnutValue {
|
||||
colors: string[];
|
||||
label: string;
|
||||
value: number | BN;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
className?: string;
|
||||
size?: number;
|
||||
values: DoughnutValue[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
colorNormal: string[];
|
||||
colorHover: string[];
|
||||
data: number[];
|
||||
labels: string[];
|
||||
}
|
||||
|
||||
function ChartDoughnut ({ className = '', size = 100, values }: Props): React.ReactElement<Props> {
|
||||
const options: Options = {
|
||||
colorHover: [],
|
||||
colorNormal: [],
|
||||
data: [],
|
||||
labels: []
|
||||
};
|
||||
|
||||
values.forEach(({ colors: [normalColor = '#00f', hoverColor], label, value }): void => {
|
||||
options.colorNormal.push(normalColor);
|
||||
options.colorHover.push(hoverColor || normalColor);
|
||||
options.data.push(bnToBn(value).toNumber());
|
||||
options.labels.push(label);
|
||||
});
|
||||
|
||||
return (
|
||||
<Base className={`${className} ui--Chart-Doughnut`}>
|
||||
<Doughnut
|
||||
data={{
|
||||
datasets: [{
|
||||
backgroundColor: options.colorNormal,
|
||||
data: options.data,
|
||||
hoverBackgroundColor: options.colorHover
|
||||
}],
|
||||
labels: options.labels
|
||||
}}
|
||||
height={size}
|
||||
width={size}
|
||||
/>
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ChartDoughnut);
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChartData, ChartOptions, TooltipItem } from 'chart.js';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
|
||||
import { bnToBn, isNumber } from '@pezkuwi/util';
|
||||
|
||||
import { alphaColor } from './utils.js';
|
||||
|
||||
interface Value {
|
||||
colors: string[];
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
value: number | BN;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
aspectRatio?: number;
|
||||
className?: string;
|
||||
max?: number;
|
||||
showLabels?: boolean;
|
||||
values: Value[];
|
||||
withColors?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
chartData?: ChartData;
|
||||
chartOptions?: ChartOptions;
|
||||
jsonValues?: string;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
labels: string[];
|
||||
datasets: {
|
||||
data: number[];
|
||||
backgroundColor: string[];
|
||||
hoverBackgroundColor: string[];
|
||||
}[];
|
||||
}
|
||||
|
||||
function calculateOptions (aspectRatio: number, values: Value[], jsonValues: string, max: number, showLabels: boolean): State {
|
||||
const chartData = values.reduce((data, { colors: [normalColor = '#00f', hoverColor], label, value }): Config => {
|
||||
const dataset = data.datasets[0];
|
||||
|
||||
dataset.backgroundColor.push(alphaColor(normalColor));
|
||||
dataset.hoverBackgroundColor.push(alphaColor(hoverColor || normalColor));
|
||||
dataset.data.push(isNumber(value) ? value : bnToBn(value).toNumber());
|
||||
data.labels.push(label);
|
||||
|
||||
return data;
|
||||
}, {
|
||||
datasets: [{
|
||||
backgroundColor: [] as string[],
|
||||
data: [] as number[],
|
||||
hoverBackgroundColor: [] as string[]
|
||||
}],
|
||||
labels: [] as string[]
|
||||
});
|
||||
|
||||
return {
|
||||
chartData,
|
||||
chartOptions: {
|
||||
aspectRatio,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (item: TooltipItem<any>): string =>
|
||||
values[item.dataIndex].tooltip || values[item.dataIndex].label
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: showLabels
|
||||
? { beginAtZero: true, max }
|
||||
: { display: false }
|
||||
}
|
||||
},
|
||||
jsonValues
|
||||
};
|
||||
}
|
||||
|
||||
function ChartHorizBar ({ aspectRatio = 8, className = '', max = 100, showLabels = false, values }: Props): React.ReactElement<Props> | null {
|
||||
const [{ chartData, chartOptions, jsonValues }, setState] = useState<State>({});
|
||||
|
||||
useEffect((): void => {
|
||||
const newJsonValues = JSON.stringify(values);
|
||||
|
||||
if (newJsonValues !== jsonValues) {
|
||||
setState(calculateOptions(aspectRatio, values, newJsonValues, max, showLabels));
|
||||
}
|
||||
}, [aspectRatio, jsonValues, max, showLabels, values]);
|
||||
|
||||
if (!chartData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// HACK on width/height to get the aspectRatio to work
|
||||
return (
|
||||
<div className={`${className} ui--Chart-HorizBar`}>
|
||||
<Bar
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data={chartData as any}
|
||||
height={null as unknown as number}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
options={chartOptions as any}
|
||||
width={null as unknown as number}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ChartHorizBar);
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChartData, ChartDataset, ChartOptions, DatasetChartOptions } from 'chart.js';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import * as Chart from 'react-chartjs-2';
|
||||
|
||||
import { isBn, objectSpread } from '@pezkuwi/util';
|
||||
|
||||
import ErrorBoundary from '../ErrorBoundary.js';
|
||||
import { styled } from '../styled.js';
|
||||
import { alphaColor } from './utils.js';
|
||||
|
||||
export interface Props {
|
||||
colors?: (string | undefined)[];
|
||||
className?: string;
|
||||
labels: string[];
|
||||
legends: string[];
|
||||
options?: ChartOptions;
|
||||
values: (number | BN)[][];
|
||||
title?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
labels: string[];
|
||||
datasets: ChartDataset<'line'>[];
|
||||
}
|
||||
|
||||
const COLORS = ['#ff8c00', '#008c8c', '#8c008c'];
|
||||
|
||||
const BASE_OPTS: ChartOptions = {
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
hoverRadius: 6,
|
||||
radius: 0
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
intersect: false
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
},
|
||||
plugins: {
|
||||
crosshair: {
|
||||
line: {
|
||||
color: '#ff8c00',
|
||||
dashPattern: [5, 5],
|
||||
width: 2
|
||||
},
|
||||
snap: {
|
||||
enabled: true
|
||||
},
|
||||
sync: {
|
||||
enabled: true
|
||||
},
|
||||
// this would be nice, but atm just doesn't quite
|
||||
// seem or feel intuitive...
|
||||
zoom: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
intersect: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
maxRotation: 60,
|
||||
minRotation: 60
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getOptions (options: ChartOptions = {}): DatasetChartOptions<'line'> {
|
||||
return objectSpread({}, BASE_OPTS, options, {
|
||||
// Re-spread plugins for deep(er) copy
|
||||
plugins: objectSpread({}, BASE_OPTS.plugins, options.plugins, {
|
||||
// Same applied to plugins, we may want specific values
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
annotation: objectSpread({}, BASE_OPTS.plugins?.annotation, options.plugins?.annotation),
|
||||
crosshair: objectSpread({}, BASE_OPTS.plugins?.crosshair, options.plugins?.crosshair),
|
||||
tooltip: objectSpread({}, BASE_OPTS.plugins?.tooltip, options.plugins?.tooltip)
|
||||
}),
|
||||
scales: objectSpread({}, BASE_OPTS.scales, options.scales, {
|
||||
x: objectSpread({}, BASE_OPTS.scales?.x, options.scales?.x),
|
||||
y: objectSpread({}, BASE_OPTS.scales?.y, options.scales?.y)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getData (colors: (string | undefined)[] = [], legends: string[], labels: string[], values: (number | BN)[][]): ChartData<'line'> {
|
||||
return values.reduce((chartData, values, index): Config => {
|
||||
const color = colors[index] || alphaColor(COLORS[index]);
|
||||
const data = values.map((value): number => isBn(value) ? value.toNumber() : value);
|
||||
|
||||
chartData.datasets.push({
|
||||
backgroundColor: color,
|
||||
borderColor: color,
|
||||
cubicInterpolationMode: 'default',
|
||||
data,
|
||||
fill: false,
|
||||
hoverBackgroundColor: color,
|
||||
label: legends[index],
|
||||
// @ts-expect-error The typings here doesn't reflect this one
|
||||
lineTension: 0.25
|
||||
});
|
||||
|
||||
return chartData;
|
||||
}, { datasets: [] as ChartDataset<'line'>[], labels });
|
||||
}
|
||||
|
||||
function LineChart ({ className = '', colors, labels, legends, options, title, values }: Props): React.ReactElement<Props> | null {
|
||||
const chartOptions = useMemo(
|
||||
() => getOptions(options),
|
||||
[options]
|
||||
);
|
||||
|
||||
const chartData = useMemo(
|
||||
() => getData(colors, legends, labels, values),
|
||||
[colors, labels, legends, values]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Chart-Line`}>
|
||||
{title && <h1 className='ui--Chart-Header'>{title}</h1>}
|
||||
<ErrorBoundary>
|
||||
<Chart.Line
|
||||
data={chartData}
|
||||
options={chartOptions}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
h1.ui--Chart-Header {
|
||||
margin-bottom: 0.25rem;
|
||||
margin-top: 1rem;
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(LineChart);
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChartType } from 'chart.js';
|
||||
import type { CrosshairOptions } from 'chartjs-plugin-crosshair';
|
||||
|
||||
declare module 'chart.js' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface PluginOptionsByType<TType extends ChartType> {
|
||||
crosshair: CrosshairOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
declare module 'chart.js/helpers' {
|
||||
export const color: (c: string) => {
|
||||
alpha: (a: number) => {
|
||||
rgbString: () => string;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChartType, Plugin } from 'chart.js';
|
||||
|
||||
import { CategoryScale, Chart, LinearScale, LineElement, PointElement, Title, Tooltip } from 'chart.js';
|
||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||
import crosshairPlugin from 'chartjs-plugin-crosshair';
|
||||
|
||||
import Doughnut from './Doughnut.js';
|
||||
import HorizBar from './HorizBar.js';
|
||||
import Line from './Line.js';
|
||||
|
||||
interface CrosshairChart {
|
||||
crosshair?: boolean;
|
||||
}
|
||||
|
||||
function CustomCrosshairPlugin (plugin: Plugin<ChartType>): Plugin<ChartType> {
|
||||
const originalAfterDraw = plugin.afterDraw;
|
||||
|
||||
if (originalAfterDraw) {
|
||||
plugin.afterDraw = function (chart, args): void {
|
||||
if ((chart as CrosshairChart).crosshair) {
|
||||
// @ts-expect-error - Pass exactly what the original plugin expects
|
||||
originalAfterDraw.call(this, chart, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
Chart.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
annotationPlugin,
|
||||
CustomCrosshairPlugin(crosshairPlugin as Plugin<ChartType>)
|
||||
);
|
||||
|
||||
export default {
|
||||
Doughnut,
|
||||
HorizBar,
|
||||
Line
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as helpers from 'chart.js/helpers';
|
||||
|
||||
export function alphaColor (hexColor: string): string {
|
||||
return helpers.color(hexColor).alpha(0.65).rgbString();
|
||||
}
|
||||
Reference in New Issue
Block a user