icon.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { Transform } from "konva/lib/Util";
  2. import {
  3. BaseItem,
  4. generateSnapInfos,
  5. getBaseItem,
  6. getRectSnapPoints,
  7. } from "../util.ts";
  8. import { getMouseColors } from "@/utils/colors.ts";
  9. import { FixScreen } from "@/utils/bound.ts";
  10. import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
  11. import { Size } from "@/utils/math.ts";
  12. import { getSvgContent, parseSvgContent } from "@/utils/resource.ts";
  13. import { Color } from "three";
  14. export const shapeName = "图例";
  15. export const defaultStyle = {
  16. coverFill: "#000000",
  17. coverOpcatiy: 0,
  18. strokeScaleEnabled: false,
  19. width: 80,
  20. height: 80,
  21. };
  22. type ColorCounts = [string, number][];
  23. const colorsManage = (counts: ColorCounts, color: any) => {
  24. if (!color) {
  25. return;
  26. }
  27. const colorHex = new Color(color).getHexString();
  28. const item = counts.find(([c]) => c === colorHex);
  29. if (!item) {
  30. counts.push([colorHex, 1]);
  31. } else {
  32. item[1]++;
  33. }
  34. };
  35. const findColor = (counts: ColorCounts) => {
  36. let ndx = -1;
  37. let max = 0;
  38. for (let i = 0; i < counts.length; i++) {
  39. if (counts[i][1] >= max) {
  40. ndx = i;
  41. max = counts[i][1];
  42. }
  43. }
  44. if (~ndx) {
  45. return `#${counts[ndx][0]}`;
  46. }
  47. };
  48. export const getIconStyle = async (
  49. url: string,
  50. width = defaultStyle.width,
  51. height = defaultStyle.height,
  52. fixed = false
  53. ) => {
  54. const svgContent = parseSvgContent(await getSvgContent(url));
  55. if (!fixed) {
  56. if (width / height > svgContent.width / svgContent.height) {
  57. width = (svgContent.width / svgContent.height) * height;
  58. } else {
  59. height = (svgContent.height / svgContent.width) * width;
  60. }
  61. }
  62. const fillColorCounts: [string, number][] = [];
  63. const strokeColorCounts: [string, number][] = [];
  64. for (let i = 0; i < svgContent.paths.length; i++) {
  65. colorsManage(fillColorCounts, svgContent.paths[i].fill);
  66. colorsManage(strokeColorCounts, svgContent.paths[i].stroke);
  67. }
  68. const color = {
  69. fill: findColor(fillColorCounts) || null,
  70. stroke: findColor(strokeColorCounts) || null,
  71. };
  72. if (!color.fill && !color.stroke) {
  73. color.stroke = "#000000";
  74. }
  75. return {
  76. url,
  77. width,
  78. height,
  79. ...color,
  80. };
  81. };
  82. export const addMode = "dot";
  83. export const getSnapInfos = (data: IconData) => {
  84. return generateSnapInfos(getSnapPoints(data), true, false);
  85. };
  86. export const getSnapPoints = (data: IconData) => {
  87. const tf = new Transform(data.mat);
  88. const w = data.width || defaultStyle.width;
  89. const h = data.height || defaultStyle.height;
  90. const points = getRectSnapPoints(w, h);
  91. return points.map((v) => tf.point(v));
  92. };
  93. export const getMouseStyle = (data: IconData) => {
  94. const fillStatus = getMouseColors(data.coverFill || defaultStyle.coverFill);
  95. const hCoverOpcaoty = data.coverOpcatiy ? data.coverOpcatiy : 0.3;
  96. return {
  97. default: {
  98. coverFill: data.coverFill || defaultStyle.coverFill,
  99. coverOpcatiy: data.coverOpcatiy || 0,
  100. },
  101. hover: { coverFill: fillStatus.hover, coverOpcatiy: hCoverOpcaoty },
  102. select: { coverFill: fillStatus.select, coverOpcatiy: hCoverOpcaoty },
  103. focus: { coverFill: fillStatus.select, coverOpcatiy: hCoverOpcaoty },
  104. press: { coverFill: fillStatus.press, coverOpcatiy: hCoverOpcaoty },
  105. };
  106. };
  107. export type IconData = Partial<typeof defaultStyle> &
  108. BaseItem &
  109. Size & {
  110. fill?: string | null;
  111. stroke?: string | null;
  112. name?: string;
  113. strokeWidth?: number;
  114. coverFill?: string;
  115. coverStroke?: string;
  116. coverStrokeWidth?: number;
  117. mat: number[];
  118. url: string;
  119. fixScreen?: FixScreen;
  120. };
  121. export const dataToConfig = (data: IconData) => {
  122. return {
  123. ...defaultStyle,
  124. ...data,
  125. };
  126. };
  127. export const interactiveToData: InteractiveTo<"icon"> = ({
  128. info,
  129. preset = {},
  130. viewTransform,
  131. ...args
  132. }) => {
  133. if (info.cur) {
  134. console.error(preset);
  135. return interactiveFixData({
  136. ...args,
  137. viewTransform,
  138. info,
  139. data: { ...getBaseItem(), ...preset } as unknown as IconData,
  140. });
  141. }
  142. };
  143. export const interactiveFixData: InteractiveFix<"icon"> = ({
  144. data,
  145. info,
  146. viewTransform,
  147. }) => {
  148. if (data.fixScreen) {
  149. if ("x" in info.cur! && "y" in info.cur!) {
  150. // 存储屏幕坐标
  151. const screen = viewTransform.point(info.cur!);
  152. data.fixScreen = {
  153. left: screen.x,
  154. top: screen.y,
  155. };
  156. }
  157. data.mat = [1, 0, 0, 1, 0, 0];
  158. } else {
  159. const mat = new Transform().translate(info.cur!.x, info.cur!.y);
  160. data.mat = mat.m;
  161. }
  162. return data;
  163. };
  164. export const matResponse = ({
  165. data,
  166. mat,
  167. increment,
  168. }: MatResponseProps<"icon">) => {
  169. data.mat = increment ? mat.copy().multiply(new Transform(data.mat)).m : mat.m;
  170. return data;
  171. };
  172. export const getPredefine = (key: keyof IconData) => {
  173. if (key === "fill" || key === "stroke") {
  174. return { canun: true };
  175. }
  176. };