diff --git a/packages/vstory/demo/src/App.tsx b/packages/vstory/demo/src/App.tsx index 7abd67d6..b4d694e8 100644 --- a/packages/vstory/demo/src/App.tsx +++ b/packages/vstory/demo/src/App.tsx @@ -15,6 +15,7 @@ import { StoryEdit } from './demos/StoryEdit'; import { Appear } from './demos/Appear'; import { GraphicEdit } from './demos/GraphicEdit'; import { Playground } from './demos/Playground'; +import { Pictogram } from './demos/infographics/Pictogram'; const App = () => { const [activeIndex, setActiveIndex] = useLocalStorage('menuIndex', 0); @@ -74,6 +75,10 @@ const App = () => { { name: 'Playground', component: Playground + }, + { + name: 'Infographic-Pictogram', + component: Pictogram } ]; const selectedMenu = menus[activeIndex ?? menus.length - 1]; diff --git a/packages/vstory/demo/src/assets/1920x1080/meeting.jpeg b/packages/vstory/demo/src/assets/1920x1080/meeting.jpeg new file mode 100644 index 00000000..870b4ed4 Binary files /dev/null and b/packages/vstory/demo/src/assets/1920x1080/meeting.jpeg differ diff --git a/packages/vstory/demo/src/demos/infographics/Pictogram.tsx b/packages/vstory/demo/src/demos/infographics/Pictogram.tsx new file mode 100644 index 00000000..07add856 --- /dev/null +++ b/packages/vstory/demo/src/demos/infographics/Pictogram.tsx @@ -0,0 +1,437 @@ +import { IActionsLink, IStorySpec } from '../../../../src/story/interface'; +import { Story } from '../../../../src/story/story'; +import React, { useEffect } from 'react'; +import MeetingImg from '../../assets/1920x1080/meeting.jpeg'; +import { ICharacterSpec } from '../../../../src/story/character'; + +const matrixLeft = [ + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] +]; + +const matrixRight = [ + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] +]; + +const icon = [ + 'M 0.012 -0.287 c 0.041 0 0.075 -0.033 0.075 -0.075 c 0 -0.041 -0.033 -0.075 -0.075 -0.075 c -0.041 0 -0.075 0.033 -0.075 0.075 C -0.063 -0.32 -0.029 -0.287 0.012 -0.287 z M 0.087 -0.27 L 0.012 -0.27 l -0.075 0 c -0.056 0 -0.093 0.05 -0.093 0.097 L -0.156 0.054 c 0 0.044 0.062 0.044 0.062 0 L -0.094 -0.156 l 0.012 0 l 0 0.571 c 0 0.061 0.084 0.059 0.086 0 L 0.004 0.086 l 0.014 0 l 0.002 0 l 0 0.329 c 0.003 0.062 0.086 0.056 0.086 0 L 0.106 -0.156 l 0.01 0 l 0 0.21 c 0 0.044 0.064 0.044 0.064 0 L 0.18 -0.173 C 0.18 -0.22 0.143 -0.27 0.087 -0.27 z', + 'M 0.012 -0.329 m -0.085 0 a 0.085 0.085 90 1 0 0.171 0 a 0.085 0.085 90 1 0 -0.171 0 Z M 0.138 -0.172 A 0.043 0.043 90 0 0 0.097 -0.201 h -0.171 a 0.043 0.043 90 0 0 -0.04 0.029 l -0.085 0.256 l 0.076 0.025 L -0.159 0.268 h 0.085 v 0.171 h 0.171 v -0.171 h 0.085 l -0.035 -0.159 l 0.076 -0.025 l -0.085 -0.256 z' +]; + +function createIconMatrix(matrix: number[][], startIndex: number, x: number, y: number) { + const characters: ICharacterSpec[] = []; + const actions: IActionsLink[] = []; + + const startX = x; + const startY = y; + const width = 26; + const height = 56; + const lineGap = 12; + let curIndex = 0; + for (let i = 0; i < matrix.length; i++) { + for (let j = 0; j < matrix[i].length; j++) { + const iconIndex = matrix[i][j]; + const id = `icon-${startIndex + i * matrix[i].length + j}-${x}-${y}`; + const character: ICharacterSpec = { + type: 'ShapeComponent', + id, + zIndex: 3, + position: { + top: startY + i * height + i * lineGap + 30, + left: startX + j * width, + width, + height + }, + options: { + graphic: { + symbolType: icon[iconIndex], + width, + height, + stroke: false, + size: 56, + scaleY: 0.9, + scaleX: 0.9, + fill: ++curIndex >= startIndex ? '#48A0CF' : 'white' + } + } + }; + characters.push(character); + actions.push({ + characterId: id, + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }); + } + } + return { characters, actions }; +} + +const leftIcons = createIconMatrix(matrixLeft, 40, 116, 330); +const rightIcons = createIconMatrix(matrixRight, 45, 1164, 330); + +export const Pictogram = () => { + const id = 'pictogram'; + + useEffect(() => { + // 准备一个图表 + const spec: IStorySpec = { + characters: [ + { + type: 'RectComponent', + id: 'background-top', + zIndex: 2, + position: { + top: 0, + left: 0, + width: 1920, + height: 254 + }, + options: { + graphic: { + fill: '#2D6BA0', + stroke: false + } + } + }, + { + type: 'RectComponent', + id: 'background-bottom-filter', + zIndex: 0, + position: { + top: 0, + left: 0, + width: 1920, + height: 1080 + }, + options: { + graphic: { + fill: '#193446', + fillOpacity: 1, + stroke: false + } + } + }, + { + type: 'ImageComponent', + id: 'background-bottom', + zIndex: 1, + position: { + top: 0, + left: 0, + width: 1920, + height: 1080 + }, + options: { + graphic: { + image: MeetingImg, + opacity: 0.2 + } + } + }, + { + type: 'RichTextComponent', + id: 'Title', + zIndex: 3, + position: { + top: 254 / 2, + left: 1920 / 2, + width: 1920, + height: 1080 + }, + options: { + graphic: { + width: 1920 - 300, + height: 1080, + fontSize: 40, + wordBreak: 'break-word', + textAlign: 'center', + fill: 'white', + fontWeight: 200, + textConfig: [ + { + text: 'According to a study conducted by ' + }, + { + text: 'Mckinsey & Company', + fontWeight: 'bold' + }, + { + text: ' on the effect the pandemic and remote working had on hiring and human resources management' + } + ] + } + } + }, + { + type: 'LineComponent', + id: 'SplitLine', + zIndex: 3, + position: { + top: 340, + left: 1920 / 2, + width: 20, + height: 560 + }, + options: { + graphic: { + stroke: '#48A0CF', + lineWith: 10, + points: [ + { x: 0, y: 0 }, + { x: 0, y: 560 } + ] + } + } + }, + { + type: 'ShapeComponent', + id: 'Star', + zIndex: 3, + position: { + top: 340 + 560, + left: 1920 / 2, + width: 100, + height: 100 + }, + options: { + graphic: { + fill: '#48A0CF', + stroke: false, + symbolType: + 'M187.5,0.2c-61.2,106-63.8,106-125,0c61.2,106,59.9,108.3-62.5,108.3 c122.4,0,123.7,2.2,62.5,108.3c61.2-106,63.8-106,125,0c-61.2-106-59.9-108.3,62.5-108.3C127.6,108.6,126.3,106.2,187.5,0.2z', + size: 140, + dx: -70, + dy: -60 + } + } + }, + { + type: 'TextComponent', + id: 'LeftPercent', + zIndex: 3, + position: { + top: 760, + left: 420, + width: 300, + height: 100 + }, + options: { + graphic: { + text: '67%', + fill: '#48A0CF', + fontSize: 120, + fontWeight: 600, + stroke: '#48A0CF', + fontFamily: 'Archivo' + } + } + }, + { + type: 'RichTextComponent', + id: 'LeftDescription', + zIndex: 3, + position: { + top: 900, + left: 420, + width: 460, + height: 108 + }, + options: { + graphic: { + fill: 'white', + fontSize: 30, + width: 460, + height: 108, + wordBreak: 'break-word', + textConfig: [ + { + text: 'Of over 120 CEOs plan to spend less time on hiring permanent recruits' + } + ] + } + } + }, + { + type: 'TextComponent', + id: 'RightPercent', + zIndex: 3, + position: { + top: 760, + left: 1483, + width: 300, + height: 100 + }, + options: { + graphic: { + text: '63%', + fill: '#48A0CF', + fontSize: 120, + fontWeight: 600, + stroke: '#48A0CF', + fontFamily: 'Archivo' + } + } + }, + { + type: 'RichTextComponent', + id: 'RightDescription', + zIndex: 3, + position: { + top: 900, + left: 1483, + width: 460, + height: 108 + }, + options: { + graphic: { + fill: 'white', + fontSize: 30, + width: 460, + height: 108, + wordBreak: 'break-word', + textConfig: [ + { + text: 'Plan to grow their IT and tech teams in response to changing ways of working' + } + ] + } + } + }, + ...leftIcons.characters, + ...rightIcons.characters + ], + acts: [ + { + id: 'page1', + scenes: [ + { + id: 'singleScene', + actions: [ + { + characterId: 'background-top', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'background-bottom-filter', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'background-bottom', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'Title', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'SplitLine', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'Star', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + ...leftIcons.actions, + ...rightIcons.actions, + { + characterId: 'LeftPercent', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'LeftDescription', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'RightPercent', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + }, + { + characterId: 'RightDescription', + characterActions: [ + { + action: 'appear', + startTime: 0, + duration: 0 + } + ] + } + ] + } + ] + } + ] + }; + const story = new Story(spec, { dom: id, playerOption: { scaleX: 0.5, scaleY: 0.5 } }); + // const story = new Story(spec, { dom: id, playerOption: {} }); + story.play(); + window.story = story; + }, []); + + return
; +}; diff --git a/packages/vstory/src/dsl/constant/index.ts b/packages/vstory/src/dsl/constant/index.ts index af82309d..53b2a506 100644 --- a/packages/vstory/src/dsl/constant/index.ts +++ b/packages/vstory/src/dsl/constant/index.ts @@ -15,7 +15,7 @@ export const enum StoryChartType { } export enum StoryGraphicType { RECT = 'RectComponent', - // Shape = 'SymbolComponent', + SHAPE = 'ShapeComponent', LINE = 'LineComponent', ARC = 'ArcComponent', // AREA = 'AreaComponent', diff --git a/packages/vstory/src/dsl/story-processor/processorMap/processorMap.ts b/packages/vstory/src/dsl/story-processor/processorMap/processorMap.ts index 2d1bf99d..7e7eac98 100644 --- a/packages/vstory/src/dsl/story-processor/processorMap/processorMap.ts +++ b/packages/vstory/src/dsl/story-processor/processorMap/processorMap.ts @@ -127,26 +127,10 @@ export const commonMarkProcessor = { bounce: bounceProcessor }; -export const processorMarkMap = { - [StoryGraphicType.RECT]: { - ...commonMarkProcessor - }, - [StoryGraphicType.QIPAO]: { - ...commonMarkProcessor - }, - [StoryGraphicType.TEXT]: { - ...commonMarkProcessor - }, - [StoryGraphicType.RICH_TEXT]: { - ...commonMarkProcessor - }, - [StoryGraphicType.LINE]: { - ...commonMarkProcessor - }, - [StoryGraphicType.IMAGE]: { - ...commonMarkProcessor - } -}; +export const processorMarkMap = {}; +Object.values(StoryGraphicType).forEach(type => { + processorMarkMap[type] = commonMarkProcessor; +}); // TODO: 按需引用, 所有processor export const processorMap = { diff --git a/packages/vstory/src/story/character/component/characters/character-shape.ts b/packages/vstory/src/story/character/component/characters/character-shape.ts new file mode 100644 index 00000000..4be65be1 --- /dev/null +++ b/packages/vstory/src/story/character/component/characters/character-shape.ts @@ -0,0 +1,11 @@ +import type { Graphic } from '../graphic/graphic'; +import { CharacterComponent } from '../character'; +import { StoryGraphicType } from '../../../../dsl/constant'; +import { GraphicSymbol } from '../graphic/symbol'; + +export class CharacterComponentShape extends CharacterComponent { + readonly graphicType: string = 'shape'; + protected _createGraphic(): Graphic { + return new GraphicSymbol(StoryGraphicType.Shape, this); + } +} diff --git a/packages/vstory/src/story/character/component/graphic/symbol.ts b/packages/vstory/src/story/character/component/graphic/symbol.ts new file mode 100644 index 00000000..b630282a --- /dev/null +++ b/packages/vstory/src/story/character/component/graphic/symbol.ts @@ -0,0 +1,36 @@ +import type { ISymbol } from '@visactor/vrender'; +import { createSymbol } from '@visactor/vrender'; +import type { IPointLike } from '@visactor/vutils'; +import { Graphic } from './graphic'; + +export class GraphicSymbol extends Graphic { + protected _graphic: ISymbol; + + getInitialAttributes() { + return { + x: 0, + y: 0, + width: 120, + height: 80, + angle: 0, + anchor: [60, 40], + lineWidth: 2, + stroke: '#000000', + shapePoints: [] as IPointLike[], + symbolType: 'circle' + }; + } + + init() { + if (!this._graphic) { + this._graphic = createSymbol( + this._transformAttributes({ + ...this.getInitialAttributes(), + ...(this._character.spec.options?.graphic ?? {}) + }) + ); + this._graphic.name = `graphic-symbol-${this._character.id}`; + this._character.getGraphicParent().add(this._graphic); + } + } +} diff --git a/packages/vstory/src/story/index.ts b/packages/vstory/src/story/index.ts index 74c632aa..3ac4c982 100644 --- a/packages/vstory/src/story/index.ts +++ b/packages/vstory/src/story/index.ts @@ -22,6 +22,7 @@ import { VChartPicker } from './character/chart/graphic/vchart-graphic-picker'; import { StoryGraphicType } from '../dsl/constant'; import { CharacterComponentLine } from './character/component/characters/character-line'; import { CharacterComponentImage } from './character/component/characters/character-image'; +import { CharacterComponentShape } from './character/component/characters/character-shape'; const splitModule = new ContainerModule((bind: any) => { // chart渲染器注入 @@ -60,6 +61,7 @@ export function registerCharacter() { StoryFactory.registerCharacter(StoryGraphicType.QIPAO, CharacterComponentQipao); StoryFactory.registerCharacter(StoryGraphicType.LINE, CharacterComponentLine); StoryFactory.registerCharacter(StoryGraphicType.IMAGE, CharacterComponentImage); + StoryFactory.registerCharacter(StoryGraphicType.SHAPE, CharacterComponentShape); container.load(splitModule); }