Skip to content

Commit

Permalink
feat(ol-style-fill): add fill color gradient (#375)
Browse files Browse the repository at this point in the history
docs were added to the polygon geometry demo
  • Loading branch information
asd123a20 authored Sep 3, 2024
1 parent 64fd40d commit dd2aebd
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 63 deletions.
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>
100 changes: 81 additions & 19 deletions src/demos/MultiPoint.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,7 @@
<ol-source-vector>
<ol-feature>
<ol-geom-multi-point
:coordinates="[
[116.544921, 40.451633],
[116.545264, 40.451649],
[116.545865, 40.451698],
[116.546144, 40.451551],
[116.546337, 40.451274],
[116.546788, 40.451143],
[116.547324, 40.451078],
[116.547539, 40.450996],
[116.547839, 40.450719],
[116.54844, 40.450506],
[116.548933, 40.450604],
[116.549448, 40.450604],
[116.550242, 40.450376],
[116.550865, 40.450163],
[116.551702, 40.449935],
[116.552581, 40.449576],
]"
:coordinates="[[116.547539, 40.450996]]"
></ol-geom-multi-point>
<ol-style>
<ol-style-circle :radius="radius">
Expand All @@ -48,6 +31,57 @@
</ol-style-circle>
</ol-style>
</ol-feature>

<ol-feature>
<ol-geom-polygon
:coordinates="[
[
[116.54825, 40.44664],
[116.55125, 40.44664],
[116.55175, 40.45364],
[116.54875, 40.45364],
[116.54825, 40.44664],
],
]"
></ol-geom-polygon>
<ol-style>
<ol-style-fill color="red" />
</ol-style>
</ol-feature>

<ol-feature>
<ol-geom-polygon
:coordinates="[
[
[116.55325, 40.44364],
[116.55625, 40.44364],
[116.55675, 40.45164],
[116.55375, 40.45164],
[116.55325, 40.44364],
],
]"
></ol-geom-polygon>
<ol-style>
<ol-style-fill :gradient="linearGradient" />
</ol-style>
</ol-feature>

<ol-feature>
<ol-geom-polygon
:coordinates="[
[
[116.53415, 40.45154],
[116.54245, 40.45154],
[116.54385, 40.44664],
[116.53555, 40.44664],
[116.53415, 40.45154],
],
]"
></ol-geom-polygon>
<ol-style>
<ol-style-fill :gradient="radialGradient" />
</ol-style>
</ol-feature>
</ol-source-vector>
</ol-vector-layer>
</ol-map>
Expand All @@ -58,9 +92,37 @@ import { ref } from "vue";
const center = ref([116.54875, 40.45064]);
const projection = ref("EPSG:4326");
const zoom = ref(17);
const zoom = ref(15);
const radius = ref(10);
const strokeWidth = ref(4);
const strokeColor = ref("red");
const fillColor = ref("white");
const linearGradient = {
type: "linear",
x0: 0,
y0: 0,
x1: 0,
y1: 256,
colorStops: [
[0, "red"], // Start color
[0.5, "yellow"], // Middle color
[1, "green"], // End color
],
};
const radialGradient = {
type: "radial",
x0: 128,
y0: 128,
r0: 0,
x1: 128,
y1: 128,
r1: 128,
colorStops: [
[0, "blue"], // Center color
[0.5, "cyan"], // Middle color
[1, "white"], // Edge color
],
};
</script>
Loading

0 comments on commit dd2aebd

Please sign in to comment.