icon.ts 4.4 KB

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