Upgrade web build to vite, move vega back up to 5+
This commit is contained in:
Родитель
31d5aa1dae
Коммит
c90b1f9746
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -17,8 +17,7 @@
|
|||
"test:ci": "jest --coverage",
|
||||
"git-is-clean": "essex git-is-clean",
|
||||
"ci": "run-s lint: build: bundle: test:ci git-is-clean",
|
||||
"release": "run-s clean: build: publish:",
|
||||
"start": "yarn start:webapp"
|
||||
"release": "run-s clean: build: publish:"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "essex clean",
|
||||
"build": "essex build",
|
||||
"start": "essex watch"
|
||||
"build": "essex build"
|
||||
},
|
||||
"dependencies": {
|
||||
"chroma-js": "^2.1.2",
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
"build_lib": "essex build",
|
||||
"copy_json_esm": "ncp src/themes dist/esm/themes",
|
||||
"copy_json_cjs": "ncp src/themes dist/cjs/themes",
|
||||
"assets": "run-p copy_json_esm copy_json_cjs",
|
||||
"start": "essex watch"
|
||||
"assets": "run-p copy_json_esm copy_json_cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@thematic/color": "workspace:packages/color",
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "essex clean",
|
||||
"build": "essex build",
|
||||
"start": "essex watch"
|
||||
"build": "essex build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@thematic/core": "workspace:packages/core",
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "essex clean",
|
||||
"build": "essex build",
|
||||
"start": "essex watch"
|
||||
"build": "essex build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@essex-js-toolkit/hooks": "^1.1.5",
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "essex clean",
|
||||
"build": "essex build",
|
||||
"start": "essex watch"
|
||||
"build": "essex build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@thematic/color": "workspace:packages/color",
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "essex clean",
|
||||
"build": "essex build",
|
||||
"start": "essex watch"
|
||||
"build": "essex build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@thematic/core": "workspace:packages/core",
|
||||
|
@ -25,10 +24,10 @@
|
|||
"devDependencies": {
|
||||
"@essex/scripts": "^19.0.5",
|
||||
"@types/node": "^14.18.5",
|
||||
"vega": "^5.21.0"
|
||||
"vega": "5.15.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vega": ">= 5.13.0"
|
||||
"vega": "5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vega": {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Gochi Hand:300,400,700"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Thematic</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -6,10 +6,10 @@
|
|||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"start:webapp": "yarn start",
|
||||
"bundle": "webpack",
|
||||
"clean": "essex clean build"
|
||||
"clean": "essex clean build",
|
||||
"build": "tsc && vite build",
|
||||
"start": "vite",
|
||||
"start:webapp": "yarn start"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -36,11 +36,11 @@
|
|||
"redux-actions": "^2.6.5",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.4.1",
|
||||
"vega": "^4"
|
||||
"vega": "5.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/scripts": "^19.0.5",
|
||||
"@essex/webpack-config": "^19.0.2",
|
||||
"@essex/vite-config": "^18.0.2",
|
||||
"@types/d3-axis": "^1.0.16",
|
||||
"@types/d3-scale": "^2.2.6",
|
||||
"@types/d3-selection": "^1.4.3",
|
||||
|
@ -52,11 +52,7 @@
|
|||
"@types/redux-actions": "^2.6.2",
|
||||
"@types/redux-logger": "^3.0.9",
|
||||
"@types/redux-thunk": "^2.1.32",
|
||||
"raw-loader": "^4.0.2",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.7.2",
|
||||
"worker-loader": "^2.0.0"
|
||||
"vite": "^2.7.10"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Theme } from '@thematic/core'
|
||||
import './App.css'
|
||||
export interface AppProps {
|
||||
theme: Theme
|
||||
}
|
||||
export declare const App: import('react-redux').ConnectedComponent<
|
||||
({ theme }: AppProps) => JSX.Element,
|
||||
import('react-redux').Omit<AppProps, 'theme'>
|
||||
>
|
|
@ -0,0 +1,71 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
import { ThematicFluentProvider } from '@thematic/fluent'
|
||||
import { ApplicationStyles } from '@thematic/react'
|
||||
import { connect } from 'react-redux'
|
||||
import { ControlPanel } from './components/ControlPanel'
|
||||
import { ThemeEditor } from './components/ThemeEditor'
|
||||
import { ThemeViewer } from './components/ThemeViewer'
|
||||
import './App.css'
|
||||
const AppComponent = ({ theme }) =>
|
||||
_jsxs(
|
||||
ThematicFluentProvider,
|
||||
{
|
||||
theme: theme,
|
||||
children: [
|
||||
_jsx(ApplicationStyles, {}, void 0),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'App',
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'controls',
|
||||
style: {
|
||||
backgroundColor: theme.application().faint().hex(),
|
||||
borderBottom: `1px solid ${theme
|
||||
.application()
|
||||
.border()
|
||||
.hex()}`,
|
||||
},
|
||||
children: _jsx(ControlPanel, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'main',
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'editor',
|
||||
children: _jsx(ThemeEditor, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'viewer',
|
||||
children: _jsx(ThemeViewer, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
export const App = connect(state => ({
|
||||
theme: state.theme,
|
||||
}))(AppComponent)
|
|
@ -0,0 +1,7 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import './index.css'
|
||||
import { FC } from 'react'
|
||||
export declare const ApplicationPalette: FC
|
|
@ -0,0 +1,87 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import './index.css'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useMemo } from 'react'
|
||||
import { ColorStrip } from '../ColorStrip'
|
||||
const applicationPrimaryKeys = ['accent', 'foreground', 'background', 'border']
|
||||
const applicationSignalKeys = ['success', 'warning', 'error']
|
||||
const applicationLowKeys = ['faint', 'lowContrast', 'lowMidContrast']
|
||||
const applicationHighKeys = ['midContrast', 'midHighContrast', 'highContrast']
|
||||
export const ApplicationPalette = () => {
|
||||
const theme = useThematic()
|
||||
const primaries = useColors(applicationPrimaryKeys, theme)
|
||||
const signals = useColors(applicationSignalKeys, theme)
|
||||
const secondaries = useColors(applicationLowKeys, theme)
|
||||
const elements = useColors(applicationHighKeys, theme)
|
||||
const styles = {
|
||||
swatch: {
|
||||
border: `1px solid ${theme.application().border().hex()}`,
|
||||
},
|
||||
label: {
|
||||
width: 80,
|
||||
},
|
||||
}
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'application-palette',
|
||||
children: [
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: primaries,
|
||||
swatchStyle: styles.swatch,
|
||||
labelStyle: styles.label,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: signals,
|
||||
swatchStyle: styles.swatch,
|
||||
labelStyle: styles.label,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: secondaries,
|
||||
swatchStyle: styles.swatch,
|
||||
labelStyle: styles.label,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: elements,
|
||||
swatchStyle: styles.swatch,
|
||||
labelStyle: styles.label,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
function useColors(keys, theme) {
|
||||
return useMemo(() => {
|
||||
const app = theme.application()
|
||||
return keys.map(key => ({
|
||||
color: app[key]().hex(),
|
||||
label: key,
|
||||
secondaryLabel: app[key]().hex(),
|
||||
}))
|
||||
}, [keys, theme])
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { FC } from 'react'
|
||||
export interface ColorBandProps {
|
||||
colors: string[]
|
||||
foreground: string
|
||||
label: string
|
||||
width: number
|
||||
}
|
||||
export declare const ColorBand: FC<ColorBandProps>
|
|
@ -0,0 +1,63 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
export const ColorBand = ({ colors, foreground, label, width }) => {
|
||||
// hard-coded 83 is the label width + (2 * padding) + (2 * margin) + (2 * band margin)
|
||||
// 65 + 10 + 4 + 4
|
||||
const slice = width ? (width - 83) / colors.length : 0
|
||||
const colorBlocks = colors.map((color, index) => {
|
||||
return _jsx(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
width: slice,
|
||||
flexShrink: 0,
|
||||
background: color,
|
||||
height: 30,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
`Color-${index}`,
|
||||
)
|
||||
})
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
},
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
width: 65,
|
||||
flexShrink: 0,
|
||||
height: 20,
|
||||
lineHeight: '20px',
|
||||
color: foreground,
|
||||
textAlign: 'right',
|
||||
margin: 2,
|
||||
padding: 5,
|
||||
},
|
||||
children: label,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
margin: 2,
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
children: colorBlocks,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { FC } from 'react'
|
||||
export interface ColorStripProps {
|
||||
colors: string[]
|
||||
labelColors?: string[]
|
||||
foreground: string
|
||||
background: string
|
||||
label: string
|
||||
}
|
||||
export declare const ColorStrip: FC<ColorStripProps>
|
|
@ -0,0 +1,70 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
export const ColorStrip = ({
|
||||
colors,
|
||||
labelColors,
|
||||
foreground,
|
||||
background,
|
||||
label,
|
||||
}) => {
|
||||
const colorBlocks = colors.map((color, index) => {
|
||||
return _jsx(
|
||||
'div',
|
||||
{
|
||||
title: `${label} color ${color}`,
|
||||
style: {
|
||||
width: 50,
|
||||
flexShrink: 0,
|
||||
background: color,
|
||||
height: 20,
|
||||
lineHeight: '20px',
|
||||
color: labelColors != null ? labelColors[index] : background,
|
||||
textAlign: 'center',
|
||||
margin: 2,
|
||||
padding: 5,
|
||||
},
|
||||
children: color,
|
||||
},
|
||||
`Color-${index}`,
|
||||
)
|
||||
})
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
},
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
width: 65,
|
||||
flexShrink: 0,
|
||||
height: 20,
|
||||
lineHeight: '20px',
|
||||
color: foreground,
|
||||
textAlign: 'right',
|
||||
margin: 2,
|
||||
padding: 5,
|
||||
},
|
||||
children: label,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
children: colorBlocks,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { FC } from 'react'
|
||||
export interface ContrastProps {
|
||||
foreground: string
|
||||
background: string
|
||||
error: string
|
||||
showLink?: boolean
|
||||
}
|
||||
export declare const Contrast: FC<ContrastProps>
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
jsx as _jsx,
|
||||
Fragment as _Fragment,
|
||||
jsxs as _jsxs,
|
||||
} from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { IconButton } from '@fluentui/react'
|
||||
import { contrast } from '@thematic/color'
|
||||
const WCAG = 4.5
|
||||
const ICON_SIZE = '0.7em'
|
||||
const NOTE = 'WCAG guidelines recommend a minimum contrast ratio of 4.5:1'
|
||||
export const Contrast = ({ foreground, background, error, showLink }) => {
|
||||
const c = contrast(foreground, background)
|
||||
const style = {}
|
||||
const low = c < WCAG
|
||||
if (low) {
|
||||
style.color = error
|
||||
}
|
||||
const display = Math.round(c * 100) / 100
|
||||
const buttonStyles = {
|
||||
root: {
|
||||
width: ICON_SIZE,
|
||||
height: ICON_SIZE,
|
||||
},
|
||||
icon: {
|
||||
fontSize: ICON_SIZE,
|
||||
},
|
||||
}
|
||||
return _jsxs(
|
||||
_Fragment,
|
||||
{
|
||||
children: [
|
||||
_jsx('span', { style: style, title: NOTE, children: display }, void 0),
|
||||
showLink && low
|
||||
? _jsx(
|
||||
IconButton,
|
||||
{
|
||||
iconProps: { iconName: 'NavigateExternalInline' },
|
||||
styles: buttonStyles,
|
||||
href: 'https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast',
|
||||
title: NOTE,
|
||||
target: '_blank',
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
: null,
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { FC } from 'react'
|
||||
export interface ColorPaletteProps {
|
||||
scaleItemCount: number
|
||||
}
|
||||
export declare const ColorPalette: FC<ColorPaletteProps>
|
|
@ -0,0 +1,258 @@
|
|||
import { jsxs as _jsxs, jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { mark2style, useThematic } from '@thematic/react'
|
||||
import { Fragment } from 'react'
|
||||
import { ApplicationPalette } from './ApplicationPalette'
|
||||
import { ColorBand } from './ColorBand'
|
||||
import { ColorStrip } from './ColorStrip'
|
||||
import { Contrast } from './Contrast'
|
||||
// this is the same number we use under the hood when constructing the continuous scales,
|
||||
// so it makes sense to use here and potentially allow the gradation bands to be visible,
|
||||
// because that's how the actual encodings will work. for many of the mid-range interpolated
|
||||
// values there can be a duplicate color at this level, so we know it is fine enough to cover
|
||||
// what the scale puts forth
|
||||
const BAND_SLICES = 100
|
||||
const BAND_WIDTH = 720
|
||||
export const ColorPalette = ({ scaleItemCount }) => {
|
||||
const theme = useThematic()
|
||||
const accentColor = theme.application().accent().hex()
|
||||
const foregroundColor = theme.application().foreground().hex()
|
||||
const backgroundColor = theme.application().background().hex()
|
||||
const errorColor = theme.application().error().hex()
|
||||
const accent = _jsx(
|
||||
Fragment,
|
||||
{
|
||||
children: _jsxs(
|
||||
'span',
|
||||
{ style: { color: accentColor }, children: ['Accent ', accentColor] },
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
const background = _jsx(
|
||||
Fragment,
|
||||
{
|
||||
children: _jsxs(
|
||||
'span',
|
||||
{
|
||||
style: { color: foregroundColor },
|
||||
children: ['Background ', backgroundColor],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
const foreground = _jsx(
|
||||
Fragment,
|
||||
{
|
||||
children: _jsxs(
|
||||
'span',
|
||||
{
|
||||
style: { color: foregroundColor },
|
||||
children: ['Foreground ', foregroundColor],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
const bandDomain = [0, BAND_SLICES - 1]
|
||||
const nominal = theme.scales().nominal(scaleItemCount).toArray()
|
||||
const nominalBold = theme.scales().nominalBold(scaleItemCount).toArray()
|
||||
const nominalMuted = theme.scales().nominalMuted(scaleItemCount).toArray()
|
||||
const sequential = theme.scales().sequential(bandDomain).toArray()
|
||||
const diverging = theme.scales().diverging(bandDomain).toArray()
|
||||
const sequential2 = theme.scales().sequential2(bandDomain).toArray()
|
||||
const diverging2 = theme.scales().diverging2(bandDomain).toArray()
|
||||
const greys = theme.scales().greys(bandDomain).toArray()
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
border: `1px solid ${foregroundColor}`,
|
||||
background: backgroundColor,
|
||||
padding: '10px 30px 30px 30px',
|
||||
color: foregroundColor,
|
||||
textAlign: 'left',
|
||||
},
|
||||
children: [
|
||||
_jsx(
|
||||
'h2',
|
||||
{ style: { color: foregroundColor }, children: 'Application colors' },
|
||||
void 0,
|
||||
),
|
||||
_jsx(ApplicationPalette, {}, void 0),
|
||||
_jsxs(
|
||||
'ul',
|
||||
{
|
||||
style: {
|
||||
fontSize: 18,
|
||||
listStyleType: 'none',
|
||||
paddingLeft: 0,
|
||||
},
|
||||
children: [
|
||||
_jsxs(
|
||||
'li',
|
||||
{
|
||||
children: [
|
||||
foreground,
|
||||
' on ',
|
||||
background,
|
||||
' (contrast ratio:',
|
||||
' ',
|
||||
_jsx(
|
||||
Contrast,
|
||||
{
|
||||
foreground: foregroundColor,
|
||||
background: backgroundColor,
|
||||
error: errorColor,
|
||||
showLink: true,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
')',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'li',
|
||||
{
|
||||
children: [
|
||||
accent,
|
||||
' on ',
|
||||
background,
|
||||
' (contrast ratio:',
|
||||
' ',
|
||||
_jsx(
|
||||
Contrast,
|
||||
{
|
||||
foreground: accentColor,
|
||||
background: backgroundColor,
|
||||
error: errorColor,
|
||||
showLink: true,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
')',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'h2',
|
||||
{ style: { color: foregroundColor }, children: 'Scales' },
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
fontSize: 12,
|
||||
width: BAND_WIDTH,
|
||||
padding: 8,
|
||||
...mark2style(theme.plotArea()),
|
||||
},
|
||||
children: [
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
label: 'Nominal ',
|
||||
foreground: foregroundColor,
|
||||
background: backgroundColor,
|
||||
colors: nominal,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
label: 'Nominal+',
|
||||
foreground: foregroundColor,
|
||||
background: backgroundColor,
|
||||
colors: nominalBold,
|
||||
labelColors: nominalMuted,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
label: 'Nominal-',
|
||||
foreground: foregroundColor,
|
||||
background: backgroundColor,
|
||||
colors: nominalMuted,
|
||||
labelColors: nominalBold,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorBand,
|
||||
{
|
||||
label: 'Sequential',
|
||||
foreground: foregroundColor,
|
||||
colors: sequential,
|
||||
width: BAND_WIDTH,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorBand,
|
||||
{
|
||||
label: 'Sequential2',
|
||||
foreground: foregroundColor,
|
||||
colors: sequential2,
|
||||
width: BAND_WIDTH,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorBand,
|
||||
{
|
||||
label: 'Diverging',
|
||||
foreground: foregroundColor,
|
||||
colors: diverging,
|
||||
width: BAND_WIDTH,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorBand,
|
||||
{
|
||||
label: 'Diverging2',
|
||||
foreground: foregroundColor,
|
||||
colors: diverging2,
|
||||
width: BAND_WIDTH,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorBand,
|
||||
{
|
||||
label: 'Greys',
|
||||
foreground: foregroundColor,
|
||||
colors: greys,
|
||||
width: BAND_WIDTH,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { CSSProperties, FC } from 'react'
|
||||
import './index.css'
|
||||
export interface ColorStripProps {
|
||||
colorDefinitions: any[]
|
||||
vertical?: boolean
|
||||
swatchStyle?: CSSProperties
|
||||
labelStyle?: CSSProperties
|
||||
}
|
||||
export declare const ColorStrip: FC<ColorStripProps>
|
|
@ -0,0 +1,77 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
import './index.css'
|
||||
const ColorBlock = ({
|
||||
color,
|
||||
label,
|
||||
secondaryLabel,
|
||||
vertical,
|
||||
swatchStyle,
|
||||
labelStyle,
|
||||
}) => {
|
||||
const direction = vertical ? 'vertical' : 'horizontal'
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
className: `color-block-${direction}`,
|
||||
children: [
|
||||
label
|
||||
? _jsx(
|
||||
'div',
|
||||
{
|
||||
className: `color-block-primary-label-${direction}`,
|
||||
style: labelStyle,
|
||||
children: label,
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
: null,
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: `color-block-swatch-${direction}`,
|
||||
style: { ...swatchStyle, backgroundColor: `${color}` },
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
secondaryLabel
|
||||
? _jsx(
|
||||
'div',
|
||||
{
|
||||
className: `color-block-secondary-label-${direction}`,
|
||||
children: secondaryLabel,
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
: null,
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const ColorStrip = ({
|
||||
colorDefinitions,
|
||||
vertical,
|
||||
swatchStyle,
|
||||
labelStyle,
|
||||
}) => {
|
||||
const direction = vertical ? 'vertical' : 'horizontal'
|
||||
return _jsx(
|
||||
'div',
|
||||
{
|
||||
className: `color-strip-${direction}`,
|
||||
children: colorDefinitions.map(def =>
|
||||
_jsx(
|
||||
ColorBlock,
|
||||
{
|
||||
...def,
|
||||
vertical: vertical,
|
||||
swatchStyle: swatchStyle,
|
||||
labelStyle: labelStyle,
|
||||
},
|
||||
`${def.color}-${def.label}`,
|
||||
),
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { ColorBlindnessMode } from '@thematic/color'
|
||||
import { ThemeListing, Theme } from '@thematic/core'
|
||||
import { FC } from 'react'
|
||||
import './index.css'
|
||||
export interface ControlPanelProps {
|
||||
themes: ThemeListing[]
|
||||
themeInfo: ThemeListing
|
||||
chartSize: number
|
||||
drawNodes: boolean
|
||||
drawLinks: boolean
|
||||
scaleItemCount: number
|
||||
colorBlindnessMode: ColorBlindnessMode
|
||||
onThemeLoaded: (theme: Theme) => void
|
||||
onThemeChange: (t: ThemeListing) => void
|
||||
onThemeVariantToggled: () => void
|
||||
onChartSizeChange: (n: number) => void
|
||||
onDrawNodesChange: (d: boolean) => void
|
||||
onDrawLinksChange: (d: boolean) => void
|
||||
onScaleItemCountChange: (value: number) => void
|
||||
onColorBlindnessModeChange: (mode: ColorBlindnessMode) => void
|
||||
}
|
||||
export declare const ControlPanel: FC<ControlPanelProps>
|
|
@ -0,0 +1,297 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Dropdown, Toggle, SpinButton, Position } from '@fluentui/react'
|
||||
import { ColorBlindnessMode, colorBlindnessInfo } from '@thematic/color'
|
||||
import { ColorPickerButton } from '@thematic/fluent'
|
||||
import { useState, useCallback } from 'react'
|
||||
import { EnumDropdown } from '../EnumDropdown'
|
||||
import './index.css'
|
||||
const SCALE_MIN = 1
|
||||
const SCALE_MAX = 1000
|
||||
export const ControlPanel = ({
|
||||
themes,
|
||||
themeInfo,
|
||||
chartSize,
|
||||
drawNodes,
|
||||
drawLinks,
|
||||
scaleItemCount,
|
||||
colorBlindnessMode,
|
||||
onThemeLoaded,
|
||||
onThemeChange,
|
||||
onThemeVariantToggled,
|
||||
onChartSizeChange,
|
||||
onDrawNodesChange,
|
||||
onDrawLinksChange,
|
||||
onScaleItemCountChange,
|
||||
onColorBlindnessModeChange,
|
||||
}) => {
|
||||
const handleThemeChange = (e, v) => {
|
||||
if (v) {
|
||||
const found = themes.find(t => t.id === v.key)
|
||||
if (found) {
|
||||
onThemeChange(found)
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleChartIncrement = useCallback(
|
||||
() => onChartSizeChange(chartSize + 100),
|
||||
[onChartSizeChange, chartSize],
|
||||
)
|
||||
const handleChartDecrement = useCallback(
|
||||
() => onChartSizeChange(chartSize - 100),
|
||||
[onChartSizeChange, chartSize],
|
||||
)
|
||||
const handleChartValidate = useCallback(
|
||||
v => {
|
||||
const num = parseInt(v, 10)
|
||||
if (!isNaN(num) && num >= 100) {
|
||||
onChartSizeChange(num)
|
||||
}
|
||||
},
|
||||
[onChartSizeChange],
|
||||
)
|
||||
const handleDarkChange = (e, v) => {
|
||||
setDark(!dark)
|
||||
onThemeVariantToggled()
|
||||
}
|
||||
const handleDrawNodesChange = (e, v) => onDrawNodesChange(v)
|
||||
const handleDrawLinksChange = (e, v) => onDrawLinksChange(v)
|
||||
const changeValue = useCallback(
|
||||
(value, change = 0) => {
|
||||
const num = parseInt(value, 10)
|
||||
if (!isNaN(num)) {
|
||||
const updated = num + change
|
||||
if (updated >= SCALE_MIN && updated <= SCALE_MAX) {
|
||||
onScaleItemCountChange(updated)
|
||||
}
|
||||
}
|
||||
},
|
||||
[onScaleItemCountChange],
|
||||
)
|
||||
const handleScaleValidate = useCallback(v => changeValue(v), [changeValue])
|
||||
const handleScaleIncrement = useCallback(
|
||||
v => {
|
||||
changeValue(v, 1)
|
||||
},
|
||||
[changeValue],
|
||||
)
|
||||
const handleScaleDecrement = useCallback(
|
||||
v => changeValue(v, -1),
|
||||
[changeValue],
|
||||
)
|
||||
const handleColorBlindnessChange = e => {
|
||||
onColorBlindnessModeChange(e)
|
||||
}
|
||||
const [dark, setDark] = useState(false)
|
||||
const cbInfo = colorBlindnessInfo(colorBlindnessMode)
|
||||
const renderDropdownOption = useCallback(option => {
|
||||
return _jsx(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
borderLeft: `8px solid ${option.data.accent}`,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
children: option.text,
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}, [])
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'control-wrapper',
|
||||
children: [
|
||||
_jsx('h1', { children: 'thematic' }, void 0),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'controls',
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'control',
|
||||
children: _jsx(
|
||||
Dropdown,
|
||||
{
|
||||
label: 'Theme',
|
||||
selectedKey: themeInfo.id,
|
||||
onChange: handleThemeChange,
|
||||
options: themes.map(t => ({
|
||||
key: t.id,
|
||||
text: t.name,
|
||||
data: t,
|
||||
})),
|
||||
onRenderOption: renderDropdownOption,
|
||||
styles: {
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'control',
|
||||
children: _jsx(
|
||||
ColorPickerButton,
|
||||
{ label: 'Accent', onChange: onThemeLoaded },
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'control',
|
||||
children: _jsx(
|
||||
Toggle,
|
||||
{
|
||||
label: 'Dark mode',
|
||||
checked: dark,
|
||||
onChange: handleDarkChange,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'control spinner',
|
||||
children: _jsx(
|
||||
SpinButton,
|
||||
{
|
||||
label: 'Scale items',
|
||||
labelPosition: Position.top,
|
||||
value: scaleItemCount.toString(),
|
||||
min: SCALE_MIN,
|
||||
max: SCALE_MAX,
|
||||
step: 1,
|
||||
onIncrement: handleScaleIncrement,
|
||||
onDecrement: handleScaleDecrement,
|
||||
onValidate: handleScaleValidate,
|
||||
incrementButtonAriaLabel: 'Increase value by 1',
|
||||
decrementButtonAriaLabel: 'Decrease value by 1',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'control spinner',
|
||||
children: _jsx(
|
||||
SpinButton,
|
||||
{
|
||||
label: 'Chart size',
|
||||
labelPosition: Position.top,
|
||||
value: chartSize.toString(),
|
||||
min: 100,
|
||||
max: 2000,
|
||||
step: 100,
|
||||
onIncrement: handleChartIncrement,
|
||||
onDecrement: handleChartDecrement,
|
||||
onValidate: handleChartValidate,
|
||||
incrementButtonAriaLabel: 'Increase value by 1',
|
||||
decrementButtonAriaLabel: 'Decrease value by 1',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'control',
|
||||
children: _jsx(
|
||||
Toggle,
|
||||
{
|
||||
label: 'Draw nodes',
|
||||
checked: drawNodes,
|
||||
onChange: handleDrawNodesChange,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'control',
|
||||
children: _jsx(
|
||||
Toggle,
|
||||
{
|
||||
label: 'Draw links',
|
||||
checked: drawLinks,
|
||||
onChange: handleDrawLinksChange,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'control',
|
||||
children: [
|
||||
_jsx(
|
||||
EnumDropdown,
|
||||
{
|
||||
enumeration: ColorBlindnessMode,
|
||||
label: 'Color blindness',
|
||||
selected: colorBlindnessMode,
|
||||
onChange: handleColorBlindnessChange,
|
||||
styles: {
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: 150,
|
||||
},
|
||||
},
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
colorBlindnessMode !== ColorBlindnessMode.None &&
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'cb-info',
|
||||
children: `${cbInfo.description}. Affecting ${
|
||||
cbInfo.incidence < 0.01
|
||||
? '<1'
|
||||
: `~${Math.round(cbInfo.incidence * 100)}`
|
||||
}% of males.`,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/// <reference types="react" />
|
||||
export declare const ControlPanel: import('react-redux').ConnectedComponent<
|
||||
import('react').FC<import('./ControlPanel').ControlPanelProps>,
|
||||
import('react-redux').Omit<
|
||||
import('./ControlPanel').ControlPanelProps,
|
||||
| 'colorBlindnessMode'
|
||||
| 'themes'
|
||||
| 'themeInfo'
|
||||
| 'chartSize'
|
||||
| 'drawNodes'
|
||||
| 'drawLinks'
|
||||
| 'scaleItemCount'
|
||||
| 'onThemeLoaded'
|
||||
| 'onThemeChange'
|
||||
| 'onThemeVariantToggled'
|
||||
| 'onChartSizeChange'
|
||||
| 'onDrawNodesChange'
|
||||
| 'onDrawLinksChange'
|
||||
| 'onScaleItemCountChange'
|
||||
| 'onColorBlindnessModeChange'
|
||||
>
|
||||
>
|
|
@ -0,0 +1,37 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
themeLoaded,
|
||||
themeSelected,
|
||||
themeVariantToggled,
|
||||
chartSizeChanged,
|
||||
drawNodesChanged,
|
||||
drawLinksChanged,
|
||||
scaleItemCountChanged,
|
||||
colorBlindnessModeChanged,
|
||||
} from '../../state/actions'
|
||||
import { ControlPanel as ControlPanelComponent } from './ControlPanel'
|
||||
export const ControlPanel = connect(
|
||||
state => ({
|
||||
themes: state.themes,
|
||||
themeInfo: state.themeInfo,
|
||||
chartSize: state.ui.chartSize,
|
||||
drawNodes: state.ui.drawNodes,
|
||||
drawLinks: state.ui.drawLinks,
|
||||
scaleItemCount: state.ui.scaleItemCount,
|
||||
colorBlindnessMode: state.ui.colorBlindnessMode,
|
||||
}),
|
||||
{
|
||||
onThemeLoaded: themeLoaded,
|
||||
onThemeChange: themeSelected,
|
||||
onThemeVariantToggled: themeVariantToggled,
|
||||
onChartSizeChange: chartSizeChanged,
|
||||
onDrawNodesChange: drawNodesChanged,
|
||||
onDrawLinksChange: drawLinksChanged,
|
||||
onScaleItemCountChange: scaleItemCountChanged,
|
||||
onColorBlindnessModeChange: colorBlindnessModeChanged,
|
||||
},
|
||||
)(ControlPanelComponent)
|
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Theme } from '@thematic/core'
|
||||
import { FC } from 'react'
|
||||
import './index.css'
|
||||
export interface ColorSelectionProps {
|
||||
onThemeLoaded: (theme: Theme) => void
|
||||
}
|
||||
export declare const ColorSelection: FC<ColorSelectionProps>
|
|
@ -0,0 +1,21 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
import { ColorPicker, ColorPickerLayout } from '@thematic/fluent'
|
||||
import './index.css'
|
||||
export const ColorSelection = ({ onThemeLoaded }) => {
|
||||
return _jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'color-controls-area',
|
||||
children: _jsx(
|
||||
ColorPicker,
|
||||
{
|
||||
onChange: onThemeLoaded,
|
||||
layout: ColorPickerLayout.SideBySide,
|
||||
styles: { sliders: { width: 400 } },
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Theme } from '@thematic/core'
|
||||
import { FC } from 'react'
|
||||
import './index.css'
|
||||
export interface CoolerPickerProps {
|
||||
themeLoaded: (theme: Theme) => void
|
||||
}
|
||||
export declare const CoolerPicker: import('react-redux').ConnectedComponent<
|
||||
FC<CoolerPickerProps>,
|
||||
import('react-redux').Omit<CoolerPickerProps, 'themeLoaded'>
|
||||
>
|
|
@ -0,0 +1,18 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
import { connect } from 'react-redux'
|
||||
import { themeLoaded } from '../../state/actions'
|
||||
import { ColorSelection } from './ColorSelection'
|
||||
import './index.css'
|
||||
const CoolerPickerComponent = ({ themeLoaded }) => {
|
||||
return _jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'cooler-picker',
|
||||
children: _jsx(ColorSelection, { onThemeLoaded: themeLoaded }, void 0),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const CoolerPicker = connect(null, {
|
||||
themeLoaded,
|
||||
})(CoolerPickerComponent)
|
|
@ -0,0 +1,12 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export declare const backgroundHueShiftOptions: {
|
||||
label: string
|
||||
value: number
|
||||
}[]
|
||||
export declare const backgroundLevelOptions: {
|
||||
label: string
|
||||
value: number
|
||||
}[]
|
|
@ -0,0 +1,45 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export const backgroundHueShiftOptions = [
|
||||
{
|
||||
label: '-170 Complement',
|
||||
value: -170,
|
||||
},
|
||||
{
|
||||
label: '-60 Analogous',
|
||||
value: -60,
|
||||
},
|
||||
{
|
||||
label: '-30 Analogous',
|
||||
value: -30,
|
||||
},
|
||||
{
|
||||
label: '0 Monochrome',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '+30 Analogous',
|
||||
value: 30,
|
||||
},
|
||||
{
|
||||
label: '+60 Analogous',
|
||||
value: 60,
|
||||
},
|
||||
{
|
||||
label: '+170 Complement',
|
||||
value: 170,
|
||||
},
|
||||
]
|
||||
const getBackgroundLevelOptions = () => {
|
||||
const options = []
|
||||
for (let i = 0; i <= 100; i += 10) {
|
||||
options.push({
|
||||
label: '' + i,
|
||||
value: i,
|
||||
})
|
||||
}
|
||||
return options
|
||||
}
|
||||
export const backgroundLevelOptions = getBackgroundLevelOptions()
|
|
@ -0,0 +1,34 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import {
|
||||
IStyleFunctionOrObject,
|
||||
ILinkStyleProps,
|
||||
ILinkStyles,
|
||||
} from '@fluentui/react'
|
||||
import { FC } from 'react'
|
||||
export interface DownloadLinkProps {
|
||||
/**
|
||||
* Filename to download with
|
||||
*/
|
||||
filename: string
|
||||
/**
|
||||
* Value of the file contents. Direct passthrough to Blob constructor.
|
||||
* https://www.w3.org/TR/FileAPI/#dfn-Blob
|
||||
*/
|
||||
blobParts: Blob[] | BufferSource[] | string[]
|
||||
/**
|
||||
* Additional blob options. Direct passthrough to Blob constructor.
|
||||
* https://www.w3.org/TR/FileAPI/#dfn-BlobPropertyBag
|
||||
*/
|
||||
options?: BlobPropertyBag
|
||||
/**
|
||||
* Display text for the link.
|
||||
* Will default to "Download <filename>" if not provided
|
||||
*/
|
||||
label?: string
|
||||
/** Optional passthrough styles for the link */
|
||||
styles?: IStyleFunctionOrObject<ILinkStyleProps, ILinkStyles>
|
||||
}
|
||||
export declare const DownloadLink: FC<DownloadLinkProps>
|
|
@ -0,0 +1,28 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Link } from '@fluentui/react'
|
||||
export const DownloadLink = ({
|
||||
filename,
|
||||
blobParts,
|
||||
options,
|
||||
label,
|
||||
styles,
|
||||
}) => {
|
||||
const blob = new Blob(blobParts, options)
|
||||
const url = URL.createObjectURL(blob)
|
||||
const lbl = label || `Download ${filename}`
|
||||
return _jsx(
|
||||
Link,
|
||||
{
|
||||
styles: styles,
|
||||
title: lbl,
|
||||
href: url,
|
||||
download: filename,
|
||||
children: lbl,
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/// <reference types="react" />
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import {
|
||||
ICommandBarStyleProps,
|
||||
ICommandBarStyles,
|
||||
IStyleFunctionOrObject,
|
||||
} from '@fluentui/react'
|
||||
export interface WrapperStyles {
|
||||
root: React.CSSProperties
|
||||
label: React.CSSProperties
|
||||
}
|
||||
export interface EnumButtonBarProps<E> {
|
||||
enumeration: any
|
||||
selected?: E
|
||||
onChange?: (selected: string | number) => void
|
||||
wrapperStyles?: WrapperStyles
|
||||
commandBarStyles?: IStyleFunctionOrObject<
|
||||
ICommandBarStyleProps,
|
||||
ICommandBarStyles
|
||||
>
|
||||
iconNames?: string[]
|
||||
iconOnly?: boolean
|
||||
label?: string
|
||||
}
|
||||
export declare function EnumButtonBar<E>({
|
||||
enumeration,
|
||||
selected,
|
||||
onChange,
|
||||
wrapperStyles,
|
||||
commandBarStyles,
|
||||
iconNames,
|
||||
iconOnly,
|
||||
label,
|
||||
}: EnumButtonBarProps<E>): JSX.Element
|
|
@ -0,0 +1,76 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { CommandBar, Label } from '@fluentui/react'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
const splitCamel = str => str.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
||||
export function EnumButtonBar({
|
||||
enumeration,
|
||||
selected,
|
||||
onChange,
|
||||
wrapperStyles,
|
||||
commandBarStyles,
|
||||
iconNames,
|
||||
iconOnly,
|
||||
label,
|
||||
}) {
|
||||
const theme = useThematic()
|
||||
const selectedKey = enumeration[enumeration[selected]]
|
||||
const handleChange = useCallback(
|
||||
s => {
|
||||
if (onChange) {
|
||||
onChange(s)
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
)
|
||||
const options = useMemo(
|
||||
() =>
|
||||
Object.entries(enumeration)
|
||||
.filter(m => isNaN(parseInt(m[0]))) // only keep the int copy from the enum obj
|
||||
.map((m, i) => {
|
||||
const key = m[1]
|
||||
const text = splitCamel(m[0])
|
||||
return {
|
||||
key,
|
||||
text,
|
||||
checked: selectedKey === key,
|
||||
iconProps: iconNames ? { iconName: iconNames[i] } : undefined,
|
||||
iconOnly,
|
||||
onClick: () => handleChange(key),
|
||||
}
|
||||
}),
|
||||
[enumeration, handleChange, iconNames, iconOnly, selectedKey],
|
||||
)
|
||||
const rootStyle = {
|
||||
// TODO: this width is used to prevent automatic collapsing with overflow items
|
||||
// it may not be what we always want, however
|
||||
width: options.length * 46.75,
|
||||
...(wrapperStyles ? wrapperStyles.root : {}),
|
||||
}
|
||||
const labelStyle = {
|
||||
...(wrapperStyles ? wrapperStyles.label : {}),
|
||||
}
|
||||
const commandStyles = {
|
||||
primarySet: {
|
||||
border: `1px solid ${theme.application().border().hex()}`,
|
||||
paddingleft: 0,
|
||||
paddingRight: 0,
|
||||
},
|
||||
...commandBarStyles,
|
||||
}
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: rootStyle,
|
||||
children: [
|
||||
label && _jsx(Label, { style: labelStyle, children: label }, void 0),
|
||||
_jsx(CommandBar, { items: options, styles: commandStyles }, void 0),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import {
|
||||
IDropdownStyles,
|
||||
IDropdownStyleProps,
|
||||
IStyleFunctionOrObject,
|
||||
} from '@fluentui/react'
|
||||
export interface EnumDropdownProps<E> {
|
||||
enumeration: any
|
||||
label?: string
|
||||
selected?: E
|
||||
onChange?: (selected: string | number) => void
|
||||
styles?: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles>
|
||||
}
|
||||
/**
|
||||
* This component wraps a Fluent Dropdown, automatically creating the options from a supplied enum.
|
||||
* https://developer.microsoft.com/en-us/fluentui#/controls/web/dropdown
|
||||
* TODO: add additional passthrough props. We really just override the options array and the onChange callback.
|
||||
*/
|
||||
export declare function EnumDropdown<E>({
|
||||
enumeration,
|
||||
label,
|
||||
selected,
|
||||
onChange,
|
||||
styles,
|
||||
}: EnumDropdownProps<E>): JSX.Element
|
|
@ -0,0 +1,45 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Dropdown } from '@fluentui/react'
|
||||
import { useCallback, useState } from 'react'
|
||||
/**
|
||||
* This component wraps a Fluent Dropdown, automatically creating the options from a supplied enum.
|
||||
* https://developer.microsoft.com/en-us/fluentui#/controls/web/dropdown
|
||||
* TODO: add additional passthrough props. We really just override the options array and the onChange callback.
|
||||
*/
|
||||
export function EnumDropdown({
|
||||
enumeration,
|
||||
label,
|
||||
selected,
|
||||
onChange = () => null,
|
||||
styles,
|
||||
}) {
|
||||
const options = Object.entries(enumeration)
|
||||
.filter(m => isNaN(parseInt(m[0])))
|
||||
.map(m => ({ key: m[1], text: m[0] }))
|
||||
const handleChange = useCallback(
|
||||
(e, v) => {
|
||||
if (v) {
|
||||
setSelectedKey(v.key)
|
||||
onChange(v.key)
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
)
|
||||
const key = enumeration[enumeration[selected]]
|
||||
const [selectedKey, setSelectedKey] = useState(key)
|
||||
return _jsx(
|
||||
Dropdown,
|
||||
{
|
||||
label: label,
|
||||
options: options,
|
||||
selectedKey: selectedKey,
|
||||
onChange: handleChange,
|
||||
styles: styles,
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Theme } from '@thematic/core'
|
||||
import { FC } from 'react'
|
||||
export interface FluentControlsComponentProps {
|
||||
themeLoaded: (theme: Theme) => void
|
||||
}
|
||||
export declare const FluentControls: import('react-redux').ConnectedComponent<
|
||||
FC<FluentControlsComponentProps>,
|
||||
import('react-redux').Omit<FluentControlsComponentProps, 'themeLoaded'>
|
||||
>
|
|
@ -0,0 +1,243 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { ScaleType } from '@thematic/core'
|
||||
import {
|
||||
ScaleDropdown,
|
||||
ScaleTypeChoiceGroup,
|
||||
ColorPicker,
|
||||
ColorPickerButton,
|
||||
} from '@thematic/fluent'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useState, useCallback, useMemo } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { themeLoaded } from '../../state/actions'
|
||||
const FluentControlsComponent = ({ themeLoaded }) => {
|
||||
const theme = useThematic()
|
||||
const [scale, setScale] = useState('<none>')
|
||||
const handleScaleChange = useCallback((e, option) => setScale(option.key), [])
|
||||
const [scaleType, setScaleType] = useState(ScaleType.Linear)
|
||||
const handleScaleTypeChange = useCallback(type => setScaleType(type), [])
|
||||
const handlePickerChange = useCallback(t => themeLoaded(t), [themeLoaded])
|
||||
const labelStyle = useMemo(
|
||||
() => ({
|
||||
fontWeight: 'bold',
|
||||
color: theme.application().accent().hex(),
|
||||
}),
|
||||
[theme],
|
||||
)
|
||||
const actionStyle = useMemo(
|
||||
() => ({
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
color: theme.application().warning().hex(),
|
||||
}),
|
||||
[theme],
|
||||
)
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
fontSize: 14,
|
||||
overflowY: 'scroll',
|
||||
},
|
||||
children: [
|
||||
_jsx(
|
||||
'p',
|
||||
{
|
||||
children:
|
||||
'The @thematic/fluent package contains a few custom Fluent controls you can use in your applications to allow Thematic-specific interactions.',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
style: controlsStyle,
|
||||
children: [
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
style: controlStyle,
|
||||
children: [
|
||||
_jsxs(
|
||||
'p',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'span',
|
||||
{ style: labelStyle, children: 'ScaleDropdown:' },
|
||||
void 0,
|
||||
),
|
||||
' a Dropdown that pre-loads Thematic scale options.',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ScaleDropdown,
|
||||
{
|
||||
placeholder: 'Choose scale',
|
||||
onChange: handleScaleChange,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'p',
|
||||
{ style: actionStyle, children: [' onChange: ', scale] },
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
style: controlStyle,
|
||||
children: [
|
||||
_jsxs(
|
||||
'p',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'span',
|
||||
{
|
||||
style: labelStyle,
|
||||
children: 'ScaleTypeChoiceGroup:',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
' a ChoiceGroup that pre-loads Thematic scale types.',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ScaleTypeChoiceGroup,
|
||||
{
|
||||
selectedType: scaleType,
|
||||
onChange: handleScaleTypeChange,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'p',
|
||||
{
|
||||
style: actionStyle,
|
||||
children: [' onChange: ', scaleType],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
style: controlStyle,
|
||||
children: [
|
||||
_jsxs(
|
||||
'p',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'span',
|
||||
{ style: labelStyle, children: 'ColorPicker:' },
|
||||
void 0,
|
||||
),
|
||||
' a ColorPicker that emits Thematic parameters.',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorPicker,
|
||||
{ theme: theme, onChange: handlePickerChange },
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'p',
|
||||
{
|
||||
style: actionStyle,
|
||||
children: [
|
||||
' ',
|
||||
'onChange: ',
|
||||
theme.application().accent().hex(),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
style: controlStyle,
|
||||
children: [
|
||||
_jsxs(
|
||||
'p',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'span',
|
||||
{
|
||||
style: labelStyle,
|
||||
children: 'ColorPickerButton:',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
' a DropdownButton that hosts a Thematic ColorPicker.',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorPickerButton,
|
||||
{ theme: theme, onChange: handlePickerChange },
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'p',
|
||||
{
|
||||
style: actionStyle,
|
||||
children: [
|
||||
' ',
|
||||
'onChange: ',
|
||||
theme.application().accent().hex(),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const FluentControls = connect(null, {
|
||||
themeLoaded,
|
||||
})(FluentControlsComponent)
|
||||
const controlsStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-around',
|
||||
}
|
||||
const controlStyle = {
|
||||
width: 320,
|
||||
padding: 8,
|
||||
margin: 8,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { FluentTheme } from '@thematic/fluent'
|
||||
import { FC } from 'react'
|
||||
export interface FluentPaletteProps {
|
||||
theme: FluentTheme
|
||||
}
|
||||
export declare const FluentPalette: FC<FluentPaletteProps>
|
|
@ -0,0 +1,129 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
import { ColorStrip } from '../ColorStrip'
|
||||
const primaryKeys = [
|
||||
'themeDarker',
|
||||
'themeDark',
|
||||
'themeDarkAlt',
|
||||
'themePrimary',
|
||||
'themeSecondary',
|
||||
'themeTertiary',
|
||||
'themeLight',
|
||||
'themeLighter',
|
||||
'themeLighterAlt',
|
||||
]
|
||||
const foregroundKeys = [
|
||||
'black',
|
||||
'neutralDark',
|
||||
'neutralPrimary',
|
||||
'neutralPrimaryAlt',
|
||||
'neutralSecondary',
|
||||
'neutralTertiary',
|
||||
]
|
||||
const backgroundKeys = [
|
||||
'neutralTertiaryAlt',
|
||||
'neutralQuaternary',
|
||||
'neutralQuaternaryAlt',
|
||||
'neutralLight',
|
||||
'neutralLighter',
|
||||
'neutralLighterAlt',
|
||||
'white',
|
||||
]
|
||||
export const FluentPalette = ({ theme }) => {
|
||||
const json = theme.toFluent()
|
||||
const mapkeys = keys =>
|
||||
keys.map(key => ({
|
||||
color: json.palette[key],
|
||||
label: key,
|
||||
secondaryLabel: json.palette[key],
|
||||
}))
|
||||
const primaries = mapkeys(primaryKeys)
|
||||
const foregrounds = mapkeys(foregroundKeys)
|
||||
const backgrounds = mapkeys(backgroundKeys)
|
||||
const styles = {
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
swatch: {
|
||||
border: `1px solid ${theme.application().border().hex()}`,
|
||||
},
|
||||
header: {
|
||||
color: theme.application().foreground().hex(),
|
||||
fontSize: 14,
|
||||
},
|
||||
}
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: styles.root,
|
||||
children: [
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{ style: styles.header, children: 'Primary' },
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: primaries,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{ style: styles.header, children: 'Foreground' },
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: foregrounds,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{ style: styles.header, children: 'Background' },
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: backgrounds,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { FC } from 'react'
|
||||
/**
|
||||
* This component hosts the Fluent Theme slots in the style of Thematic.
|
||||
* A running version can be found here: https://aka.ms/themedesigner
|
||||
*/
|
||||
export declare const FluentViewer: FC
|
|
@ -0,0 +1,40 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { loadFluentTheme } from '@thematic/fluent'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useMemo } from 'react'
|
||||
import { DownloadLink } from '../DownloadLink'
|
||||
import { FluentPalette } from './FluentPalette'
|
||||
/**
|
||||
* This component hosts the Fluent Theme slots in the style of Thematic.
|
||||
* A running version can be found here: https://aka.ms/themedesigner
|
||||
*/
|
||||
export const FluentViewer = () => {
|
||||
const theme = useThematic()
|
||||
const fluentTheme = useMemo(() => loadFluentTheme(theme), [theme])
|
||||
const value = useMemo(
|
||||
() => JSON.stringify(fluentTheme.toFluent(), null, 2),
|
||||
[fluentTheme],
|
||||
)
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(FluentPalette, { theme: fluentTheme }, void 0),
|
||||
_jsx(
|
||||
DownloadLink,
|
||||
{
|
||||
filename: `${theme.name}-${theme.variant}-fluent.json`,
|
||||
blobParts: [value],
|
||||
styles: { root: { fontSize: '0.5em' } },
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import { FC } from 'react'
|
||||
export declare const GimpEditor: FC
|
|
@ -0,0 +1,55 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { TextField } from '@fluentui/react'
|
||||
import { gimp } from '@thematic/core'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useMemo } from 'react'
|
||||
import { DownloadLink } from '../DownloadLink'
|
||||
// the deep nesting of the component requires several layers of size adjustment
|
||||
const styles = {
|
||||
root: {
|
||||
width: '100%',
|
||||
height: '90%',
|
||||
},
|
||||
wrapper: {
|
||||
height: '100%',
|
||||
},
|
||||
fieldGroup: {
|
||||
height: '100%',
|
||||
},
|
||||
field: {
|
||||
height: '100%',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 11,
|
||||
},
|
||||
}
|
||||
export const GimpEditor = () => {
|
||||
const theme = useThematic()
|
||||
const value = useMemo(() => theme.transform(gimp), [theme])
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: { width: '100%' },
|
||||
children: [
|
||||
_jsx(
|
||||
TextField,
|
||||
{ styles: styles, multiline: true, readOnly: true, value: value },
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
DownloadLink,
|
||||
{
|
||||
filename: `${theme.name}-${theme.variant}.gpl`,
|
||||
blobParts: [value],
|
||||
styles: { root: { fontSize: '0.5em' } },
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { FC } from 'react'
|
||||
export interface JSONPaneProps {
|
||||
scaleItemCount?: number
|
||||
}
|
||||
export declare const JSONPane: import('react-redux').ConnectedComponent<
|
||||
FC<JSONPaneProps>,
|
||||
import('react-redux').Omit<JSONPaneProps, 'scaleItemCount'>
|
||||
>
|
|
@ -0,0 +1,39 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useMemo } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { DownloadLink } from '../DownloadLink'
|
||||
import { JSONEditor } from '../JSONEditor'
|
||||
const JSONPaneComponent = ({ scaleItemCount }) => {
|
||||
const theme = useThematic()
|
||||
const value = useMemo(
|
||||
() => theme.toJSON({ scaleItemCount }),
|
||||
[theme, scaleItemCount],
|
||||
)
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: { width: '100%', height: '90%' },
|
||||
children: [
|
||||
_jsx(JSONEditor, { value: value }, void 0),
|
||||
_jsx(
|
||||
DownloadLink,
|
||||
{
|
||||
filename: `${theme.name}-${theme.variant}.json`,
|
||||
blobParts: [JSON.stringify(value, null, 2)],
|
||||
styles: { root: { fontSize: '0.5em' } },
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const JSONPane = connect(state => ({
|
||||
scaleItemCount: state.ui.scaleItemCount,
|
||||
}))(JSONPaneComponent)
|
|
@ -0,0 +1,5 @@
|
|||
import { FC } from 'react'
|
||||
export interface JSONEditorProps {
|
||||
value: any
|
||||
}
|
||||
export declare const JSONEditor: FC<JSONEditorProps>
|
|
@ -0,0 +1,36 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { TextField } from '@fluentui/react'
|
||||
// the deep nesting of the component requires several layers of size adjustment
|
||||
const styles = {
|
||||
root: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
wrapper: {
|
||||
height: '100%',
|
||||
},
|
||||
fieldGroup: {
|
||||
height: '100%',
|
||||
},
|
||||
field: {
|
||||
height: '100%',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 11,
|
||||
},
|
||||
}
|
||||
export const JSONEditor = ({ value }) => {
|
||||
return _jsx(
|
||||
TextField,
|
||||
{
|
||||
styles: styles,
|
||||
multiline: true,
|
||||
readOnly: true,
|
||||
value: JSON.stringify(value, null, 2),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { SelectionState } from '@thematic/core'
|
||||
import { FC } from 'react'
|
||||
export interface GridCellProps {
|
||||
name: string
|
||||
size: number
|
||||
selectionState?: SelectionState
|
||||
}
|
||||
export declare const GridCell: FC<GridCellProps>
|
|
@ -0,0 +1,70 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
import { useThematic, mark2style } from '@thematic/react'
|
||||
import { useMemo } from 'react'
|
||||
import { Rect, Circle, Line, Arc, Text } from '../svg'
|
||||
const selectMark = key => {
|
||||
switch (key) {
|
||||
case 'rect':
|
||||
case 'plotArea':
|
||||
case 'area':
|
||||
case 'tooltip':
|
||||
return Rect
|
||||
case 'circle':
|
||||
case 'process':
|
||||
case 'node':
|
||||
return Circle
|
||||
case 'line':
|
||||
case 'rule':
|
||||
case 'flow':
|
||||
case 'link':
|
||||
case 'axisLine':
|
||||
case 'axisTicks':
|
||||
case 'gridLines':
|
||||
return Line
|
||||
case 'arc':
|
||||
return Arc
|
||||
case 'text':
|
||||
case 'axisTickLabels':
|
||||
case 'axisTitle':
|
||||
return Text
|
||||
default:
|
||||
throw new Error('Unsupported mark type')
|
||||
}
|
||||
}
|
||||
export const GridCell = ({ name, size, selectionState }) => {
|
||||
const theme = useThematic()
|
||||
const Mark = selectMark(name)
|
||||
const exampleSize = size * 1.5
|
||||
const exampleStyle = useMemo(
|
||||
() => ({
|
||||
...mark2style(theme.plotArea()),
|
||||
width: exampleSize,
|
||||
height: exampleSize,
|
||||
}),
|
||||
[theme, exampleSize],
|
||||
)
|
||||
const config = theme[name]({ selectionState })
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'mark-grid-cell',
|
||||
children: [
|
||||
_jsx(
|
||||
'h2',
|
||||
{ className: 'mark-grid-cell-title', children: name },
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'mark-grid-cell-example',
|
||||
style: exampleStyle,
|
||||
children: _jsx(Mark, { config: config, size: size }, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import { FC } from 'react'
|
||||
import './index.css'
|
||||
export declare const MarkGrid: FC
|
|
@ -0,0 +1,105 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { SelectionState } from '@thematic/core'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useState, useCallback, useMemo } from 'react'
|
||||
import { EnumButtonBar } from '../EnumButtonBar'
|
||||
import { GridCell } from './GridCell'
|
||||
import './index.css'
|
||||
const SIZE = 30
|
||||
const markKeys = [
|
||||
'rect',
|
||||
'area',
|
||||
'circle',
|
||||
'arc',
|
||||
'line',
|
||||
'rule',
|
||||
'node',
|
||||
'link',
|
||||
'process',
|
||||
'flow',
|
||||
'text',
|
||||
]
|
||||
const chromeKeys = [
|
||||
'plotArea',
|
||||
'gridLines',
|
||||
'axisLine',
|
||||
'axisTicks',
|
||||
'axisTickLabels',
|
||||
'axisTitle',
|
||||
'tooltip',
|
||||
]
|
||||
export const MarkGrid = () => {
|
||||
const theme = useThematic()
|
||||
const [selectionState, setSelectionState] = useState(SelectionState.Normal)
|
||||
const handleSelectionStateChange = useCallback(s => {
|
||||
setSelectionState(s)
|
||||
}, [])
|
||||
const labelStyle = useMemo(
|
||||
() => ({
|
||||
fontSize: '0.5em',
|
||||
fontWeight: 'bold',
|
||||
color: theme.application().lowContrast().hex(),
|
||||
}),
|
||||
[theme],
|
||||
)
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: { display: 'flex', flexDirection: 'column' },
|
||||
children: [
|
||||
_jsx('div', { style: labelStyle, children: 'On-chart marks' }, void 0),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'mark-grid',
|
||||
children: [
|
||||
markKeys.map(key =>
|
||||
_jsx(
|
||||
GridCell,
|
||||
{ name: key, size: SIZE, selectionState: selectionState },
|
||||
key,
|
||||
),
|
||||
),
|
||||
_jsx(
|
||||
EnumButtonBar,
|
||||
{
|
||||
enumeration: SelectionState,
|
||||
selected: selectionState,
|
||||
onChange: handleSelectionStateChange,
|
||||
iconNames: [
|
||||
'CheckMark',
|
||||
'CheckBoxComposite',
|
||||
'CheckBoxCompositeReversed',
|
||||
'GenericScanFilled',
|
||||
'Hide',
|
||||
'StatusCircleQuestionMark',
|
||||
],
|
||||
iconOnly: true,
|
||||
label: 'Selection state',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx('div', { style: labelStyle, children: 'Chart chrome' }, void 0),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'mark-grid',
|
||||
children: chromeKeys.map(key =>
|
||||
_jsx(GridCell, { name: key, size: SIZE }, key),
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { FC } from 'react'
|
||||
export interface OfficePaletteProps {
|
||||
colors: any
|
||||
}
|
||||
export declare const OfficePalette: FC<OfficePaletteProps>
|
|
@ -0,0 +1,95 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { ColorStrip } from '../ColorStrip'
|
||||
const mainKeys = [
|
||||
'dark1',
|
||||
'light1',
|
||||
'dark2',
|
||||
'light2',
|
||||
'hyperlink',
|
||||
'followedHyperlink',
|
||||
]
|
||||
const accentKeys = [
|
||||
'accent1',
|
||||
'accent2',
|
||||
'accent3',
|
||||
'accent4',
|
||||
'accent5',
|
||||
'accent6',
|
||||
]
|
||||
export const OfficePalette = ({ colors }) => {
|
||||
const theme = useThematic()
|
||||
const mapkeys = keys =>
|
||||
keys.map(key => ({
|
||||
color: colors[key],
|
||||
label: key,
|
||||
secondaryLabel: colors[key],
|
||||
}))
|
||||
const mains = mapkeys(mainKeys)
|
||||
const accents = mapkeys(accentKeys)
|
||||
const styles = {
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
swatch: {
|
||||
border: `1px solid ${theme.application().border().hex()}`,
|
||||
},
|
||||
header: {
|
||||
color: theme.application().foreground().hex(),
|
||||
fontSize: 14,
|
||||
},
|
||||
}
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: styles.root,
|
||||
children: [
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{ style: styles.header, children: 'Primary' },
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: mains,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx('div', { style: styles.header, children: 'Accent' }, void 0),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: accents,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import { FC } from 'react'
|
||||
export declare const Office: FC
|
|
@ -0,0 +1,33 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { office } from '@thematic/core'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useMemo } from 'react'
|
||||
import { DownloadLink } from '../DownloadLink'
|
||||
import { OfficePalette } from './OfficePalette'
|
||||
export const Office = () => {
|
||||
const theme = useThematic()
|
||||
const ofc = useMemo(() => theme.transform(office), [theme])
|
||||
const str = useMemo(() => JSON.stringify(ofc, null, 2), [ofc])
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(OfficePalette, { colors: ofc }, void 0),
|
||||
_jsx(
|
||||
DownloadLink,
|
||||
{
|
||||
filename: `${theme.name}-${theme.variant}-office.json`,
|
||||
blobParts: [str],
|
||||
styles: { root: { fontSize: '0.5em' } },
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { FC } from 'react'
|
||||
export interface PowerBIPaletteProps {
|
||||
colors: any
|
||||
}
|
||||
export declare const PowerBIPalette: FC<PowerBIPaletteProps>
|
|
@ -0,0 +1,109 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { ColorStrip } from '../ColorStrip'
|
||||
const mainKeys = ['foreground', 'background', 'tableAccent']
|
||||
export const PowerBIPalette = ({ colors }) => {
|
||||
const theme = useThematic()
|
||||
const mapkeys = keys =>
|
||||
keys.map(key => ({
|
||||
color: colors[key],
|
||||
label: key,
|
||||
secondaryLabel: colors[key],
|
||||
}))
|
||||
const mains = mapkeys(mainKeys)
|
||||
const dataColorsLeft = colors.dataColors.slice(0, 6).map(c => ({
|
||||
color: c,
|
||||
label: c,
|
||||
}))
|
||||
const dataColorsRight = colors.dataColors.slice(6).map(c => ({
|
||||
color: c,
|
||||
secondaryLabel: c,
|
||||
}))
|
||||
const styles = {
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
header: {
|
||||
color: theme.application().foreground().hex(),
|
||||
fontSize: 14,
|
||||
},
|
||||
swatch: {
|
||||
border: `1px solid ${theme.application().border().hex()}`,
|
||||
},
|
||||
}
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
style: styles.root,
|
||||
children: [
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{ style: styles.header, children: 'Primary' },
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: mains,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
'div',
|
||||
{ style: styles.header, children: 'Data Colors' },
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
style: { display: 'flex', alignItems: ' flex-start' },
|
||||
children: [
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: dataColorsLeft,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
ColorStrip,
|
||||
{
|
||||
vertical: true,
|
||||
colorDefinitions: dataColorsRight,
|
||||
swatchStyle: styles.swatch,
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import { FC } from 'react'
|
||||
export declare const PowerBiEditor: FC
|
|
@ -0,0 +1,33 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { powerbi } from '@thematic/core'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { useMemo } from 'react'
|
||||
import { DownloadLink } from '../DownloadLink'
|
||||
import { PowerBIPalette } from './PowerBIPalette'
|
||||
export const PowerBiEditor = () => {
|
||||
const theme = useThematic()
|
||||
const pbi = useMemo(() => theme.transform(powerbi), [theme])
|
||||
const str = useMemo(() => JSON.stringify(pbi, null, 2), [pbi])
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
children: [
|
||||
_jsx(PowerBIPalette, { colors: pbi }, void 0),
|
||||
_jsx(
|
||||
DownloadLink,
|
||||
{
|
||||
filename: `${theme.name}-${theme.variant}-powerbi.json`,
|
||||
blobParts: [str],
|
||||
styles: { root: { fontSize: '0.5em' } },
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { FC } from 'react'
|
||||
import './index.css'
|
||||
export interface ThemeEditorProps {
|
||||
scaleItemCount: number
|
||||
}
|
||||
export declare const ThemeEditor: FC<ThemeEditorProps>
|
|
@ -0,0 +1,150 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Pivot, PivotItem } from '@fluentui/react'
|
||||
import { ColorPalette } from '../ColorPalette'
|
||||
import { CoolerPicker } from '../CoolerPicker'
|
||||
import { FluentViewer } from '../FluentViewer'
|
||||
import { GimpEditor } from '../GIMP'
|
||||
import { JSONPane } from '../JSON'
|
||||
import { MarkGrid } from '../MarkGrid'
|
||||
import { Office } from '../Office'
|
||||
import { PowerBiEditor } from '../PowerBI'
|
||||
import './index.css'
|
||||
export const ThemeEditor = ({ scaleItemCount }) => {
|
||||
return _jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'editor-wrapper',
|
||||
children: [
|
||||
_jsxs(
|
||||
Pivot,
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
className: 'tab',
|
||||
headerText: 'Color Picker',
|
||||
children: _jsx(CoolerPicker, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
className: 'tab',
|
||||
headerText: 'Marks',
|
||||
children: _jsx(MarkGrid, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
className: 'tab',
|
||||
headerText: 'Fluent UI',
|
||||
children: _jsx(FluentViewer, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
className: 'tab',
|
||||
headerText: 'Office',
|
||||
children: _jsx(Office, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
className: 'tab',
|
||||
headerText: 'Power BI',
|
||||
children: _jsx(PowerBiEditor, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
className: 'tab',
|
||||
headerText: 'GIMP',
|
||||
children: _jsx(GimpEditor, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
className: 'tab',
|
||||
headerText: 'JSON',
|
||||
children: _jsx(JSONPane, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(ColorPalette, { scaleItemCount: scaleItemCount }, void 0),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'footer',
|
||||
children: [
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'privacy',
|
||||
children: [
|
||||
'This site does not collect any personal information or use cookies.\u00A0',
|
||||
_jsx(
|
||||
'a',
|
||||
{
|
||||
target: '_blank',
|
||||
rel: 'noreferrer',
|
||||
href: 'https://privacy.microsoft.com/en-us/privacystatement/',
|
||||
children:
|
||||
"Read Microsoft's statement on Privacy and Cookies",
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
'.',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'github',
|
||||
children: [
|
||||
'Contribute at\u00A0',
|
||||
_jsx(
|
||||
'a',
|
||||
{
|
||||
target: '_blank',
|
||||
rel: 'noreferrer',
|
||||
href: 'https://github.com/microsoft/thematic',
|
||||
children: 'GitHub',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
'.',
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="react" />
|
||||
export declare const ThemeEditor: import('react-redux').ConnectedComponent<
|
||||
import('react').FC<import('./ThemeEditor').ThemeEditorProps>,
|
||||
import('react-redux').Omit<
|
||||
import('./ThemeEditor').ThemeEditorProps,
|
||||
'scaleItemCount'
|
||||
>
|
||||
>
|
|
@ -0,0 +1,9 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { connect } from 'react-redux'
|
||||
import { ThemeEditor as ThemeEditorComponent } from './ThemeEditor'
|
||||
export const ThemeEditor = connect(state => ({
|
||||
scaleItemCount: state.ui.scaleItemCount,
|
||||
}))(ThemeEditorComponent)
|
|
@ -0,0 +1,10 @@
|
|||
import { FC } from 'react'
|
||||
import { Graph } from '../../interfaces'
|
||||
import './index.css'
|
||||
export interface ThemeViewerProps {
|
||||
graph: Graph
|
||||
chartSize: number
|
||||
drawNodes: boolean
|
||||
drawLinks: boolean
|
||||
}
|
||||
export declare const ThemeViewer: FC<ThemeViewerProps>
|
|
@ -0,0 +1,114 @@
|
|||
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Pivot, PivotItem } from '@fluentui/react'
|
||||
import { FluentControls } from '../FluentControls'
|
||||
import { D3Chart } from '../charts/D3'
|
||||
import { VegaChart, charts } from '../charts/Vega'
|
||||
import { D3Graph } from '../graphs/D3'
|
||||
import './index.css'
|
||||
export const ThemeViewer = ({ graph, chartSize, drawNodes, drawLinks }) => {
|
||||
const common = {
|
||||
width: chartSize,
|
||||
height: chartSize * 0.75,
|
||||
}
|
||||
const graphs = [
|
||||
_jsx(
|
||||
D3Graph,
|
||||
{ graph: graph, ...common, drawNodes: drawNodes, drawLinks: drawLinks },
|
||||
'graph-1',
|
||||
),
|
||||
_jsx(
|
||||
D3Graph,
|
||||
{
|
||||
graph: graph,
|
||||
...common,
|
||||
drawNodes: drawNodes,
|
||||
drawLinks: drawLinks,
|
||||
categoricalFill: true,
|
||||
},
|
||||
'graph-2',
|
||||
),
|
||||
_jsx(
|
||||
D3Graph,
|
||||
{
|
||||
graph: graph,
|
||||
...common,
|
||||
drawNodes: drawNodes,
|
||||
drawLinks: drawLinks,
|
||||
sequentialFill: true,
|
||||
},
|
||||
'graph-3',
|
||||
),
|
||||
]
|
||||
return _jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'theme-wrapper',
|
||||
children: _jsxs(
|
||||
Pivot,
|
||||
{
|
||||
children: [
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
headerText: 'Thematic controls',
|
||||
children: _jsx(FluentControls, {}, void 0),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
_jsx(
|
||||
PivotItem,
|
||||
{
|
||||
headerText: 'Example visualizations',
|
||||
children: _jsxs(
|
||||
'div',
|
||||
{
|
||||
className: 'example-grid',
|
||||
children: [
|
||||
graphs.map((graph, i) =>
|
||||
_jsx(
|
||||
'div',
|
||||
{ className: 'example-grid-item', children: graph },
|
||||
`example-graph-${i}`,
|
||||
),
|
||||
),
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'example-grid-item',
|
||||
children: _jsx(D3Chart, { ...common }, void 0),
|
||||
},
|
||||
`example-rar-chart`,
|
||||
),
|
||||
charts.map(chart =>
|
||||
_jsx(
|
||||
'div',
|
||||
{
|
||||
className: 'example-grid-item',
|
||||
children: _jsx(
|
||||
VegaChart,
|
||||
{ type: chart, ...common },
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
`example-chart-${chart}`,
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
],
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="react" />
|
||||
export declare const ThemeViewer: import('react-redux').ConnectedComponent<
|
||||
import('react').FC<import('./ThemeViewer').ThemeViewerProps>,
|
||||
import('react-redux').Omit<
|
||||
import('./ThemeViewer').ThemeViewerProps,
|
||||
'chartSize' | 'drawNodes' | 'drawLinks' | 'graph'
|
||||
>
|
||||
>
|
|
@ -0,0 +1,12 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { connect } from 'react-redux'
|
||||
import { ThemeViewer as ThemeViewerComponent } from './ThemeViewer'
|
||||
export const ThemeViewer = connect(state => ({
|
||||
chartSize: state.ui.chartSize,
|
||||
drawNodes: state.ui.drawNodes,
|
||||
drawLinks: state.ui.drawLinks,
|
||||
graph: state.graph,
|
||||
}))(ThemeViewerComponent)
|
|
@ -0,0 +1,9 @@
|
|||
import { FC } from 'react'
|
||||
export interface ChartProps {
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
/**
|
||||
* Simple chart using raw d3, intended to show how scale bindings work
|
||||
*/
|
||||
export declare const D3Chart: FC<ChartProps>
|
|
@ -0,0 +1,175 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { SelectionState } from '@thematic/core'
|
||||
import { chart, plotArea, axis, circle } from '@thematic/d3'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { axisLeft, axisBottom } from 'd3-axis'
|
||||
import { scaleLinear } from 'd3-scale'
|
||||
import { select } from 'd3-selection'
|
||||
import { useLayoutEffect, useRef, useState, useMemo } from 'react'
|
||||
const MARGIN = 25
|
||||
const LEGEND_WIDTH = 20
|
||||
const MIN_RADIUS = 5
|
||||
const MAX_RADIUS = 15
|
||||
const CIRCLE_FILL_OPACITY = 0.8
|
||||
const DATA_LENGTH = 20
|
||||
/**
|
||||
* Simple chart using raw d3, intended to show how scale bindings work
|
||||
*/
|
||||
export const D3Chart = ({ width = 800, height = 600 }) => {
|
||||
const theme = useThematic()
|
||||
const ref = useRef(null)
|
||||
const [svg, setSvg] = useState()
|
||||
const [plot, setPlot] = useState()
|
||||
// random data for scatterplot
|
||||
// TODO: load in a meaningful dataset to demonstrate the scatterplot better?
|
||||
const data = useMemo(() => {
|
||||
return new Array(DATA_LENGTH).fill({}).map(() => ({
|
||||
weight: 1 - Math.random() * 2,
|
||||
count: Math.random(),
|
||||
x: Math.random(),
|
||||
y: Math.random(),
|
||||
}))
|
||||
}, [])
|
||||
const plotHeight = useMemo(() => height - MARGIN * 2, [height])
|
||||
const plotWidth = useMemo(() => width - MARGIN * 2 - LEGEND_WIDTH, [width])
|
||||
const xScale = useMemo(() => scaleLinear().range([0, plotWidth]), [plotWidth])
|
||||
const yScale = useMemo(
|
||||
() => scaleLinear().range([plotHeight, 0]),
|
||||
[plotHeight],
|
||||
)
|
||||
const cxScale = useMemo(
|
||||
() => scaleLinear().range([MAX_RADIUS, plotWidth - MAX_RADIUS]),
|
||||
[plotWidth],
|
||||
)
|
||||
const cyScale = useMemo(
|
||||
() => scaleLinear().range([plotHeight - MAX_RADIUS, MAX_RADIUS]),
|
||||
[plotHeight],
|
||||
)
|
||||
useLayoutEffect(() => {
|
||||
if (ref) select(ref.current).select('*').remove()
|
||||
const svg = select(ref.current).call(chart, theme, {
|
||||
width,
|
||||
height,
|
||||
})
|
||||
setSvg(svg)
|
||||
}, [height, ref, theme, width])
|
||||
useLayoutEffect(() => {
|
||||
if (svg) {
|
||||
svg.selectAll('g').remove()
|
||||
const g = svg.append('g').call(plotArea, theme, {
|
||||
// room for axes
|
||||
marginBottom: MARGIN,
|
||||
marginLeft: MARGIN,
|
||||
marginTop: MARGIN,
|
||||
marginRight: MARGIN + LEGEND_WIDTH,
|
||||
})
|
||||
setPlot(g)
|
||||
}
|
||||
}, [svg, theme])
|
||||
useLayoutEffect(() => {
|
||||
if (plot) {
|
||||
plot.selectAll('circle').remove()
|
||||
// create a series of random circles, with the fill bound to a sequential scale
|
||||
const circ = theme.circle({
|
||||
selectionState: d =>
|
||||
d.weight < 50 ? SelectionState.Selected : SelectionState.Suppressed,
|
||||
scaleBindings: {
|
||||
fill: {
|
||||
scale: theme.scales().diverging2([-1, 1]),
|
||||
accessor: d => d.weight,
|
||||
},
|
||||
radius: {
|
||||
scale: scaleLinear().range([MIN_RADIUS, MAX_RADIUS]),
|
||||
accessor: d => d.count,
|
||||
},
|
||||
},
|
||||
overrides: {
|
||||
strokeWidth: 2,
|
||||
strokeOpacity: 0.8,
|
||||
stroke: '#222',
|
||||
fillOpacity: CIRCLE_FILL_OPACITY,
|
||||
},
|
||||
})
|
||||
plot
|
||||
.selectAll('circle')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.call(circle, circ)
|
||||
.attr('cx', d => cxScale(d.x))
|
||||
.attr('cy', d => cyScale(d.y))
|
||||
}
|
||||
}, [theme, plot, cxScale, cyScale, data])
|
||||
useLayoutEffect(() => {
|
||||
if (svg) {
|
||||
// x axis
|
||||
svg.append('g').call(axis, theme, axisBottom(xScale), {
|
||||
attr: {
|
||||
transform: `translate(${MARGIN}, ${plotHeight + MARGIN})`,
|
||||
},
|
||||
})
|
||||
// y axis
|
||||
svg.append('g').call(axis, theme, axisLeft(yScale), {
|
||||
attr: {
|
||||
transform: `translate(${MARGIN}, ${MARGIN})`,
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [theme, svg, plotHeight, xScale, yScale])
|
||||
// add a very simple legend to show the gradient
|
||||
useLayoutEffect(() => {
|
||||
if (svg) {
|
||||
svg.select('.legend').remove()
|
||||
const g = svg
|
||||
.append('g')
|
||||
.attr('class', 'legend')
|
||||
.attr('transform', `translate(${plotWidth + MARGIN},${MARGIN})`)
|
||||
g.append('rect')
|
||||
.attr('width', LEGEND_WIDTH)
|
||||
.attr('height', plotHeight)
|
||||
.call(plotArea, theme)
|
||||
const slices = new Array(plotHeight).fill(1)
|
||||
const scale = theme.scales().diverging2([0, plotHeight])
|
||||
g.selectAll('rect')
|
||||
.data(slices)
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('width', LEGEND_WIDTH)
|
||||
.attr('height', 1)
|
||||
.attr('x', 1)
|
||||
.attr('y', (d, i) => plotHeight - i)
|
||||
.attr('fill', (d, i) => scale(i).hex())
|
||||
.attr('fill-opacity', CIRCLE_FILL_OPACITY)
|
||||
const labels = [
|
||||
{
|
||||
t: '1',
|
||||
y: 8,
|
||||
},
|
||||
{
|
||||
t: '0',
|
||||
y: plotHeight / 2,
|
||||
},
|
||||
{
|
||||
t: '-1',
|
||||
y: plotHeight - 8,
|
||||
},
|
||||
]
|
||||
g.selectAll('text')
|
||||
.data(labels)
|
||||
.enter()
|
||||
.append('text')
|
||||
.text(d => d.t)
|
||||
.attr('fill', theme.axisTickLabels().fill().hex())
|
||||
.attr('font-size', 10)
|
||||
.attr('x', LEGEND_WIDTH / 2)
|
||||
.attr('y', d => d.y)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
}
|
||||
}, [theme, svg, plotWidth, plotHeight])
|
||||
return _jsx('svg', { ref: ref }, void 0)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { FC } from 'react'
|
||||
export declare const charts: string[]
|
||||
export interface VegaChartProps {
|
||||
type: string
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
export declare const VegaChart: FC<VegaChartProps>
|
|
@ -0,0 +1,52 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { vega as decorator } from '@thematic/vega'
|
||||
import { memo, useLayoutEffect, useRef, useMemo } from 'react'
|
||||
import { parse, View } from 'vega'
|
||||
import area from './specs/area.json'
|
||||
import bar from './specs/bar.json'
|
||||
import donut from './specs/donut.json'
|
||||
import heatmap from './specs/heatmap.json'
|
||||
import line from './specs/line.json'
|
||||
import pyramid from './specs/pyramid.json'
|
||||
import scatterPlot from './specs/scatter-plot.json'
|
||||
import stackedArea from './specs/stacked-area.json'
|
||||
import stackedBar from './specs/stacked-bar.json'
|
||||
import sunburst from './specs/sunburst.json'
|
||||
const specs = {
|
||||
heatmap,
|
||||
bar,
|
||||
stackedBar,
|
||||
line,
|
||||
area,
|
||||
stackedArea,
|
||||
scatterPlot,
|
||||
donut,
|
||||
pyramid,
|
||||
sunburst,
|
||||
}
|
||||
export const charts = Object.keys(specs)
|
||||
export const VegaChart = memo(function VegaChart({
|
||||
type,
|
||||
width = 800,
|
||||
height = 600,
|
||||
}) {
|
||||
const theme = useThematic()
|
||||
const ref = useRef(null)
|
||||
const view = useMemo(() => {
|
||||
const spec = specs[type]
|
||||
const merged = decorator(theme, spec, width, height)
|
||||
const parsed = parse(merged)
|
||||
return new View(parsed).renderer('svg')
|
||||
}, [height, theme, type, width])
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current != null) {
|
||||
view.initialize(ref.current).run()
|
||||
}
|
||||
})
|
||||
return _jsx('div', { ref: ref }, void 0)
|
||||
})
|
|
@ -8,23 +8,31 @@ import { memo, useLayoutEffect, useRef, useMemo, FC } from 'react'
|
|||
|
||||
import { parse, View } from 'vega'
|
||||
|
||||
export const charts = [
|
||||
'heatmap',
|
||||
'bar',
|
||||
'stacked-bar',
|
||||
'line',
|
||||
'area',
|
||||
'stacked-area',
|
||||
'scatter-plot',
|
||||
'donut',
|
||||
'pyramid',
|
||||
'sunburst',
|
||||
]
|
||||
import area from './specs/area.json'
|
||||
import bar from './specs/bar.json'
|
||||
import donut from './specs/donut.json'
|
||||
import heatmap from './specs/heatmap.json'
|
||||
import line from './specs/line.json'
|
||||
import pyramid from './specs/pyramid.json'
|
||||
import scatterPlot from './specs/scatter-plot.json'
|
||||
import stackedArea from './specs/stacked-area.json'
|
||||
import stackedBar from './specs/stacked-bar.json'
|
||||
import sunburst from './specs/sunburst.json'
|
||||
|
||||
const specs = charts.reduce((acc, cur) => {
|
||||
acc[cur] = require(`./specs/${cur}.json`)
|
||||
return acc
|
||||
}, {})
|
||||
const specs = {
|
||||
heatmap,
|
||||
bar,
|
||||
stackedBar,
|
||||
line,
|
||||
area,
|
||||
stackedArea,
|
||||
scatterPlot,
|
||||
donut,
|
||||
pyramid,
|
||||
sunburst,
|
||||
}
|
||||
|
||||
export const charts = Object.keys(specs)
|
||||
|
||||
export interface VegaChartProps {
|
||||
type: string
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { FC } from 'react'
|
||||
import { Graph } from '../../../interfaces'
|
||||
export interface GraphProps {
|
||||
graph: Graph
|
||||
width?: number
|
||||
height?: number
|
||||
drawNodes?: boolean
|
||||
drawLinks?: boolean
|
||||
categoricalFill?: boolean
|
||||
sequentialFill?: boolean
|
||||
}
|
||||
export declare const D3Graph: FC<GraphProps>
|
|
@ -0,0 +1,177 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { ScaleType, SelectionState } from '@thematic/core'
|
||||
import {
|
||||
circle,
|
||||
line,
|
||||
applyNominalAttrWithSignalState,
|
||||
sequential,
|
||||
chart,
|
||||
plotArea,
|
||||
} from '@thematic/d3'
|
||||
import { useThematic } from '@thematic/react'
|
||||
import { scaleLinear } from 'd3-scale'
|
||||
import { select } from 'd3-selection'
|
||||
import {
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { bounds } from '../../../util/graph'
|
||||
export const D3Graph = ({
|
||||
graph,
|
||||
width = 800,
|
||||
height = 600,
|
||||
drawNodes = true,
|
||||
drawLinks = true,
|
||||
categoricalFill = false,
|
||||
sequentialFill = false,
|
||||
}) => {
|
||||
const theme = useThematic()
|
||||
const { nodes, edges } = graph
|
||||
const ref = useRef(null)
|
||||
const [nodeHover, setNodeHover] = useState(null)
|
||||
const [nodeSelect, setNodeSelect] = useState(null)
|
||||
const getNodeSelectionState = useCallback(
|
||||
d => {
|
||||
if (d.id === nodeHover) {
|
||||
return SelectionState.Hovered
|
||||
}
|
||||
if (d.id === nodeSelect) {
|
||||
return SelectionState.Selected
|
||||
}
|
||||
if (nodeSelect) {
|
||||
return SelectionState.Suppressed
|
||||
}
|
||||
return SelectionState.Normal
|
||||
},
|
||||
[nodeHover, nodeSelect],
|
||||
)
|
||||
useLayoutEffect(() => {
|
||||
const [xmin, xmax, ymin, ymax] = bounds(nodes)
|
||||
const r = theme.node().radius()
|
||||
const xScale = scaleLinear()
|
||||
.domain([xmin, xmax])
|
||||
.range([r, width - r])
|
||||
const yScale = scaleLinear()
|
||||
.domain([ymin, ymax])
|
||||
.range([r, height - r])
|
||||
select(ref.current).select('*').remove()
|
||||
const svg = select(ref.current).call(chart, theme, { width, height })
|
||||
const g = svg.append('g').call(plotArea, theme, {
|
||||
on: {
|
||||
mouseup: () => setNodeSelect(null),
|
||||
},
|
||||
})
|
||||
const nmap = nodes.reduce((acc, cur) => {
|
||||
acc[cur.id] = cur
|
||||
return acc
|
||||
}, {})
|
||||
g.selectAll('line')
|
||||
.data(edges)
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('class', 'link')
|
||||
.attr('x1', d => xScale(nmap[d.source].x))
|
||||
.attr('x2', d => xScale(nmap[d.target].x))
|
||||
.attr('y1', d => yScale(nmap[d.source].y))
|
||||
.attr('y2', d => yScale(nmap[d.target].y))
|
||||
.call(line, theme.link({ selectionState: SelectionState.Hidden }))
|
||||
g.selectAll('.node')
|
||||
.data(nodes)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('class', 'node')
|
||||
.attr('cx', d => xScale(d.x))
|
||||
.attr('cy', d => yScale(d.y))
|
||||
.style('cursor', 'pointer')
|
||||
.on('mouseover', d => setNodeHover(d.id))
|
||||
.on('mouseout', () => setNodeHover(null))
|
||||
.on('mouseup', d => setNodeSelect(d.id))
|
||||
.call(circle, theme.node({ selectionState: SelectionState.Hidden }))
|
||||
}, [theme, graph, width, height, nodes, edges])
|
||||
useEffect(() => {
|
||||
select(ref.current)
|
||||
.selectAll('line')
|
||||
.call(
|
||||
line,
|
||||
theme.link({
|
||||
selectionState: drawLinks
|
||||
? SelectionState.Normal
|
||||
: SelectionState.Hidden,
|
||||
}),
|
||||
)
|
||||
}, [theme, graph, drawLinks, width, height])
|
||||
useEffect(() => {
|
||||
select(ref.current)
|
||||
.selectAll('.node')
|
||||
.call(
|
||||
circle,
|
||||
theme.node({
|
||||
selectionState: drawNodes
|
||||
? SelectionState.Normal
|
||||
: SelectionState.Hidden,
|
||||
}),
|
||||
)
|
||||
}, [theme, graph, drawNodes, width, height])
|
||||
useEffect(() => {
|
||||
const n = select(ref.current).selectAll('.node')
|
||||
if (drawNodes) {
|
||||
if (categoricalFill) {
|
||||
const unique = nodes.reduce((acc, cur) => {
|
||||
if (cur.community != null) {
|
||||
acc[cur.community] = true
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
n.call(
|
||||
applyNominalAttrWithSignalState,
|
||||
'fill',
|
||||
getNodeSelectionState,
|
||||
d => d.community,
|
||||
theme,
|
||||
Object.keys(unique).length,
|
||||
)
|
||||
} else if (sequentialFill) {
|
||||
const domain = nodes.map(node => node.weight).sort((a, b) => a - b)
|
||||
n.call(
|
||||
circle,
|
||||
theme.node({
|
||||
selectionState: getNodeSelectionState,
|
||||
scaleBindings: {
|
||||
fill: {
|
||||
scale: sequential(theme, domain, ScaleType.Quantile),
|
||||
accessor: d => d.weight,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
n.call(
|
||||
circle,
|
||||
theme.node({
|
||||
selectionState: getNodeSelectionState,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
theme,
|
||||
graph,
|
||||
drawNodes,
|
||||
width,
|
||||
height,
|
||||
nodeHover,
|
||||
nodeSelect,
|
||||
categoricalFill,
|
||||
sequentialFill,
|
||||
nodes,
|
||||
getNodeSelectionState,
|
||||
])
|
||||
return _jsx('svg', { ref: ref }, void 0)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { FC, ReactNode } from 'react'
|
||||
export interface MarkProps {
|
||||
children: ReactNode
|
||||
size: number
|
||||
}
|
||||
export interface ThemedMarkProps {
|
||||
config: any
|
||||
size: number
|
||||
}
|
||||
export declare const Rect: FC<ThemedMarkProps>
|
||||
export declare const Circle: FC<ThemedMarkProps>
|
||||
export declare const Line: FC<ThemedMarkProps>
|
||||
export declare const Arc: FC<ThemedMarkProps>
|
||||
export declare const Text: FC<ThemedMarkProps>
|
|
@ -0,0 +1,98 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
const svgAttrs = config => ({
|
||||
fill: config.fill().hex(),
|
||||
fillOpacity: config.fillOpacity(),
|
||||
stroke: config.stroke().hex(),
|
||||
strokeOpacity: config.strokeOpacity(),
|
||||
strokeWidth: config.strokeWidth(),
|
||||
})
|
||||
const Mark = props => {
|
||||
const { children, size } = props
|
||||
return _jsx(
|
||||
'svg',
|
||||
{
|
||||
width: size,
|
||||
height: size,
|
||||
style: { backgroundColor: 'none' },
|
||||
children: children,
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const Rect = props => {
|
||||
const { config, size } = props
|
||||
return _jsx(
|
||||
Mark,
|
||||
{
|
||||
...props,
|
||||
children: _jsx(
|
||||
'rect',
|
||||
{ width: size, height: size, ...svgAttrs(config) },
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const Circle = props => {
|
||||
const { config, size } = props
|
||||
return _jsx(
|
||||
Mark,
|
||||
{
|
||||
...props,
|
||||
children: _jsx(
|
||||
'circle',
|
||||
{ ...svgAttrs(config), cx: size / 2, cy: size / 2, r: size * 0.48 },
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const Line = props => {
|
||||
const { config, size } = props
|
||||
return _jsx(
|
||||
Mark,
|
||||
{
|
||||
...props,
|
||||
children: _jsx(
|
||||
'line',
|
||||
{ ...svgAttrs(config), x1: 0, x2: size, y1: size / 2, y2: size / 2 },
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const Arc = props => {
|
||||
const { config, size } = props
|
||||
const d = `M0 0 L ${size} 0 A ${size / 4} ${size / 5} 0 0 1 0 0`
|
||||
return _jsx(
|
||||
Mark,
|
||||
{ ...props, children: _jsx('path', { ...svgAttrs(config), d: d }, void 0) },
|
||||
void 0,
|
||||
)
|
||||
}
|
||||
export const Text = props => {
|
||||
const { config, size } = props
|
||||
return _jsx(
|
||||
Mark,
|
||||
{
|
||||
...props,
|
||||
children: _jsx(
|
||||
'text',
|
||||
{
|
||||
...svgAttrs(config),
|
||||
fontSize: `${config.fontSize()}px`,
|
||||
fontWeight: config.fontWeight(),
|
||||
x: size / 2,
|
||||
y: size * 0.67,
|
||||
textAnchor: 'middle',
|
||||
children: '10',
|
||||
},
|
||||
void 0,
|
||||
),
|
||||
},
|
||||
void 0,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
export declare const graph: {
|
||||
nodes: (
|
||||
| {
|
||||
id: number
|
||||
weight: number
|
||||
community: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
| {
|
||||
id: number
|
||||
weight: number
|
||||
x: number
|
||||
y: number
|
||||
community?: undefined
|
||||
}
|
||||
)[]
|
||||
edges: {
|
||||
source: number
|
||||
target: number
|
||||
weight: number
|
||||
}[]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import graphData from './graph.json'
|
||||
export const graph = graphData.graph
|
|
@ -0,0 +1 @@
|
|||
import './index.css'
|
|
@ -0,0 +1,17 @@
|
|||
import { jsx as _jsx } from 'react/jsx-runtime'
|
||||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { render } from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import { App } from './App'
|
||||
import { store, init } from './state'
|
||||
import './index.css'
|
||||
store.dispatch(init)
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
render(
|
||||
_jsx(Provider, { store: store, children: _jsx(App, {}, void 0) }, void 0),
|
||||
root,
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export declare type NodeId = string | number
|
||||
export interface Node {
|
||||
id: NodeId
|
||||
weight: number
|
||||
community?: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
export interface Edge {
|
||||
source: NodeId
|
||||
target: NodeId
|
||||
weight: number
|
||||
}
|
||||
export interface Graph {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export {}
|
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 2.6 KiB |
|
@ -1,65 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: 'development' | 'production' | 'test'
|
||||
PUBLIC_URL: string
|
||||
}
|
||||
}
|
||||
|
||||
declare module '*.bmp' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.webp' {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
import * as React from 'react'
|
||||
|
||||
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>
|
||||
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module '*.module.scss' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module '*.module.sass' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Params, ColorBlindnessMode } from '@thematic/color'
|
||||
import { Theme, ThemeListing } from '@thematic/core'
|
||||
import { Dispatch } from 'redux'
|
||||
import { Graph } from '../../interfaces'
|
||||
export declare const themesLoaded: import('redux-actions').ActionFunction1<
|
||||
ThemeListing[],
|
||||
import('redux-actions').Action<ThemeListing[]>
|
||||
>
|
||||
export declare const themeInfoSelected: import('redux-actions').ActionFunction1<
|
||||
ThemeListing,
|
||||
import('redux-actions').Action<ThemeListing>
|
||||
>
|
||||
export declare const themeLoaded: import('redux-actions').ActionFunction1<
|
||||
Theme,
|
||||
import('redux-actions').Action<Theme>
|
||||
>
|
||||
export declare const themeEdited: import('redux-actions').ActionFunctionAny<
|
||||
import('redux-actions').Action<any>
|
||||
>
|
||||
export declare const themeVariantToggled: import('redux-actions').ActionFunctionAny<
|
||||
import('redux-actions').Action<any>
|
||||
>
|
||||
export declare const paramsChanged: import('redux-actions').ActionFunction1<
|
||||
Params,
|
||||
import('redux-actions').Action<Params>
|
||||
>
|
||||
export declare const chartSizeChanged: import('redux-actions').ActionFunction1<
|
||||
number,
|
||||
import('redux-actions').Action<number>
|
||||
>
|
||||
export declare const drawNodesChanged: import('redux-actions').ActionFunction1<
|
||||
boolean,
|
||||
import('redux-actions').Action<boolean>
|
||||
>
|
||||
export declare const drawLinksChanged: import('redux-actions').ActionFunction1<
|
||||
boolean,
|
||||
import('redux-actions').Action<boolean>
|
||||
>
|
||||
export declare const graphLoaded: import('redux-actions').ActionFunction1<
|
||||
Graph,
|
||||
import('redux-actions').Action<Graph>
|
||||
>
|
||||
export declare const scaleItemCountChanged: import('redux-actions').ActionFunction1<
|
||||
number,
|
||||
import('redux-actions').Action<number>
|
||||
>
|
||||
export declare const colorBlindnessModeChanged: import('redux-actions').ActionFunction1<
|
||||
ColorBlindnessMode,
|
||||
import('redux-actions').Action<ColorBlindnessMode>
|
||||
>
|
||||
export declare const themeSelected: (
|
||||
themeInfo: ThemeListing,
|
||||
) => (dispatch: Dispatch) => void
|
|
@ -0,0 +1,23 @@
|
|||
import { loadById } from '@thematic/core'
|
||||
import { createAction } from 'redux-actions'
|
||||
export const themesLoaded = createAction('App:Themes:Loaded')
|
||||
export const themeInfoSelected = createAction('App:Themes:Selected')
|
||||
export const themeLoaded = createAction('App:Theme:Loaded')
|
||||
export const themeEdited = createAction('App:Theme:Edited')
|
||||
export const themeVariantToggled = createAction('App:Theme:VariantToggled')
|
||||
export const paramsChanged = createAction('App:CoolerPicker:Params:All:Changed')
|
||||
export const chartSizeChanged = createAction('App:Viewer:ChartSize:Changed')
|
||||
export const drawNodesChanged = createAction('App:Viewer:DrawNodes:Changed')
|
||||
export const drawLinksChanged = createAction('App:Viewer:DrawLinks:Changed')
|
||||
export const graphLoaded = createAction('App:Viewer:Graph:Loaded')
|
||||
export const scaleItemCountChanged = createAction(
|
||||
'App:Viewer:ScaleItems:Changed',
|
||||
)
|
||||
export const colorBlindnessModeChanged = createAction(
|
||||
'App:Viewer:ColorBlindnessMode:Changed',
|
||||
)
|
||||
export const themeSelected = themeInfo => dispatch => {
|
||||
const theme = loadById(themeInfo.id)
|
||||
dispatch(themeInfoSelected(themeInfo))
|
||||
dispatch(themeLoaded(theme))
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* State entry point for the app - kicks off async starter data, etc.
|
||||
*/
|
||||
export declare const init: any
|
||||
export declare const store: import('redux').Store<
|
||||
import('redux').EmptyObject & {
|
||||
themes: import('@thematic/core').ThemeListing[]
|
||||
themeInfo: import('@thematic/core').ThemeListing
|
||||
theme: any
|
||||
ui: import('redux').CombinedState<{
|
||||
chartSize: number
|
||||
drawNodes: boolean
|
||||
drawLinks: boolean
|
||||
scaleItemCount: number
|
||||
colorBlindnessMode: import('@thematic/color').ColorBlindnessMode
|
||||
}>
|
||||
graph: {
|
||||
nodes: import('../interfaces').Node[]
|
||||
edges: import('../interfaces').Edge[]
|
||||
}
|
||||
params: {
|
||||
accentHue: number
|
||||
accentSaturation: number
|
||||
accentLuminance: number
|
||||
backgroundHueShift: number
|
||||
backgroundLevel: number
|
||||
nominalHueStep: number
|
||||
}
|
||||
},
|
||||
| import('redux-actions').Action<any>
|
||||
| import('redux-actions').Action<import('@thematic/core').ThemeListing[]>
|
||||
| import('redux-actions').Action<import('@thematic/core').ThemeListing>
|
||||
| import('redux-actions').Action<number>
|
||||
| import('redux-actions').Action<boolean>
|
||||
| import('redux-actions').Action<import('../interfaces').Graph>
|
||||
| import('redux-actions').Action<import('@thematic/color').ColorBlindnessMode>
|
||||
| import('redux-actions').Action<{
|
||||
accentHue: number
|
||||
accentSaturation: number
|
||||
accentLuminance: number
|
||||
backgroundHueShift: number
|
||||
backgroundLevel: number
|
||||
nominalHueStep: number
|
||||
}>
|
||||
> & {
|
||||
dispatch: unknown
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { defaultThemes } from '@thematic/core'
|
||||
import { createStore, applyMiddleware, compose } from 'redux'
|
||||
import logger from 'redux-logger'
|
||||
import thunk from 'redux-thunk'
|
||||
import { graph } from '../data'
|
||||
import { themeSelected, graphLoaded } from './actions'
|
||||
import { reducer } from './reducers'
|
||||
/**
|
||||
* State entry point for the app - kicks off async starter data, etc.
|
||||
*/
|
||||
export const init = dispatch => {
|
||||
dispatch(themeSelected(defaultThemes[0]))
|
||||
dispatch(graphLoaded(graph))
|
||||
}
|
||||
export const store = createStore(
|
||||
reducer,
|
||||
compose(applyMiddleware(thunk, logger)),
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { ActionFunctionAny, ReduxCompatibleReducer } from 'redux-actions'
|
||||
/**
|
||||
* Simpler helper for basic state items that just need to set a value
|
||||
*/
|
||||
export declare function handle<State>(
|
||||
action: ActionFunctionAny<any>,
|
||||
defaultState: State,
|
||||
): ReduxCompatibleReducer<State, State>
|
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { handleAction } from 'redux-actions'
|
||||
/**
|
||||
* Simpler helper for basic state items that just need to set a value
|
||||
*/
|
||||
export function handle(action, defaultState) {
|
||||
return handleAction(action, (s, action) => action.payload, defaultState)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { Action } from 'redux-actions'
|
||||
import { Graph } from '../../interfaces'
|
||||
export declare const reducer: import('redux').Reducer<
|
||||
import('redux').CombinedState<{
|
||||
themes: import('@thematic/core').ThemeListing[]
|
||||
themeInfo: import('@thematic/core').ThemeListing
|
||||
theme: any
|
||||
ui: import('redux').CombinedState<{
|
||||
chartSize: number
|
||||
drawNodes: boolean
|
||||
drawLinks: boolean
|
||||
scaleItemCount: number
|
||||
colorBlindnessMode: import('@thematic/color').ColorBlindnessMode
|
||||
}>
|
||||
graph: {
|
||||
nodes: import('../../interfaces').Node[]
|
||||
edges: import('../../interfaces').Edge[]
|
||||
}
|
||||
params: {
|
||||
accentHue: number
|
||||
accentSaturation: number
|
||||
accentLuminance: number
|
||||
backgroundHueShift: number
|
||||
backgroundLevel: number
|
||||
nominalHueStep: number
|
||||
}
|
||||
}>,
|
||||
| Action<any>
|
||||
| Action<import('@thematic/core').ThemeListing[]>
|
||||
| Action<import('@thematic/core').ThemeListing>
|
||||
| Action<number>
|
||||
| Action<boolean>
|
||||
| Action<Graph>
|
||||
| Action<import('@thematic/color').ColorBlindnessMode>
|
||||
| Action<{
|
||||
accentHue: number
|
||||
accentSaturation: number
|
||||
accentLuminance: number
|
||||
backgroundHueShift: number
|
||||
backgroundLevel: number
|
||||
nominalHueStep: number
|
||||
}>
|
||||
>
|
|
@ -0,0 +1,64 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { defaultParams } from '@thematic/color'
|
||||
import { defaultThemes, clone } from '@thematic/core'
|
||||
import { combineReducers } from 'redux'
|
||||
import { handleAction, handleActions } from 'redux-actions'
|
||||
import {
|
||||
themesLoaded,
|
||||
themeInfoSelected,
|
||||
themeLoaded,
|
||||
themeEdited,
|
||||
themeVariantToggled,
|
||||
graphLoaded,
|
||||
paramsChanged,
|
||||
colorBlindnessModeChanged,
|
||||
} from '../actions'
|
||||
import { handle } from './handle'
|
||||
import { ui } from './ui'
|
||||
const themes = handle(themesLoaded, defaultThemes)
|
||||
const themeInfo = handle(themeInfoSelected, defaultThemes[0])
|
||||
const theme = handleActions(
|
||||
{
|
||||
[`${themeLoaded}`]: (s, action) => action.payload,
|
||||
[`${themeEdited}`]: (s, action) => {
|
||||
const { name, value } = action.payload
|
||||
const update = {
|
||||
[`${name}`]: value,
|
||||
}
|
||||
return clone(s, update)
|
||||
},
|
||||
[`${themeVariantToggled}`]: (s, action) => {
|
||||
if (s.variant === 'light') {
|
||||
return s.dark()
|
||||
}
|
||||
return s.light()
|
||||
},
|
||||
[`${colorBlindnessModeChanged}`]: (s, action) =>
|
||||
s.colorBlindness(action.payload),
|
||||
},
|
||||
null,
|
||||
)
|
||||
const graph = handleAction(
|
||||
graphLoaded,
|
||||
(s, action) => {
|
||||
// strip off the outer 'graph' root field used in rainbow hydra datasets
|
||||
// return action.payload.graph
|
||||
return {
|
||||
nodes: action.payload.nodes,
|
||||
edges: action.payload.edges,
|
||||
}
|
||||
},
|
||||
{ nodes: [], edges: [] },
|
||||
)
|
||||
const params = handle(paramsChanged, defaultParams)
|
||||
export const reducer = combineReducers({
|
||||
themes,
|
||||
themeInfo,
|
||||
theme,
|
||||
ui,
|
||||
graph,
|
||||
params,
|
||||
})
|
|
@ -0,0 +1,17 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { ColorBlindnessMode } from '@thematic/color'
|
||||
export declare const ui: import('redux').Reducer<
|
||||
import('redux').CombinedState<{
|
||||
chartSize: number
|
||||
drawNodes: boolean
|
||||
drawLinks: boolean
|
||||
scaleItemCount: number
|
||||
colorBlindnessMode: ColorBlindnessMode
|
||||
}>,
|
||||
| import('redux-actions').Action<number>
|
||||
| import('redux-actions').Action<boolean>
|
||||
| import('redux-actions').Action<ColorBlindnessMode>
|
||||
>
|
|
@ -0,0 +1,33 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { ColorBlindnessMode } from '@thematic/color'
|
||||
import { combineReducers } from 'redux'
|
||||
import {
|
||||
chartSizeChanged,
|
||||
drawNodesChanged,
|
||||
drawLinksChanged,
|
||||
scaleItemCountChanged,
|
||||
colorBlindnessModeChanged,
|
||||
} from '../actions'
|
||||
import { handle } from './handle'
|
||||
// chart width for the examples
|
||||
// for the main charts this'll be a 4/3 aspect ratio
|
||||
// for graphs we'll use it square
|
||||
const DEFAULT_CHART_SIZE = 400
|
||||
const chartSize = handle(chartSizeChanged, DEFAULT_CHART_SIZE)
|
||||
const drawNodes = handle(drawNodesChanged, true)
|
||||
const drawLinks = handle(drawLinksChanged, false)
|
||||
const scaleItemCount = handle(scaleItemCountChanged, 10)
|
||||
const colorBlindnessMode = handle(
|
||||
colorBlindnessModeChanged,
|
||||
ColorBlindnessMode.None,
|
||||
)
|
||||
export const ui = combineReducers({
|
||||
chartSize,
|
||||
drawNodes,
|
||||
drawLinks,
|
||||
scaleItemCount,
|
||||
colorBlindnessMode,
|
||||
})
|
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Node } from '../interfaces'
|
||||
/**
|
||||
* Compute the bounds for a node list
|
||||
* @param nodes
|
||||
*/
|
||||
export declare function bounds(nodes: Node[]): [number, number, number, number]
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче