Коммит
03ff6923b3
|
@ -1,6 +0,0 @@
|
|||
storybook_static/
|
||||
build/
|
||||
lib/
|
||||
dist/
|
||||
.yarn/
|
||||
CODE_OF_CONDUCT.md
|
3
.docsrc
3
.docsrc
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"allow": ["color", "colors", "hook", "hooks", "periods", "stroke"]
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
36
.spelling
36
.spelling
|
@ -1,36 +0,0 @@
|
|||
node_modules
|
||||
http
|
||||
https
|
||||
theming
|
||||
useThematic
|
||||
d3
|
||||
e.g.
|
||||
ThematicProvider
|
||||
ThemeProvider
|
||||
FluentTheme
|
||||
jsx
|
||||
office-ui-fabric-react
|
||||
ApplicationStyles
|
||||
fluentui
|
||||
monorepo
|
||||
vega
|
||||
svg
|
||||
MarkConfig
|
||||
blindnesses
|
||||
npm
|
||||
webapp
|
||||
codebase
|
||||
plotArea
|
||||
selection.call
|
||||
attr
|
||||
README.md
|
||||
Webpack
|
||||
filenames
|
||||
microsoft.com
|
||||
DotNet
|
||||
AspNet
|
||||
Xamarin
|
||||
msrc
|
||||
repos
|
||||
microsoft.github.io
|
||||
localhost:8080
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,11 @@
|
|||
releases:
|
||||
"@thematic/color": major
|
||||
"@thematic/core": major
|
||||
"@thematic/d3": major
|
||||
"@thematic/fluent": major
|
||||
"@thematic/react": major
|
||||
"@thematic/vega": major
|
||||
"@thematic/webapp": major
|
||||
|
||||
declined:
|
||||
- "@thematic/root"
|
34
.yarnrc.yml
34
.yarnrc.yml
|
@ -1,3 +1,19 @@
|
|||
changesetBaseRefs:
|
||||
- main
|
||||
- origin/main
|
||||
- upstream/main
|
||||
|
||||
changesetIgnorePatterns:
|
||||
- '**/*.spec.{js,ts,tsx}'
|
||||
|
||||
packageExtensions:
|
||||
'@fluentui/font-icons-mdl2@*':
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
'@fluentui/style-utilities@*':
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: '@yarnpkg/plugin-workspace-tools'
|
||||
|
@ -8,20 +24,4 @@ plugins:
|
|||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
spec: '@yarnpkg/plugin-typescript'
|
||||
|
||||
yarnPath: .yarn/releases/yarn-2.3.3.cjs
|
||||
|
||||
changesetBaseRefs:
|
||||
- main
|
||||
- origin/main
|
||||
- upstream/main
|
||||
|
||||
changesetIgnorePatterns:
|
||||
- '**/*.spec.{js,ts,tsx}'
|
||||
|
||||
packageExtensions:
|
||||
'@uifabric/icons@*':
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
'@uifabric/styling@*':
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
yarnPath: .yarn/releases/yarn-2.4.1.cjs
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-typescript',
|
||||
'@babel/preset-react',
|
||||
],
|
||||
}
|
|
@ -3,4 +3,4 @@
|
|||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
const { configure } = require('@essex/jest-config')
|
||||
module.exports = configure(['<rootDir>/jest.setup.js'], 'tsconfig.jest.json')
|
||||
module.exports = configure()
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
require('regenerator-runtime/runtime')
|
||||
const enzyme = require('enzyme')
|
||||
const Adapter = require('enzyme-adapter-react-16')
|
||||
|
||||
enzyme.configure({ adapter: new Adapter() })
|
27
package.json
27
package.json
|
@ -10,12 +10,12 @@
|
|||
"start_all": "yarn workspaces foreach -piv run start",
|
||||
"publish_all": "yarn workspaces foreach --exclude '@thematic/root' -pv npm publish --tolerate-republish --access public",
|
||||
"lint": "essex lint",
|
||||
"lint:ci": "essex lint --docs",
|
||||
"lint:fix": "essex lint --fix",
|
||||
"prettify": "essex prettify",
|
||||
"test": "essex test",
|
||||
"test:ci": "essex test --coverage",
|
||||
"git-is-clean": "essex git-is-clean",
|
||||
"ci": "npm-run-all -p lint:ci build_all bundle_all test:ci -s git-is-clean",
|
||||
"ci": "npm-run-all -p lint build_all bundle_all test:ci -s git-is-clean",
|
||||
"release": "run-s build_all publish_all",
|
||||
"start": "yarn start:webapp"
|
||||
},
|
||||
|
@ -25,32 +25,29 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/eslint-config": "^10.0.1",
|
||||
"@essex/eslint-plugin": "^10.0.1",
|
||||
"@essex/jest-config": "^10.0.2",
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/preset-env": "^7.14.1",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@essex/eslint-config": "^14.0.0",
|
||||
"@essex/eslint-plugin": "^14.0.0",
|
||||
"@essex/jest-config": "^14.0.0",
|
||||
"@essex/prettier-config": "^10.0.0",
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.0",
|
||||
"@types/prettier": "^2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.4.1",
|
||||
"@typescript-eslint/parser": "^4.4.1",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.5",
|
||||
"eslint-import-resolver-node": "^0.3.4",
|
||||
"husky": "^4.3.0",
|
||||
"lint-staged": "^10.4.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"ts-jest": "^26.4.1",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "essex pre-commit"
|
||||
}
|
||||
},
|
||||
"prettier": "@essex/prettier-config"
|
||||
}
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
"hsluv": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@types/node": "^14.14.0"
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"@types/node": "^14.14.0",
|
||||
"core-js": "^3.11.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "*"
|
||||
"@types/node": "*",
|
||||
"core-js": "^3.11.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,23 +14,29 @@ import { css2rgbaVector, css2rgbaNumber, css2css, css2hex } from './chroma'
|
|||
* Note that the naming of these intentionally matches the string values on the ColorSpace enum.
|
||||
*/
|
||||
export class Color {
|
||||
private _css: string
|
||||
private _raw: string
|
||||
private _alpha: number
|
||||
constructor(css: string, alpha?: number) {
|
||||
this._css = css
|
||||
this._raw = css
|
||||
this._alpha = alpha !== undefined ? alpha : 1.0
|
||||
}
|
||||
/**
|
||||
* Direct passthrough of string used to create the color, to avoid any transform.
|
||||
*/
|
||||
get raw(): string {
|
||||
return this._raw
|
||||
}
|
||||
hex(alpha?: number): string {
|
||||
return css2hex(this._css, alpha || this._alpha)
|
||||
return css2hex(this._raw, alpha || this._alpha)
|
||||
}
|
||||
css(alpha?: number): string {
|
||||
return css2css(this._css, alpha || this._alpha)
|
||||
return css2css(this._raw, alpha || this._alpha)
|
||||
}
|
||||
rgbav(alpha?: number): [number, number, number, number] {
|
||||
return css2rgbaVector(this._css, alpha || this._alpha)
|
||||
return css2rgbaVector(this._raw, alpha || this._alpha)
|
||||
}
|
||||
rgbaint(alpha?: number): number {
|
||||
return css2rgbaNumber(this._css, alpha || this._alpha)
|
||||
return css2rgbaNumber(this._raw, alpha || this._alpha)
|
||||
}
|
||||
toString(): string {
|
||||
return this.hex()
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { ColorMaker, polynomial_scale } from '../HsluvColorLogic'
|
||||
import { ColorMaker, polynomial_scale } from '../scheme/HsluvColorLogic'
|
||||
|
||||
const GREYS = [
|
||||
[50, 0, 92],
|
||||
|
|
|
@ -177,6 +177,7 @@ export function css2css(css: string, alpha?: number): string {
|
|||
* Note that we handle 'none' as 'none' here because it is used frequently to indicate empty fills and strokes in svg,
|
||||
* even though it is not a valid hex code.
|
||||
* Note: if you are using this for a css color, use css2css as it will return valid 'transparent' for our empty 'none'.
|
||||
* Also note that if alpha is passed, chroma-js will create an 8-letter hex string.
|
||||
* @param css
|
||||
* @param alpha
|
||||
*/
|
||||
|
|
|
@ -6,7 +6,7 @@ export * from './interfaces'
|
|||
export * from './chroma'
|
||||
export * from './colorBlindness'
|
||||
export * from './Color'
|
||||
export { getScheme } from './getScheme'
|
||||
export * from './scheme'
|
||||
|
||||
// TODO: best would be to update the scheme compute to accept undefined or baseline params and handle an "empty" case, which is maybe grayscale
|
||||
export const defaultParams = {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { Params, Scheme } from './interfaces'
|
||||
import { Params, Scheme } from '../interfaces'
|
||||
const chroma = require('chroma-js')
|
||||
const hsluv = require('hsluv')
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
|
||||
import { Color } from '../Color'
|
||||
import { Scheme } from '../interfaces'
|
||||
|
||||
/**
|
||||
* Extracts a thematic Color using its scheme "path".
|
||||
* @param scheme
|
||||
* @param path
|
||||
*/
|
||||
export function getNamedSchemeColor(scheme: Scheme, path?: string): Color {
|
||||
if (!path || path === 'none') {
|
||||
return new Color('none')
|
||||
}
|
||||
const indexed = indexedTest(path)
|
||||
if (indexed) {
|
||||
return new Color((scheme as any)[indexed.name][indexed.index])
|
||||
}
|
||||
return new Color((scheme as any)[path])
|
||||
}
|
||||
|
||||
function indexedTest(path: string) {
|
||||
const indexedName = path.match(/(\w+)\[/)
|
||||
const indexedIndex = path.match(/\[(\d{1,2})\]/)
|
||||
if (indexedName && indexedIndex) {
|
||||
return {
|
||||
name: indexedName[1],
|
||||
index: +indexedIndex[1],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export { getScheme } from './getScheme'
|
||||
export * from './getNamedSchemeColor'
|
||||
export * from './isNominal'
|
|
@ -0,0 +1,7 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export function isNominal(name?: string): boolean {
|
||||
return name === 'nominal' || name === 'nominalBold' || name === 'nominalMuted'
|
||||
}
|
|
@ -26,15 +26,17 @@
|
|||
"murmurhash-js": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"@types/d3-scale": "^2.2.4",
|
||||
"@types/ncp": "^2",
|
||||
"@types/node": "^14.14.0",
|
||||
"core-js": "^3.11.1",
|
||||
"ncp": "^2.0.0",
|
||||
"npm-run-all": "^4.1.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/d3-scale": ">= 2",
|
||||
"@types/node": "*"
|
||||
"@types/node": "*",
|
||||
"core-js": "^3.11.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ export const defaultThemes: ThemeListing[] = Object.entries(themes).map(
|
|||
)
|
||||
|
||||
/**
|
||||
* Load the default Essex theme
|
||||
* Load the default theme
|
||||
* Use config to supply overrides to specific definition blocks
|
||||
* @param config
|
||||
*/
|
||||
|
|
|
@ -36,7 +36,7 @@ export function log(domain: number[], clamp = true): (value: number) => number {
|
|||
|
||||
// holds an array of numbers along with the bounds
|
||||
// this replicates the histogram format of d3
|
||||
interface Bin extends Array<number> {
|
||||
export interface Bin extends Array<number> {
|
||||
x0?: number
|
||||
x1?: number
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ import {
|
|||
} from '@thematic/color'
|
||||
|
||||
// these are static default settings for the marks that are not derived from the computed scheme
|
||||
|
||||
const DEFAULT_NOMINAL_ITEMS = 10
|
||||
const DEFAULT_SEQUENTIAL_ITEMS = 100
|
||||
|
||||
/**
|
||||
* Creates a completed Params block from a ThemeDefinition, making sure missing optional fields are populated.
|
||||
* @param themeDefinition
|
||||
|
|
|
@ -6,15 +6,15 @@ export enum ScaleType {
|
|||
/**
|
||||
* Maps using a linear transform.
|
||||
*/
|
||||
Linear,
|
||||
Linear = 'linear',
|
||||
/**
|
||||
* Maps with a log transform. Useful for wide data ranges, but cannot handle 0 values.
|
||||
*/
|
||||
Log,
|
||||
Log = 'log',
|
||||
/**
|
||||
* This creates a set of quantile bins to partition the data into, with an aim for equal size bins to smooth out the scale.
|
||||
*/
|
||||
Quantile,
|
||||
Quantile = 'quantile',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,14 +19,16 @@
|
|||
"d3-selection": "^1.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"@types/d3-axis": "^1",
|
||||
"@types/d3-selection": "^1.4.3",
|
||||
"@types/node": "^14.14.0"
|
||||
"@types/node": "^14.14.0",
|
||||
"core-js": "^3.11.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/d3-axis": ">= 1",
|
||||
"@types/d3-selection": ">= 1",
|
||||
"@types/node": "*"
|
||||
"@types/node": "*",
|
||||
"core-js": "^3.11.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,7 +198,7 @@ function applyBaseOptions(
|
|||
selection: Selection<Element, any, Element, any>,
|
||||
options?: SelectionOptions,
|
||||
): Selection<Element, any, Element, any> {
|
||||
// TODO: we could enumate all of the core d3-selection functions
|
||||
// TODO: we could enumerate all of the core d3-selection functions
|
||||
// html, text, etc., for completeness
|
||||
return selection
|
||||
.call(on, options?.on)
|
||||
|
|
|
@ -22,8 +22,8 @@ Here is an example:
|
|||
import { load } from '@thematic/core'
|
||||
import { ThematicProvider } from '@thematic/react'
|
||||
import { loadFluentTheme } from '@thematic/fluent'
|
||||
import { initializeIcons } from '@uifabric/icons'
|
||||
import { ThemeProvider } from '@fluentui/react/lib/Foundation'
|
||||
import { initializeIcons } from '@fluentui/font-icons-mdl2'
|
||||
import { ThemeProvider } from '@fluentui/react'
|
||||
|
||||
const theme = load()
|
||||
const fluentTheme = loadFluentTheme(theme)
|
||||
|
|
|
@ -14,23 +14,28 @@
|
|||
"start": "essex watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@essex-js-toolkit/hooks": "^1.1.5",
|
||||
"@thematic/color": "^0.9.0",
|
||||
"@thematic/core": "^0.9.0",
|
||||
"@thematic/react": "^0.9.0"
|
||||
"@thematic/react": "^0.9.0",
|
||||
"d3-scale": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@fluentui/react": "^7.147.1",
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"@fluentui/font-icons-mdl2": "^8.1.0",
|
||||
"@fluentui/react": "^8.14.2",
|
||||
"@types/d3-scale": "^3.2.2",
|
||||
"@types/node": "^14.14.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@uifabric/icons": "^7.5.12",
|
||||
"core-js": "^3.11.1",
|
||||
"react": "^16.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fluentui/react": ">= 7",
|
||||
"@fluentui/react": ">= 8",
|
||||
"@fluentuit/font-icons-mdl2": ">= 8",
|
||||
"@types/node": "*",
|
||||
"@types/react": ">= 16",
|
||||
"@uifabric/icons": ">= 7",
|
||||
"core-js": "^3.11.1",
|
||||
"react": ">= 16.8.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
Slider,
|
||||
IColor,
|
||||
} from '@fluentui/react'
|
||||
import * as React from 'react'
|
||||
import { CSSProperties } from 'react'
|
||||
import { css2hsluv } from '@thematic/color'
|
||||
import React, { CSSProperties } from 'react'
|
||||
|
||||
import { css2hsluv, Params } from '@thematic/color'
|
||||
import { Theme } from '@thematic/core'
|
||||
import { useThematic } from '@thematic/react'
|
||||
|
||||
|
@ -23,21 +23,15 @@ export interface ColorPickerStyles {
|
|||
slider?: CSSProperties
|
||||
}
|
||||
|
||||
interface ColorPickerProps {
|
||||
export interface ColorPickerProps {
|
||||
onChange?: (theme: Theme) => void
|
||||
/** Optional theme to use, otherwise it will be pulled from context */
|
||||
theme?: Theme
|
||||
layout?: ColorPickerLayout
|
||||
styles?: ColorPickerStyles
|
||||
}
|
||||
|
||||
// TODO: this is a copy of the color/Params interface, but with each item optional
|
||||
interface PartialParams {
|
||||
accentHue?: number
|
||||
accentSaturation?: number
|
||||
accentLuminance?: number
|
||||
backgroundHueShift?: number
|
||||
backgroundLevel?: number
|
||||
nominalHueStep?: number
|
||||
}
|
||||
export type PartialParams = Partial<Params>
|
||||
|
||||
/**
|
||||
* This is a simple ColorPicker that you can show users, allowing them to choose a custom accent color.
|
||||
|
@ -48,19 +42,21 @@ interface PartialParams {
|
|||
*/
|
||||
export const ColorPicker: React.FC<ColorPickerProps> = ({
|
||||
onChange,
|
||||
theme,
|
||||
layout,
|
||||
styles,
|
||||
}) => {
|
||||
const theme = useThematic()
|
||||
const contextTheme = useThematic()
|
||||
const activeTheme = theme || contextTheme
|
||||
|
||||
const lyt = layout || ColorPickerLayout.PickerOnly
|
||||
|
||||
const updateParams = (params: PartialParams) => {
|
||||
if (onChange) {
|
||||
onChange(
|
||||
theme.clone({
|
||||
activeTheme.clone({
|
||||
params: {
|
||||
...theme.params,
|
||||
...activeTheme.params,
|
||||
...params,
|
||||
},
|
||||
}),
|
||||
|
@ -97,7 +93,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
|||
backgroundLevel,
|
||||
backgroundHueShift,
|
||||
nominalHueStep,
|
||||
} = theme.params
|
||||
} = activeTheme.params
|
||||
|
||||
// TODO: it would be really nice to make these IStyle objects and pass directly to
|
||||
// child component instead of wrapping them in a div
|
||||
|
@ -113,7 +109,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
|||
return (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<FluentColorPicker
|
||||
color={theme.application().accent().hex()}
|
||||
color={activeTheme.application().accent().hex()}
|
||||
onChange={handlePickerChange}
|
||||
alphaType="none"
|
||||
/>
|
|
@ -0,0 +1,5 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export * from './ColorPicker'
|
|
@ -3,10 +3,10 @@
|
|||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { IconButton, Label } from '@fluentui/react'
|
||||
import * as React from 'react'
|
||||
import { memo, CSSProperties } from 'react'
|
||||
import { ColorPicker } from './ColorPicker'
|
||||
import React, { memo, CSSProperties } from 'react'
|
||||
import { ColorPicker } from '../ColorPicker'
|
||||
import { Theme } from '@thematic/core'
|
||||
import { useThematic } from '@thematic/react'
|
||||
|
||||
export interface ColorPickerButtonStyles {
|
||||
label?: CSSProperties
|
||||
|
@ -14,6 +14,7 @@ export interface ColorPickerButtonStyles {
|
|||
|
||||
export interface ColorPickerButtonProps {
|
||||
onChange?: (theme: Theme) => void
|
||||
theme?: Theme
|
||||
label?: string
|
||||
styles?: ColorPickerButtonStyles
|
||||
}
|
||||
|
@ -22,7 +23,9 @@ export interface ColorPickerButtonProps {
|
|||
* This is a dropdown button that displays a thematic ColorPicker.
|
||||
*/
|
||||
export const ColorPickerButton: React.FC<ColorPickerButtonProps> = memo(
|
||||
function ColorPickerButton({ onChange, label, styles }) {
|
||||
function ColorPickerButton({ onChange, theme, label, styles }) {
|
||||
const contextTheme = useThematic()
|
||||
const activeTheme = theme || contextTheme
|
||||
const labelStyle = {
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
|
@ -42,7 +45,9 @@ export const ColorPickerButton: React.FC<ColorPickerButtonProps> = memo(
|
|||
{
|
||||
key: 'colorPicker',
|
||||
// eslint-disable-next-line react/display-name
|
||||
onRender: () => <ColorPicker onChange={onChange} />,
|
||||
onRender: () => (
|
||||
<ColorPicker theme={theme} onChange={onChange} />
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
|
@ -50,6 +55,9 @@ export const ColorPickerButton: React.FC<ColorPickerButtonProps> = memo(
|
|||
root: {
|
||||
width: 48,
|
||||
},
|
||||
icon: {
|
||||
color: activeTheme.application().accent().hex(),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export * from './ColorPickerButton'
|
|
@ -0,0 +1,42 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { scaleLinear } from 'd3-scale'
|
||||
import React, { useMemo } from 'react'
|
||||
import { ChipsProps } from './types'
|
||||
|
||||
export const ColorChips: React.FC<ChipsProps> = ({
|
||||
scale,
|
||||
width = 200,
|
||||
height = 10,
|
||||
maxItems = 10,
|
||||
}) => {
|
||||
const blocks = useMemo(() => {
|
||||
if (height <= 1) {
|
||||
return []
|
||||
}
|
||||
const r = height / 2 - 1
|
||||
const x = scaleLinear()
|
||||
.domain([0, maxItems - 1])
|
||||
.range([r, width - r])
|
||||
return scale
|
||||
.toArray(maxItems)
|
||||
.map((color, i) => (
|
||||
<circle
|
||||
key={`color-chips-${color}-${i}`}
|
||||
fill={color}
|
||||
stroke={'none'}
|
||||
cx={x(i)}
|
||||
cy={r}
|
||||
r={r}
|
||||
height={height}
|
||||
/>
|
||||
))
|
||||
}, [scale, width, height, maxItems])
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
{blocks}
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import React, { useMemo } from 'react'
|
||||
import { ChipsProps } from './types'
|
||||
|
||||
export const ContinuousBand: React.FC<ChipsProps> = ({
|
||||
scale,
|
||||
width,
|
||||
height,
|
||||
}) => {
|
||||
const blocks = useMemo(() => {
|
||||
return scale
|
||||
.toArray(width)
|
||||
.map((color, i) => (
|
||||
<rect
|
||||
key={`continuous-band-${color}-${i}`}
|
||||
fill={color}
|
||||
stroke={'none'}
|
||||
x={i}
|
||||
width={2}
|
||||
height={height}
|
||||
/>
|
||||
))
|
||||
}, [scale, width, height])
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
{blocks}
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Dropdown } from '@fluentui/react'
|
||||
import React, { useCallback, useRef } from 'react'
|
||||
import { ScaleDropdownItem } from './ScaleDropdownItem'
|
||||
import {
|
||||
usePaletteWidth,
|
||||
usePaletteHeight,
|
||||
useItemStyle,
|
||||
useThematicScaleOptions,
|
||||
} from './hooks/theme'
|
||||
import { useSafeDimensions } from './hooks/useSafeDimensions'
|
||||
import { ScaleDropdownProps } from './types'
|
||||
|
||||
/**
|
||||
* Represents a Fluent dropdown of Thematic scale options.
|
||||
* The scale names can be accompanied by a visual rendering of the scale colors.
|
||||
* This bascially extends Dropdown, overriding the options and item rendering.
|
||||
*/
|
||||
export const ScaleDropdown: React.FC<ScaleDropdownProps> = props => {
|
||||
const ref = useRef(null)
|
||||
const { width, height } = useSafeDimensions(ref)
|
||||
const paletteWidth = usePaletteWidth(width)
|
||||
const paletteHeight = usePaletteHeight(height, props.label)
|
||||
const itemStyle = useItemStyle(width)
|
||||
const options = useThematicScaleOptions()
|
||||
|
||||
const handleRenderTitle = useCallback(
|
||||
([option]) => {
|
||||
return (
|
||||
<ScaleDropdownItem
|
||||
paletteWidth={paletteWidth}
|
||||
paletteHeight={paletteHeight}
|
||||
option={option}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[paletteWidth, paletteHeight],
|
||||
)
|
||||
|
||||
const handleRenderOption = useCallback(
|
||||
option => {
|
||||
return (
|
||||
<ScaleDropdownItem
|
||||
key={`scale-dropdown-item-${option.key}`}
|
||||
paletteWidth={paletteWidth}
|
||||
paletteHeight={paletteHeight}
|
||||
option={option}
|
||||
style={itemStyle}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[paletteWidth, paletteHeight, itemStyle],
|
||||
)
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<Dropdown
|
||||
{...props}
|
||||
options={options}
|
||||
onRenderTitle={handleRenderTitle}
|
||||
onRenderOption={handleRenderOption}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import React from 'react'
|
||||
import { useSafeCollapseDimensions } from './hooks/useSafeDimensions'
|
||||
import { ScaleDropdownItemProps } from './types'
|
||||
import { selectColorPalette, useScale } from './util'
|
||||
|
||||
export const ScaleDropdownItem: React.FC<ScaleDropdownItemProps> = ({
|
||||
option,
|
||||
paletteWidth,
|
||||
paletteHeight,
|
||||
style,
|
||||
}) => {
|
||||
const { key } = option
|
||||
const Palette = selectColorPalette(key)
|
||||
const [width, height] = useSafeCollapseDimensions(paletteWidth, paletteHeight)
|
||||
const scale = useScale(option.key, paletteWidth)
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...containerStyle,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<div style={{ width: 74 }}>{option.text}</div>
|
||||
<Palette scale={scale} width={width} height={height} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const containerStyle = {
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
minWidth: 10,
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { IDropdownOption } from '@fluentui/react'
|
||||
import { useMemo } from 'react'
|
||||
import { useThematic } from '@thematic/react'
|
||||
|
||||
const ITEM_LEFT_PADDING = 8 // defined default in fluent dropdown
|
||||
const CARET_PADDING = 30 // defined default in fluent dropdown
|
||||
const TEXT_WIDTH = 80 // TODO: adjust this based on font size/max measured
|
||||
const LABEL_HEIGHT = 29 // defined default in fluent dropdown
|
||||
|
||||
// we may want this to be an optional prop once extracted
|
||||
// visually we'll keep it lowercase in this app for visual consistency
|
||||
const TITLE_CASE = false
|
||||
|
||||
export function usePaletteWidth(width: number): number {
|
||||
// subtract space for the caret, left pad, and text
|
||||
return width - CARET_PADDING - ITEM_LEFT_PADDING - TEXT_WIDTH
|
||||
}
|
||||
|
||||
export function usePaletteHeight(height: number, label?: string): number {
|
||||
// the measured component dimensions will include the label if present
|
||||
const root = label ? height - LABEL_HEIGHT : height
|
||||
return root / 2
|
||||
}
|
||||
/**
|
||||
* This provides unique style overrides for the dropdown items,
|
||||
* NOT the title. The paddings here are to align the item
|
||||
* contents visually with the title, so it has a seamless
|
||||
* appearance whether expanded or not.
|
||||
*/
|
||||
export function useItemStyle(width: number): React.CSSProperties {
|
||||
return useMemo(
|
||||
() => ({
|
||||
width: width - CARET_PADDING,
|
||||
paddingLeft: ITEM_LEFT_PADDING,
|
||||
paddingRight: CARET_PADDING,
|
||||
}),
|
||||
[width],
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: it would be helpful to provide a filter function so scales can be
|
||||
// constrained to categorical or sequential when data is string versus numeric
|
||||
export function useThematicScaleOptions(): IDropdownOption[] {
|
||||
const theme = useThematic()
|
||||
return useMemo(() => {
|
||||
const keys = Object.keys(theme.scales())
|
||||
return keys.map(key => {
|
||||
// pretty print the scale names
|
||||
// (1) uppercase first letter
|
||||
// (2) use +/- for bold/muted
|
||||
const text = key
|
||||
.replace(/^(\w{1})/, c => (TITLE_CASE ? c.toLocaleUpperCase() : c))
|
||||
.replace('Bold', '+')
|
||||
.replace('Muted', '-')
|
||||
return {
|
||||
key,
|
||||
text,
|
||||
}
|
||||
})
|
||||
}, [theme])
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Dimensions, useDimensions } from '@essex-js-toolkit/hooks'
|
||||
import React from 'react'
|
||||
|
||||
const DEFAULT_WIDTH = 200
|
||||
const DEFAULT_HEIGHT = 32
|
||||
export function useSafeDimensions(
|
||||
ref: React.RefObject<HTMLDivElement>,
|
||||
): Dimensions {
|
||||
const dimensions = useDimensions(ref)
|
||||
return (
|
||||
dimensions || {
|
||||
width: DEFAULT_WIDTH,
|
||||
height: DEFAULT_HEIGHT,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a non-zero width/height, because collapsible panels can force invalid color arrays
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
export function useSafeCollapseDimensions(
|
||||
width: number,
|
||||
height: number,
|
||||
): [number, number] {
|
||||
return [width <= 0 ? 1 : width, height <= 0 ? 1 : height]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
export * from './ScaleDropdown'
|
|
@ -0,0 +1,25 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { IDropdownProps } from '@fluentui/react'
|
||||
import {
|
||||
ContinuousColorScaleFunction,
|
||||
NominalColorScaleFunction,
|
||||
} from '@thematic/core'
|
||||
|
||||
export type ScaleDropdownProps = Omit<IDropdownProps, 'options'>
|
||||
|
||||
export interface ScaleDropdownItemProps {
|
||||
option: any
|
||||
paletteWidth: number
|
||||
paletteHeight: number
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export interface ChipsProps {
|
||||
scale: ContinuousColorScaleFunction | NominalColorScaleFunction
|
||||
width?: number
|
||||
height?: number
|
||||
maxItems?: number
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { useMemo } from 'react'
|
||||
import { ColorChips } from './ColorChips'
|
||||
import { ContinuousBand } from './ContinuousBand'
|
||||
import { ChipsProps } from './types'
|
||||
import {
|
||||
Theme,
|
||||
NominalColorScaleFunction,
|
||||
ContinuousColorScaleFunction,
|
||||
} from '@thematic/core'
|
||||
import { useThematic } from '@thematic/react'
|
||||
|
||||
export function useScale(
|
||||
key: string,
|
||||
width: number,
|
||||
): NominalColorScaleFunction | ContinuousColorScaleFunction {
|
||||
const theme = useThematic()
|
||||
const scale = useMemo(() => chooseScale(theme, key, width), [
|
||||
theme,
|
||||
key,
|
||||
width,
|
||||
])
|
||||
return scale
|
||||
}
|
||||
|
||||
export function chooseScale(
|
||||
theme: Theme,
|
||||
key: string,
|
||||
width: number,
|
||||
): NominalColorScaleFunction | ContinuousColorScaleFunction {
|
||||
const scales = theme.scales()
|
||||
const domain = [0, width]
|
||||
switch (key) {
|
||||
case 'sequential':
|
||||
return scales.sequential(domain)
|
||||
case 'sequential2':
|
||||
return scales.sequential2(domain)
|
||||
case 'diverging':
|
||||
return scales.diverging(domain)
|
||||
case 'diverging2':
|
||||
return scales.diverging2(domain)
|
||||
case 'greys':
|
||||
return scales.greys(domain)
|
||||
case 'nominalMuted':
|
||||
return scales.nominalMuted()
|
||||
case 'nominalBold':
|
||||
return scales.nominalBold()
|
||||
case 'nominal':
|
||||
default:
|
||||
return scales.nominal()
|
||||
}
|
||||
}
|
||||
|
||||
export function selectColorPalette(key: string): React.FC<ChipsProps> {
|
||||
if (key === 'nominal' || key === 'nominalMuted' || key === 'nominalBold') {
|
||||
return ColorChips
|
||||
}
|
||||
return ContinuousBand
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import {
|
||||
ChoiceGroup,
|
||||
IChoiceGroupOption,
|
||||
IChoiceGroupStyles,
|
||||
} from '@fluentui/react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { ScaleType } from '@thematic/core'
|
||||
|
||||
interface ScaleTypeChoiceGroupProps {
|
||||
selectedType: ScaleType
|
||||
onChange?: (scaleType: ScaleType) => void
|
||||
label?: string
|
||||
suppressQuantile?: boolean
|
||||
styles?: IChoiceGroupStyles
|
||||
}
|
||||
|
||||
const CHOICE_STYLE = {
|
||||
flexContainer: { display: 'flex', justifyContent: 'space-around' },
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a strongly typed ChoiceGroup for selecting thematic ScaleTypes.
|
||||
*/
|
||||
export const ScaleTypeChoiceGroup: React.FC<ScaleTypeChoiceGroupProps> = ({
|
||||
selectedType,
|
||||
onChange,
|
||||
label,
|
||||
suppressQuantile = false,
|
||||
styles,
|
||||
}) => {
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
...CHOICE_STYLE,
|
||||
...styles,
|
||||
}),
|
||||
[styles],
|
||||
)
|
||||
const typeOptions = useTypeOptions(suppressQuantile)
|
||||
|
||||
const handleTypeChange = useCallback(
|
||||
(_, option) => {
|
||||
onChange && onChange(option.key)
|
||||
},
|
||||
[onChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<ChoiceGroup
|
||||
styles={style}
|
||||
label={label}
|
||||
options={typeOptions}
|
||||
selectedKey={selectedType}
|
||||
onChange={handleTypeChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function useTypeOptions(suppressQuantile = false): IChoiceGroupOption[] {
|
||||
return useMemo(() => {
|
||||
const options = [
|
||||
{
|
||||
key: 'linear',
|
||||
text: 'Linear',
|
||||
},
|
||||
{
|
||||
key: 'log',
|
||||
text: 'Log',
|
||||
},
|
||||
]
|
||||
if (!suppressQuantile) {
|
||||
options.push({
|
||||
key: 'quantile',
|
||||
text: 'Quantile',
|
||||
})
|
||||
}
|
||||
return options
|
||||
}, [suppressQuantile])
|
||||
}
|
|
@ -2,10 +2,9 @@
|
|||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { ThemeProvider } from '@fluentui/react/lib/Foundation'
|
||||
import { initializeIcons } from '@uifabric/icons'
|
||||
import * as React from 'react'
|
||||
import { useMemo, ReactNode } from 'react'
|
||||
import { initializeIcons } from '@fluentui/font-icons-mdl2'
|
||||
import { ThemeProvider } from '@fluentui/react'
|
||||
import React, { useMemo, ReactNode } from 'react'
|
||||
import { loadFluentTheme } from './loader'
|
||||
import { Theme } from '@thematic/core'
|
||||
import { ThematicProvider } from '@thematic/react'
|
||||
|
|
|
@ -7,3 +7,5 @@ export * from './loader'
|
|||
export * from './ColorPicker'
|
||||
export * from './ColorPickerButton'
|
||||
export * from './ThematicFluentProvider'
|
||||
export * from './ScaleDropdown'
|
||||
export * from './ScaleTypeChoiceGroup'
|
||||
|
|
|
@ -14,18 +14,20 @@
|
|||
"start": "essex watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"@thematic/color": "^0.9.0",
|
||||
"@thematic/core": "^0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.14.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"core-js": "^3.11.1",
|
||||
"react": "^16.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
"core-js": "^3.11.1",
|
||||
"react": ">= 16.13.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
import React from 'react'
|
||||
import { useThematic } from './'
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
import React from 'react'
|
||||
import { loadById, Theme } from '@thematic/core'
|
||||
export const defaultTheme = loadById('default') as Theme
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
import React, { ReactNode, useMemo } from 'react'
|
||||
import { ThematicContext, defaultTheme } from './ThematicContext'
|
||||
import { Theme } from '@thematic/core'
|
||||
|
||||
|
@ -19,9 +18,7 @@ export const ThematicProvider: React.FC<ThematicProviderProps> = ({
|
|||
theme,
|
||||
children,
|
||||
}) => {
|
||||
const t = useMemo(() => {
|
||||
return theme ? theme : defaultTheme
|
||||
}, [theme])
|
||||
const t = useMemo(() => (theme ? theme : defaultTheme), [theme])
|
||||
return (
|
||||
<ThematicContext.Provider value={t}>{children}</ThematicContext.Provider>
|
||||
)
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
"@thematic/core": "^0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"@types/node": "^14.14.0",
|
||||
"core-js": "^3.11.1",
|
||||
"vega": "^5.17.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"core-js": "^3.11.1",
|
||||
"vega": ">= 5.13.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
"clean": "essex clean build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@essex/webpack-config": "^10.0.2",
|
||||
"@fluentui/react": "^7.147.1",
|
||||
"@essex/webpack-config": "^14.0.0",
|
||||
"@fluentui/font-icons-mdl2": "^8.1.0",
|
||||
"@fluentui/react": "^8.14.2",
|
||||
"@thematic/color": "^0.9.0",
|
||||
"@thematic/core": "^0.9.0",
|
||||
"@thematic/d3": "^0.9.0",
|
||||
|
@ -31,10 +32,10 @@
|
|||
"@types/redux-actions": "^2.6.1",
|
||||
"@types/redux-logger": "^3.0.8",
|
||||
"@types/redux-thunk": "^2.1.32",
|
||||
"core-js": "^3.11.1",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-scale": "^3.2.3",
|
||||
"d3-selection": "^1.4.2",
|
||||
"office-ui-fabric-react": "^7.147.1",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-redux": "^7.2.1",
|
||||
|
@ -45,14 +46,14 @@
|
|||
"vega": "^5.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@essex/scripts": "^11.0.1",
|
||||
"@essex/scripts": "^14.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"worker-loader": "^2.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not ie 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,16 +2,21 @@
|
|||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Dropdown, IDropdownOption, Toggle, SpinButton } from '@fluentui/react'
|
||||
import { Position } from 'office-ui-fabric-react/lib/utilities/positioning'
|
||||
import {
|
||||
Dropdown,
|
||||
IDropdownOption,
|
||||
Toggle,
|
||||
SpinButton,
|
||||
Position,
|
||||
} from '@fluentui/react'
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { EnumDropdown } from '../EnumDropdown'
|
||||
import { ColorBlindnessMode, colorBlindnessInfo } from '@thematic/color'
|
||||
import { ThemeListing, Theme } from '@thematic/core'
|
||||
import { ColorPickerButton } from '@thematic/fluent'
|
||||
// XXX: @fluentui doesn't export everything correctly yet
|
||||
|
||||
import './index.css'
|
||||
|
||||
export interface ControlPanelProps {
|
||||
themes: ThemeListing[]
|
||||
themeInfo: ThemeListing
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*!
|
||||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import React, { useState, useCallback, useMemo } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { themeLoaded } from '../../state/actions'
|
||||
import { ScaleType, Theme } from '@thematic/core'
|
||||
import {
|
||||
ScaleDropdown,
|
||||
ScaleTypeChoiceGroup,
|
||||
ColorPicker,
|
||||
ColorPickerButton,
|
||||
} from '@thematic/fluent'
|
||||
import { useThematic } from '@thematic/react'
|
||||
|
||||
interface FluentControlsComponentProps {
|
||||
themeLoaded: (theme: Theme) => void
|
||||
}
|
||||
|
||||
const FluentControlsComponent: React.FC<FluentControlsComponentProps> = ({
|
||||
themeLoaded,
|
||||
}) => {
|
||||
const theme = useThematic()
|
||||
const [scale, setScale] = useState<string>('<none>')
|
||||
const handleScaleChange = useCallback((e, option) => setScale(option.key), [])
|
||||
const [scaleType, setScaleType] = useState<ScaleType>(ScaleType.Linear)
|
||||
const handleScaleTypeChange = useCallback(type => setScaleType(type), [])
|
||||
const handlePickerChange = useCallback(t => themeLoaded(t), [themeLoaded])
|
||||
const labelStyle = useMemo(
|
||||
() => ({
|
||||
fontWeight: 'bold' as const,
|
||||
color: theme.application().accent().hex(),
|
||||
}),
|
||||
[theme],
|
||||
)
|
||||
const actionStyle = useMemo(
|
||||
() => ({
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
color: theme.application().warning().hex(),
|
||||
}),
|
||||
[theme],
|
||||
)
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 14,
|
||||
overflowY: 'scroll',
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
The @thematic/fluent package contains a few custom Fluent controls you
|
||||
can use in your applications to allow Thematic-specific interactions.
|
||||
</p>
|
||||
<div style={controlsStyle}>
|
||||
<div style={controlStyle}>
|
||||
<p>
|
||||
<span style={labelStyle}>ScaleDropdown:</span> a Dropdown that
|
||||
pre-loads Thematic scale options.
|
||||
</p>
|
||||
<ScaleDropdown
|
||||
placeholder={'Choose scale'}
|
||||
onChange={handleScaleChange}
|
||||
/>
|
||||
<p style={actionStyle}> onChange: {scale}</p>
|
||||
</div>
|
||||
<div style={controlStyle}>
|
||||
<p>
|
||||
<span style={labelStyle}>ScaleTypeChoiceGroup:</span> a ChoiceGroup
|
||||
that pre-loads Thematic scale types.
|
||||
</p>
|
||||
<ScaleTypeChoiceGroup
|
||||
selectedType={scaleType}
|
||||
onChange={handleScaleTypeChange}
|
||||
/>
|
||||
<p style={actionStyle}> onChange: {scaleType}</p>
|
||||
</div>
|
||||
<div style={controlStyle}>
|
||||
<p>
|
||||
<span style={labelStyle}>ColorPicker:</span> a ColorPicker that
|
||||
emits Thematic parameters.
|
||||
</p>
|
||||
<ColorPicker theme={theme} onChange={handlePickerChange} />
|
||||
<p style={actionStyle}>
|
||||
{' '}
|
||||
onChange: {theme.application().accent().hex()}
|
||||
</p>
|
||||
</div>
|
||||
<div style={controlStyle}>
|
||||
<p>
|
||||
<span style={labelStyle}>ColorPickerButton:</span> a DropdownButton
|
||||
that hosts a Thematic ColorPicker.
|
||||
</p>
|
||||
<ColorPickerButton theme={theme} onChange={handlePickerChange} />
|
||||
<p style={actionStyle}>
|
||||
{' '}
|
||||
onChange: {theme.application().accent().hex()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const FluentControls = connect(null, {
|
||||
themeLoaded,
|
||||
})(FluentControlsComponent)
|
||||
|
||||
const controlsStyle = {
|
||||
display: 'flex' as const,
|
||||
flexDirection: 'row' as const,
|
||||
flexWrap: 'wrap' as const,
|
||||
justifyContent: 'space-around' as const,
|
||||
}
|
||||
|
||||
const controlStyle = {
|
||||
width: 320,
|
||||
padding: 8,
|
||||
margin: 8,
|
||||
}
|
|
@ -22,7 +22,7 @@ export const ThemeEditor: React.FC<ThemeEditorProps> = ({ scaleItemCount }) => {
|
|||
return (
|
||||
<div className="editor-wrapper">
|
||||
<Pivot>
|
||||
<PivotItem className="tab" headerText="Cooler Picker">
|
||||
<PivotItem className="tab" headerText="Color Picker">
|
||||
<CoolerPicker />
|
||||
</PivotItem>
|
||||
<PivotItem className="tab" headerText="Marks">
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
* Copyright (c) Microsoft. All rights reserved.
|
||||
* Licensed under the MIT license. See LICENSE file in the project.
|
||||
*/
|
||||
import { Pivot, PivotItem } from '@fluentui/react'
|
||||
import React from 'react'
|
||||
import { Graph } from '../../interfaces'
|
||||
import { FluentControls } from '../FluentControls'
|
||||
import { D3Chart } from '../charts/D3'
|
||||
import { VegaChart, charts } from '../charts/Vega'
|
||||
import { D3Graph } from '../graphs/D3'
|
||||
|
@ -55,21 +57,28 @@ export const ThemeViewer: React.FC<ThemeViewerProps> = ({
|
|||
|
||||
return (
|
||||
<div className="theme-wrapper">
|
||||
<div className="example-grid">
|
||||
{graphs.map((graph, i) => (
|
||||
<div className="example-grid-item" key={`example-graph-${i}`}>
|
||||
{graph}
|
||||
<Pivot>
|
||||
<PivotItem headerText="Thematic controls">
|
||||
<FluentControls />
|
||||
</PivotItem>
|
||||
<PivotItem headerText="Example visualizations">
|
||||
<div className="example-grid">
|
||||
{graphs.map((graph, i) => (
|
||||
<div className="example-grid-item" key={`example-graph-${i}`}>
|
||||
{graph}
|
||||
</div>
|
||||
))}
|
||||
<div className="example-grid-item" key={`example-rar-chart`}>
|
||||
<D3Chart {...common} />
|
||||
</div>
|
||||
{charts.map(chart => (
|
||||
<div className="example-grid-item" key={`example-chart-${chart}`}>
|
||||
<VegaChart type={chart} {...common} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div className="example-grid-item" key={`example-rar-chart`}>
|
||||
<D3Chart {...common} />
|
||||
</div>
|
||||
{charts.map(chart => (
|
||||
<div className="example-grid-item" key={`example-chart-${chart}`}>
|
||||
<VegaChart type={chart} {...common} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.example-grid-item {
|
||||
margin: 20px;
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"jsx": "react",
|
||||
"types": ["node", "jest"],
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
12590
yarn.lock
12590
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче