Skip to content

Add polygon fill color gradient #375

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/componentsguide/styles/fill/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,36 @@ Styling a feature
- **Type**: `Number`

The color either in hexadecimal or as RGB array with red, green, and blue values between 0 and 255 and alpha between 0 and 1 inclusive.

### gradient
- **Type**: `Object`
```
{
"type": "linear", // Type of gradient. Here, it's a linear gradient.
"x0": 0, // x-coordinate of the starting point. Indicates the horizontal position where the gradient starts.
"y0": 0, // y-coordinate of the starting point. Indicates the vertical position where the gradient starts.
"x1": 0, // x-coordinate of the ending point. This is the horizontal position where the gradient ends. It’s the same as x0, meaning the gradient is vertical.
"y1": 256, // y-coordinate of the ending point. Indicates the vertical position where the gradient ends, extending the gradient to y = 256.
"colorStops": [
[0, "red"], // Start color at position 0. The color at the beginning of the gradient is red.
[0.5, "yellow"], // Middle color at position 0.5. The color at the midpoint of the gradient (y = 128) is yellow.
[1, "green"] // End color at position 1. The color at the end of the gradient (y = 256) is green.
]
}

{
"type": "radial", // Type of gradient. Here, it's a radial gradient.
"x0": 128, // x-coordinate of the starting circle's center. Specifies the horizontal position of the starting circle.
"y0": 128, // y-coordinate of the starting circle's center. Specifies the vertical position of the starting circle.
"r0": 0, // Radius of the starting circle. Here, it's 0, meaning the gradient starts at a single point.
"x1": 128, // x-coordinate of the ending circle's center. Same as x0, so the ending circle is centered at the same horizontal position.
"y1": 128, // y-coordinate of the ending circle's center. Same as y0, so the ending circle is centered at the same vertical position.
"r1": 128, // Radius of the ending circle. Specifies the radius of the circle where the gradient ends, extending to a radius of 128 units.
"colorStops": [
[0, "blue"], // Color at the center of the gradient (r = 0). The color at the very center of the radial gradient is blue.
[0.5, "cyan"], // Color at the midpoint of the gradient. At the midpoint of the radius (r = 64), the color is cyan.
[1, "white"] // Color at the edge of the gradient (r = 128). The color at the outer edge of the radial gradient is white.
]
}

```
207 changes: 163 additions & 44 deletions src/components/styles/OlStyleFill.vue
Original file line number Diff line number Diff line change
@@ -1,56 +1,143 @@
<template>
<div v-if="false"></div>
</template>

<script setup lang="ts">
import { type Ref, inject, watch, onMounted, onUnmounted, ref } from "vue";
import Fill from "ol/style/Fill";
import type CircleStyle from "ol/style/Circle";

import type { Ref } from "vue";
import { inject, watch, onMounted, onUnmounted } from "vue";
import type Style from "ol/style/Style";
import usePropsAsObjectProperties from "@/composables/usePropsAsObjectProperties";

const props = withDefaults(
defineProps<{
color?: string;
}>(),
{},
);
// Define the type for gradient color stops
type GradientColorStop = [number, string];

// Define the type for linear gradients
type LinearGradient = {
type: "linear";
x0: number;
y0: number;
x1: number;
y1: number;
colorStops: GradientColorStop[];
};

// Define the type for radial gradients
type RadialGradient = {
type: "radial";
x0: number;
y0: number;
r0: number;
x1: number;
y1: number;
r1: number;
colorStops: GradientColorStop[];
};

// Define the type for conic gradients
type ConicGradient = {
type: "conic";
cx: number;
cy: number;
radius: number;
startAngle: number;
endAngle: number;
colorStops: GradientColorStop[];
};

// Define a union type for all gradient types
type Gradient = LinearGradient | RadialGradient | ConicGradient;

// Define the props type for the component
const props = defineProps<{
color?: string;
gradient?: Gradient;
}>();

const style = inject<Ref<Style | null> | null>("style", null);
const circle = inject<Ref<CircleStyle | null> | null>("circle", null);
const styledObj = inject<Ref<Style | null> | null>("styledObj", null);
// Inject possible nullable Ref objects from the parent component
const style = inject<Ref<Style | null>>("style", ref(null));
const circle = inject<Ref<CircleStyle | null>>("circle", ref(null));
const styledObj = inject<Ref<Style | null>>("styledObj", ref(null));

// Use a custom composable to convert props into an object
const properties = usePropsAsObjectProperties(props);

if (style != null && circle == null) {
// in style object
let fill = new Fill(properties);
style?.value?.setFill(fill);

const applyFill = () => {
style?.value?.setFill(new Fill());
fill = new Fill(properties);
style?.value?.setFill(fill);
};
watch(properties, () => {
applyFill();
});
// Function to create a gradient fill
const createGradientFill = (
gradient?: Gradient,
width: number = 256,
height: number = 256,
): Fill => {
const gradientCanvas = document.createElement("canvas");
const ctx = gradientCanvas.getContext("2d");

watch(style, () => {
applyFill();
});
if (!ctx) throw new Error("Unable to get canvas context");

onMounted(() => {
style?.value?.setFill(fill);
});
gradientCanvas.width = width;
gradientCanvas.height = height;

onUnmounted(() => {
style?.value?.setFill(new Fill());
});
} else if (circle != null) {
// in circle
const applyFilltoCircle = (color?: string) => {
let grad: CanvasGradient;

// Create the corresponding CanvasGradient object based on the gradient type
if (gradient) {
switch (gradient.type) {
case "linear":
grad = ctx.createLinearGradient(
gradient.x0,
gradient.y0,
gradient.x1,
gradient.y1,
);
break;
case "radial":
grad = ctx.createRadialGradient(
gradient.x0,
gradient.y0,
gradient.r0,
gradient.x1,
gradient.y1,
gradient.r1,
);
break;
case "conic":
// Conic gradients are not directly supported by the Canvas API, use a linear gradient as a fallback
grad = ctx.createLinearGradient(0, 0, width, height);
break;
default:
throw new Error("Unsupported gradient type");
}

// Add color stops to the gradient
gradient.colorStops.forEach(([offset, gradientColor]) => {
grad.addColorStop(offset, gradientColor);
});

ctx.fillStyle = grad;
} else {
ctx.fillStyle = properties.color ?? "transparent";
}

ctx.fillRect(0, 0, width, height);

const dataURL = gradientCanvas.toDataURL();
return new Fill({ color: { src: dataURL } });
};

// Function to apply fill style to a Style object
const applyFillToStyle = () => {
if (style.value) {
const fill = properties.gradient
? createGradientFill(properties.gradient)
: new Fill({ color: properties.color || "transparent" });

style.value.setFill(fill);
}
};

// Function to apply fill style to a CircleStyle object
const applyFillToCircle = (color?: string) => {
if (circle.value) {
// @ts-ignore
circle?.value?.getFill().setColor(color || null);
circle?.value?.setRadius(circle?.value.getRadius()); // force render
try {
Expand All @@ -60,15 +147,47 @@ if (style != null && circle == null) {
// @ts-ignore
styledObj?.value.changed();
}
};
}
};

applyFilltoCircle(properties.color);
// Conditional style handling
if (style.value && !circle.value) {
// If a Style object exists but not a CircleStyle object, apply style to the Style
applyFillToStyle();

watch(properties, () => {
applyFilltoCircle(properties.color);
});
watch(circle, () => {
applyFilltoCircle(properties.color);
// Watch for changes in properties and reapply style
watch(properties, applyFillToStyle, { immediate: true });
watch(
style,
(newStyle) => {
if (newStyle) applyFillToStyle();
},
{ immediate: true },
);

onMounted(applyFillToStyle);
onUnmounted(() => {
style.value?.setFill(new Fill());
});
} else if (circle.value) {
// If a CircleStyle object exists, apply style to the CircleStyle
applyFillToCircle(properties.color);

// Watch for changes in properties and the CircleStyle object and reapply style
watch(
properties,
() => {
if (circle.value) applyFillToCircle(properties.color);
},
{ immediate: true },
);

watch(
circle,
(newCircle) => {
if (newCircle) applyFillToCircle(properties.color);
},
{ immediate: true },
);
}
</script>
Loading