Skip to content
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

feat: canvas props config #1031

Merged
merged 14 commits into from
Apr 27, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<template>
<main ref="canvasRef">
<div>
<slot name="foreground" />
</div>
<div class="data-layer" ref="dataLayerRef">
<slot name="data" />
</div>
<svg class="background-layer" ref="backgroundLayerRef" :width="width" :height="height">
<slot name="background" />
</svg>
</main>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import * as d3 from 'd3';

const props = withDefaults(
defineProps<{
debugMode?: boolean;
scaleExtent?: [number, number];
lastTransform?: { k: number; x: number; y: number };
}>(),
{
debugMode: false,
scaleExtent: () => [0.1, 10],
mwdchang marked this conversation as resolved.
Show resolved Hide resolved
lastTransform: undefined
}
);

const emit = defineEmits(['save-transform']);

let x: d3.ScaleLinear<number, number, never>;
let y: d3.ScaleLinear<number, number, never>;
let xAxis: d3.Axis<d3.NumberValue>;
let yAxis: d3.Axis<d3.NumberValue>;
let gX: d3.Selection<SVGGElement, any, null, any>;
let gY: d3.Selection<SVGGElement, any, null, any>;
let currentTransform: d3.ZoomTransform;

const width = ref(0);
const height = ref(0);
const canvasRef = ref<HTMLElement>();
const dataLayerRef = ref<HTMLDivElement>();
const backgroundLayerRef = ref<SVGElement>();

const handleZoom = (evt: any, container: d3.Selection<SVGGElement, any, null, any>) => {
container.attr('transform', evt.transform);

d3.select(dataLayerRef.value as HTMLDivElement)
.style(
'transform',
`translate(${evt.transform.x}px, ${evt.transform.y}px) scale(${evt.transform.k})`
)
.style('transform-origin', '0 0');

if (props.debugMode) {
gX.call(xAxis.scale(evt.transform.rescaleX(x)));
gY.call(yAxis.scale(evt.transform.rescaleY(y)));
}

currentTransform = evt.transform;
};

function updateDimensions() {
// Update dimensions
width.value = canvasRef.value?.clientWidth ?? window.innerWidth;
height.value = canvasRef.value?.clientHeight ?? window.innerHeight;

if (props.debugMode) {
// Update debug values
x = d3
.scaleLinear()
.domain([-1, width.value + 1])
.range([-1, width.value + 1]);
y = d3
.scaleLinear()
.domain([-1, height.value + 1])
.range([-1, height.value + 1]);
xAxis = d3
.axisBottom(x)
.ticks(((width.value + 2) / (height.value + 2)) * 10)
.tickSize(height.value)
.tickPadding(8 - height.value);
yAxis = d3
.axisRight(y)
.ticks(10)
.tickSize(width.value)
.tickPadding(8 - width.value);

if (currentTransform) {
gX.call(xAxis.scale(currentTransform.rescaleX(x)));
gY.call(yAxis.scale(currentTransform.rescaleY(y)));
}
}
}

const resizeObserver = new ResizeObserver(() => updateDimensions());

onMounted(() => {
const svg = d3.select(backgroundLayerRef.value as SVGGElement); // Parent SVG
const container = svg.append('g'); // Pan/zoom area

// Zoom config is applied and event handler
const zoom = d3
.zoom()
.scaleExtent(props.scaleExtent)
.on('zoom', (e) => handleZoom(e, container));
svg.call(zoom as any).on('dblclick.zoom', null);

// Initializes/watches resize of component so SVG layer fills it
updateDimensions();
if (canvasRef.value) resizeObserver.observe(canvasRef.value);

// Assign debug grid
if (props.debugMode) {
gX = svg.append('g').attr('class', 'axis axis--x').call(xAxis);
gY = svg.append('g').attr('class', 'axis axis--y').call(yAxis);
}

// Initialize starting position
if (props.lastTransform) {
zoom.scaleTo(svg as any, props.lastTransform.k);
zoom.translateTo(svg as any, props.lastTransform.x, props.lastTransform.y);
} else {
// Default position - triggers handleZoom which in turn sets currentTransform
svg.transition().call(zoom.transform as any, d3.zoomIdentity);
}

container.append('circle').attr('cx', 400).attr('cy', 300).attr('r', 20).attr('fill', 'red');
});

onUnmounted(() => {
emit('save-transform', currentTransform);
shawnyama marked this conversation as resolved.
Show resolved Hide resolved
});
</script>

<style scoped>
main {
width: 100%;
height: 100%;
}

.data-layer {
position: absolute;
}
shawnyama marked this conversation as resolved.
Show resolved Hide resolved

.background-layer {
cursor: grab;
width: 100%;
height: 100%;
}

.background-layer:deep(.tick line) {
color: var(--surface-border);
}

svg:active {
cursor: grabbing;
}
</style>
120 changes: 0 additions & 120 deletions packages/client/hmi-client/src/temp/tera-infinite-canvas.vue

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<infinite-canvas>
<infinite-canvas debug-mode :last-transform="lastTransform" @save-transform="saveTransform">
<template #foreground></template>
<template #data>
<div style="font-size: 24px; padding: 10px; background: #9ef">This is a DataLayer DIV</div>
Expand All @@ -8,5 +8,15 @@
</template>

<script setup lang="ts">
import InfiniteCanvas from '@/temp/tera-infinite-canvas.vue';
import InfiniteCanvas from '@/components/widgets/tera-infinite-canvas.vue';

const lastTransform = {
k: 0.75,
x: 0,
y: 0
};

function saveTransform(transform: d3.ZoomTransform) {
console.log(transform);
}
</script>